Connect to IBKR with Python

Python
API
Downloading data
Code
This post shows how to connect Python to Interactive Brokers with ib_async and download historical prices into pandas.
Published

July 19, 2026

Source: Image by author using ChatGPT

With a few lines of Python, you can connect to an Interactive Brokers account. The same connection can download market data, read account information, and, if you choose, automate a trading strategy.

The connection runs through Trader Workstation. Your Python code sends requests to the desktop app on your computer, and Trader Workstation passes them to IBKR. The diagram above shows the three parts.

In this post, I will show how to make that connection with ib_async and download daily stock prices into a pandas DataFrame. I use a paper account and its default port, 7497, throughout. The same code works with a live account when Trader Workstation is logged into the live session and the connection uses its configured live port, normally 7496. The IP address remains 127.0.0.1 when Python and Trader Workstation run on the same computer.

The code requires Trader Workstation to be open and logged in, so it is shown but not run when this page is built. Every request in this tutorial is read-only.

What you end up with

  • A Python connection to an IBKR paper account
  • Daily bars for a stock, in a pandas DataFrame
  • The common mistakes on a first connection, and the fix for each

Step 1. Import and connect

IB() creates the object Python uses to communicate with Trader Workstation. ib.connect(...) opens the connection: host tells Python where Trader Workstation runs, port selects the session, and clientId identifies the connection.

In a Jupyter notebook, add util.startLoop() so the notebook can continue running while it waits for replies from Trader Workstation. Leave this line out in an ordinary .py file.

from ib_async import IB, Stock, Forex, util

util.startLoop()          # let ib_async use the notebook's event loop (Jupyter only)

Install both with pip install ib_async pandas. ib_async is the maintained successor to ib_insync. pandas is a separate install: ib_async does not depend on it, but util.df() needs it to build a DataFrame.

Now connect. Open Trader Workstation in paper mode and enable the API first: Configure, then API, then Settings, then Enable ActiveX and Socket Clients. Note the socket port; for paper trading it defaults to 7497. Leave Read-Only API enabled. That setting is what blocks orders submitted through the API.

ib = IB()
ib.connect(
    "127.0.0.1",        # the computer running Trader Workstation
    7497,               # the socket port set in Trader Workstation
    clientId=1,
    readonly=True,      # TWS is configured in read-only mode
)
ib.reqMarketDataType(3) # allow delayed data when live data is unavailable

7496 is the default port for live trading, and IB Gateway defaults to 4002 for paper and 4001 for live. Whichever you use, the port in the code must match the socket port set inside Trader Workstation. clientId is a label for the connection: any non-negative integer, as long as it is unique among the connections Trader Workstation has open at once.

Step 2. Download the price history

First, identify the stock. Stock("AAPL", "SMART", "USD") means Apple shares in US dollars, using IBKR’s SMART routing. ib.qualifyContracts(aapl) matches this description to IBKR’s exact contract, while util.df(bars) converts the downloaded prices into a pandas DataFrame.

aapl = Stock("AAPL", "SMART", "USD")   # the contract: which instrument
ib.qualifyContracts(aapl)               # fill in IBKR's official contract details

bars = ib.reqHistoricalData(
    aapl,
    endDateTime="",             # empty means up to now
    durationStr="1 Y",          # how far back
    barSizeSetting="1 day",     # one bar per trading day
    whatToShow="ADJUSTED_LAST", # closes adjusted for splits and dividends
    useRTH=True,                # regular hours only
)
df = util.df(bars)              # the bars as a pandas DataFrame

The result has one row per trading day, with the date, open, high, low, close and volume. durationStr="1 Y" requests one year, while barSizeSetting="1 day" creates daily rows. endDateTime="" ends the sample at the current time.

whatToShow="ADJUSTED_LAST" adjusts prices for splits and dividends; "TRADES" adjusts for splits only. For return calculations, use ADJUSTED_LAST. useRTH=True keeps regular trading hours.

reqMarketDataType(3) lets Trader Workstation fall back to delayed data when live data is unavailable. IBKR supports delayed data for historical requests as well as streaming quotes, though availability depends on the instrument and on the account’s permissions. A linked paper account can also share the live user’s subscriptions. This request worked on my paper account; if yours returns no bars or a subscription error, check the account’s market-data permissions.

A note on account currency, before you size positions

The steps above are enough to download prices. Before using them to size positions, check the account currency. managedAccounts() lists the accounts this login can see, and accountSummary(account) returns the values for one of them.

accounts = ib.managedAccounts()
print(accounts)                     # every account this login can see

account = accounts[0]               # one paper login usually lists one account
nlv = next(v for v in ib.accountSummary(account) if v.tag == "NetLiquidation")

A single paper login usually lists one account, so accounts[0] is that account. If the printed list shows several, set the account yourself instead, for example account = "DU1234567", so the value cannot come from the wrong one.

nlv.value is the account value and nlv.currency its currency. Position sizing only works when the account value and the stock price use the same currency. Paper accounts are often held in USD, in which case no conversion is needed. For a SEK account buying a US stock, convert first. IBKR returns nlv.value as text, so float() turns it into a number.

account_usd = float(nlv.value)

if nlv.currency != "USD":                                    # a USD account needs no conversion
    inverted = nlv.currency in {"EUR", "GBP", "AUD", "NZD"}  # these are listed as EURUSD, not USDEUR
    pair = Forex(nlv.currency + "USD") if inverted else Forex("USD" + nlv.currency)
    ib.qualifyContracts(pair)
    fx = ib.reqHistoricalData(pair, "", "1 D", "1 day", "MIDPOINT", useRTH=False)
    if not fx:
        raise RuntimeError(f"no {nlv.currency} exchange rate returned")
    rate = float(fx[-1].close)
    # USDSEK is SEK per 1 USD, so divide; EURUSD is USD per 1 EUR, so multiply
    account_usd = float(nlv.value) * rate if inverted else float(nlv.value) / rate

At a USDSEK rate of 10.5, 250,000 SEK is about 23,800 USD. The direction matters, because IBKR lists each pair once. USDSEK is SEK per 1 USD, so you divide. EUR, GBP, AUD and NZD are listed the other way round, as EURUSD, which is USD per 1 EUR, so you multiply. USDEUR is not a contract at all, which is why the code chooses the pair before it chooses the arithmetic. When you are finished, close the connection.

ib.disconnect()

What connecting does not get you

A connection is not market-data access. It only opens the line between Python and TWS or IB Gateway. What each request returns still depends on your subscriptions. Delayed quotes and historical bars may be available without a live subscription, while non-delayed data requires the relevant subscription.

Paper trading is still a simulation. A paper account can share the live account’s data subscriptions, but its order fills are simulated. It is useful for testing code and logic, not slippage or execution quality.

TWS or IB Gateway must stay running. Python connects through one of these applications. If it closes, logs out, or restarts, the connection drops.

Connecting is only the start. A DataFrame shows that the download worked, not that a strategy will. Order types, costs, sizing, currencies, and execution are separate problems.

What it means

Connecting to IBKR from Python depends on three things: the correct port, matching currencies, and the required market-data permissions. Get those right and you can download prices into pandas and test your code on a paper account before placing any live order.

Disclaimer: a getting-started guide on a paper account. No orders are placed. Not investment advice.

Sources: ib_async and its docs; the IBKR TWS API documentation and configuring TWS for the API on IBKR Campus. The older interactivebrokers.github.io TWS API pages are now marked deprecated.