base
¶
Normalizing one broker's ticks into values comparable with every other broker's.
Every market feed already emits the same twenty keys,
but the same key does not mean the same thing across brokers. The token and exchange are in the
broker's own vocabulary; MCX quantities are lots at one broker and units at another; close is the
previous session's close at one broker and today's close after the bell at another; some timestamps
are missing or on a shifted clock; float32 prices carry noise in the last digits. A TickNormalizer
subclass states those facts about one broker, and this base class applies them.
The work is split so that the per-tick cost is arithmetic only:
feed_keyturns a tick's token into the broker token and canonical segments thatunified.broker_mappingsis searched with. It runs once per token per mapping date.InstrumentPlanis compiled from that lookup - the instrument id, its identity, and every per-field multiplier and flag - once per token per mapping date.normalizeapplies a plan to a tick, and runs on every tick.
Nothing here touches Redis or the database, so the same code serves the live service and is tested offline.
Classes:
| Name | Description |
|---|---|
InstrumentPlan |
Everything needed to normalize one broker token's ticks, decided once rather than per tick. |
TickNormalizer |
Base class for one broker's tick normalization. |
Functions:
| Name | Description |
|---|---|
family_segments |
The canonical segments for a family of instruments on some exchanges. |
identity_json |
The identity part of a unified quote, serialized once for an instrument. |
InstrumentPlan
¶
Everything needed to normalize one broker token's ticks, decided once rather than per tick.
Attributes:
| Name | Type | Description |
|---|---|---|
instrument_id |
str
|
The unified.instruments id. |
broker |
str
|
The broker the token belongs to. |
broker_token |
str
|
The token as stored in unified.broker_mappings. |
exchange |
str
|
Canonical exchange. |
segment |
str
|
Exchange-prefixed canonical segment. |
shape |
str
|
"security", "future" or "option". |
session |
Session
|
The window its ticks are accepted in. |
lot_size |
int | None
|
Underlying units per lot, or None when unknown. |
multipliers |
dict
|
Quantity field to the multiplier turning the broker's figure into units, or None when that field cannot be converted. |
price_decimals |
int
|
Places prices are rounded to. |
negative_prices |
bool
|
Whether a negative price is real (commodity futures have traded below zero). |
close_policy |
str
|
One of CLOSE_ALWAYS, CLOSE_BEFORE_SESSION_END, CLOSE_NEVER. |
has_open_interest |
bool
|
False for securities, whose open interest is meaningless. |
trusts_last_trade_time |
bool
|
Whether the broker's last trade time is used. |
trusts_exchange_time |
bool
|
Whether the broker's exchange timestamp is used. |
identity_json |
str
|
The identity fields serialized once, without the enclosing braces, for splicing into every quote. |
Build a plan from every attribute given by name.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
**values
|
dict
|
One keyword argument per name in |
{}
|
Raises:
| Type | Description |
|---|---|
KeyError
|
A name in |
Example programs
"""Builds the plan for one MCX crude oil future and applies it to a tick.
An `InstrumentPlan` holds every decision about one broker token that does not change from tick to tick: which instrument it is, the window its ticks are accepted in, the multiplier that turns each quantity into units, how many places prices are rounded to, and which of the broker's fields are trusted. The live service compiles one per token through `TickResolver`; this program fills one in by hand, so every attribute is visible, and then hands it to `ZerodhaTickNormalizer.normalize` to show what the plan is for.
The identity part of a unified quote is serialized once into the plan's `identity_json`, so the per-tick path only splices it in. The tick has a fixed receipt time, so the output is identical on every run. Nothing here needs Redis, a database or a broker.
Run it from the project root:
python examples/stock_brokers/instruments/ticks/base/InstrumentPlan/example_1_a_plan_for_one_future.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.utilities import sessions
from stock_brokers.instruments.ticks.zerodha import (
ZerodhaTickNormalizer,
)
INDIA = zoneinfo.ZoneInfo('Asia/Kolkata')
class FuturePlanExample:
"""Builds a plan for a crude oil future, prints it and normalizes one tick with it.
Attributes:
plan (InstrumentPlan): The plan being shown.
received_at (float): When the tick was received, in epoch seconds.
"""
def __init__(self):
"""Builds the plan with a 100-barrel lot and quantities reported in lots.
Returns:
None: This method returns nothing.
"""
identity = {
'exchange': 'mcx',
'segment': 'mcx_commodity_futures',
'shape': 'future',
'symbol': 'CRUDEOIL26OCTFUT',
'underlying_symbol': 'CRUDEOIL',
'expiry_date': datetime.date(2026, 10, 19),
'strike_price': None,
'option_type': None,
}
multipliers = {}
for field in base.QUANTITY_FIELDS:
multipliers[field] = 100
self.plan = InstrumentPlan(
instrument_id='11111111-1111-5111-8111-000000000101',
broker='zerodha',
broker_token='5720583',
exchange='mcx',
segment='mcx_commodity_futures',
shape='future',
session=sessions.session_for('mcx_commodity_futures'),
lot_size=100,
multipliers=multipliers,
price_decimals=2,
negative_prices=True,
close_policy=base.CLOSE_ALWAYS,
has_open_interest=True,
trusts_last_trade_time=True,
trusts_exchange_time=True,
identity_json=base.identity_json(
'11111111-1111-5111-8111-000000000101',
'zerodha',
'5720583',
identity,
100,
),
)
received = datetime.datetime(2026, 9, 15, 22, 46, tzinfo=INDIA)
self.received_at = received.timestamp()
def run(self):
"""Prints every attribute of the plan, then a tick normalized with it.
Returns:
None: This method returns nothing.
"""
plan = self.plan
print(f'instrument_id: {plan.instrument_id}')
print(f'broker: {plan.broker}')
print(f'broker_token: {plan.broker_token}')
print(f'exchange: {plan.exchange}')
print(f'segment: {plan.segment}')
print(f'shape: {plan.shape}')
print(f'session: {plan.session}')
print(f'lot_size: {plan.lot_size}')
print(f'multipliers: {plan.multipliers}')
print(f'price_decimals: {plan.price_decimals}')
print(f'negative_prices: {plan.negative_prices}')
print(f'close_policy: {plan.close_policy}')
print(f'has_open_interest: {plan.has_open_interest}')
print(f'trusts_last_trade_time: {plan.trusts_last_trade_time}')
print(f'trusts_exchange_time: {plan.trusts_exchange_time}')
print(f'identity_json: {plan.identity_json}')
tick = {
'instrument_token': 5720583,
'last_price': 9731.0,
'last_quantity': 2,
'volume': 67958,
'ohlc': {
'open': 9702.0,
'high': 9745.0,
'low': 9688.0,
'close': 9722.0,
},
'oi': 18095,
'received_at': self.received_at,
}
values = ZerodhaTickNormalizer().normalize(tick, self.plan, True)
print('A tick normalized with this plan:')
print(f" last_price {values['last_price']}, last_quantity {values['last_quantity']}, volume {values['volume']}, oi {values['oi']}, reported_close {values['reported_close']}")
if __name__ == '__main__':
FuturePlanExample().run()
It prints:
instrument_id: 11111111-1111-5111-8111-000000000101
broker: zerodha
broker_token: 5720583
exchange: mcx
segment: mcx_commodity_futures
shape: future
session: Session(calendar='commodity', opens=32400, evening_opens=61200, trading_close=86100, window_close=86399)
lot_size: 100
multipliers: {'last_quantity': 100, 'volume': 100, 'buy_quantity': 100, 'sell_quantity': 100, 'depth_quantity': 100, 'oi': 100, 'oi_day_high': 100, 'oi_day_low': 100}
price_decimals: 2
negative_prices: True
close_policy: always
has_open_interest: True
trusts_last_trade_time: True
trusts_exchange_time: True
identity_json: "instrument_id":"11111111-1111-5111-8111-000000000101","broker":"zerodha","broker_token":"5720583","exchange":"mcx","segment":"mcx_commodity_futures","shape":"future","symbol":"CRUDEOIL26OCTFUT","underlying_symbol":"CRUDEOIL","expiry_date":"2026-10-19","strike_price":null,"option_type":null,"lot_size":100
A tick normalized with this plan:
last_price 9731.0, last_quantity 200, volume 6795800, oi 1809500, reported_close 9722.0
"""Shows that a plan must be given every attribute, and cannot take one it does not declare.
`InstrumentPlan` declares its attributes in `__slots__` and its constructor reads each of them from the keyword arguments by name. That is deliberate: a plan is built once per token and read on every tick, so a plan missing a field would fail on the hot path, far from the mistake. Leaving one out fails at construction instead, with a `KeyError` naming the missing attribute.
Because of `__slots__`, a plan also refuses an attribute that is not declared, which catches a misspelt name. This program builds an equity plan without `identity_json` and catches the error, then builds a complete one and tries to set a misspelt attribute on it. It needs no data store or broker.
Run it from the project root:
python examples/stock_brokers/instruments/ticks/base/InstrumentPlan/example_2_a_missing_attribute.py
"""
from stock_brokers.instruments.ticks import base
from stock_brokers.instruments.ticks.base import (
InstrumentPlan,
)
from stock_brokers.instruments.ticks.utilities import sessions
class MissingAttributeExample:
"""Builds a plan without one attribute, then a complete one, and shows both refusals.
Attributes:
values (dict): The attributes of an NSE equity plan, without `identity_json`.
"""
def __init__(self):
"""Prepares the attributes of a plan for RELIANCE on NSE, leaving one out.
Returns:
None: This method returns nothing.
"""
multipliers = {}
for field in base.QUANTITY_FIELDS:
multipliers[field] = 1
self.values = {
'instrument_id': '11111111-1111-5111-8111-000000000102',
'broker': 'zerodha',
'broker_token': '738561',
'exchange': 'nse',
'segment': 'nse_equities',
'shape': 'security',
'session': sessions.session_for('nse_equities'),
'lot_size': 1,
'multipliers': multipliers,
'price_decimals': 2,
'negative_prices': False,
'close_policy': base.CLOSE_ALWAYS,
'has_open_interest': False,
'trusts_last_trade_time': True,
'trusts_exchange_time': True,
}
def run(self):
"""Prints the two refusals and the plan that was built.
Returns:
None: This method returns nothing.
"""
try:
InstrumentPlan(**self.values)
except KeyError as error:
print(f'Refused, missing attribute: {error}')
self.values['identity_json'] = '"instrument_id":"11111111-1111-5111-8111-000000000102"'
plan = InstrumentPlan(**self.values)
print(f'Built a plan for {plan.segment} with lot size {plan.lot_size} and session {plan.session}')
try:
plan.lotsize = 5
except AttributeError as error:
print(f'Refused, undeclared attribute: {error}')
if __name__ == '__main__':
MissingAttributeExample().run()
It prints:
Refused, missing attribute: 'identity_json'
Built a plan for nse_equities with lot size 1 and session Session(calendar='equity', opens=32400, evening_opens=None, trading_close=55800, window_close=57600)
Refused, undeclared attribute: 'InstrumentPlan' object has no attribute 'lotsize' and no __dict__ for setting new attributes
Source code in stock_brokers/instruments/ticks/base.py
TickNormalizer
¶
Base class for one broker's tick normalization.
A subclass implements feed_key and sets the class attributes that describe the broker. The
defaults are the conservative reading: quantities in units, close not trusted as the previous
close, both timestamps trusted.
Attributes:
| Name | Type | Description |
|---|---|---|
BROKER_NAME |
str
|
The broker this normalizer is for. |
LOT_FIELDS |
dict
|
Exchange to the set of QUANTITY_FIELDS the broker reports in lots there. |
BROKER_LOT_FIELDS |
dict
|
Exchange to the set of QUANTITY_FIELDS the broker reports as lots times its own lot size there. |
CLOSE_POLICY |
dict
|
Exchange to one of the CLOSE_* policies. Exchanges absent use CLOSE_NEVER. |
TRUSTS_LAST_TRADE_TIME |
bool
|
Whether the broker's last_trade_time is a true instant. |
TRUSTS_EXCHANGE_TIME |
bool
|
Whether the broker's exchange_timestamp is a true instant. |
Example programs
"""Writes the normalizer for a new broker by subclassing `TickNormalizer`, and asks it the questions the resolver asks.
A broker's normalizer states facts about that broker and leaves the work to the base class. It implements `feed_key`, which turns the token on a tick into the broker token and canonical segments the mappings are searched with, and sets class attributes that say which quantities arrive in lots, when `close` is the previous close and which timestamps are true instants. The subclass here is for an imaginary broker whose feed spells tokens `NSE-2885` and `MCX-440001`, reports MCX volume and open interest in lots, and trusts its close on NSE only.
The program then calls the base class's own methods on it: `quantity_basis` and `close_policy`, which the resolver asks when it compiles a plan, and `tick_spelling`, whose default keeps a subscription token as it is. Everything is pure computation, so no data store or broker is involved.
Run it from the project root:
python examples/stock_brokers/instruments/ticks/base/TickNormalizer/example_1_a_new_broker_subclass.py
"""
from stock_brokers.instruments.ticks import base
from stock_brokers.instruments.ticks.base import (
TickNormalizer,
)
class ImaginaryBrokerTickNormalizer(TickNormalizer):
"""Normalizes ticks from an imaginary broker whose tokens are spelled `EXCHANGE-TOKEN`."""
BROKER_NAME = 'imaginary'
FAMILIES = {
'NSE': 'cash_or_index',
'NFO': 'derivative',
'MCX': 'derivative',
}
EXCHANGES = {
'NSE': 'nse',
'NFO': 'nse',
'MCX': 'mcx',
}
LOT_FIELDS = {
'mcx': frozenset(
[
'volume',
'oi',
]
),
}
CLOSE_POLICY = {
'nse': base.CLOSE_ALWAYS,
}
TRUSTS_EXCHANGE_TIME = False
def feed_key(self, instrument_token):
"""Reads the broker token and segments out of an `EXCHANGE-TOKEN` token.
Args:
instrument_token (str): The tick's token, such as `NSE-2885`.
Returns:
FeedKey | None: The key, or None for a token this normalizer cannot place.
"""
exchange, _, token = str(instrument_token).partition('-')
if exchange not in self.FAMILIES or not token:
return None
segments = base.family_segments(
[
self.EXCHANGES[exchange],
],
self.FAMILIES[exchange],
)
return base.FeedKey(token, segments)
class NewBrokerSubclassExample:
"""Asks the imaginary broker's normalizer everything the resolver asks.
Attributes:
normalizer (ImaginaryBrokerTickNormalizer): The normalizer being shown.
"""
def __init__(self):
"""Builds the normalizer.
Returns:
None: This method returns nothing.
"""
self.normalizer = ImaginaryBrokerTickNormalizer()
def run(self):
"""Prints feed keys, tick spellings, quantity bases and close policies.
Returns:
None: This method returns nothing.
"""
tokens = [
'NSE-2885',
'MCX-440001',
'LSE-1',
]
for token in tokens:
feed_key = self.normalizer.feed_key(token)
if feed_key is None:
print(f'{token}: cannot be placed')
continue
print(f'{token}: broker token {feed_key.broker_token}, {len(feed_key.segments)} segments starting with {feed_key.segments[0]}')
print(f"Tick spelling of 'NSE-2885': {self.normalizer.tick_spelling('NSE-2885')!r}")
fields = [
'last_quantity',
'volume',
'oi',
]
exchanges = [
'nse',
'mcx',
]
for exchange in exchanges:
for field in fields:
basis = self.normalizer.quantity_basis(exchange, field)
print(f'{exchange} {field}: {basis}')
print(f'{exchange} close policy: {self.normalizer.close_policy(exchange)}')
try:
TickNormalizer().feed_key('NSE-2885')
except NotImplementedError:
print('The base class alone cannot place a token: feed_key raises NotImplementedError')
if __name__ == '__main__':
NewBrokerSubclassExample().run()
It prints:
NSE-2885: broker token 2885, 10 segments starting with nse_equities
MCX-440001: broker token 440001, 16 segments starting with mcx_fixed_income_futures
LSE-1: cannot be placed
Tick spelling of 'NSE-2885': 'NSE-2885'
nse last_quantity: units
nse volume: units
nse oi: units
nse close policy: always
mcx last_quantity: units
mcx volume: lots
mcx oi: lots
mcx close policy: never
The base class alone cannot place a token: feed_key raises NotImplementedError
"""Shows the rules the base class's `normalize` applies to every broker's ticks, by feeding it awkward ticks.
`TickNormalizer.normalize` is shared by every broker. Beyond multiplying quantities, it refuses values that cannot be right: a tick with no last price, or a zero or negative one where negative prices are impossible, gives `None`; a timestamp more than seven days before the tick was received, or more than five seconds after it, is dropped as a clock nobody corrected; an order book level without both a price and a quantity is dropped and the book is cut to five levels; and a quantity whose multiplier is unknown is left as `None` rather than guessed.
The program uses a tiny subclass for an imaginary broker, because `normalize` needs only a plan and the broker's class attributes. Two plans are built by hand: an NSE share, and an MCX future whose lot size is unknown. Every time is fixed, so the output is the same on every run, and nothing touches a data store or a broker.
Run it from the project root:
python examples/stock_brokers/instruments/ticks/base/TickNormalizer/example_2_what_normalize_refuses.py
"""
import datetime
import zoneinfo
from stock_brokers.instruments.ticks import base
from stock_brokers.instruments.ticks.base import (
InstrumentPlan,
TickNormalizer,
)
from stock_brokers.instruments.ticks.utilities import sessions
INDIA = zoneinfo.ZoneInfo('Asia/Kolkata')
class ImaginaryBrokerTickNormalizer(TickNormalizer):
"""Normalizes ticks from an imaginary broker that reports MCX quantities in lots."""
BROKER_NAME = 'imaginary'
LOT_FIELDS = {
'mcx': frozenset(base.QUANTITY_FIELDS),
}
def feed_key(self, instrument_token):
"""Places every token among NSE shares, which is all this example needs.
Args:
instrument_token (str): The tick's token.
Returns:
FeedKey: The key.
"""
segments = (
'nse_equities',
)
return base.FeedKey(str(instrument_token), segments)
class WhatNormalizeRefusesExample:
"""Normalizes a series of awkward ticks and prints what survives.
Attributes:
normalizer (ImaginaryBrokerTickNormalizer): The normalizer being shown.
received_at (float): When every tick was received, in epoch seconds.
"""
def __init__(self):
"""Builds the normalizer and fixes the receipt time.
Returns:
None: This method returns nothing.
"""
self.normalizer = ImaginaryBrokerTickNormalizer()
received = datetime.datetime(2026, 9, 15, 11, 5, tzinfo=INDIA)
self.received_at = received.timestamp()
def plan(self, exchange, segment, shape, lot_size):
"""Builds a plan the way the resolver would, using the normalizer's rules.
Args:
exchange (str): The canonical exchange.
segment (str): The canonical segment.
shape (str): "security", "future" or "option".
lot_size (int | None): Units per lot, or None when unknown.
Returns:
InstrumentPlan: The plan.
"""
multipliers = {}
for field in base.QUANTITY_FIELDS:
if self.normalizer.quantity_basis(exchange, field) == base.BASIS_UNITS:
multipliers[field] = 1
else:
multipliers[field] = lot_size
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"',
)
def run(self):
"""Prints what `normalize` makes of each awkward tick.
Returns:
None: This method returns nothing.
"""
share = self.plan('nse', 'nse_equities', 'security', 1)
impossible_prices = [
None,
0,
-3.5,
]
for last_price in impossible_prices:
outcome = self.normalizer.normalize(
{
'last_price': last_price,
'received_at': self.received_at,
},
share,
True,
)
print(f'last_price {last_price!r} -> {outcome}')
eight_days = 8 * 86400
tick = {
'last_price': 1262.70000001,
'volume': 1885670.0,
'last_trade_time': self.received_at - eight_days,
'exchange_timestamp': self.received_at + 60,
'received_at': self.received_at,
'oi': 0,
'depth': {
'buy': [
{
'price': 1262.6,
'quantity': 120,
'orders': 4.0,
},
{
'price': 0.0,
'quantity': 0,
'orders': 0,
},
{
'price': 1262.4,
'quantity': 0,
'orders': 0,
},
{
'price': 1262.3,
'quantity': 50,
'orders': None,
},
],
},
}
values = self.normalizer.normalize(tick, share, True)
print(f"Share: last_price {values['last_price']}, volume {values['volume']}, oi {values['oi']}")
print(f"Share: last_trade_time eight days old -> {values['last_trade_time']}, exchange_time a minute ahead -> {values['exchange_time']}")
print(f"Share: bids {values['bids']}, asks {values['asks']}")
print(f"Share: close policy {share.close_policy}, reported_close {values['reported_close']}")
future = self.plan('mcx', 'mcx_commodity_futures', 'future', None)
values = self.normalizer.normalize(
{
'last_price': -12.5,
'volume': 400,
'oi': 900,
'received_at': self.received_at,
},
future,
True,
)
print(f"Future with an unknown lot: last_price {values['last_price']}, volume {values['volume']}, oi {values['oi']}")
if __name__ == '__main__':
WhatNormalizeRefusesExample().run()
It prints:
last_price None -> None
last_price 0 -> None
last_price -3.5 -> None
Share: last_price 1262.7, volume 1885670, oi None
Share: last_trade_time eight days old -> None, exchange_time a minute ahead -> None
Share: bids [(1262.6, 120, 4), (1262.3, 50, None)], asks []
Share: close policy never, reported_close None
Future with an unknown lot: last_price -12.5, volume None, oi None
Methods:
| Name | Description |
|---|---|
feed_key |
Turn the token a feed puts on its ticks into what the broker's mappings are searched with. |
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. |
feed_key
¶
Turn the token a feed puts on its ticks into what the broker's mappings are searched with.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
instrument_token
|
int | str
|
The tick's |
required |
Returns:
| Type | Description |
|---|---|
FeedKey | None
|
The key, or None for a token this normalizer cannot place. |
Example usage
Source code in stock_brokers/instruments/ticks/base.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. |
Source code in stock_brokers/instruments/ticks/base.py
family_segments
¶
The canonical segments for a family of instruments on some exchanges.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
exchanges
|
list[str]
|
Canonical exchanges, for example ["nse"]. |
required |
family
|
str
|
"cash", "index", "cash_or_index" or "derivative". |
required |
Returns:
| Type | Description |
|---|---|
tuple[str]
|
Exchange-prefixed segment names. |
Source code in stock_brokers/instruments/ticks/base.py
identity_json
¶
The identity part of a unified quote, serialized once for an instrument.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
instrument_id
|
str
|
The unified.instruments id. |
required |
broker
|
str
|
The broker supplying the quote. |
required |
broker_token
|
str
|
The broker's token for the instrument. |
required |
identity
|
dict
|
The identity dict the mapping cache returns. |
required |
lot_size
|
int | None
|
Underlying units per lot. |
required |
Returns:
| Type | Description |
|---|---|
str
|
A JSON object's members without the enclosing braces. |
Source code in stock_brokers/instruments/ticks/base.py
_price
¶
A price rounded to the instrument's precision, or None for a missing, zero or impossible one.
Rounding is what removes float32 noise - 2232.6001 from a feed decoding single precision floats is 2232.60 - and fixes every broker's figure to the same number of places.
Source code in stock_brokers/instruments/ticks/base.py
_quantity
¶
A quantity in whole underlying units, or None for a missing, negative or unconvertible one.
Source code in stock_brokers/instruments/ticks/base.py
_levels
¶
Order book levels with a price and a quantity, best first, at most DEPTH_LEVELS.
Brokers pad an empty book with zero rows, some do and some do not; dropping every level without both a price and a quantity makes an empty level mean the same thing everywhere.