Build an option chain
An option chain is just a filtered, pivoted view of the NFO instrument master for one underlying and one expiry, then joined with live quotes.
def get_option_chain(kite, nfo_df: pd.DataFrame, underlying: str, expiry: str) -> pd.DataFrame:
chain = nfo_df[
(nfo_df.name == underlying) &
(nfo_df.expiry == expiry) &
(nfo_df.instrument_type.isin(["CE", "PE"]))
].copy()
symbols = [f"NFO:{s}" for s in chain.tradingsymbol]
quotes = kite.quote(symbols)
chain["ltp"] = chain.tradingsymbol.apply(lambda s: quotes[f"NFO:{s}"]["last_price"])
chain["oi"] = chain.tradingsymbol.apply(lambda s: quotes[f"NFO:{s}"]["oi"])
chain["volume"] = chain.tradingsymbol.apply(lambda s: quotes[f"NFO:{s}"]["volume"])
calls = chain[chain.instrument_type == "CE"].set_index("strike")[["ltp", "oi", "volume"]]
puts = chain[chain.instrument_type == "PE"].set_index("strike")[["ltp", "oi", "volume"]]
return calls.join(puts, lsuffix="_call", rsuffix="_put").sort_index()
chain = get_option_chain(kite, nfo_df, "NIFTY", "2024-12-26")
print(chain.loc[24000:25000])
Output shape (strike-indexed, calls and puts side by side):
ltp_call oi_call volume_call ltp_put oi_put volume_put
strike
24000 612.4 145000 89000 42.1 210000 156000
24500 312.8 198000 142000 142.5 187000 132000
25000 112.3 167000 98000 342.6 134000 87000
Finding ATM strike
def find_atm_strike(chain: pd.DataFrame, spot: float) -> float:
return min(chain.index, key=lambda k: abs(k - spot))
Rate limit awareness
kite.quote() accepts up to 500 instruments per call — a full NIFTY option chain (one expiry) is usually well under that, so one call suffices. Requesting *multiple* expiries' full chains in a tight loop is where you'll hit rate limits — batch and cache aggressively (chapter 20).