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. On a paper account, a short stock position’s borrow fee, charged for every calendar day from settlement, accrued in the AccruedCash of the stock’s currency, was booked several days late, and posted to that currency’s cash early in the next month as the month’s “Net Short Stock Interest”. So there, until it posted, total left it out and total + accrued included it; a live account may differ. For a short held from 12 October to 6 November 2026, in US Eastern dates:

  • The fees for 13–15 October first appeared on 16 October.
  • October’s total posted to cash on 4 November, the third business day, as IB support had said.
  • No fee for November had appeared when the position was closed at the open on 6 November. The first part appeared by early 7 November, so a fee can still arrive after the position that incurred it is gone.

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
  • No order modification. This client never modifies an open order, though IB allows it. An order modified outside it, such as by hand in TWS, is still reported with the price, quantity and time in force it was placed with (see OrderContext).

§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, and the account reads as a transient ConnectivityError (see Account read errors). 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. The stream reports one as it happens only when IB tags its execution with this client’s ID: ibapi drops a live execution tagged with another client’s, so such a fill reaches only fill recovery and fetch_trades. Which ID IB gives a liquidation’s execution is not documented.

§Account read errors

The account reads (ExecutionClient::account_snapshot, ExecutionClient::fetch_balances, ExecutionClient::fetch_open_orders, ExecutionClient::fetch_trades and OrderStatusClient::fetch_ended_orders) fail with:

  • ConnectivityError::Socket when the connection drops during the read, or ibapi refuses the request while it reconnects. Transient: retry once ibapi has reconnected.
  • ConnectivityError::Timeout when IB sends nothing for 10 seconds before the end of a listing, or does not answer the request for the login’s accounts within 30 seconds. Transient: retry.
  • UnindexedClientError::Internal for any other failure, among them a client ibapi has given up reconnecting or one that has shut down. Retrying cannot help: replace the client (see Caller Responsibilities). Each method names its other cases.

A read that fails returns nothing of what it read: part of a listing is not mistaken for all of it.

account_snapshot sends its request for the login’s accounts only while the client is connected, so it fails at once while the connection is down. When the connection drops between that check and the request, ibapi holds the request while it reconnects, for at most the 30 seconds it gives that request: the snapshot then fails with ConnectivityError::Timeout if the reconnect has not ended by then.

§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.