raw_attributes
¶
The extra instrument attributes each broker publishes, under one shared vocabulary.
A broker's instrument file carries far more than the mapping needs to place an order. Dhan sends an
ISIN, a freeze quantity and a price band; Kotak sends margin percentages; Groww says whether an
instrument may be bought and sold at all. None of that reaches unified.broker_mappings, because the
mapping only keeps the token, the symbols, the lot size and the tick size.
This module names the columns worth keeping and gives every broker's spelling of them one unified
name, so that an ISIN is isin whether the broker called it isin, pisin or isin_code. The
attributes are read at mapping time, where the raw row is already in hand, because there is no stable
key to find that row again afterwards: a broker's token is not unique within a snapshot for seven of
the ten brokers, and the symbol columns the mapping stores change from segment to segment.
Every attribute in ATTRIBUTE_NAMES is present for every broker, and a broker that does not publish
one has None there, which is the same rule the tick, order and position contracts follow.
Classes:
| Name | Description |
|---|---|
RawAttributes |
The map from each broker's raw instrument columns onto the shared attribute vocabulary. |
RawAttributes
¶
The map from each broker's raw instrument columns onto the shared attribute vocabulary.
Attributes:
| Name | Type | Description |
|---|---|---|
COLUMNS |
dict
|
Mapping of broker name to a dict of unified attribute name to that broker's column name. A broker's dict names only the attributes it publishes. |
Example programs
"""Reads the extra attributes out of one raw Dhan instrument row and expands them for a caller.
A broker's instrument file carries more than an order needs. `extract` picks out the columns `RawAttributes` knows for that broker and renames them to the shared vocabulary, so Dhan's `sm_freeze_qty` becomes `freeze_quantity` and its `asm_gsm_category` becomes `surveillance_category`. Values are kept as text, stripped of spaces, and a column that is empty in the row is left out rather than stored as null.
`fill` then turns the stored attributes back into the full vocabulary of sixteen names, with None for anything Dhan does not publish, which is the shape the REST API answers with. `columns_for` shows the column map the extraction followed.
The row is a made-up but realistic Dhan row for INFY on NSE. No database is needed.
Run it from the project root:
python examples/stock_brokers/instruments/mapping/utilities/raw_attributes/RawAttributes/example_1_reading_a_dhan_row.py
"""
from stock_brokers.instruments.mapping.utilities.raw_attributes import (
RawAttributes,
)
class ReadingADhanRowExample:
"""Extracts, prints and fills the attributes of one Dhan row.
Attributes:
raw_attributes (RawAttributes): The column map being shown.
raw_row (dict): One raw row from Dhan's instrument table.
"""
def __init__(self):
"""Builds the column map and the raw row.
Returns:
None: This method returns nothing.
"""
self.raw_attributes = RawAttributes()
self.raw_row = {
'security_id': '1594',
'exch_id': 'NSE',
'symbol_name': 'INFOSYS LIMITED',
'isin': 'INE009A01021',
'display_name': ' Infosys ',
'instrument_type': 'ES',
'series': 'EQ',
'sm_freeze_qty': '',
'sm_upper_limit': '1709.60',
'sm_lower_limit': '1398.80',
'underlying_security_id': None,
'asm_gsm_category': 'NA',
'mtf_leverage': '4.00',
}
def run(self):
"""Prints Dhan's column map, the extracted attributes and the filled answer.
Returns:
None: This method returns nothing.
"""
columns = self.raw_attributes.columns_for('dhan')
print('Dhan column map:')
for attribute_name in columns:
print(f' {attribute_name} <- {columns[attribute_name]}')
attributes = self.raw_attributes.extract('dhan', self.raw_row)
print(f'Stored: {attributes}')
filled = self.raw_attributes.fill(attributes)
print('Answered:')
for attribute_name in filled:
print(f' {attribute_name}: {filled[attribute_name]}')
if __name__ == '__main__':
ReadingADhanRowExample().run()
It prints:
Dhan column map:
isin <- isin
display_name <- display_name
instrument_type <- instrument_type
series <- series
freeze_quantity <- sm_freeze_qty
price_band_high <- sm_upper_limit
price_band_low <- sm_lower_limit
underlying_token <- underlying_security_id
surveillance_category <- asm_gsm_category
margin_trading_leverage <- mtf_leverage
Stored: {'isin': 'INE009A01021', 'display_name': 'Infosys', 'instrument_type': 'ES', 'series': 'EQ', 'price_band_high': '1709.60', 'price_band_low': '1398.80', 'surveillance_category': 'NA', 'margin_trading_leverage': '4.00'}
Answered:
isin: INE009A01021
display_name: Infosys
instrument_type: ES
series: EQ
freeze_quantity: None
price_band_high: 1709.60
price_band_low: 1398.80
multiplier: None
underlying_token: None
surveillance_category: NA
permitted_to_trade: None
buy_allowed: None
sell_allowed: None
intraday_leverage: None
margin_trading_leverage: 4.00
pledge_eligible: None
"""Finds which brokers publish an attribute, and shows how missing and messy values are cleaned.
`publishers_of` answers questions such as "which brokers can tell me an ISIN?" from the column map, in the map's order. `columns_for` returns an empty map for a broker the map does not know, so extracting from such a broker gives nothing rather than an error.
`clean` is the rule every value goes through: None, a pandas missing value and blank text all become None, and anything else becomes stripped text. The program also extracts a Zerodha row, whose file has only two extra columns, and a Groww row with a pandas missing value in it, to show those rules at work. The rows are made up; no database is needed.
Run it from the project root:
python examples/stock_brokers/instruments/mapping/utilities/raw_attributes/RawAttributes/example_2_who_publishes_what.py
"""
import math
import pandas as pd
from stock_brokers.instruments.mapping.utilities.raw_attributes import (
RawAttributes,
)
class WhoPublishesWhatExample:
"""Lists the publishers of three attributes, cleans a few values and extracts two rows.
Attributes:
raw_attributes (RawAttributes): The column map being shown.
"""
def __init__(self):
"""Builds the column map.
Returns:
None: This method returns nothing.
"""
self.raw_attributes = RawAttributes()
def run(self):
"""Prints the publishers, the cleaned values and two extractions.
Returns:
None: This method returns nothing.
"""
attribute_names = [
'isin',
'freeze_quantity',
'buy_allowed',
]
for attribute_name in attribute_names:
print(f'{attribute_name}: {self.raw_attributes.publishers_of(attribute_name)}')
values = [
None,
pd.NA,
math.nan,
' ',
' 1800 ',
25,
]
for value in values:
print(f'clean({value!r}) = {self.raw_attributes.clean(value)!r}')
print(f'Columns for an unknown broker: {self.raw_attributes.columns_for("upstox")}')
zerodha_row = {
'instrument_token': '408065',
'tradingsymbol': 'INFY',
'name': 'INFOSYS',
'instrument_type': 'EQ',
'lot_size': '1',
}
print(f'Zerodha: {self.raw_attributes.extract("zerodha", zerodha_row)}')
groww_row = {
'exchange_token': '1594',
'isin': 'INE009A01021',
'name': 'Infosys',
'instrument_type': 'EQ',
'series': 'EQ',
'freeze_quantity': pd.NA,
'underlying_exchange_token': '',
'buy_allowed': '1',
'sell_allowed': '1',
}
print(f'Groww: {self.raw_attributes.extract("groww", groww_row)}')
if __name__ == '__main__':
WhoPublishesWhatExample().run()
It prints:
isin: ['dhan', 'kotak', 'groww', 'stoxkart', 'fyers', 'wisdom_capital', 'indmoney']
freeze_quantity: ['dhan', 'kotak', 'groww', 'wisdom_capital', 'indmoney']
buy_allowed: ['groww']
clean(None) = None
clean(<NA>) = None
clean(nan) = None
clean(' ') = None
clean(' 1800 ') = '1800'
clean(25) = '25'
Columns for an unknown broker: {}
Zerodha: {'display_name': 'INFOSYS', 'instrument_type': 'EQ'}
Groww: {'isin': 'INE009A01021', 'display_name': 'Infosys', 'instrument_type': 'EQ', 'series': 'EQ', 'buy_allowed': '1', 'sell_allowed': '1'}
Methods:
| Name | Description |
|---|---|
columns_for |
Finds the raw columns one broker publishes attributes in. |
publishers_of |
Finds every broker that publishes one attribute. |
extract |
Reads one broker's extra attributes out of one raw instrument row. |
clean |
Turns one raw column value into the text stored for it, or None. |
fill |
Expands stored attributes back to the full vocabulary, for an answer a caller reads. |
columns_for
¶
Finds the raw columns one broker publishes attributes in.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
broker
|
str
|
The broker name, as it appears in unified.broker_mappings. |
required |
Returns:
| Type | Description |
|---|---|
dict
|
Mapping of unified attribute name to that broker's column name, empty when the broker publishes nothing beyond what the mapping already keeps. |
Example usage
print(f'Columns for an unknown broker: {self.raw_attributes.columns_for("upstox")}')
Source code in stock_brokers/instruments/mapping/utilities/raw_attributes.py
publishers_of
¶
Finds every broker that publishes one attribute.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
attribute_name
|
str
|
A name from ATTRIBUTE_NAMES. |
required |
Returns:
| Type | Description |
|---|---|
list[str]
|
The broker names, in the order the column map lists them. |
Example usage
print(f'{attribute_name}: {self.raw_attributes.publishers_of(attribute_name)}')
Source code in stock_brokers/instruments/mapping/utilities/raw_attributes.py
extract
¶
Reads one broker's extra attributes out of one raw instrument row.
Only the attributes this broker actually published are in the answer. An attribute it does not publish at all, and one whose column is empty in this row, is left out rather than stored as null, because writing all sixteen names for every broker on every instrument costs about three times what the values themselves do. The endpoint fills the missing names back in from ATTRIBUTE_NAMES, so a caller still sees every name.
Values are kept as the broker sent them, stripped of surrounding whitespace and turned into text, because a raw instrument table stores every column as text and nothing here is a number to compute with.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
broker
|
str
|
The broker name, as it appears in unified.broker_mappings. |
required |
raw_row
|
dict
|
One raw row from that broker's instrument table. |
required |
Returns:
| Type | Description |
|---|---|
dict
|
Mapping of unified attribute name to a string value, holding only the attributes this broker published for this row. |
Example usage
print(f'Zerodha: {self.raw_attributes.extract("zerodha", zerodha_row)}')
print(f'Groww: {self.raw_attributes.extract("groww", groww_row)}')
Source code in stock_brokers/instruments/mapping/utilities/raw_attributes.py
clean
¶
Turns one raw column value into the text stored for it, or None.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
value
|
object
|
The value as the raw instrument table returned it, which may be None, a pandas missing value, or text. |
required |
Returns:
| Type | Description |
|---|---|
str | None
|
The value as stripped text, or None when it is missing or empty. |
Example usage
print(f'clean({value!r}) = {self.raw_attributes.clean(value)!r}')
Source code in stock_brokers/instruments/mapping/utilities/raw_attributes.py
fill
¶
Expands stored attributes back to the full vocabulary, for an answer a caller reads.
Storage keeps only what a broker published; a caller should not have to check whether a name is there before reading it. This is the one place the two shapes meet.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
attributes
|
dict
|
The attributes as stored, holding only the names the broker published. |
required |
Returns:
| Type | Description |
|---|---|
dict
|
Mapping of every name in ATTRIBUTE_NAMES to its stored value or None. |
Example usage