The Error That Stops Every IB Bot Builder
You write your first reqMktData call. You connect to TWS or IB Gateway. And instead of streaming prices, you get this:
Error 354: Requested market data is not subscribed.
Delayed market data is available.
Or the even more cryptic variant:
Error 10167: Requested market data is not subscribed.
Displaying delayed market data.
I have been running a production USDJPY momentum strategy on IB Gateway for months. I have hit both of these errors — during initial setup, after account changes, and when switching between paper and live environments. Every time, the root cause was one of six specific things.
This guide covers all of them, in order of how likely they are to be your problem.
---
Quick Diagnosis: Which Error Are You Getting?
Before diving into fixes, identify your exact situation:
| Error Code | Message | Most Likely Cause |
|---|---|---|
| 354 | "Requested market data is not subscribed" | Missing market data subscription for that exchange |
| 10167 | "Requested market data is not subscribed. Displaying delayed market data" | Same as 354, but TWS is falling back to delayed data |
| 10168 | "Market data farm connection is inactive" | TWS/Gateway not connected to data servers |
| 2104 | "Market data farm connection is OK" | Not an error — this is a status message confirming connection |
---
Fix 1: You Actually Need a Market Data Subscription (Most Common)
This is the cause 80% of the time. Interactive Brokers requires active market data subscriptions for most exchanges — and the subscriptions you need depend on *what* you are trading.
How to Check Your Current Subscriptions
1. Log in to Client Portal
2. Go to Settings → User Settings → Market Data Subscriptions 3. You will see a list of your active subscriptions (or an empty list if you have none)What You Need by Asset Class
US Stocks (NYSE, NASDAQ, ARCA):| Subscription | Monthly Cost | What It Covers |
|---|---|---|
| US Securities Snapshot & Futures Value Bundle | $10.00 | NYSE, NASDAQ, AMEX, ARCA — streaming L1 |
| US Equity and Options Add-On Streaming Bundle | $4.50 | Adds streaming (vs snapshot-only) |
| Total for most US stock traders | $14.50 | Or $0 if you trade actively enough |
Here is the part that trips up a lot of algo traders: forex market data on IB is free. You do not need to subscribe to anything. IBKR provides IDEAL Pro forex quotes at no charge for funded accounts.
My Python trading bot connects to IB Gateway, calls reqMktData for USDJPY, and gets real-time streaming prices without paying for any market data subscription. Total monthly cost for forex data: $0.00.
But you still need a funded account with at least $500 USD (or equivalent).
Futures (ES, NQ, CL):| Subscription | Monthly Cost | What It Covers |
|---|---|---|
| CME Market Data (Non-Professional) | $1.00 | E-mini S&P 500, E-mini NASDAQ, Micro contracts |
| NYMEX Market Data | $1.00 | Crude oil (CL), natural gas (NG) |
| Total for basic futures | $2.00 | Non-professional classification required |
US options data comes bundled with the US Securities Snapshot bundle ($10/mo). No separate subscription needed.
The Free Data Waiver
If you generate at least $30 USD/month in commissions, IB waives up to $30/month in market data fees. For active algo traders, this often means your data subscriptions are effectively free.
Check your commission spend: Client Portal → Reports → Activity → look at monthly commission totals.
---
Fix 2: Paper Trading Account Without Subscriptions
This catches almost every beginner.
The problem: Paper trading accounts on IB do not automatically inherit your live account's market data subscriptions. If you subscribed to US equities data on your live account but are testing your bot on a paper account, you will get error 354. The fix:Paper accounts get free delayed data (15-20 minutes delayed) by default. To use it in your API code, you need to explicitly request delayed data:
# Python — ib_insync
from ib_insync import *
ib = IB()
ib.connect('127.0.0.1', 7497, clientId=1) # 7497 = paper trading port
# Tell the API to accept delayed data
ib.reqMarketDataType(3) # 3 = delayed data
contract = Stock('AAPL', 'SMART', 'USD')
ib.qualifyContracts(contract)
ticker = ib.reqMktData(contract)
ib.sleep(2)
print(ticker)
# Python — ibapi (official TWS API)
from ibapi.client import EClient
from ibapi.wrapper import EWrapper
class MyWrapper(EWrapper):
def error(self, reqId, errorCode, errorString, advancedOrderRejectJson=""):
print(f"Error {errorCode}: {errorString}")
def tickPrice(self, reqId, tickType, price, attrib):
print(f"Price: {price}")
class MyClient(EClient):
def __init__(self, wrapper):
EClient.__init__(self, wrapper)
app = MyWrapper()
client = MyClient(app)
client.connect("127.0.0.1", 7497, clientId=1)
# Request delayed market data
client.reqMarketDataType(3) # 1=live, 2=frozen, 3=delayed, 4=delayed-frozen
Market data types explained:
| Type | Code | Description | Requires Subscription? |
|---|---|---|---|
| Live | 1 | Real-time streaming | Yes |
| Frozen | 2 | Last available price when market closed | Yes |
| Delayed | 3 | 15-20 min delayed | No |
| Delayed Frozen | 4 | Last delayed price when market closed | No |
reqMarketDataType *before* your first reqMktData. If you call it after, you may need to cancel and re-request the data.
---
Fix 3: Wrong Contract Definition
The API is extremely picky about contract specifications. A slightly wrong contract will return error 354 even if your subscriptions are correct.
Common mistakes:
# WRONG — missing exchange
contract = Contract()
contract.symbol = "AAPL"
contract.secType = "STK"
contract.currency = "USD"
# Missing: contract.exchange = "SMART"
# WRONG — wrong secType for forex
contract = Contract()
contract.symbol = "USD"
contract.secType = "STK" # Should be "CASH"
contract.exchange = "IDEALPRO"
contract.currency = "JPY"
# CORRECT — US stock
contract = Contract()
contract.symbol = "AAPL"
contract.secType = "STK"
contract.exchange = "SMART"
contract.currency = "USD"
# CORRECT — forex pair (how I request USDJPY)
contract = Contract()
contract.symbol = "USD"
contract.secType = "CASH"
contract.exchange = "IDEALPRO"
contract.currency = "JPY"
# CORRECT — E-mini S&P 500 futures
contract = Contract()
contract.symbol = "ES"
contract.secType = "FUT"
contract.exchange = "CME"
contract.currency = "USD"
contract.lastTradeDateOrContractMonth = "202606" # Must specify expiry
Like what you're reading? Try it yourself — this link supports ChartedTrader at no cost to you.
Open an IBKR Account →
The debug trick: Use reqContractDetails to verify IB recognizes your contract before requesting market data:
# ib_insync
from ib_insync import *
ib = IB()
ib.connect('127.0.0.1', 7496, clientId=1)
contract = Stock('AAPL', 'SMART', 'USD')
details = ib.reqContractDetails(contract)
if details:
print(f"Found: {details[0].contract.symbol} on {details[0].contract.exchange}")
print(f"Market data requires: check subscriptions for {details[0].contract.primaryExchange}")
else:
print("Contract not found — fix your contract definition first")
If reqContractDetails returns nothing, your contract definition is wrong. Fix that before troubleshooting subscriptions.
---
Fix 4: Professional vs Non-Professional Classification
This is the silent budget killer. IB classifies users as either Professional or Non-Professional for market data pricing. The difference is massive:
| Data Bundle | Non-Professional | Professional |
|---|---|---|
| US Securities Snapshot & Futures Value Bundle | $10/mo | $125/mo |
| CME Real-Time Data | $1/mo | $85/mo |
| NASDAQ TotalView (Level 2) | $1.50/mo | $25/mo |
- Are you registered with any securities regulatory authority?
- Are you using the data for business purposes or on behalf of an employer?
- Do you use data for managing someone else's money?
---
Fix 5: Exceeded Market Data Lines Limit
IB limits the number of concurrent market data lines (tickers you can stream simultaneously). The default is 100 lines for accounts generating less than $40/month in commissions.
If your bot requests data for 150 symbols, symbols beyond the limit will throw errors.
How to check your limit:
# The API does not directly report your line limit.
# But you can see it in Client Portal:
# Settings → User Settings → Market Data → Streaming Market Data Lines
How to increase it:
- Generate more commissions (automatic: every $8 in monthly commissions = 1 additional line)
- Or pay for additional lines: $1/line/month for quotes, $5/line/month for Level 2
# ib_insync — rotate through symbols
for symbol in large_symbol_list:
contract = Stock(symbol, 'SMART', 'USD')
ticker = ib.reqMktData(contract)
ib.sleep(2)
# Process data...
ib.cancelMktData(contract) # Free up the line
---
Fix 6: TWS/Gateway Connection and Port Issues
Sometimes error 354 is actually a connection problem masquerading as a subscription issue.
Verify your connection:| Platform | Default Port (Live) | Default Port (Paper) |
|---|---|---|
| TWS | 7496 | 7497 |
| IB Gateway | 4001 | 4002 |
1. Connecting to paper port (7497/4002) when you meant live (7496/4001) — paper has no subscriptions by default
2. API connections not enabled — In TWS: Edit → Global Configuration → API → Settings → Enable ActiveX and Socket Clients 3. IB Gateway not authenticated — Gateway needs IB Key 2FA on every restart. If authentication failed silently, you will get data errors instead of connection errors.If you are running IB Gateway for automated trading, I wrote a detailed guide on fixing IB Key 2FA issues — the most common reason Gateway fails to authenticate after a restart.
Quick connection test:
from ib_insync import *
ib = IB()
try:
ib.connect('127.0.0.1', 4001, clientId=1)
print(f"Connected: {ib.isConnected()}")
print(f"Accounts: {ib.managedAccounts()}")
# Check connection to data farms
# If this returns empty, data farm connection failed
ib.reqMarketDataType(1)
contract = Forex('USDJPY')
ticker = ib.reqMktData(contract)
ib.sleep(3)
if ticker.last != ticker.last: # NaN check
print("No data received — check data farm connection")
else:
print(f"USDJPY: {ticker.last}")
except Exception as e:
print(f"Connection failed: {e}")
finally:
ib.disconnect()
---
My Production Setup: What Actually Works
Here is the exact configuration running my USDJPY momentum strategy on IB Gateway, with zero market data errors for months:
Environment:- IB Gateway (headless, Linux server)
- Port 4001 (live trading)
- Python with
ib_insync - Market data type: 1 (live, real-time)
- No paid market data subscriptions (forex is free)
import asyncio
from ib_insync import *
async def connect_and_verify():
ib = IB()
await ib.connectAsync('127.0.0.1', 4001, clientId=1)
# Always set market data type explicitly
ib.reqMarketDataType(1) # 1 = live streaming
# Verify data connection with a known contract
contract = Forex('USDJPY')
ib.qualifyContracts(contract)
ticker = ib.reqMktData(contract)
await asyncio.sleep(3)
if ticker.last != ticker.last: # NaN = no data
raise RuntimeError("Market data not flowing — check Gateway auth and subscriptions")
print(f"Data OK — USDJPY: {ticker.last}")
return ib
Key lessons from production:
1. Always callreqMarketDataType before any data request, every time you connect
2. Verify data is actually flowing with a canary request (I use USDJPY since it is free)
3. If Gateway restarts, re-authenticate IB Key 2FA *before* your bot reconnects — otherwise you get silent data failures
4. Use clientId consistently — if another process already connected with the same clientId, your data requests may not work
---
The Complete Troubleshooting Checklist
Run through this when you hit "market data not subscribed":
- [ ] Am I on the right port? Live (7496/4001) vs paper (7497/4002)
- [ ] Is my account funded? Need $500+ USD equivalent for market data access
- [ ] Do I have the right subscription? Client Portal → Settings → Market Data Subscriptions
- [ ] Am I classified as non-professional? Client Portal → Market Data Subscriber Status
- [ ] Is my contract definition correct? Run
reqContractDetailsfirst - [ ] Did I call
reqMarketDataType? Set to 3 for paper, 1 for live - [ ] Am I under the data lines limit? Cancel unused
reqMktDatacalls - [ ] Is TWS/Gateway authenticated? Check IB Key 2FA status
- [ ] Are API connections enabled? TWS: Edit → Global Configuration → API → Settings
---
Still Stuck? Three More Things to Try
1. Restart TWS/Gateway completely. Not just reconnect your bot — close TWS/Gateway, re-authenticate with IB Key, and start fresh. Stale sessions can cause phantom data errors. 2. Check IB system status. Occasionally IB's data farms have outages. Check the IB System Status page before spending hours debugging your code. 3. Call IB support. If your subscription looks correct but data still will not flow, IB support can check if there is a backend issue with your account's data entitlements. This has happened to me once — a subscription was marked as active in Client Portal but not actually provisioned on the backend. Support fixed it in minutes.---
What to Read Next
If you are building an automated trading system on Interactive Brokers, these guides cover the rest of the stack:
- IB Python API: Build a Live Trading System — full working code for connecting and placing orders
- IB Bracket Orders: How to Set Stop Loss and Take Profit on TWS — risk management for your algo
- IB Market Data Subscriptions: Which Do You Need? — deeper dive into every subscription bundle
- IB Key 2FA Not Working? How to Fix Every Issue — essential if you run IB Gateway