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
Practical application
The "build once, reuse everywhere" trading app is the heart of this topic. Six files make up the reusable scaffolding.
-
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
ibapiPython package from the IB GitHub release. -
Scaffold the
trading-apppackage. 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), andutils.py(shared constants likeTRADE_BAR_PROPERTIES). -
Wire the connection.
IBApp.__init__callsIBWrapper.__init__(self), thenIBClient.__init__(self, wrapper=self), thenself.connect(ip, port, client_id). Start a daemon thread targetingself.runso the socket message loop runs in the background. Sleep two seconds to let the connection establish before sending requests. -
Add per-feature request/callback pairs. For each capability — historical bars, snapshot, streaming, contract details — override one or more
EWrappercallbacks to write into an instance dict keyed byrequest_id, and add a correspondingIBClientmethod that calls the appropriatereqXXXand waits long enough to accumulate the response. -
Persist streaming data through a sink. Expose
connectionas a@propertythat returns a freshsqlite3.connect(..., isolation_level=None)(autocommit). Runcreate_tableat startup to ensure the schema exists.stream_to_sqliteconsumes 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.
Related lessons
Related concepts
- Async Callbackslinked concept
- Event-Driven Programminglinked concept
- Request-Response Patternlinked concept
- Connection Managementlinked concept
- Paper Tradinglinked concept