instrument_token vs tradingsymbol
Two different identifiers, used in different API calls — mixing them up is a very common source of confusing bugs.
| Identifier | Type | Used for | Stable? |
|---|---|---|---|
instrument_token | integer | WebSocket subscription, historical data API | Stable for the life of the instrument; F&O contracts get a *new* token each expiry cycle |
tradingsymbol (+ exchange) | string | Order placement, quote lookups by symbol | Human-readable, but options/futures symbols change every expiry |
# Order placement uses tradingsymbol + exchange
kite.place_order(
exchange="NSE",
tradingsymbol="INFY",
...
)
# Historical data and WebSocket use instrument_token
kite.historical_data(instrument_token=408065, ...)
kws.subscribe([408065])
Why two systems exist
tradingsymbol is what a human (and the exchange's own order-matching system) recognizes; instrument_token is Zerodha's internal, numeric, collision-free identifier optimized for high-frequency lookups (ticks arrive tagged by token, not by string, because parsing/comparing integers is cheaper at WebSocket tick volumes).
The trap: F&O tokens change every expiry
NIFTY24SEPFUT's instrument_token this month is not the same token as NIFTY24OCTFUT next month, even though both represent "NIFTY near-month future" conceptually. Any code that hardcodes a token for a derivative contract will silently start tracking the wrong (or a dead) instrument after rollover.
def resolve_current_month_future(df, underlying: str) -> dict:
futures = df[
(df.name == underlying) &
(df.instrument_type == "FUT")
].sort_values("expiry")
return futures.iloc[0].to_dict() # nearest expiry = current month
Always resolve derivative tokens dynamically from a freshly-cached instrument master (chapter 21), never hardcode them.