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.

§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

§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)
  • Order lifecycle events missed during a gap are not recovered (see below)

§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 as a Trade exactly once. 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.

§Caller Responsibilities

  1. Order reconciliation: order lifecycle events sent during a gap are lost. An order cancelled, expired or rejected in that window is not reported. After a reconnect, call ExecutionClient::fetch_open_orders to reconcile open-order state. The IBKR account snapshot carries no open orders (#371). On ibapi 4.2.0 that call is unreliable, and fails for a while after a reconnect: see Known Issues.
  2. Permanent disconnect: when ibapi gives up reconnecting, or fill recovery fails repeatedly, account_stream ends with StreamTerminated within about a second. Its reader thread then stays blocked until TWS sends another event, and another account_stream call on the client fails until the thread exits. After recovery fails, the client is still connected, so the reader exits on the next TWS event and account_stream works again. After a shutdown, no event follows, so replace the client. Shutdown is detected only while a stream is open, so account_stream on a client that has already shut down returns a stream that never ends; see ExecutionClient::account_stream. Replacing the client, by reconnecting with IbkrClient::connect_sync and choosing the client ID, is the caller’s decision. A new IbkrClient does not know the orders the old one placed, so their later 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.

§Known Issues: ibapi 4.2.0 Shared Queues

ibapi 4.2.0 answers the open-orders and positions requests from one queue per request type. Every call on the client reads from the same queue, and nothing clears it between calls. Besides each call’s own reply, two things land in it:

  • A connection drop queues one ConnectionReset per response type: three on the open-orders queue, two on the positions queue.
  • Every OpenOrder and OrderStatus message for an order with no live placement subscription is copied into ibapi’s three open-orders queues. That covers every update for an order this client placed, once its placement call has returned, and every row of an open-orders reply.

As a result:

  • ExecutionClient::fetch_open_orders can report orders that have since filled or been cancelled as open. Each connection drop makes three calls fail and adds three calls of lag, which never clears. See that method.
  • ExecutionClient::account_snapshot fails twice after each connection drop, then recovers.
  • This client never reads two of the three open-orders queues, and reads the third only when fetch_open_orders is called. They keep a copy of every order update until read, so the two unread ones grow for the life of the client: ibapi’s own reconnect does not clear them. ibapi logs a warning each time one of them passes a multiple of 10,000 queued messages.

Account order events come through a separate channel and are unaffected.

Fixed upstream after 4.2.0 by rust-ibapi#836, which gives each call its own queue and drops messages no call asked for.

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

ContractConfig
Pre-configured contract for startup registration.
IbkrClient
Interactive Brokers execution client.
IbkrConfig
Configuration for the IBKR execution client.