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:
| Application | Live Port | Paper Port |
|---|---|---|
| TWS | 7496 | 7497 |
| IB Gateway | 4001 | 4002 |
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
- 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_ordersto reconcile open-order state. The IBKR account snapshot carries no open orders (#371). Onibapi4.2.0 that call is unreliable, and fails for a while after a reconnect: see Known Issues. - Permanent disconnect: when
ibapigives up reconnecting, or fill recovery fails repeatedly,account_streamends withStreamTerminatedwithin about a second. Its reader thread then stays blocked until TWS sends another event, and anotheraccount_streamcall 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 andaccount_streamworks again. After a shutdown, no event follows, so replace the client. Shutdown is detected only while a stream is open, soaccount_streamon a client that has already shut down returns a stream that never ends; seeExecutionClient::account_stream. Replacing the client, by reconnecting withIbkrClient::connect_syncand choosing the client ID, is the caller’s decision. A newIbkrClientdoes not know the orders the old one placed, so their later events are dropped. - Stale state cleanup: Periodically call
IbkrClient::clear_stale_executions,IbkrClient::clear_stale_order_ids, andIbkrClient::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
ConnectionResetper response type: three on the open-orders queue, two on the positions queue. - Every
OpenOrderandOrderStatusmessage for an order with no live placement subscription is copied intoibapi’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_orderscan 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_snapshotfails 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_ordersis 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.ibapilogs 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
- IB API Documentation
rustrade_data::exchange::ibkrfor market data
Re-exports§
pub use order::BracketOrderRequest;pub use order::BracketOrderResult;pub use execution::parse_ib_timestamp;
Modules§
Structs§
- Contract
Config - Pre-configured contract for startup registration.
- Ibkr
Client - Interactive Brokers execution client.
- Ibkr
Config - Configuration for the IBKR execution client.