- Solve real problems with our hands-on interface
- Progress from basic puts and calls to advanced strategies

Posted November 2, 2026 at 11:40 am
The author of this article is not affiliated with Interactive Brokers.
Almost every automated trading system needs past prices. A backtest needs years of bars. A live system needs the last few hours to warm up its indicators before the open, and it needs to fill the gap after a restart. The Interactive Brokers TWS API gives you all of this through one call, reqHistoricalData, but the call comes with limits that are easy to break. Break them and requests fail with a pacing violation, and a downloader that keeps retrying can get the whole API session disconnected.
This article explains, in plain terms, how a historical data request works, which limits matter, what data you cannot get, and how to build a downloader that stays inside the rules. It applies to TWS and IB Gateway alike, and to any language the TWS API supports. The example is in Python.
A historical data request asks for bars for one instrument. You tell IBKR:
The bars arrive one at a time in historicalData, and historicalDataEnd tells you the request is complete. Here is a minimal example using IBKR’s own ibapi package for Python. It waits for nextValidId, the API’s signal that the connection is ready, before it asks for anything:
from ibapi.client import EClient
from ibapi.wrapper import EWrapper
from ibapi.contract import Contract
class App(EWrapper, EClient):
def __init__(self):
EClient.__init__(self, self)
def nextValidId(self, orderId):
contract = Contract()
contract.symbol = "AAPL"
contract.secType = "STK"
contract.exchange = "SMART"
contract.currency = "USD"
# One day of 1 minute trade bars, regular hours only, times in epoch seconds
self.reqHistoricalData(
1, contract, "20260115 16:00:00 US/Eastern",
"1 D", "1 min", "TRADES", 1, 2, False, []
)
def historicalData(self, reqId, bar):
print(bar.date, bar.open, bar.high, bar.low, bar.close, bar.volume)
def historicalDataEnd(self, reqId, start, end):
print("Finished", start, end)
self.disconnect()
app = App()
app.connect("127.0.0.1", 7497, clientId=1)
app.run()
Port 7497 is the default for TWS on a paper account; IB Gateway on a paper account uses 4002.
IBKR expects each request to return no more than a few thousand bars. Its documentation lists which bar sizes are allowed for each duration: a request covering 1800 seconds can use bars from 1 second up to 30 minutes, while a request covering one day starts at 1 minute bars.
So to collect a long history of small bars, split it into many requests. The usual way is to walk backwards: ask for the most recent chunk, set endDateTime to the start of what you just received, and ask again. Every step is a separate request, which is why the next section matters.
For bars of 30 seconds or less, IBKR treats any of these as a pacing violation:
Requests for BID_ASK data count twice. These limits cannot be raised.
For bars of 1 minute and larger, IBKR has lifted these hard limits, but it still slows down clients that ask for too much, and very heavy use can end in a disconnect. Separately, no more than 50 historical data requests can be open at the same time, and in practice keeping far fewer pending works better.
One easy trap: reqHeadTimestamp, which tells you where the available data begins, counts as a historical data request under the small bar rules, whatever bar size you are interested in. Cancel it with cancelHeadTimeStamp once you have the answer.
Some data is not available through the API at all, and retrying will not change that:
Historical ticks, requested with reqHistoricalTicks, come back at most 1000 per request and never span more than one trading session, so a full day of ticks for a busy instrument takes many requests.
Before downloading years of data, call reqHeadTimestamp to learn where the history actually begins. It saves requests that would only come back empty.
Historical data generally needs a market data subscription for the instrument, just like live data. If a request fails with a permissions message, retrying will not help: the subscription has to be added to the account first.
If endDateTime has no time zone, TWS returns warning 2174, which says requests without one will stop being supported. Write the zone every time, for example “20260115 16:00:00 US/Eastern”, or use UTC. Setting formatDate to 2 returns intraday bar times as seconds since 1970 in UTC, which removes any guessing when you store them.
The limits are easy to respect if every historical request goes through one place:
Historical data from the IBKR API is dependable once you work with its limits rather than against them: ask for a few thousand bars at a time, keep small bar requests under 60 in ten minutes, check what history exists before you ask for it, always name the time zone, and save what you download so you never ask twice. The downloader then becomes the dull, reliable part of the system, which is exactly what it should be.
This article is for information about software engineering only and is not investment advice.
Check out the first article in this series: Keeping an IBKR API System Running Unattended
Already an Interactive Brokers Client?
New to Interactive Brokers?
Information posted on IBKR Campus that is provided by third-parties does NOT constitute a recommendation that you should contract for the services of that third party. Third-party participants who contribute to IBKR Campus are independent of Interactive Brokers and Interactive Brokers does not make any representations or warranties concerning the services offered, their past or future performance, or the accuracy of the information provided by the third party. Past performance is no guarantee of future results.
This material is from Trade Vectors LLP and is being posted with its permission. The views expressed in this material are solely those of the author and/or Trade Vectors LLP and Interactive Brokers is not endorsing or recommending any investment or trading discussed in the material. This material is not and should not be construed as an offer to buy or sell any security. It should not be construed as research or investment advice or a recommendation to buy, sell or hold any security or commodity. This material does not and is not intended to take into account the particular financial conditions, investment objectives or requirements of individual customers. Before acting on this material, you should consider whether it is suitable for your particular circumstances and, as necessary, seek professional advice.
The third-party code discussed within this article is not investment or trading advice, and is for proof-of-concept, educational, and illustrative purposes only. IBKR makes no representations or warranty regarding its accuracy or completeness. Users are solely responsible for conducting their own independent testing and due diligence before applying any code or concepts in a live or production environment
Please keep in mind that the examples discussed in this material are purely for technical demonstration purposes, and do not constitute trading advice. Also, it is important to remember that placing trades in a paper account is recommended before any live trading.
The order types available through Interactive Brokers LLC's trading platforms are designed to help you limit your loss and/or lock in a profit. Market conditions and other factors may affect execution. In general, orders guarantee a fill or guarantee a price, but not both. In extreme market conditions, an order may either be executed at a different price than anticipated or may not be filled in the marketplace.
Join The Conversation
For specific platform feedback and suggestions, please submit it directly to our team using these instructions.
If you have an account-specific question or concern, please reach out to Client Services.
We encourage you to look through our FAQs before posting. Your question may already be covered!