kotak
¶
Kotak Neo ticks.
Kotak names an instrument as exchange_segment|token in its own segment vocabulary - nse_cm,
nse_fo, mcx_fo - and stores the bare token, which collides across segments, so the segment
narrows the search.
What Kotak's values mean, as measured on Kotak's HSM feed (wss://mlhsm.kotaksecurities.com) against
Zerodha's ticks received at the same moment during the session of 2026-09-15:
closeis the previous session's close: TCS read 2200.80, HDFCBANK 708.25 and CRUDEOIL SEP 9717.0, all Zerodha's figures, while their last prices moved.- On MCX every quantity - last quantity, volume, total buy and sell quantity, open interest and every order book quantity - is lots times Kotak's own lot size. GOLD OCT (Kotak's lot 1) read volume 562 and open interest 9474, Zerodha's lots exactly; CRUDEOIL SEP (Kotak's lot 100) read 276900 and 1598900 against Zerodha's 2769 and 15989; NATURALGAS SEP (Kotak's lot 1250) read 4943750 and 49037500 against 3955 and 39230. So each is divided by Kotak's lot size and multiplied by the authoritative one.
- On NSE and BSE quantities are units, as at Zerodha.
- Both times are true instants: the last trade time and the exchange timestamp matched Zerodha's to the second on TCS, HDFCBANK, CRUDEOIL, NATURALGAS and GOLD.
The earlier sfeed feed, measured on the weekend snapshots of 2026-09-12 and 2026-09-13, sent a date
with no time and scaled only the MCX last quantity; bin/kotak/instruments/websocket_quotes no longer uses it.
Classes:
| Name | Description |
|---|---|
KotakTickNormalizer |
Normalizes Kotak Neo ticks. |
KotakTickNormalizer
¶
Bases: TickNormalizer
Normalizes Kotak Neo ticks.
Build the lookup from each Kotak exchange segment to the canonical segments it can hold.
The table is worked out once here from EXCHANGE_SEGMENTS, so feed_key only has to look a tick's exchange segment up rather than recompute its segments on every call.
Example programs
"""Places Kotak Neo feed tokens, spelled `exchange_segment|token`, on the canonical segments they can be in.
A Kotak Neo token names its exchange segment before a bar. The cash segments `nse_cm` and `bse_cm` hold both shares and indices, and the `_fo`, `_cd` and `_co` segments hold derivatives. The segment is read without regard to case, so `BSE_CM|500325` works. A token with nothing after the bar, or a segment Kotak does not have, prints `None`.
The normalizer only works on the text of each token, so the program needs no Redis, database, network or stand-in of any kind. Each output line shows a token as the feed spells it, then the broker token that `unified.broker_mappings` is searched with, the order symbol when the broker names instruments by symbol, and how many canonical segments the search is limited to, with the first of them. A token the normalizer cannot place prints `None`.
Run it from the project root:
python examples/stock_brokers/instruments/ticks/kotak/KotakTickNormalizer/example_1_placing_feed_tokens.py
"""
from stock_brokers.instruments.ticks.kotak import (
KotakTickNormalizer,
)
class PlacingFeedTokensExample:
"""Asks the Kotak normalizer where each of a few feed tokens belongs.
Attributes:
normalizer (KotakTickNormalizer): The normalizer being shown.
tokens (list): The tokens to place, spelled as the feed spells them.
"""
def __init__(self):
"""Builds the normalizer and the tokens to place.
Returns:
None: This method returns nothing.
"""
self.normalizer = KotakTickNormalizer()
self.tokens = [
'nse_cm|2885',
'nse_fo|35003',
'mcx_fo|440001',
'BSE_CM|500325',
'nse_cm|',
'nyse|1',
]
def describe(self, feed_key):
"""Describes a feed key in one line.
Args:
feed_key (FeedKey | None): The key the normalizer returned.
Returns:
str: The description.
"""
if feed_key is None:
return 'None'
segment_count = len(feed_key.segments)
first_segment = feed_key.segments[0]
return f'broker_token={feed_key.broker_token!r} order_symbol={feed_key.order_symbol!r} segments={segment_count} starting with {first_segment}'
def run(self):
"""Prints where each token belongs.
Returns:
None: This method returns nothing.
"""
print(f'Broker: {KotakTickNormalizer.BROKER_NAME}')
for token in self.tokens:
feed_key = self.normalizer.feed_key(token)
print(f'{token!r} -> {self.describe(feed_key)}')
if __name__ == '__main__':
PlacingFeedTokensExample().run()
It prints:
Broker: kotak
'nse_cm|2885' -> broker_token='2885' order_symbol=None segments=10 starting with nse_equities
'nse_fo|35003' -> broker_token='35003' order_symbol=None segments=16 starting with nse_fixed_income_futures
'mcx_fo|440001' -> broker_token='440001' order_symbol=None segments=16 starting with mcx_fixed_income_futures
'BSE_CM|500325' -> broker_token='500325' order_symbol=None segments=10 starting with bse_equities
'nse_cm|' -> None
'nyse|1' -> None
"""Normalizes one Kotak tick for an MCX gold future, whose quantities are in Kotak's own lots.
Kotak's MCX quantities are lots times Kotak's own lot size, which for GOLD is 1 where the contract's is 100. The normalizer reports those fields as `broker_lots`, so the plan's multiplier is the authoritative lot divided by Kotak's, 100, and a last quantity of 1 becomes 100 units. Kotak's `close` is always the previous close, so `reported_close` is kept both before and after the session ends.
The plan that `normalize` applies is normally compiled by `TickResolver` from the mapping cache. This program builds it by hand with the same rules, asking the normalizer how it reports each quantity field and when its close may be trusted, so it needs no database. The tick is shaped like the ones the broker's market feed publishes, with fixed times, so the output is the same on every run. The program normalizes the tick twice, once as received before the session's close and once as received after it, which only changes whether `reported_close` is kept. Order book levels are printed as (price, quantity, orders).
Run it from the project root:
python examples/stock_brokers/instruments/ticks/kotak/KotakTickNormalizer/example_2_quantities_in_its_own_lots.py
"""
import datetime
import zoneinfo
from stock_brokers.instruments.ticks import base
from stock_brokers.instruments.ticks.base import (
InstrumentPlan,
)
from stock_brokers.instruments.ticks.kotak import (
KotakTickNormalizer,
)
from stock_brokers.instruments.ticks.utilities import sessions
INDIA = zoneinfo.ZoneInfo('Asia/Kolkata')
class GoldTickExample:
"""Normalizes one Kotak tick through a hand-built plan and prints the result.
Attributes:
normalizer (KotakTickNormalizer): The normalizer being shown.
received_at (float): When the tick was received, in epoch seconds.
tick (dict): The tick as the market feed published it.
plan (InstrumentPlan): The plan for the tick's instrument.
"""
def __init__(self):
"""Builds the normalizer, the tick and its plan.
Returns:
None: This method returns nothing.
"""
self.normalizer = KotakTickNormalizer()
received = datetime.datetime(2026, 9, 15, 14, 20, tzinfo=INDIA)
self.received_at = received.timestamp()
self.tick = {
'instrument_token': 'mcx_fo|440001',
'last_price': 109850.0,
'last_quantity': 1,
'average_price': 109712.4,
'volume': 12840,
'buy_quantity': 322,
'sell_quantity': 417,
'ohlc': {
'open': 109500.0,
'high': 110020.0,
'low': 109388.0,
'close': 109460.0,
},
'oi': 15632,
'oi_day_high': 15890,
'oi_day_low': 15411,
'last_trade_time': self.received_at - 2.0,
'exchange_timestamp': self.received_at - 1.0,
'received_at': self.received_at,
'depth': {
'buy': [
{
'price': 109848.0,
'quantity': 2,
'orders': 2,
},
{
'price': 109845.0,
'quantity': 5,
'orders': 3,
},
],
'sell': [
{
'price': 109850.0,
'quantity': 1,
'orders': 1,
},
{
'price': 109853.0,
'quantity': 4,
'orders': 2,
},
],
},
}
self.plan = self.build_plan()
def build_plan(self):
"""Compiles the plan for the tick's instrument the way the resolver would.
Returns:
InstrumentPlan: The plan.
"""
exchange = 'mcx'
segment = 'mcx_commodity_futures'
lot_size = 100
broker_lot_size = 1
multipliers = {}
for field in base.QUANTITY_FIELDS:
basis = self.normalizer.quantity_basis(exchange, field)
if basis == base.BASIS_LOTS:
multipliers[field] = lot_size
elif basis == base.BASIS_BROKER_LOTS:
multipliers[field] = lot_size // broker_lot_size
else:
multipliers[field] = 1
identity = {
'exchange': 'mcx',
'segment': 'mcx_commodity_futures',
'shape': 'future',
'symbol': 'GOLD26OCTFUT',
'underlying_symbol': 'GOLD',
'expiry_date': '2026-10-05',
'strike_price': None,
'option_type': None,
}
instrument_id = '11111111-1111-5111-8111-000000000106'
broker_token = '440001'
return InstrumentPlan(
instrument_id=instrument_id,
broker=KotakTickNormalizer.BROKER_NAME,
broker_token=broker_token,
exchange=exchange,
segment=segment,
shape=identity['shape'],
session=sessions.session_for(segment),
lot_size=lot_size,
multipliers=multipliers,
price_decimals=2,
negative_prices=True,
close_policy=self.normalizer.close_policy(exchange),
has_open_interest=identity['shape'] != 'security',
trusts_last_trade_time=KotakTickNormalizer.TRUSTS_LAST_TRADE_TIME,
trusts_exchange_time=KotakTickNormalizer.TRUSTS_EXCHANGE_TIME,
identity_json=base.identity_json(
instrument_id,
KotakTickNormalizer.BROKER_NAME,
broker_token,
identity,
lot_size,
),
)
def run(self):
"""Prints the feed key, the plan's rules and the normalized values.
Returns:
None: This method returns nothing.
"""
token = self.tick['instrument_token']
feed_key = self.normalizer.feed_key(token)
print(f'Token {token!r}: broker_token={feed_key.broker_token!r} order_symbol={feed_key.order_symbol!r}')
volume_basis = self.normalizer.quantity_basis(self.plan.exchange, 'volume')
print(f'Volume on {self.plan.exchange} is reported in {volume_basis}, multiplier {self.plan.multipliers["volume"]}')
print(f'Close policy on {self.plan.exchange}: {self.normalizer.close_policy(self.plan.exchange)}')
before_close = self.normalizer.normalize(self.tick, self.plan, True)
print('Normalized, received before the close:')
for name, value in before_close.items():
print(f' {name}: {value}')
after_close = self.normalizer.normalize(self.tick, self.plan, False)
print(f'reported_close when received after the close: {after_close["reported_close"]}')
if __name__ == '__main__':
GoldTickExample().run()
It prints:
Token 'mcx_fo|440001': broker_token='440001' order_symbol=None
Volume on mcx is reported in broker_lots, multiplier 100
Close policy on mcx: always
Normalized, received before the close:
last_price: 109850.0
last_quantity: 100
average_price: 109712.4
volume: 1284000
buy_quantity: 32200
sell_quantity: 41700
open: 109500.0
high: 110020.0
low: 109388.0
reported_close: 109460.0
oi: 1563200
oi_day_high: 1589000
oi_day_low: 1541100
last_trade_time: 1789462198.0
exchange_time: 1789462199.0
bids: [(109848.0, 200, 2), (109845.0, 500, 3)]
asks: [(109850.0, 100, 1), (109853.0, 400, 2)]
reported_close when received after the close: 109460.0
Methods:
| Name | Description |
|---|---|
feed_key |
The broker token and segments for a Kotak |
tick_spelling |
A subscription-set token spelled the way the feed spells it on ticks. |
quantity_basis |
Whether the broker reports a quantity field in lots or units on an exchange. |
close_policy |
When the broker's |
normalize |
Apply a plan to one tick. |
Source code in stock_brokers/instruments/ticks/kotak.py
feed_key
¶
The broker token and segments for a Kotak exchange_segment|token.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
instrument_token
|
str
|
The tick's |
required |
Returns:
| Type | Description |
|---|---|
FeedKey | None
|
The key, or None for a malformed token or an unknown segment. |
Example usage
Source code in stock_brokers/instruments/ticks/kotak.py
tick_spelling
¶
A subscription-set token spelled the way the feed spells it on ticks.
Plans are filed under the tick's spelling, because that is what the per-tick path looks up. A subscription set holds strings in the spelling the broker's quote feed subscribes with, which is not always the tick's: Kite ticks carry an integer, Dhan ticks a numeric segment code.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
subscription_token
|
str
|
A member of |
required |
Returns:
| Type | Description |
|---|---|
int | str | None
|
The tick's spelling, or None for a token this normalizer cannot place. |
Example usage
print(f"Tick spelling of 'NSE-2885': {self.normalizer.tick_spelling('NSE-2885')!r}")
Source code in stock_brokers/instruments/ticks/base.py
quantity_basis
¶
Whether the broker reports a quantity field in lots or units on an exchange.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
exchange
|
str
|
Canonical exchange. |
required |
field
|
str
|
One of QUANTITY_FIELDS. |
required |
Returns:
| Type | Description |
|---|---|
str
|
BASIS_UNITS, BASIS_LOTS or BASIS_BROKER_LOTS. |
Example usage
Source code in stock_brokers/instruments/ticks/base.py
close_policy
¶
When the broker's close is the previous session's close on an exchange.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
exchange
|
str
|
Canonical exchange. |
required |
Returns:
| Type | Description |
|---|---|
str
|
One of the CLOSE_* policies. |
Example usage
print(f'{exchange} close policy: {self.normalizer.close_policy(exchange)}')
return InstrumentPlan(
instrument_id='11111111-1111-5111-8111-000000000102',
broker='imaginary',
broker_token='2885',
exchange=exchange,
segment=segment,
shape=shape,
session=sessions.session_for(segment),
lot_size=lot_size,
multipliers=multipliers,
price_decimals=2,
negative_prices=shape != 'security' and exchange == 'mcx',
close_policy=self.normalizer.close_policy(exchange),
has_open_interest=shape != 'security',
trusts_last_trade_time=True,
trusts_exchange_time=True,
identity_json='"instrument_id":"11111111-1111-5111-8111-000000000102"',
)
Source code in stock_brokers/instruments/ticks/base.py
normalize
¶
Apply a plan to one tick.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
tick
|
dict
|
A tick as the broker's market feed published it. |
required |
plan
|
InstrumentPlan
|
The compiled plan for the tick's token. |
required |
before_trading_close
|
bool
|
Whether the tick was received before its session's close, for the close policy. |
required |
Returns:
| Type | Description |
|---|---|
dict | None
|
The normalized values, or None when the tick carries no usable last price. Keys are last_price, last_quantity, average_price, volume, buy_quantity, sell_quantity, open, high, low, reported_close, oi, oi_day_high, oi_day_low, last_trade_time, exchange_time, bids and asks, where bids and asks are lists of (price, quantity, orders) best first. |
Example usage
values = self.normalizer.normalize(
{
'last_price': -12.5,
'volume': 400,
'oi': 900,
'received_at': self.received_at,
},
future,
True,
)
outcome = self.normalizer.normalize(
{
'last_price': last_price,
'received_at': self.received_at,
},
share,
True,
)
Source code in stock_brokers/instruments/ticks/base.py
_instant
¶
A broker timestamp as a true epoch, or None when it is missing or implausible.
Brokers that stamp times on a shifted clock have that corrected in their market feed; what is left to catch here is a clock nobody has corrected yet, which shows as an instant days away from when the tick arrived.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
value
|
float | int | None
|
Epoch seconds as the feed published them. |
required |
received_at
|
float
|
When the feed decoded the tick. |
required |
Returns:
| Type | Description |
|---|---|
float | None
|
The instant, or None. |