Section 1
LESSON 16 · WEBSOCKET TICK FEED
REST vs WebSocket
advancedreal-time
The REST API (lessons 14–15) requires you to poll repeatedly — call, wait, call again. WebSocket is a persistent connection that pushes data to you every time a trade happens on NSE. This is how all serious live algo systems receive market data.
Tick Stream
Every trade that happens on NSE generates a tick — LTP, volume, bid/ask, OI. KiteSocket delivers these in milliseconds.
Tick → Candle
Your code aggregates incoming ticks into OHLCV candles (5-min, 15-min) and appends them to the DataFrame when each candle closes.
Quote Modes
LTP mode (lightest), Quote mode (adds depth/OI), Full mode (everything including 5-level depth). Use LTP for indices.
Auto Reconnect
KiteSocket handles reconnection automatically. Your on_ticks callback runs after every reconnection — no manual retry code needed.
NSE trade
→
KiteSocket tick
→
on_ticks()
→
TickToCandle
→
Candle closes
→
Signal engine
→
Order / Log
Section 2
KiteTicker — Basic Setup
from kiteconnect import KiteTicker import json, threading # Load credentials with open("config.json") as f: cfg = json.load(f) # Instrument tokens to subscribe TOKENS = { 256265: "NIFTY 50", 260105: "BANKNIFTY" } # ── Create KiteTicker instance ── kws = KiteTicker(api_key=cfg["api_key"], access_token=cfg["access_token"]) # ── Callback: runs on every tick ── def on_ticks(ws, ticks): for tick in ticks: token = tick["instrument_token"] ltp = tick["last_price"] vol = tick.get("volume", 0) timestamp = tick["exchange_timestamp"] symbol = TOKENS.get(token, str(token)) print(f"[{timestamp.strftime('%H:%M:%S')}] {symbol:12} LTP: ₹{ltp:,.2f} Vol: {vol:,}") # ── Callback: runs on WebSocket connect ── def on_connect(ws, response): print("✅ Connected to KiteTicker") ws.subscribe(list(TOKENS.keys())) ws.set_mode(ws.MODE_LTP, list(TOKENS.keys())) # lightest mode for indices # ── Callback: runs on disconnect ── def on_close(ws, code, reason): print(f"❌ Disconnected: {code} — {reason}") # ── Callback: runs on error ── def on_error(ws, code, reason): print(f"⚠️ Error: {code} — {reason}") # ── Assign callbacks ── kws.on_ticks = on_ticks kws.on_connect = on_connect kws.on_close = on_close kws.on_error = on_error # ── Connect (runs in background thread) ── kws.connect(threaded=True)
Live Tick Stream
✅ Connected to KiteTicker
[09:15:01] NIFTY 50 LTP: ₹21,850.20 Vol: 0
[09:15:01] BANKNIFTY LTP: ₹47,210.50 Vol: 0
[09:15:02] NIFTY 50 LTP: ₹21,853.40 Vol: 1,250
[09:15:02] BANKNIFTY LTP: ₹47,218.90 Vol: 850
[09:15:03] NIFTY 50 LTP: ₹21,849.70 Vol: 2,100MODE_LTP — receive only the last traded price. Use for indices and when you only need price. MODE_QUOTE adds OHLC + depth. MODE_FULL adds 5-level market depth + OI. Start with LTP and upgrade when needed.
Section 3
Tick → Candle Builder
Ticks arrive multiple times per second. You need to aggregate them into OHLCV candles. The TickToCandle class builds candles from raw ticks and fires a callback when each candle closes.
from datetime import datetime import pandas as pd class TickToCandle: """ Converts a stream of LTP ticks into OHLCV candles. Fires on_candle_close(candle_dict) when a candle completes. """ def __init__(self, interval_mins: int, on_candle_close): self.interval = interval_mins self.on_candle_close = on_candle_close self._reset() def _reset(self): self.candle_open_time = None self.open = None self.high = None self.low = None self.close = None self.volume = 0 self._prev_vol = 0 def _candle_slot(self, ts: datetime) -> datetime: """Round timestamp down to the start of the candle's time slot.""" minute_floor = (ts.minute // self.interval) * self.interval return ts.replace(minute=minute_floor, second=0, microsecond=0) def on_tick(self, ltp: float, volume: int, timestamp: datetime): """Process a single tick.""" slot = self._candle_slot(timestamp) if self.candle_open_time is None: # First tick — start the first candle self.candle_open_time = slot self.open = ltp self.high = ltp self.low = ltp self._prev_vol = volume elif slot > self.candle_open_time: # New candle started — fire callback with completed candle completed_candle = { "datetime": self.candle_open_time, "open": self.open, "high": self.high, "low": self.low, "close": self.close, "volume": self.volume } self.on_candle_close(completed_candle) # Reset for new candle self._reset() self.candle_open_time = slot self.open = ltp self._prev_vol = volume # Update current candle self.high = max(self.high, ltp) self.low = min(self.low, ltp) self.close = ltp self.volume += max(0, volume - self._prev_vol) self._prev_vol = volume
Section 4
Wiring It All Together
import pandas as pd from datetime import datetime, time as dtime # ── In-memory candle buffer (pre-filled with warm-up data from Lesson 15) ── candle_buffer = warm_df.copy() # DataFrame with historical OHLCV + indicators def on_new_candle(candle: dict): """Called each time a candle closes. Updates indicators and runs signal engine.""" global candle_buffer # Append the new candle row new_row = pd.DataFrame([candle]).set_index("datetime") candle_buffer = pd.concat([candle_buffer, new_row]) # Recalculate indicators incrementally on last 50 rows tail = candle_buffer.tail(50) ema9 = tail["close"].ewm(span=9, adjust=False).mean() ema21 = tail["close"].ewm(span=21, adjust=False).mean() latest_ema9 = ema9.iloc[-1] latest_ema21 = ema21.iloc[-1] latest_close = candle["close"] signal = "BUY" if latest_ema9 > latest_ema21 else "SELL" print(f"[{candle['datetime'].strftime('%H:%M')}] " f"NIFTY ₹{latest_close:,.1f} | EMA9={latest_ema9:.1f} EMA21={latest_ema21:.1f} | {signal}") # ── Create tick builder for NIFTY 5-minute candles ── nifty_builder = TickToCandle(interval_mins=5, on_candle_close=on_new_candle) # ── Updated on_ticks callback that uses the builder ── def on_ticks(ws, ticks): now = datetime.now() # Stop trading after 15:15 (no new entries in last 15 min) if now.time() > dtime(15, 15): return for tick in ticks: if tick["instrument_token"] == 256265: # NIFTY 50 only nifty_builder.on_tick( ltp = tick["last_price"], volume = tick.get("volume_traded", 0), timestamp = tick["exchange_timestamp"] )
Live Candle Output
[09:20] NIFTY ₹21,876.4 | EMA9=21862.1 EMA21=21845.3 | BUY
[09:25] NIFTY ₹21,891.2 | EMA9=21868.7 EMA21=21849.8 | BUY
[09:30] NIFTY ₹21,845.6 | EMA9=21863.2 EMA21=21849.1 | BUY
[09:35] NIFTY ₹21,820.3 | EMA9=21851.4 EMA21=21847.5 | BUY
[09:40] NIFTY ₹21,798.9 | EMA9=21838.2 EMA21=21844.6 | SELLSection 5
Multi-Symbol WebSocket
# Subscribe multiple tokens and route ticks to separate builders NIFTY_TOKEN = 256265 BANKNIFTY_TOKEN = 260105 # Separate TickToCandle instances for each instrument nifty_builder = TickToCandle(5, lambda c: on_candle("NIFTY", c)) banknifty_builder = TickToCandle(5, lambda c: on_candle("BANKNIFTY", c)) builders = { NIFTY_TOKEN: nifty_builder, BANKNIFTY_TOKEN: banknifty_builder } def on_candle(symbol: str, candle: dict): print(f"✅ New {symbol} candle: {candle['datetime'].strftime('%H:%M')} " f"O={candle['open']:.1f} H={candle['high']:.1f} " f"L={candle['low']:.1f} C={candle['close']:.1f} V={candle['volume']:,}") def on_ticks(ws, ticks): for tick in ticks: token = tick["instrument_token"] if token in builders: builders[token].on_tick( ltp = tick["last_price"], volume = tick.get("volume_traded", 0), timestamp = tick["exchange_timestamp"] ) def on_connect(ws, response): print("✅ WebSocket connected") tokens = [NIFTY_TOKEN, BANKNIFTY_TOKEN] ws.subscribe(tokens) ws.set_mode(ws.MODE_LTP, tokens)
Architecture Rule
One WebSocket, multiple instruments. KiteConnect allows subscribing up to 3,000 tokens on a single WebSocket connection. Never open multiple WebSocket connections — use the routing pattern above to handle all instruments from one callback.Section 6
Market Hours Kill Switch
from datetime import datetime, time as dtime MARKET_OPEN = dtime(9, 15) MARKET_CLOSE = dtime(15, 30) LAST_ENTRY = dtime(15, 15) # No new entries after this def is_trading_time() -> bool: """Return True only during NSE trading hours.""" now = datetime.now().time() return MARKET_OPEN <= now <= LAST_ENTRY def is_session_active() -> bool: """Return True while WebSocket should remain connected.""" now = datetime.now().time() return MARKET_OPEN <= now <= MARKET_CLOSE # Updated on_ticks with time gate def on_ticks(ws, ticks): if not is_session_active(): ws.close() print("🔴 Market closed — WebSocket disconnected") return for tick in ticks: token = tick["instrument_token"] if token in builders: builders[token].on_tick( tick["last_price"], tick.get("volume_traded", 0), tick["exchange_timestamp"] ) # Only run signal engine during active entry window if is_trading_time(): run_signal_engine(token)
Section 7
Quiz
Q1. What is the main advantage of WebSocket over polling the REST API for live data?
Q2. In the TickToCandle class, when does the on_candle_close callback fire?
Q3. How many WebSocket connections should you open to monitor NIFTY and BANKNIFTY simultaneously?
Section 8
Exercises
Exercise 01
Tick Logger
Set up a KiteTicker that subscribes to NIFTY 50 and BANKNIFTY. In the on_ticks callback, append every tick to a CSV file with columns: timestamp, symbol, ltp, volume. Run for 5 minutes during market hours and inspect the CSV.
Exercise 02
Candle Builder Unit Test
Without a live API, simulate 60 ticks for a 5-minute NIFTY candle (use datetime objects spaced 5 seconds apart, with realistic LTP values near 21900). Feed them to TickToCandle and verify: (1) candle open = first tick's LTP, (2) candle high = max LTP, (3) candle low = min LTP, (4) candle close = last tick's LTP before the next slot.
Exercise 03
Live Signal Monitor
Combine everything: warm-up 50 candles of NIFTY data from the historical API. Set up KiteTicker with TickToCandle for 5-minute candles. When each candle closes, recalculate EMA9/EMA21 on the last 30 candles and print a signal (BUY/SELL/WAIT) with the timestamp and current EMA values.
Section 9
Lesson Summary
KiteTicker
Persistent WebSocket. Callbacks: on_ticks, on_connect, on_close, on_error. Set threaded=True for non-blocking.
Subscribe Modes
MODE_LTP (price only), MODE_QUOTE (OHLC+depth), MODE_FULL (full depth+OI). LTP is sufficient for index signals.
TickToCandle
Detects new candle slot on each tick. Fires on_candle_close with complete OHLCV dict when slot changes.
Multi-Symbol
One WebSocket, many tokens. Route ticks to separate TickToCandle instances via token lookup dict.
Time Gate
is_trading_time() guards all signal logic. Stop new entries at 15:15, close WebSocket at 15:30.
Next Lesson
Lesson 17 combines everything into a complete live data pipeline — Phase 2 finale!
