I'm having a problem getting options data with IB's API. The data seems not to be correct. In my code I'm getting some 0DTE call options for the Mini SP500 March Futures contract and printing their bid, ask and delta. For this I'm using the ib_sync which simplifies development greatly. This is the code:

from datetime import datetime
from ib_insync import *
import pandas as pd
import math

from datetime import datetime, time

def week_of_month(dt):
    """ Returns the week of the month for the specified date.
    first_day = dt.replace(day=1)

    dom = dt.day
    adjusted_dom = dom + first_day.weekday()

    return int(math.ceil(adjusted_dom/7.0))

def get_trading_class(dt: datetime):
    week = week_of_month(dt)
    day = dt.weekday()
    res = 'E' + str(week)
    if day == 4:
        return 'EW' + str(week)
        return 'E' + str(week) + chr(60+day)
ib = IB().connect('winhost',  7496, clientId=123, timeout=15)

es = Future('ES', '202303', 'CME')


[ticker] = ib.reqTickers(es)

# spx current price
spxValue =  ticker.marketPrice() 
print('es futue value: ', spxValue)

chains = ib.reqSecDefOptParams(es.symbol, 'CME', es.secType, es.conId)

# chainsDf = util.df(chains)
# print(chainsDf.to_string(max_colwidth=10))
# chainsDf = chainsDf[(chains.exchange == 'SMART') & (chains.tradingClass == 'SPXW')]

print('week of the month:', week_of_month(datetime.now()))
trading_cls = get_trading_class(datetime.now())
print('trading class:', trading_cls)
chain = next(c for c in chains if c.tradingClass == trading_cls and c.exchange == 'CME')
expiration = chain.expirations[0]


call_strikes = [strike for strike in chain.strikes
        if strike % 5 == 0
        and spxValue - 10 < strike < spxValue + 30]

put_strikes = [strike for strike in chain.strikes
        if strike % 5 == 0
        and spxValue - 30 < strike < spxValue]
rights = ['P', 'C']

contracts = [Option('SPX', expiration, strike, 'C', 'SMART', tradingClass='SPXW') for strike in call_strikes]
contracts = ib.qualifyContracts(*contracts)

tickers = ib.reqTickers(*contracts)
for ticker in tickers:
    print('call strike', ticker.contract.strike, 'bid:', ticker.bid, 'ask:', ticker.ask, 'delta:', ticker.lastGreeks.delta)


And this is the output:

[Future(conId=495512572, symbol='ES', lastTradeDateOrContractMonth='20230317', multiplier='50', exchange='CME', currency='USD', localSymbol='ESH3', tradingClass='ES')]
es futue value:  4061.75
week of the month: 3
trading class: EW3
expiration: 20230217
call strike 4055.0 bid: 10.1 ask: 10.3 delta: 0.472330335007325
call strike 4060.0 bid: 7.9 ask: 8.1 delta: 0.3769870767527141
call strike 4065.0 bid: 6.1 ask: 6.3 delta: 0.29022751622672416
call strike 4070.0 bid: 4.6 ask: 4.8 delta: 0.212861073049887
call strike 4075.0 bid: 3.4 ask: 3.6 delta: 0.1491749774504159
call strike 4080.0 bid: 2.55 ask: 2.6 delta: 0.09797399088652259
call strike 4085.0 bid: 1.85 ask: 1.95 delta: 0.06485900831271653
call strike 4090.0 bid: 1.35 ask: 1.4 delta: 0.04255032736417017

I've double checked the price of the contract, and it's correct: 4061.75.

Now, looking at the TWS at the time I make this request, the bid asks and deltas of the options are incorrect. Look at the attached image.

I think I'm getting delayed data, because if the price of the futures contract is 4061.75, the first ITM call option with a delta over 50 should be the 4060, but in my output, the delta for that option is 0.37. Not even the 4055 call is ITM.

So my question is. How is it possible that I'm seeing real time data on the TWS but I'm getting delayed data through the API? Am I missing a Real Time data subscription for the API? In that case, which one?

These are my Market Data Subscriptions:

My Market Data Subscriptions


  • 1
    $\begingroup$ In case you don't get an answer here, there is fairly active mailing list for ib_insync: groups.io/g/insync $\endgroup$ Feb 20 at 19:49
  • $\begingroup$ what you see in tws and the api should be exactly the same, the behavior by default is to return fresh data if subscribed and stale if not, i don't use insync, but in the native api there is a reqMarketDataType method to handle this behavior, can you check it on your side? $\endgroup$
    – John
    Feb 21 at 9:05

1 Answer 1


My mistake. The definition of the Option contract was wrong. So after changing the line:

contracts = [Option('SPX', expiration, strike, 'C', 'SMART', tradingClass='SPXW') for strike in call_strikes]


contracts = [FuturesOption(es.symbol, expiration, strike, 'C', 'CME', '50', 'USD', tradingClass=trading_cls) for strike in call_strikes]

It worked as expected


Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.