WJECS5UEYKERBGCBZROJLHR7LKA2UJOCEIXXCMZWRJXCZM5E7GYAC defmodule ReqMatchingSymbols doalias Ibex.Messages.Types.Outbound, as: Outbounddef t(reqId, symbol) docode = to_string(Outbound.parse(:req_matching_symbols))[code,reqId,symbol]endenddefmodule ReqHistoricalData doalias 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) docode = to_string(Outbound.parse(:req_historical_data))contract_fields = contract.to_fields()[code,@version,req_id,contract[:con_id]]endend
iex> Ibex.hello():world"""def hello do:worldend
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 falseuse Applicationrequire Logger@impl truedef start(_type, _args) doLogger.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)endend
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 ->stateend
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 ->stateend
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}enddef 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>> = reststripped = 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 douse GenServerrequire Logger@initial_state %{}def start doLogger.debug("Starting Inbound...")GenServer.start(__MODULE__, @initial_state)enddef start_link(_opt) doLogger.info("Starting Inbound as a supervised process...")GenServer.start_link(__MODULE__, @initial_state, name: __MODULE__)enddef init(state) do{:ok, state}enddef handle_info({:dispatch, fields}, state) domsg_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}enddefp 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 == "" donilelsesend(Ibex.Contracts.Storage, {:add_contract, contract})endend)enddefp chomp(fields) doEnum.split(fields, 1)endenddefmodule Ibex.Contracts.Inbound.SymbolSamples dorequire Loggeralias Ibex.Contracts.ContractDescription, as: CDdef extract_contracts(fields, con_descs) do{fields, con_descs} = extract_contract(fields, con_descs)case length(fields) > 0 dotrue ->extract_contracts(fields, con_descs)false ->con_descsendenddef extract_contract(fields, con_descs) doncdesc = 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, [])}elsent = 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]}enddefp chomp(fields) doresult = Enum.split(fields, 1)resultendend
defmodule Ibex.Contracts.Storage douse GenServerrequire Logger@initial_state %{contracts: %{}}def start_link(_) doLogger.info("Starting Inbound as a supervised process...")GenServer.start_link(__MODULE__, @initial_state, name: __MODULE__)enddef init(state) do{:ok, table_name} = :dets.open_file(:disk_storage, [type: :set])state = Map.put(state, :dets_table, table_name){:ok, state}enddef handle_info({:add_contract, contract_description}, state) dosymbol = contract_description.contract.symbolcase :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}enddef handle_call({:get_table_name}, _from, state) do{:reply, state[:dets_table], state}endend
defmodule Ibex.Contracts.Contract dodefstruct [: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__{}endenddefmodule Ibex.Contracts.ContractDescription dodefstruct [:contract, :derivative_sec_types]def t() do%__MODULE__{contract: Ibex.Contracts.Contract.t(),derivative_sec_types: []}enddef insert_derivative_types(cd, derivs) doMap.put(cd, :derivative_sec_types, derivs)enddef insert_into_contract(cd, field, value) docontract = Map.get(cd, :contract)contract = Map.put(contract, field, value)Map.put(cd, :contract, contract)endend