Second update

[?]
EDe4uae8Qq8PE9CSEr1tKCsAFQ2XmsskNU3CkZx76JUS
Aug 27, 2023, 4:29 AM
WJECS5UEYKERBGCBZROJLHR7LKA2UJOCEIXXCMZWRJXCZM5E7GYAC

Dependencies

Change contents

  • edit in test/message_test.exs at line 16
    [2.432][2.432:433]()
  • replacement in test/message_test.exs at line 26
    [2.734][2.734:811]()
    msg = << msg_code::binary, version::binary, uhh::binary, last::binary >>
    [2.734]
    [2.811]
    msg = <<msg_code::binary, version::binary, uhh::binary, last::binary>>
  • replacement in test/message_test.exs at line 29
    [2.897][2.897:964]()
    assert(made_msg == <<0, 0, 0, 8, 71, 0, 2, 0, 2, 0, 0, 0, 0>>)
    [2.897]
    [2.964]
    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
    [2.1008][2.1008:1056]()
    test_msg = Ibex.Messages.Inbound.ReqIds.t()
    [2.1008]
    [2.1056]
    test_msg = Ibex.Messages.Outbound.ReqIds.t()
  • edit in test/message_test.exs at line 37
    [2.1163]
    [2.1163]
    test "request contract details" do
    test_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
    [2.1262][2.1262:1332]()
    test "greets the world" do
    assert Ibex.hello() == :world
    end
  • replacement in mix.exs at line 17
    [2.1670][2.1670:1706]()
    extra_applications: [:logger]
    [2.1670]
    [2.1706]
    extra_applications: [:logger],
    mod: {Ibex.Application, []}
  • file deletion: historical.ex (----------)
    [2.1974][2.9070:9107](),[2.9107][2.9108:9108]()
    defmodule Ibex.Messages.Historical do
    defmodule EarliestAvailableData do
    defstruct [:instrument, :contract, :what_to_show, :use_rth, :format_date]
    end
    end
  • replacement in lib/messages/message.ex at line 19
    [2.8084][2.8084:8085]()
    [2.8084]
    [2.8085]
    end
  • edit in lib/messages/message.ex at line 21
    [2.8086][2.8086:8090]()
    end
  • edit in lib/messages/message.ex at line 27
    [2.8190]
    [2.8190]
  • edit in lib/messages/message.ex at line 34
    [2.8323]
    [2.8323]
    Logger.debug("Print: #{inspect(fields)}")
  • edit in lib/messages/message.ex at line 39
    [2.8435]
    [2.8435]
  • replacement in lib/messages/message.ex at line 47
    [2.8613][2.8613:8692]()
    Ibex.Messages.Types.Inbound.parse(String.to_integer(code))
    {l, fields}
    [2.8613]
    [2.8692]
    code = Ibex.Messages.Types.Inbound.parse(String.to_integer(code))
    {l, code, fields}
  • edit in lib/messages/message.ex at line 57
    [2.8832][2.8832:8884]()
    import Ibex.Message, only: [message_version: 0]
  • replacement in lib/messages/message.ex at line 60
    [2.8949][2.8949:8988]()
    Logger.debug("Code is: #{code}")
    [2.8949]
    [2.8988]
  • edit in lib/messages/message.ex at line 65
    [2.9043]
    [2.9043]
    ]
    end
    end
    defmodule ReqContractDetails do
    alias Ibex.Messages.Types.Outbound, as: Outbound
    def t(req_id, symbol, sec_type) do
    code = to_string(Outbound.parse(:req_contract_data))
    [
    code,
    <<"8">>,
    req_id,
    symbol,
    sec_type
  • edit in lib/messages/message.ex at line 84
    [2.9065]
    [2.9065]
    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
  • replacement in lib/ibex.ex at line 5
    [2.9376][2.9376:9418]()
    @doc """
    Hello world.
    ## Examples
    [2.9376]
    [2.9418]
    end
  • replacement in lib/ibex.ex at line 7
    [2.9419][2.9419:9495]()
    iex> Ibex.hello()
    :world
    """
    def hello do
    :world
    end
    [2.9419]
    [2.9495]
    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
  • file addition: ibex (d--r------)
    [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 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
  • replacement in lib/ibclient.ex at line 9
    [2.9699][2.9699:9700]()
    [2.9699]
    [2.9700]
    @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
    [2.9844][2.9844:10012]()
    GenServer.start(__MODULE__, %{
    socket: nil,
    conn_state: :disconnected,
    server_api: nil,
    server_connect_time: nil,
    next_id: 0
    })
    [2.9844]
    [2.10012]
    GenServer.start(__MODULE__, @initial_state)
  • edit in lib/ibclient.ex at line 26
    [2.10019]
    [2.10019]
    def start_link(opt) do
    Logger.info("Starting IBClient as a supervised process...")
    GenServer.start_link(__MODULE__, @initial_state, name: __MODULE__)
    end
  • replacement in lib/ibclient.ex at line 38
    [2.10135][2.10135:10604]()
    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
    [2.10135]
    [2.10604]
    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
  • edit in lib/ibclient.ex at line 75
    [2.10923][2.10923:10970]()
    Logger.debug("Requesting account summary")
  • edit in lib/ibclient.ex at line 81
    [2.11137][2.11137:11185]()
    Logger.info("Requesting next valid IDs...")
  • replacement in lib/ibclient.ex at line 84
    [2.11266][2.11266:11311]()
    Logger.debug("Sending: #{inspect(msg)}")
    [2.11266]
    [2.11311]
  • replacement in lib/ibclient.ex at line 86
    [2.11356][2.11356:11380]()
    {:noreply, state}
    [2.11356]
    [2.11380]
    {:noreply, state}
  • replacement in lib/ibclient.ex at line 90
    [2.11432][2.11432:11479]()
    Logger.debug("Received: #{inspect(data)}")
    [2.11432]
    [2.11479]
    <<l::size(32), rest::binary>> = data
    Logger.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
    [2.11651]
    [2.11651]
    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)
  • edit in lib/ibclient.ex at line 114
    [2.11652]
    [2.11652]
    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
    [2.11907]
    [2.11907]
    @spec handle({:disconnect, any}, any) :: {:noreply, any}
  • edit in lib/ibclient.ex at line 150
    [2.12319]
    [2.12319]
  • edit in lib/ibclient.ex at line 159
    [2.12528]
    [2.12528]
  • edit in lib/ibclient.ex at line 163
    [2.12678]
    [2.12678]
  • edit in lib/ibclient.ex at line 172
    [2.12896]
    [2.12896]
  • edit in lib/ibclient.ex at line 176
    [2.13053]
    [2.13053]
  • replacement in lib/ibclient.ex at line 185
    [2.13286][2.13286:13329]()
    {_length, data} = wait_for_data(state)
    [2.13286]
    [2.13329]
    {length, code, data} = wait_for_data(state)
  • replacement in lib/ibclient.ex at line 190
    [2.13381][2.13381:13624]()
    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)}
    [2.13381]
    [2.13624]
    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
    [2.13637]
    [2.13637]
    Logger.info("Resposne from API Version Request was longer: #{inspect(data)}")
  • edit in lib/ibclient.ex at line 201
    [2.13663]
    [2.13663]
  • replacement in lib/ibclient.ex at line 207
    [2.13810][2.13810:13835]()
    wait_for_data(state)
    [2.13810]
    [2.13835]
    {: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))}
  • edit in lib/ibclient.ex at line 219
    [2.13928]
    [2.13928]
    Logger.info("Waiting synchronously for data...")
  • replacement in lib/ibclient.ex at line 227
    [2.14172][2.14172:14212]()
    %{ state | conn_state: :connected }
    [2.14172]
    [2.14212]
    %{state | conn_state: :connected}
  • edit in lib/ibclient.ex at line 230
    [2.14219]
    [2.14219]
    defp next_id(state) do
    next_id = state[:next_id]
    state = %{state | next_id: next_id + 1}
    {next_id, state}
    end
  • file addition: handlers (d--r------)
    [2.1952]
  • file addition: inbound.ex (----------)
    [0.5609]
    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
  • file addition: contracts (d--r------)
    [2.1952]
  • file addition: storage.ex (----------)
    [0.8928]
    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
  • file addition: contract.ex (----------)
    [0.8928]
    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