1. Overview
The system uses a dual-path real-time model: raw broker data is processed on the server, then pushed to browsers via Socket.io. Automated strategies read from an in-memory tick cache; the UI listens for refresh signals and re-fetches via REST where needed. Before live ticks flow, symbol and instrument metadata is synced from Kite into MongoDB. Active index settings drive WebSocket subscriptions; underlying LTP drives dynamic option strike selection; snapshots are built every second and fan out to the UI, database, and automation.2. Real-Time Market Data Pipeline
Live market data is not a single WebSocket hook — it is a multi-stage pipeline from instrument metadata through strike calculation to snapshot broadcast.2.1 Instrument & symbol sync → DB
Source:indexSettingGenerator.ts — syncIndexSettings()
- Fetches instruments from Kite for NSE, NFO, MCX, CDS via
getInstruments(exchange). - For each symbol, builds or updates a document in the
indexSettingcollection:cash— NSE EQinstrumentToken, lot size, tradingsymbolfuturesChainByExpiry— all FUT expiries mapped to token, tradingsymbol, expiryType (weekly/monthly)optionChainByExpiry— per expiry:strikeList,baseDiff,strikesmap (CE/PE tokens per strike)optionExpiriesByFuturesExpiry— which option expiries are valid for each futures expiryoptionsUnderlyingType—SPOTorFUTURES(options priced off cash or linked futures)
- Upserts by
index(lowercase symbol). Preserves admin-controlled fields:isActive,tradingModes, recurring/trailing settings, strike differences. - Emits
indexSettingEmitteron updates so the live processor can reload subscriptions.
2.2 Active symbols → token registration
Source:zerodha-event.utils.ts — loadIndexSettings(), registerBaseToken(), subscribeBaseTokens()
When ZerodhaEventProcessor.start() runs:
- Loads
indexSettingModel.find({ isActive: true })— only active indices participate in streaming. - For each active index, enriches
optionExpiriesByFuturesExpiryviaenrichOptionExpiriesMap(). - Registers base tokens when the corresponding
tradingModes.*flag is enabled:
- Subscribes all base tokens through
ZerodhaConnectionManager.subscribe().
indexSettingEmitter fires indexSettingUpdated, onIndexSettingUpdated reloads settings, clears strike/snapshot cache for the affected index, resubscribes base tokens, and recalculates option subscriptions.
2.3 Real-time LTP (Kite WebSocket)
Source:zerodha-connection.utils.ts, processTick()
ZerodhaConnectionManagercreates a Kite Ticker instance, connects, and subscribes tokens in FULL mode (depth + OI).- Every incoming tick is stored in the
liveDataMap keyed byinstrument_token— this holds all subscribed tokens including option legs. - Each tick is forwarded to
ZerodhaEventProcessor.processTick(). - Base segment ticks (cash, futures, options underlying) update
segmentTicks[indexName][segment]. - An options underlying tick additionally triggers
handleOptionUnderlyingTick()for strike recalculation. - Option leg ticks do not enter
segmentTicks. They remain inliveDataand are read at snapshot time viazerodhaConnection.getLatest(token)andgetBidAsk(token).
2.4 Option strike calculation & dynamic subscribe
Source:calculateRequiredStrikes(), updateOptionSubscriptions(), handleOptionUnderlyingTick()
When the options underlying LTP moves, the processor computes which strikes to track and which option instrument tokens to subscribe.
Strike formulas (from calculateRequiredStrikes):
From these strikes, 8 instrument tokens are resolved from
optionChainByExpiry[expiryDate].strikes (CE/PE for each role) and subscribed via updateOptionSubscriptions(). Tokens are added or removed only when strikes change (compared against lastCalculatedStrikes).
When optionsUnderlyingType === "FUTURES", option expiry is validated against the linked futures expiry via getOptionExpiriesForFutures() — invalid pairings skip subscription.
2.5 Snapshot build (buildSnapshot)
Source: buildSnapshot(), buildCashSnapshot(), buildFuturesSnapshot(), buildOptionsSnapshot()
Every 1000ms, broadcastAll() calls buildSnapshot() for each active index:
- Reads latest ticks from
segmentTicksfor cash, futures, and options underlying. - Sets
baseValuefromactiveSegmentsetting:cash→cash.lastPricefutures→futures.lastPriceoptions→options.underlyingLastPrice- Fallback chain: options underlying → futures → cash
- Builds nested segment objects (
cash,futures,options) with quote fields (lastPrice, bidPrice, askPrice, oi, volumeTraded). - For options, reads leg premiums from
liveDataviagetOptionQuote()and attaches anoptions.legs[]array (role, strike, right, prices). - Merges
legacyFields(flat strike numbers + bid/ask premiums) onto the snapshot root for backward compatibility with automation and UI.
2.6 Persist, emit, fan-out
Source:saveToDbAndEmit(), socket-manager.ts
Each broadcast cycle produces three outputs:
Additionally,
ZerodhaEventProcessor.latestSnapshotData holds the most recent snapshot per index (getLatestData()).
Write rate: approximately one IndexData document per active index per second.
2.7 Streaming lifecycle
Market data starts when a valid Zerodha access token exists:handlePostZerodhaLogin()→ensureMarketDataRunning({ forceRestart: true })→restartMarketData()→ZerodhaEventProcessor.start()- Stopped on disconnect, 6 AM token refresh, or server shutdown via
handleZerodhaDisconnect()
backfillOptionExpiriesMap() → Zerodha login → ZerodhaEventProcessor.start() → live ticks; full instrument sync runs after each exchange session close.
3. Snapshot Schema Reference
Each tick snapshot is a single JSON object stored inIndexData and emitted over Socket.io. The shape is built by buildSnapshot() in zerodha-event.utils.ts.
Root metadata
Legacy option fields (root level)
These flat fields are merged fromoptions.legacyFields for backward compatibility. Strike keys hold strike prices (numbers); premium keys hold bid/ask quotes.
Nested segment objects
cash (when tradingModes.cash enabled):
futures (when tradingModes.futures enabled):
options (when tradingModes.options enabled):
options.legs[] entry:
Example snapshot (trimmed)
4. Application Consumers
Market data reaches the application through two paths: direct Socket.io for live UI updates, andlatestDataMap for server-side automation.
React UI (Socket.io direct)
Components subscribe to${indexName}TickData and update local state for live prices, strikes, and tables.
Each page passes tick data to
IndexDataTable for historical/recent tick display.
Automation (latestDataMap)
Automated buy/sell logic does not subscribe to Kite directly. Controllers read latestDataMap[\$TickData`]` on each interval tick.
Recurring buy — recurringOrder.controller.ts
executeBuyOrderreads the latest snapshot for tradingsymbol, strike, and price:- Options:
putCall1/callPut1for strike;putCallAskPrice1/callPutAskPrice1for price - Cash:
cash.lastPriceorbaseValue - Futures:
futures.lastPriceorbaseValue
- Options:
- Skips if tick data is missing or exchange is closed.
scheduleOrder.controller.ts
startInterval()runs every 1 second, iterating active symbols.- Reads
latestDataMap[\$TickData`]and passes it totrailingSell()`. - For candle min/max over the sell duration window, also aggregates from
indexDataModel(and an in-memory tick buffer at candle end). - On price match, calls
handlePlaceOrderfor MARKET SELL.
zerodha.controller.ts
- Resolves order price and strike from
latestDataMapwhen placing limit or protected market orders.
5. Order Placement Flow
All live and dummy orders converge on a single function:handlePlaceOrder in zerodha.controller.ts.
After placement
Once an order is saved, the controller emits internal events:registerOrderListeners() in socket-manager.ts bridges these to client-facing Socket.io events (see §7).
6. Order Status Update Flow (Webhook)
Kite sends asynchronous order updates to the backend webhook. Route:POST /api/zerodha/webhook → handlePostback in zerodha.controller.ts
Step-by-step
- Kite POSTs order status (e.g.
COMPLETE,REJECTED, partial fills). - Backend parses payload via
parseZerodhaPostback. - Order document is upserted in MongoDB; final statuses (
COMPLETE,REJECTED) andisSoldare preserved on update. orderEmitter.emit("orderUpdated", updatedOrder)andorderEmitter.emit("notification", …)fire.- Socket bridge emits
fetchOrderDataandfetchFinancialDatato all clients. - Frontend order tables listen on
fetchOrderDataand re-fetch order lists via REST.
7. Socket.io Bridge (Internal → Client)
The backend uses a two-layer event model: internal NodeEventEmitter instances decouple business logic from Socket.io transport.
Event mapping table
Connection setup
- Frontend:
socketConnect.tsconnects withquery: { userId }and a 15s heartbeat (ping/pong). - Backend:
userSocketMapmapsuserId→ array of socket IDs for targeted emits (updateUserSettingData). - Most refresh events use
io.emit(broadcast to all connected clients).
8. Financial / PnL Flow
Position and PnL data runs on a separate 5-second loop, started alongside market data after Zerodha login. Source:startFinacialInterval() in zerodha.controller.ts
Step-by-step
- Every 5 seconds,
startFinacialIntervalfetches net positions from Kite REST. getLivePnl()opens (or reuses) a dedicated Kite Ticker WebSocket for position instrument tokens.- On each tick batch, realized/unrealized PnL is recalculated per position.
orderEmitter.emit("fetchFinancialSocketData", financialData)pushes the array to clients.- Admin dashboard listens on
fetchFinancialSocketDataand updates the financial table. fetchFinancialData(generic refresh signal) is also emitted on order place/update events.
ensureMarketDataRunning() → startFinacialInterval(). Stopped via handleZerodhaDisconnect() → stopInterval('startFinacialInterval') + resetFinancialWebSocket().
9. Settings Sync Flow
When index or user settings change via API, the backend updates MongoDB and notifies clients.Index settings
- Emitted from
indexSetting.controller.ts,order.controller.ts,indexSettingGenerator.ts, andrecurringOrder.controller.ts. indexSettingUpdatedtriggers:- Socket:
fetchIndexSettingData→ all clients refetch settings - Market data: processor reloads active settings and resubscribes tokens (see §2.2)
- Socket:
indexRecurringSettingUpdatedalso triggersfetchIndexSettingData.
User settings
- Emitted from
user.controller.tsandindexSetting.controller.tswhen page access changes. userSettingUpdatedsends populated user document to only that user’s socket IDs viaupdateUserSettingData.
10. Session Lifecycle vs Data Flow
When each data flow is active relative to market sessions and Zerodha token state.
Market data streaming is not gated on market hours — ticks may show last-close prices when exchanges are closed. Order automation is gated via
isExchangeMarketOpen() in controllers and marketSessionManager.ts.
11. End-to-End Combined Flow
High-level path from login through automated trade to UI update: Plain-language sequence:- Admin logs into Zerodha → WebSocket streaming starts (after active index settings are loaded).
- Ticks broadcast to UI and populate
latestDataMap. - Recurring buy reads latest tick → places BUY via
handlePlaceOrder. - Kite webhook confirms BUY complete → order record updated → UI refreshes.
- Schedule order created for trailing sell → 1s loop watches
baseValuevs strike/target. - On match, MARKET SELL placed → webhook updates → UI refreshes again.
12. Code Map
13. Related Documentation
Summary
- Instrument sync pulls NSE/NFO/MCX/CDS instruments from Kite into
IndexSetting; onlyisActive: trueindices stream live data. - Market ticks flow through base-token LTP → dynamic option strike subscribe → 1s snapshot → three outputs: Socket.io, MongoDB, and
latestDataMap. - Option leg prices live in
liveData(notsegmentTicks); snapshots read them at build time into nestedoptions.legs[]and flat legacy fields. latestDataMapis the shared hub — recurring buy and trailing sell read from it every interval, not from Kite directly.- Orders always flow through
handlePlaceOrder; internalorderEmitterevents are bridged to client refresh signals bysocket-manager.ts. - Kite webhooks update order status asynchronously; the UI refetches order lists on
fetchOrderDatarather than receiving full order payloads over Socket.io. - Financial PnL runs on a separate 5s loop with its own Kite Ticker subscription, pushed via
fetchFinancialSocketData. - Settings changes propagate via emitters, trigger market-data resubscription, and cause targeted or broadcast UI refreshes.