WJECS5UEYKERBGCBZROJLHR7LKA2UJOCEIXXCMZWRJXCZM5E7GYAC
defmodule ReqMatchingSymbols do
alias Ibex.Messages.Types.Outbound, as: Outbound
def t(reqId, symbol) do
code = to_string(Outbound.parse(:req_matching_symbols))
[
code,
reqId,
symbol
]
end
end
defmodule ReqHistoricalData do
alias Ibex.Messages.Types.Outbound, as: Outbound
@version <<"6">>
def t(
req_id,
contract,
end_date_time,
duration_str,
bar_size_setting,
what_to_show,
useRTH,
formatDate,
keepUpToDate,
chartOption
) do
code = to_string(Outbound.parse(:req_historical_data))
contract_fields = contract.to_fields()
[
code,
@version,
req_id,
contract[:con_id]
]
end
end
iex> Ibex.hello()
:world
"""
def hello do
:world
end
defmodule Ibex.Types do
@type contract_symbol :: String.t()
@type contract_currency :: String.t()
@type contract_sec_type ::
:STK
| :ETF
| :OPT
| :FUT
| :IND
| :FOP
| :CASH
| :BAG
| :WAR
| :BOND
| :CMDTY
| :NEWS
| :FUND
defmodule Ibex.Application do
# See https://hexdocs.pm/elixir/Application.html
# for more information on OTP Applications
@moduledoc false
use Application
require Logger
@impl true
def start(_type, _args) do
Logger.info("Starting Ibex.Application...")
children = [
IBClient,
Ibex.Inbound.Handler,
Ibex.Contracts.Storage,
]
opts = [strategy: :one_for_one, name: Ibex.Supervisor]
Supervisor.start_link(children, opts)
end
end
case state[:conn_state] do
:disconnected ->
connect_to_server(state)
:send_api_version ->
send_version(state)
:waiting_on_version_resp ->
wait_for_api_version_response(state)
:sending_start_api ->
start_api(state)
:waiting_on_start_api_resp ->
wait_for_start_api_resp(state)
:transition_to_non_blocking ->
transition_to_nonblocking(state)
:connected ->
state
end
case state[:conn_state] do
:disconnected ->
connect_to_server(state)
:send_api_version ->
send_version(state)
:waiting_on_version_resp ->
wait_for_api_version_response(state)
:sending_start_api ->
start_api(state)
:waiting_on_start_api_resp ->
wait_for_start_api_resp(state)
:transition_to_non_blocking ->
transition_to_nonblocking(state)
:connected ->
state
end
def handle_info({:req_stock_contract_details, ticker}, state) do
{req_id, state} = next_id(state)
msg =
Ibex.Messages.Outbound.ReqContractDetails.t(to_string(req_id), ticker, "STK")
|> IBMessage.to_ib_api()
:ok = :gen_tcp.send(state[:socket], msg)
{:noreply, state}
end
def handle_info({:symbol_search, symbol}, state) do
{req_id, state} = next_id(state)
Logger.info("Extracting Server API version from fields. Version: #{Enum.at(data, 0)}. Time: #{Enum.at(data, 1)}")
state = %{ state | server_api: Enum.at(data, 0)}
%{ state | server_connect_time: Enum.at(data, 1)}
Logger.info(
"Extracting Server API version from fields. Version: #{Enum.at(data, 0)}. Time: #{Enum.at(data, 1)}"
)
state = %{state | server_api: Enum.at(data, 0)}
%{state | server_connect_time: Enum.at(data, 1)}
wait_for_data(state)
{:ok, data} = :gen_tcp.recv(state[:socket], 0)
<<l::size(32), rest::binary>> = data
<<_::binary-size(l), rest::binary>> = rest
<<l::size(32), msg2::binary-size(l), rest::binary>> = rest
stripped = String.split(msg2, <<0>>)
Logger.debug("Next valid order ID is #{Enum.at(stripped, 1)}")
state = %{state | next_id: String.to_integer(Enum.at(stripped, 1))}
defmodule Ibex.Inbound.Handler do
use GenServer
require Logger
@initial_state %{}
def start do
Logger.debug("Starting Inbound...")
GenServer.start(__MODULE__, @initial_state)
end
def start_link(_opt) do
Logger.info("Starting Inbound as a supervised process...")
GenServer.start_link(__MODULE__, @initial_state, name: __MODULE__)
end
def init(state) do
{:ok, state}
end
def handle_info({:dispatch, fields}, state) do
msg_code = String.to_integer(Enum.at(fields, 0))
msg_code = Ibex.Messages.Types.Inbound.parse(msg_code)
req_id = Enum.at(fields, 1)
msg_body = Enum.slice(fields, 2..-1)
case msg_code do
:contract_data ->
Logger.info("Received contact data: #{inspect(msg_body)}")
:symbol_samples ->
process_symbol_samples(Enum.slice(fields, 1..-1))
_ ->
Logger.info("Received unhandled message type #{msg_code}. Body was: #{inspect(msg_body)}")
end
{:noreply, state}
end
defp process_symbol_samples(fields) do
{req_id, fields} = chomp(fields)
{num_descriptions, fields} = chomp(fields)
nd = Enum.at(num_descriptions, 0)
nd = String.to_integer(nd)
contracts = Ibex.Contracts.Inbound.SymbolSamples.extract_contracts(fields, [])
Enum.each(contracts, fn contract ->
Logger.info("Sending #{inspect(contract.contract.con_id)} to storage")
if contract.contract.con_id == "" do
nil
else
send(Ibex.Contracts.Storage, {:add_contract, contract})
end
end)
end
defp chomp(fields) do
Enum.split(fields, 1)
end
end
defmodule Ibex.Contracts.Inbound.SymbolSamples do
require Logger
alias Ibex.Contracts.ContractDescription, as: CD
def extract_contracts(fields, con_descs) do
{fields, con_descs} = extract_contract(fields, con_descs)
case length(fields) > 0 do
true ->
extract_contracts(fields, con_descs)
false ->
con_descs
end
end
def extract_contract(fields, con_descs) do
ncdesc = Ibex.Contracts.ContractDescription.t()
{con_id, fields} = chomp(fields)
{symbol, fields} = chomp(fields)
{sec_type, fields} = chomp(fields)
{primary_exchange, fields} = chomp(fields)
{currency, fields} = chomp(fields)
{nDerivativeSecTypes, fields} = chomp(fields)
nt = Enum.at(nDerivativeSecTypes, 0)
{fields, ncdesc} =
if nt == nil do
{fields, CD.insert_derivative_types(ncdesc, [])}
else
nt = String.to_integer(nt)
{derivs, fields} = Enum.split(fields, nt)
{fields, CD.insert_derivative_types(ncdesc, derivs)}
end
{description, fields} = chomp(fields)
{issuer_id, fields} = chomp(fields)
ncdesc =
CD.insert_into_contract(ncdesc, :con_id, to_string(con_id))
|> CD.insert_into_contract(:symbol, to_string(symbol))
|> CD.insert_into_contract(:sec_type, sec_type)
|> CD.insert_into_contract(:primary_exchange, primary_exchange)
|> CD.insert_into_contract(:currency, currency)
|> CD.insert_into_contract(:description, description)
|> CD.insert_into_contract(:issuer_id, issuer_id)
{fields, con_descs ++ [ncdesc]}
end
defp chomp(fields) do
result = Enum.split(fields, 1)
result
end
end
defmodule Ibex.Contracts.Storage do
use GenServer
require Logger
@initial_state %{contracts: %{}}
def start_link(_) do
Logger.info("Starting Inbound as a supervised process...")
GenServer.start_link(__MODULE__, @initial_state, name: __MODULE__)
end
def init(state) do
{:ok, table_name} = :dets.open_file(:disk_storage, [type: :set])
state = Map.put(state, :dets_table, table_name)
{:ok, state}
end
def handle_info({:add_contract, contract_description}, state) do
symbol = contract_description.contract.symbol
case :dets.lookup(state[:dets_table], symbol) do
[{^symbol, value}] ->
Logger.info("Appending contract to symbol #{symbol}")
value = Map.put(value, contract_description.contract.con_id, contract_description)
:dets.insert(state[:dets_table], {symbol, value})
[] ->
Logger.info("First contract for symbol #{contract_description.contract.symbol}")
:dets.insert(state[:dets_table], {contract_description.contract.symbol, %{}})
end
{:noreply, state}
end
def handle_call({:get_table_name}, _from, state) do
{:reply, state[:dets_table], state}
end
end
defmodule Ibex.Contracts.Contract do
defstruct [
:con_id,
:symbol,
:sec_type,
:last_trade_date_or_contract_month,
:strike,
:right,
:multiplier,
:exchange,
:primary_exchange,
:currency,
:localSYmbol,
:trading_class,
:include_expired,
:sec_id_type,
:sec_id,
:description,
:issuer_id,
:combo_legs_descrip,
:combo_legs,
:delta_neutral_contract
]
def t() do
%__MODULE__{}
end
end
defmodule Ibex.Contracts.ContractDescription do
defstruct [:contract, :derivative_sec_types]
def t() do
%__MODULE__{
contract: Ibex.Contracts.Contract.t(),
derivative_sec_types: []
}
end
def insert_derivative_types(cd, derivs) do
Map.put(cd, :derivative_sec_types, derivs)
end
def insert_into_contract(cd, field, value) do
contract = Map.get(cd, :contract)
contract = Map.put(contract, field, value)
Map.put(cd, :contract, contract)
end
end