Skip to main content

Module ibkr

Module ibkr 

Source
Expand description

Interactive Brokers ExecutionClient implementation.

Uses the ibapi crate for IB TWS/Gateway connectivity. Supports equities, futures, options, and forex.

§Testing Status

NOT TESTED in CI. IBKR has not confirmed permission to use credentials for CI, and requires IB Gateway/TWS running locally.

Tested locally: All execution tests (connection, orders, account streaming) run on paper trading accounts — no market data subscriptions required, except for shortability.

§Connection

Requires TWS or IB Gateway running locally with API enabled:

ApplicationLive PortPaper Port
TWS74967497
IB Gateway40014002

Enable API in TWS/Gateway: Configure → API → Settings → Enable ActiveX and Socket Clients. For order placement, uncheck “Read-Only API”.

§Architecture

  • Connection: TCP socket to TWS/Gateway
  • Orders: Subscription-based events (OrderStatus, ExecutionData, CommissionReport)
  • Account: Subscription-based position and balance updates

§Balances and carrying costs

Balances are read from IB’s account summary, one per currency in IB’s ledger ($LEDGER:ALL), named by the currency’s code. Every account the login manages is summed per currency. ExecutionClient::account_stream delivers no balances.

  • total is the currency’s TotalCashBalance (else CashBalance): its cash, negative while the account borrows it. It is not converted into the base currency.
  • accrued (Balance::accrued) is the currency’s AccruedCash: interest accrued this month and not yet posted, positive when the account earns it.
  • free is total, capped at the account’s AvailableFunds converted into the currency at IB’s ledger rate. IB reports AvailableFunds only for the whole account, so every currency is capped by the same funds and the free values do not add up. It is negative when total is. With several accounts, each is capped by its own funds before they are summed. Without a rate to convert the funds, free is total, logged at WARN.

The ledger’s BASE row, every currency converted into the base currency and summed, is not returned.

As IB documents it, interest accrues daily through the month in AccruedCash and is posted to cash at the month’s end or in the first days of the next. Until then total leaves it out, so the cash the account will hold is total + accrued. Stock borrow fees accrue daily too; IB does not document whether they accrue in AccruedCash or when they post. Fills carry none of these charges, so PnL computed from them excludes them too.

§Fill fees

A fill’s Trade::fees is the commission of the CommissionReport IB sends for its execution, in that report’s currency, unchanged. That figure includes the regulatory fees IB passes through and any tax IB charges on the commission itself, such as GST. Taxes IB bills separately from commissions, such as stamp duty or a financial transaction tax, are not known to be included, so on markets that levy them fees may understate what a fill cost. A commission that is not a finite number, or is too large for a Decimal, becomes a zero fee, logged at WARN; IB should never send one for a real fill. An execution whose report IB did not send has a zero fee in UNKNOWN_FEE_ASSET.

§Shortability

ShortabilityClient reads what IB says about lending a stock from its shortable market data ticks, so unlike the rest of this client it uses one of the account’s market data lines while it runs. It asks for IB’s delayed data, which IB serves only where the account has no live subscription, so an account without one still gets an answer. See fetch_shortability under IbkrClient’s trait implementations, and IbkrConfig::shortability_timeout.

§Limitations

  • Order types: Market, Limit, Stop, StopLimit, TrailingStop, TrailingStopLimit, and Bracket (entry + take-profit + stop-loss) supported. No Algo orders.
  • TimeInForce: No post_only (IB has no maker-only orders)
  • Client order ids travel as each order’s IB order reference, so they are at most 128 ASCII characters

§Reconnection & Recovery

ibapi reconnects its socket to TWS/Gateway by itself, with the same client ID. In-flight requests fail with ConnectionReset at the drop, and new sends are refused until the reconnect completes. Order sends classify that as a transient OrderError::Connectivity. If the reconnect fails, ibapi shuts the client down.

ExecutionClient::account_stream stays open across a successful reconnect. It detects the gap and, once delivery is restored, recovers the fills TWS sent while the socket was down, emitting each exactly once: as a Trade, or as a TradeAmended when it corrects an earlier execution. It then reports how each order it held as live ended during the gap, if it did. The same applies when TWS loses its own link to IB’s servers (notice 1100) and later restores it (1101/1102). See that method for the details.

Each order carries its client order id as its IB order reference, which IB lists with the order, its executions and its completion. So a client finds its orders by the id they were placed with even across a restart: ExecutionClient::account_snapshot and ExecutionClient::fetch_open_orders list them under it and track them again, and OrderStatusClient finds how they ended.

Fills of orders that other API clients or TWS placed on the account are reported only when IbkrConfig::other_clients_fills is set, as trades of origin External. A fill IB flags as an IB-initiated liquidation is reported either way, as a Liquidation, on the stream, by fill recovery and by fetch_trades, whenever IB sends it to this connection.

§Caller Responsibilities

  1. Fill reconciliation: when fill recovery fails repeatedly, the stream reports the gap as AccountEventKind::FillRecoveryGaveUp and stays open. Read the gap’s fills with ExecutionClient::fetch_trades. A VenueRetention gap is the part IB no longer holds: fetch_trades cannot read it, so reconcile it from another source.
  2. Permanent disconnect: when ibapi gives up reconnecting, IbkrClient::disconnect is called, or TWS/Gateway ends the API session, account_stream ends with StreamTerminated, and account_stream on the shut-down client fails, so replace the client, by reconnecting with IbkrClient::connect_sync and choosing the client ID: the caller’s decision. The old client’s connection is closed in each case, so TWS/Gateway frees its ID once it has processed the close (see IbkrClient::disconnect). A new IbkrClient tracks the orders the old one placed once an account snapshot or fetch_open_orders lists them; until then their events are dropped.
  3. Stale state cleanup: Periodically call IbkrClient::clear_stale_executions, IbkrClient::clear_stale_order_ids, and IbkrClient::clear_stale_pending_cancels

Rationale: IBKR uses TCP to local TWS/Gateway, not cloud WebSocket. ibapi owns the transient reconnect. Replacing a client that is gone for good requires IB Gateway availability and client ID coordination, decisions that belong in the caller’s wrapper, not the library.

§See Also

Re-exports§

pub use order::BracketOrderRequest;
pub use order::BracketOrderResult;
pub use execution::parse_ib_timestamp;

Modules§

account
contract
Contract builders for IB integration.
execution
order

Structs§

ConnectOutcome
The result of IbkrClient::connect_sync_lenient: the connected client, and the configured contracts it connected without.
ContractConfig
Pre-configured contract for startup registration.
IbkrClient
Interactive Brokers execution client.
IbkrConfig
Configuration for the IBKR execution client.
SkippedContract
A contract in IbkrConfig::contracts that could not be registered when the client connected.

Enums§

ContractSkipReason
Why a configured contract could not be registered. Each variant carries the error of the step that failed: building, resolving or registering it.
IbkrConnectError
Why IbkrClient::connect_sync failed.

Constants§

DEFAULT_SHORTABILITY_TIMEOUT
How long IbkrClient::fetch_shortability waits for IB’s shortability ticks, unless IbkrConfig::shortability_timeout says otherwise.