Set Up the Interactive Brokers Python API

5 min read

Core idea

The Interactive Brokers Python API is not a function-call API; it is a two-party message bus. Your code sends requests through one object (EClient), and responses arrive — asynchronously, on a background thread — through callback methods on a second object (EWrapper). You don't get return values; you override callback methods and stash incoming data in shared state. Every recipe in this topic — connecting to TWS, building Contract and Order objects, fetching historical bars, taking a snapshot, streaming ticks, persisting them to SQLite — is a variation of the same pattern: reqXXX() initiates, xxxData() callback delivers, you bridge the two through instance dictionaries.

Author's framing: TWS exposes its full feature set through an asynchronous, non-blocking API. The request-callback pattern is fundamental — understanding it makes the rest of the IB API straightforward; missing it makes everything feel broken.

Why it matters

Async-callback is not optional, it is architectural

Order books move in milliseconds. A synchronous "wait for response" model would idle the entire process during every network round-trip and serialize requests that should be in flight at once. The IB API's split between EClient (outbound requests, no return values) and EWrapper (inbound callbacks, invoked from the API's background thread) is what makes streaming hundreds of tick subscriptions, an order book request, and an open-order modification possible from a single process. The architecture sidesteps Python's Global Interpreter Lock by keeping network I/O off the main thread.

Inheritance is the bridge

Python doesn't have a native "implement these abstract methods" enforcement, so the IB API's contract is: extend EClient and EWrapper, override the callbacks you care about, and a self.run() loop dispatches incoming socket messages to those overrides. The trading app inherits from both — class IBApp(IBWrapper, IBClient): — giving you a single object that exposes request methods and holds the state populated by callbacks. The pattern is unusual but consistent across every IB integration.

Contracts and Orders are uniform objects across asset classes

IB calls everything tradable a Contract — stocks, options, futures, forex, bonds. The secType attribute (STK, OPT, FUT) disambiguates; attributes like strike, right, lastTradeDateOrContractMonth come into play only for derivatives. The Order object has a similar shape: action (BUY/SELL), orderType (MKT/LMT/STP), totalQuantity, and type-specific extras (lmtPrice, auxPrice, tif). Writing factory functions like stock(symbol, exchange, currency), future(...), option(...), and market(action, qty), limit(...) keeps the asset-class details out of strategy code.

Historical, snapshot, and streaming data are three flavors of the same pattern

Historical bars arrive one bar at a time via historicalData(req_id, bar). Snapshots arrive once via tickPrice(req_id, tick_type, price, attrib). Streaming ticks arrive continuously via tickByTickBidAsk(req_id, time, bid, ask, ...). Each handler keys into a dictionary by request_id and accumulates state. The streaming variant adds a threading.Event to signal the consumer (a generator yielding the latest tick) that new data is ready. Once you understand one, you understand all three.

Storage is downstream and decoupled

Tick streams turn into bytes quickly. Persisting to SQLite during the stream — via a connection property on the trading app and a stream_to_sqlite method that consumes the streaming generator — is the textbook pattern for bounded-rate persistence without dropping data. The same shape generalizes to any sink: ArcticDB, Kafka, S3 batch dumps. The trading app holds the connection; the streaming generator stays a pure producer.

Key takeaways

Mental model

Mental model

Practical application

The "build once, reuse everywhere" trading app is the heart of this topic. Six files make up the reusable scaffolding.

  1. Install TWS and the Python API. Download Trader Workstation, log in (paper account works), and enable Enable ActiveX and Socket Clients under Edit → Global Configuration → API → Settings. Note the port (7497 for paper, 7496 for live). Restrict to localhost for security. Install the ibapi Python package from the IB GitHub release.

  2. Scaffold the trading-app package. Create __init__.py, client.py (IBClient — outbound), wrapper.py (IBWrapper — inbound), app.py (IBApp — combines them), contract.py (factory functions for stock/future/option contracts), order.py (factory functions for market/limit/stop orders), and utils.py (shared constants like TRADE_BAR_PROPERTIES).

  3. Wire the connection. IBApp.__init__ calls IBWrapper.__init__(self), then IBClient.__init__(self, wrapper=self), then self.connect(ip, port, client_id). Start a daemon thread targeting self.run so the socket message loop runs in the background. Sleep two seconds to let the connection establish before sending requests.

  4. Add per-feature request/callback pairs. For each capability — historical bars, snapshot, streaming, contract details — override one or more EWrapper callbacks to write into an instance dict keyed by request_id, and add a corresponding IBClient method that calls the appropriate reqXXX and waits long enough to accumulate the response.

  5. Persist streaming data through a sink. Expose connection as a @property that returns a fresh sqlite3.connect(..., isolation_level=None) (autocommit). Run create_table at startup to ensure the schema exists. stream_to_sqlite consumes the streaming generator in a loop and inserts each tick until a time budget expires.

Example

You are building a system that needs to monitor the bid-ask spread on twenty optionable stocks all day, in real time, while also pulling end-of-day historical bars for a separate factor refresh. With the request-callback architecture, both flows coexist in one Python process:

The factor refresh calls get_historical_data(req_id=1..20, contract, '1 D', '1 min') for each ticker sequentially. Each request returns through the same historicalData callback, but request_id keys into a different dictionary slot. While these are in flight, twenty reqTickByTickData(req_id=21..40, contract, 'BidAsk', ...) subscriptions are already running, populating a streaming_data dictionary continuously through the tickByTickBidAsk callback. A threading.Event per subscription notifies a consumer generator that yields the latest tick. That generator feeds a stream_to_sqlite consumer that writes a row to bid_ask_data for every tick.

Three concurrent flows, one socket, one thread, zero blocking. The request-callback pattern is what makes that geometry possible. A synchronous API would force you to serialize the historical pulls behind the streaming subscriptions or spawn twenty processes, each holding its own TWS connection — which TWS doesn't even allow past thirty-two parallel clients.

Continue exploring

Tags