Second update
[?]
EDe4uae8Qq8PE9CSEr1tKCsAFQ2XmsskNU3CkZx76JUS
Aug 27, 2023, 4:29 AM
WJECS5UEYKERBGCBZROJLHR7LKA2UJOCEIXXCMZWRJXCZM5E7GYACDependencies
- [2]
C56DKOEPInitial commit
Change contents
- edit in test/message_test.exs at line 16
- replacement in test/message_test.exs at line 26
msg = << msg_code::binary, version::binary, uhh::binary, last::binary >>msg = <<msg_code::binary, version::binary, uhh::binary, last::binary>> - replacement in test/message_test.exs at line 29
assert(made_msg == <<0, 0, 0, 8, 71, 0, 2, 0, 2, 0, 0, 0, 0>>)assert(made_msg == <<0, 0, 0, 9, 71, 0, 2, 0, 2, 0, 0, 0, 0>>) - replacement in test/message_test.exs at line 33
test_msg = Ibex.Messages.Inbound.ReqIds.t()test_msg = Ibex.Messages.Outbound.ReqIds.t() - edit in test/message_test.exs at line 37
test "request contract details" dotest_msg = Ibex.Messages.Outbound.ReqContractDetails.t("1001", "AAPL", "STK")converted = IBMessage.to_ib_api(test_msg)assert(converted ==<<0, 0, 0, 19, 49, 48, 48, 49, 0, 56, 0, 57, 0, 65, 65, 80, 76, 0, 83, 84, 75, 0, 0>>)end - edit in test/ibex_test.exs at line 4
test "greets the world" doassert Ibex.hello() == :worldend - replacement in mix.exs at line 17
extra_applications: [:logger]extra_applications: [:logger],mod: {Ibex.Application, []} - file deletion: historical.ex
defmodule Ibex.Messages.Historical dodefmodule EarliestAvailableData dodefstruct [:instrument, :contract, :what_to_show, :use_rth, :format_date]endend - replacement in lib/messages/message.ex at line 19
end - edit in lib/messages/message.ex at line 21
end - edit in lib/messages/message.ex at line 27
- edit in lib/messages/message.ex at line 34
Logger.debug("Print: #{inspect(fields)}") - edit in lib/messages/message.ex at line 39
- replacement in lib/messages/message.ex at line 47
Ibex.Messages.Types.Inbound.parse(String.to_integer(code)){l, fields}code = Ibex.Messages.Types.Inbound.parse(String.to_integer(code)){l, code, fields} - edit in lib/messages/message.ex at line 57
import Ibex.Message, only: [message_version: 0] - replacement in lib/messages/message.ex at line 60
Logger.debug("Code is: #{code}") - edit in lib/messages/message.ex at line 65
]endenddefmodule ReqContractDetails doalias Ibex.Messages.Types.Outbound, as: Outbounddef t(req_id, symbol, sec_type) docode = to_string(Outbound.parse(:req_contract_data))[code,<<"8">>,req_id,symbol,sec_type - edit in lib/messages/message.ex at line 84
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 - replacement in lib/ibex.ex at line 5
@doc """Hello world.## Examplesend - replacement in lib/ibex.ex at line 7
iex> Ibex.hello():world"""def hello do:worldenddefmodule 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 - file addition: ibex[2.1952]
- file addition: application.ex[0.2223]
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 - replacement in lib/ibclient.ex at line 9
@initial_state %{socket: nil,conn_state: :disconnected,server_api: nil,server_connect_time: nil,next_id: 0,inbound_handler: nil} - replacement in lib/ibclient.ex at line 23
GenServer.start(__MODULE__, %{socket: nil,conn_state: :disconnected,server_api: nil,server_connect_time: nil,next_id: 0})GenServer.start(__MODULE__, @initial_state) - edit in lib/ibclient.ex at line 26
def start_link(opt) doLogger.info("Starting IBClient as a supervised process...")GenServer.start_link(__MODULE__, @initial_state, name: __MODULE__)end - replacement in lib/ibclient.ex at line 38
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 ->stateendcase 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 - edit in lib/ibclient.ex at line 75
Logger.debug("Requesting account summary") - edit in lib/ibclient.ex at line 81
Logger.info("Requesting next valid IDs...") - replacement in lib/ibclient.ex at line 84
Logger.debug("Sending: #{inspect(msg)}") - replacement in lib/ibclient.ex at line 86
{:noreply, state}{:noreply, state} - replacement in lib/ibclient.ex at line 90
Logger.debug("Received: #{inspect(data)}")<<l::size(32), rest::binary>> = dataLogger.debug("Data is: #{inspect(data)}. Length is: #{inspect(l)}")fields = String.split(rest, <<0>>)send(Ibex.Inbound.Handler, {:dispatch, fields}) - edit in lib/ibclient.ex at line 99
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) - edit in lib/ibclient.ex at line 114
msg =Ibex.Messages.Outbound.ReqMatchingSymbols.t(to_string(req_id), symbol)|> IBMessage.to_ib_api():ok = :gen_tcp.send(state[:socket], msg){:noreply, state}end@doc """Catch-call for handle_info""" - edit in lib/ibclient.ex at line 135
@spec handle({:disconnect, any}, any) :: {:noreply, any} - edit in lib/ibclient.ex at line 150
- edit in lib/ibclient.ex at line 159
- edit in lib/ibclient.ex at line 163
- edit in lib/ibclient.ex at line 172
- edit in lib/ibclient.ex at line 176
- replacement in lib/ibclient.ex at line 185
{_length, data} = wait_for_data(state){length, code, data} = wait_for_data(state) - replacement in lib/ibclient.ex at line 190
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)} - edit in lib/ibclient.ex at line 198
Logger.info("Resposne from API Version Request was longer: #{inspect(data)}") - edit in lib/ibclient.ex at line 201
- replacement in lib/ibclient.ex at line 207
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))} - edit in lib/ibclient.ex at line 219
Logger.info("Waiting synchronously for data...") - replacement in lib/ibclient.ex at line 227
%{ state | conn_state: :connected }%{state | conn_state: :connected} - edit in lib/ibclient.ex at line 230
defp next_id(state) donext_id = state[:next_id]state = %{state | next_id: next_id + 1}{next_id, state}end - file addition: handlers[2.1952]
- file addition: inbound.ex[0.5609]
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 - file addition: contracts[2.1952]
- file addition: storage.ex[0.8928]
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 - file addition: contract.ex[0.8928]
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