Why Interactive Brokers?
Interactive Brokers (IBKR) is the dominant retail and small-fund algorithmic trading broker globally. Three reasons:
- Coverage. Access to 150+ exchanges across 33 countries, plus options, futures, FX, bonds, mutual funds and crypto.
- Cost. Per-share fees ($0.005/share) are competitive even for high-frequency strategies; for retail-scale algo trading, costs are typically 30-70% lower than at most competitors.
- API quality. The API has flaws (which we'll cover) but it's mature, well-documented, and supports virtually every order type and asset class IB offers.
This tutorial covers the practical realities of connecting to IB's API in 2026, with Python code examples for the most common workflows. For broader broker comparison, see our best brokers for algo trading.
Account Setup
Before writing code, you need:
- An IB account. Either Individual or Pro. Pro accounts have lower data fees and better API access; Individual accounts work for non-professional traders.
- TWS (Trader Workstation) or IB Gateway. Both bridge between your code and IB's servers. Gateway is lighter (no GUI) and preferred for algo trading.
- API enabled in your account settings. In TWS/Gateway: Edit > Global Configuration > API > Settings. Tick "Enable ActiveX and Socket Clients."
- Set the API port. Default is 7497 for paper trading TWS, 7496 for live TWS, 4002 for paper Gateway, 4001 for live Gateway. Note these for your connection code.
For testing, always start with paper trading. IB provides full paper accounts that mirror live functionality without risking real money.
Two Python Libraries
You have two main choices for Python:
ib_insync (recommended for most users)
A friendly Python wrapper around the underlying API. Maintained by Ewald de Wit. Significantly easier to use than the native API.
pip install ib_insync
ibapi (the native API)
IB's official Python library. More verbose; closer to how the underlying API actually works. Use if you need very fine control or are integrating with existing IB-native code.
pip install ibapi # Or download from IB and install manually for the most current version
This tutorial uses ib_insync for clarity. The patterns translate to ibapi with more boilerplate.
Step 1: Connect
from ib_insync import IB ib = IB() # Paper trading TWS default port ib.connect('127.0.0.1', 7497, clientId=1) print(ib.isConnected()) # True if successful
Notes:
clientIdis a unique integer per connection. If you connect from multiple scripts, each needs a different ID.- If TWS/Gateway isn't running, connection fails immediately.
- The API connection is over TCP localhost; nothing leaves your machine until TWS/Gateway sends it to IB.
Step 2: Get Account Information
account_summary = ib.accountSummary() for tag in account_summary: if tag.tag == 'NetLiquidation': print(f"Account value: ${tag.value}") positions = ib.positions() for p in positions: print(f"{p.contract.symbol}: {p.position} shares")
accountSummary() returns dozens of fields (cash, margin, P&L, etc.). positions() returns current holdings.
Step 3: Define a Contract
A "contract" in IB terminology is the financial instrument you want to trade.
US stock
from ib_insync import Stock apple = Stock('AAPL', 'SMART', 'USD') ib.qualifyContracts(apple)
SMART is IB's smart routing exchange. qualifyContracts confirms with IB that the contract is valid.
Option
from ib_insync import Option apple_call = Option('AAPL', '20260117', 200, 'C', 'SMART') ib.qualifyContracts(apple_call)
'20260117' is the expiry (YYYYMMDD), 200 is the strike, 'C' for call.
Future
from ib_insync import Future es = Future('ES', '20260321', 'CME') # March 2026 S&P futures ib.qualifyContracts(es)
FX
from ib_insync import Forex eurusd = Forex('EURUSD') ib.qualifyContracts(eurusd)
Step 4: Get Market Data
Snapshot quote
ticker = ib.reqMktData(apple) ib.sleep(2) # Wait for data print(f"Bid: {ticker.bid}, Ask: {ticker.ask}, Last: {ticker.last}") ib.cancelMktData(apple)
Streaming quotes
ticker = ib.reqMktData(apple) def on_update(t): print(f"{t.contract.symbol}: bid={t.bid}, ask={t.ask}") ticker.updateEvent += on_update # Stream for 30 seconds ib.sleep(30) ticker.updateEvent -= on_update ib.cancelMktData(apple)
Historical bars
bars = ib.reqHistoricalData( apple, endDateTime='', durationStr='30 D', barSizeSetting='1 day', whatToShow='TRADES', useRTH=True ) import pandas as pd df = pd.DataFrame(bars) print(df.head())
whatToShow options: TRADES, BID, ASK, MIDPOINT, etc. useRTH=True restricts to regular trading hours.
Step 5: Place an Order
Market order
from ib_insync import MarketOrder order = MarketOrder('BUY', 100) trade = ib.placeOrder(apple, order) # Wait for fill ib.sleep(5) print(f"Status: {trade.orderStatus.status}") print(f"Filled: {trade.orderStatus.filled}") print(f"Avg fill price: {trade.orderStatus.avgFillPrice}")
Limit order
from ib_insync import LimitOrder order = LimitOrder('BUY', 100, 195.50) trade = ib.placeOrder(apple, order)
Bracket order (parent + take-profit + stop-loss)
parent = LimitOrder('BUY', 100, 195.50) parent.transmit = False # Don't send yet take_profit = LimitOrder('SELL', 100, 205.00) take_profit.parentId = parent.orderId take_profit.transmit = False stop_loss = StopOrder('SELL', 100, 190.00) stop_loss.parentId = parent.orderId stop_loss.transmit = True # Send all three when this is placed ib.placeOrder(apple, parent) ib.placeOrder(apple, take_profit) ib.placeOrder(apple, stop_loss)
Cancel an order
ib.cancelOrder(trade.order)
Step 6: Handle Fills and Errors
Subscribe to fills
def on_fill(trade, fill): print(f"Filled: {fill.execution.shares} @ {fill.execution.price}") ib.fillEvent += on_fill
Subscribe to errors
def on_error(reqId, errorCode, errorString, contract): print(f"ERROR {errorCode}: {errorString}") ib.errorEvent += on_error
Common error codes to watch for:
- 104 - Cannot modify a filled order. Common when retrying.
- 201 - Order rejected. Read the message - usually margin or risk-limit related.
- 2103 - Market data farm connection issue. Usually self-resolves.
- 10089 - Requested market data is not subscribed. You need to subscribe to that data feed.
Step 7: Common Patterns
Wait for connection
def wait_for_connection(ib, timeout=30): start = time.time() while not ib.isConnected() and time.time() - start < timeout: ib.sleep(0.5) if not ib.isConnected(): raise TimeoutError("Could not connect to IB")
Reconnect on disconnect
def on_disconnect(): print("Disconnected. Reconnecting...") ib.connect('127.0.0.1', 7497, clientId=1) ib.disconnectedEvent += on_disconnect
Place an order with retry
def safe_place(contract, order, retries=3): for attempt in range(retries): try: trade = ib.placeOrder(contract, order) ib.sleep(0.5) if trade.orderStatus.status not in ('Cancelled', 'ApiCancelled'): return trade except Exception as e: print(f"Attempt {attempt+1} failed: {e}") ib.sleep(1) raise RuntimeError(f"Could not place order after {retries} retries")
Common Pitfalls
1. Pacing violations
IB throttles API requests. Common limits:
- 50 messages per second
- 60 historical data requests per 10 minutes
- 100 messages per second total
Hitting these triggers errors. Add throttling for high-volume use cases:
from time import sleep for symbol in symbols: bars = ib.reqHistoricalData(...) sleep(0.2) # Stay under request rate
2. Market data subscriptions
IB requires you to subscribe (and pay) for market data feeds. Most casual algo traders only need US Equities (free for $30+/month account value). Options data adds cost; international markets add cost.
3. Threading
ib_insync is asyncio-based. Don't mix with threading without care. If you need parallel work, use asyncio.gather() or separate IB instances with different clientIds.
4. Time zones
IB times are UTC by default. Convert carefully:
from zoneinfo import ZoneInfo ny = ZoneInfo('America/New_York') local_time = bar.date.astimezone(ny)
5. Don't run live code accidentally
Always check: connect to port 7497 (paper) when developing, not 7496 (live). One bad clientId mix-up can place real orders.
Where to Go From Here
For complete algo trading workflows beyond just the IB API:
- Backtesting platforms comparison - choose a platform that integrates with IB
- QuantConnect review - QuantConnect uses IB as its primary live broker
- Best brokers for algo trading - alternatives if IB doesn't fit
- Pairs trading tutorial - example strategy implementation
- Statistical arbitrage strategies - more complex strategy patterns
For broader Python skills:
Skip the £25k programme - try the alternative
Master's programmes are slow and expensive. Quantt is a self-paced alternative built around the actual skills firms hire for: Python, mathematics, derivatives pricing, options Greeks and live trading-system design. 50+ courses, interactive coding tests, and the same end-state as a one-year MFE - in your evenings.
No prerequisites - start at any level