Option chains

In [1]:
from ib_insync import *
util.startLoop()

ib = IB()
ib.connect('127.0.0.1', 7497, clientId=12)
Out[1]:
<IB connected to 127.0.0.1:7497 clientId=12>

Suppose we want to find the options on the SPX, with the following conditions:

  • Use the next three monthly expiries;
  • Use strike prices within +- 20 dollar of the current SPX value;
  • Use strike prices that are a multitude of 5 dollar.

To get the current market value, first create a contract for the underlyer (the S&P 500 index):

In [2]:
spx = Index('SPX', 'CBOE')
ib.qualifyContracts(spx)
Out[2]:
[Index(conId=416904, symbol='SPX', exchange='CBOE', currency='USD', localSymbol='SPX')]

To avoid issues with market data permissions, we'll use delayed data:

In [3]:
ib.reqMarketDataType(4)

Then get the ticker. Requesting a ticker can take up to 11 seconds.

In [4]:
[ticker] = ib.reqTickers(spx)
ticker
Out[4]:
Ticker(contract=Index(conId=416904, symbol='SPX', exchange='CBOE', currency='USD', localSymbol='SPX'), time=datetime.datetime(2019, 12, 31, 16, 58, 32, 252008, tzinfo=datetime.timezone.utc), last=3216.19, lastSize=0, high=3225.1, low=3212.03, close=3221.29, ticks=[TickData(time=datetime.datetime(2019, 12, 31, 16, 58, 32, 252008, tzinfo=datetime.timezone.utc), tickType=4, price=3216.19, size=0), TickData(time=datetime.datetime(2019, 12, 31, 16, 58, 32, 252008, tzinfo=datetime.timezone.utc), tickType=6, price=3225.1, size=0), TickData(time=datetime.datetime(2019, 12, 31, 16, 58, 32, 252008, tzinfo=datetime.timezone.utc), tickType=7, price=3212.03, size=0), TickData(time=datetime.datetime(2019, 12, 31, 16, 58, 32, 252008, tzinfo=datetime.timezone.utc), tickType=9, price=3221.29, size=0)])

Take the current market value of the ticker:

In [5]:
spxValue = ticker.marketPrice()
spxValue
Out[5]:
3221.29

The following request fetches a list of option chains:

In [6]:
chains = ib.reqSecDefOptParams(spx.symbol, '', spx.secType, spx.conId)

util.df(chains)
Out[6]:
exchange underlyingConId tradingClass multiplier expirations strikes
0 SMART 416904 SPX 100 [20200116, 20200220, 20200319, 20200416, 20200... [100.0, 200.0, 300.0, 400.0, 500.0, 600.0, 650...
1 CBOE 416904 SPXW 100 [20191231, 20200103, 20200106, 20200108, 20200... [1000.0, 1100.0, 1150.0, 1200.0, 1225.0, 1250....
2 SMART 416904 SPXW 100 [20191231, 20200103, 20200106, 20200108, 20200... [1000.0, 1100.0, 1150.0, 1200.0, 1225.0, 1250....
3 CBOE 416904 SPX 100 [20200116, 20200220, 20200319, 20200416, 20200... [100.0, 200.0, 300.0, 400.0, 500.0, 600.0, 650...

These are four option chains that differ in exchange and tradingClass. The latter is 'SPX' for the monthly and 'SPXW' for the weekly options. Note that the weekly expiries are disjoint from the monthly ones, so when interested in the weekly options the monthly options can be added as well.

In this case we're only interested in the monthly options trading on SMART:

In [7]:
chain = next(c for c in chains if c.tradingClass == 'SPX' and c.exchange == 'SMART')
chain
Out[7]:
OptionChain(exchange='SMART', underlyingConId='416904', tradingClass='SPX', multiplier='100', expirations=['20200116', '20200220', '20200319', '20200416', '20200514', '20200618', '20200917', '20201015', '20201119', '20201217', '20210114', '20210318', '20210617', '20211216', '20221215'], strikes=[100.0, 200.0, 300.0, 400.0, 500.0, 600.0, 650.0, 700.0, 750.0, 800.0, 850.0, 900.0, 950.0, 1000.0, 1050.0, 1100.0, 1150.0, 1200.0, 1225.0, 1250.0, 1275.0, 1300.0, 1325.0, 1350.0, 1375.0, 1400.0, 1425.0, 1450.0, 1475.0, 1500.0, 1525.0, 1550.0, 1575.0, 1600.0, 1625.0, 1650.0, 1675.0, 1700.0, 1725.0, 1750.0, 1775.0, 1800.0, 1825.0, 1850.0, 1875.0, 1900.0, 1925.0, 1950.0, 1975.0, 2000.0, 2025.0, 2050.0, 2075.0, 2090.0, 2100.0, 2110.0, 2120.0, 2125.0, 2130.0, 2140.0, 2150.0, 2160.0, 2170.0, 2175.0, 2180.0, 2190.0, 2200.0, 2210.0, 2220.0, 2225.0, 2230.0, 2240.0, 2250.0, 2260.0, 2270.0, 2275.0, 2280.0, 2290.0, 2300.0, 2310.0, 2320.0, 2325.0, 2330.0, 2340.0, 2350.0, 2360.0, 2370.0, 2375.0, 2380.0, 2390.0, 2395.0, 2400.0, 2405.0, 2410.0, 2415.0, 2420.0, 2425.0, 2430.0, 2435.0, 2440.0, 2445.0, 2450.0, 2455.0, 2460.0, 2465.0, 2470.0, 2475.0, 2480.0, 2485.0, 2490.0, 2495.0, 2500.0, 2505.0, 2510.0, 2515.0, 2520.0, 2525.0, 2530.0, 2535.0, 2540.0, 2545.0, 2550.0, 2555.0, 2560.0, 2565.0, 2570.0, 2575.0, 2580.0, 2585.0, 2590.0, 2595.0, 2600.0, 2605.0, 2610.0, 2615.0, 2620.0, 2625.0, 2630.0, 2635.0, 2640.0, 2645.0, 2650.0, 2655.0, 2660.0, 2665.0, 2670.0, 2675.0, 2680.0, 2685.0, 2690.0, 2695.0, 2700.0, 2705.0, 2710.0, 2715.0, 2720.0, 2725.0, 2730.0, 2735.0, 2740.0, 2745.0, 2750.0, 2755.0, 2760.0, 2765.0, 2770.0, 2775.0, 2780.0, 2785.0, 2790.0, 2795.0, 2800.0, 2805.0, 2810.0, 2815.0, 2820.0, 2825.0, 2830.0, 2835.0, 2840.0, 2845.0, 2850.0, 2855.0, 2860.0, 2865.0, 2870.0, 2875.0, 2880.0, 2885.0, 2890.0, 2895.0, 2900.0, 2905.0, 2910.0, 2915.0, 2920.0, 2925.0, 2930.0, 2935.0, 2940.0, 2945.0, 2950.0, 2955.0, 2960.0, 2965.0, 2970.0, 2975.0, 2980.0, 2985.0, 2990.0, 2995.0, 3000.0, 3005.0, 3010.0, 3015.0, 3020.0, 3025.0, 3030.0, 3035.0, 3040.0, 3045.0, 3050.0, 3055.0, 3060.0, 3065.0, 3070.0, 3075.0, 3080.0, 3085.0, 3090.0, 3095.0, 3100.0, 3105.0, 3110.0, 3115.0, 3120.0, 3125.0, 3130.0, 3135.0, 3140.0, 3145.0, 3150.0, 3155.0, 3160.0, 3165.0, 3170.0, 3175.0, 3180.0, 3185.0, 3190.0, 3195.0, 3200.0, 3205.0, 3210.0, 3215.0, 3220.0, 3225.0, 3230.0, 3235.0, 3240.0, 3245.0, 3250.0, 3255.0, 3260.0, 3265.0, 3270.0, 3275.0, 3280.0, 3285.0, 3290.0, 3295.0, 3300.0, 3305.0, 3310.0, 3315.0, 3320.0, 3325.0, 3330.0, 3335.0, 3340.0, 3345.0, 3350.0, 3355.0, 3360.0, 3365.0, 3370.0, 3375.0, 3380.0, 3385.0, 3390.0, 3395.0, 3400.0, 3405.0, 3410.0, 3415.0, 3420.0, 3425.0, 3430.0, 3435.0, 3440.0, 3445.0, 3450.0, 3455.0, 3460.0, 3465.0, 3470.0, 3475.0, 3480.0, 3490.0, 3500.0, 3510.0, 3520.0, 3525.0, 3530.0, 3540.0, 3550.0, 3560.0, 3575.0, 3600.0, 3625.0, 3650.0, 3700.0, 3750.0, 3800.0, 3850.0, 3900.0, 3950.0, 4000.0, 4050.0, 4100.0, 4200.0, 4300.0, 4400.0, 4500.0, 4600.0])

What we have here is the full matrix of expirations x strikes. From this we can build all the option contracts that meet our conditions:

In [8]:
strikes = [strike for strike in chain.strikes
        if strike % 5 == 0
        and spxValue - 20 < strike < spxValue + 20]
expirations = sorted(exp for exp in chain.expirations)[:3]
rights = ['P', 'C']

contracts = [Option('SPX', expiration, strike, right, 'SMART', tradingClass='SPX')
        for right in rights
        for expiration in expirations
        for strike in strikes]

contracts = ib.qualifyContracts(*contracts)
len(contracts)
Out[8]:
48
In [9]:
contracts[0]
Out[9]:
Option(conId=384688665, symbol='SPX', lastTradeDateOrContractMonth='20200116', strike=3205.0, right='P', multiplier='100', exchange='SMART', currency='USD', localSymbol='SPX   200117P03205000', tradingClass='SPX')

Now to get the market data for all options in one go:

In [10]:
tickers = ib.reqTickers(*contracts)

tickers[0]
Out[10]:
Ticker(contract=Option(conId=384688665, symbol='SPX', lastTradeDateOrContractMonth='20200116', strike=3205.0, right='P', multiplier='100', exchange='SMART', currency='USD', localSymbol='SPX   200117P03205000', tradingClass='SPX'), time=datetime.datetime(2019, 12, 31, 16, 58, 41, 258764, tzinfo=datetime.timezone.utc), bid=28.1, bidSize=260, ask=28.5, askSize=75, last=26.2, lastSize=1, volume=17, high=29.15, low=26.2, close=26.65, bidGreeks=OptionComputation(impliedVol=0.12479434613887233, delta=-0.4467205699460659, optPrice=28.100000381469727, pvDividend=3.171605793435112, gamma=0.004541203481480199, vega=2.7584286390644355, theta=-0.9351184483257037, undPrice=3216.19), askGreeks=OptionComputation(impliedVol=0.1273924345982469, delta=-0.44758003325183715, optPrice=28.5, pvDividend=3.171605793435112, gamma=0.004449873325559049, vega=2.759225320523277, theta=-0.956080972911877, undPrice=3216.19), lastGreeks=OptionComputation(impliedVol=0.1121163044425008, delta=-0.441885782487882, optPrice=26.200000762939453, pvDividend=3.171605793435112, gamma=0.005046062232473765, vega=2.7537040141291262, theta=-0.8328175501618498, undPrice=3216.19), modelGreeks=OptionComputation(impliedVol=0.12597515955300287, delta=-0.44711612427890945, optPrice=30.020053965440823, pvDividend=3.171605793435112, gamma=0.004499237656712854, vega=2.7587969178159018, theta=-0.9446458451581966, undPrice=3216.19))

The option greeks are available from the modelGreeks attribute, and if there is a bid, ask resp. last price available also from bidGreeks, askGreeks and lastGreeks. For streaming ticks the greek values will be kept up to date to the current market situation.

In [11]:
ib.disconnect()