contract_sizes
¶
Decide, once per mapping date, how many quotation units one lot of every currency and commodity derivative is.
python -m stock_brokers.instruments.mapping.utilities.contract_sizes
python -m stock_brokers.instruments.mapping.utilities.contract_sizes --date 2026-09-15
The brokers' own lot_size figures cannot be compared on these markets, because each broker counts a lot in its own unit: on MCX, GOLD's lot is 1 at Zerodha (one lot), 1 at Kotak (one kilogram) and 100 at Groww (quotation units of 10 grams). Several brokers' instrument files also carry the exchange's contract size fields, and those are read here as independent sources, each in quotation units: Wisdom Capital's multiplier, Kotak's lot size times its general numerator over its general denominator (or its multiplier on the currency segment), Groww's commodity lot size, Shoonya's lot size times its multiplier on NSE currencies, and Stoxkart's lot size on the currency and NCDEX segments.
A contract is confirmed when at least two sources give a size and every one gives the same size. It is single_source when exactly one does, conflict when sources disagree or one source gives two different sizes for it, and no_source when none does. A source's file can list a contract twice, as Stoxkart's did for 34 nse currency options on 2026-09-28 with lots of 1000 and 2000; its sizes are then recorded together, such as 1000|2000, rather than one of them being kept by the order the rows happen to arrive in. tradeable is true for a confirmed contract, and for a single-source contract only on BSE currencies and NCDEX, where Stoxkart is the only broker that lists them.
On MCX a newly listed far-month contract often has one source only, because some brokers list new expiries later than others. Such a contract is sibling_confirmed and tradeable when every confirmed contract of the same underlying in the same segment on the date has one and the same size, and that size is the single source's figure. An underlying whose confirmed contracts already come in more than one size, as when the exchange has revised a lot size, gives no such answer, and its single-source contracts stay untradeable. The decision and every source's figure are written to unified.contract_sizes for the date, so an order reads a stored decision rather than making one.
Classes:
| Name | Description |
|---|---|
ContractSizeSource |
One broker instrument file field, or combination of fields, that states a contract's size in quotation units. |
WisdomCapitalMultiplierSource |
Wisdom Capital's XTS |
KotakContractSizeSource |
Kotak's lot size in its trading unit, converted to quotation units with its general numerator and denominator, and scaled by its multiplier where it gives one, as on currencies. |
GrowwLotSizeSource |
Groww's lot size on its commodity segment, which Groww gives in quotation units. |
ShoonyaCurrencySource |
Shoonya's lot size times its multiplier on NSE currencies, where it gives a lot size of 1 and a multiplier of the contract size. |
StoxkartLotSizeSource |
Stoxkart's lot size on the NSE and BSE currency segments and NCDEX. |
ContractSizeDecision |
The rule that turns the sources' figures for one contract into a stored decision. |
ContractSizeResolver |
Reads every source for a mapping date, decides each contract's size and writes the decisions. |
ContractSizeCommand |
The command line: decide a date's contract sizes and print how many contracts each segment has in each status. |
ContractSizeSource
¶
One broker instrument file field, or combination of fields, that states a contract's size in quotation units.
A subclass sets the class attributes and units_expression. The source is read by joining the date's broker mappings to that broker's raw snapshot on the broker token, restricted to the raw exchange labels the source is trusted on, because a token is not unique across a broker's exchanges.
Attributes:
| Name | Type | Description |
|---|---|---|
NAME |
str
|
The source's name, as it is recorded in |
BROKER |
str
|
The broker whose snapshot is read. |
TOKEN_COLUMN |
str
|
The snapshot column holding the broker token. |
EXCHANGE_COLUMN |
str
|
The snapshot column holding the exchange label. |
EXCHANGES |
list
|
The exchange labels the source is read on. |
Example programs
"""Writes a new contract size source by subclassing `ContractSizeSource` and prints the query it builds.
A source names one broker, the snapshot columns holding its token and exchange label, the exchange labels it is trusted on, and an SQL expression for the size in quotation units. This program invents a source that reads Dhan's `lot_size` column on MCX, which is not one of the project's sources, to show how little a subclass has to say.
`numeric` wraps a snapshot column in a guard that turns anything that is not a number into null, so a broker file holding text in a numeric column cannot break the query. `statement` then joins the date's broker mappings to the broker's raw snapshot on the token and the download date, and keeps only live contracts in the requested segments on the trusted exchange labels. Nothing is executed here, so no database is needed.
Run it from the project root:
python examples/stock_brokers/instruments/mapping/utilities/contract_sizes/ContractSizeSource/example_1_writing_a_new_source.py
"""
from stock_brokers.instruments.mapping.utilities.contract_sizes import (
ContractSizeSource,
)
class DhanLotSizeSource(ContractSizeSource):
"""An illustrative source reading Dhan's `lot_size` column on MCX."""
NAME = 'dhan_lot_size'
BROKER = 'dhan'
TOKEN_COLUMN = 'security_id'
EXCHANGE_COLUMN = 'exch_id'
EXCHANGES = [
'MCX',
]
def units_expression(self):
"""The `lot_size` column, read as a number.
Returns:
str: The SQL expression.
"""
return self.numeric('lot_size')
class WritingANewSourceExample:
"""Builds the illustrative source and prints its settings, its expressions and its query.
Attributes:
source (DhanLotSizeSource): The source being shown.
"""
def __init__(self):
"""Builds the source.
Returns:
None: This method returns nothing.
"""
self.source = DhanLotSizeSource()
def run(self):
"""Prints the source's settings, the numeric guard, the size expression and the full query.
Returns:
None: This method returns nothing.
"""
print(f'Name: {self.source.NAME}')
print(f'Broker: {self.source.BROKER}')
print(f'Trusted exchange labels: {self.source.EXCHANGES}')
print(f'Numeric guard on multiplier: {self.source.numeric("multiplier")}')
print(f'Size expression: {self.source.units_expression()}')
print('Query:')
print(str(self.source.statement()))
if __name__ == '__main__':
WritingANewSourceExample().run()
It prints:
Name: dhan_lot_size
Broker: dhan
Trusted exchange labels: ['MCX']
Numeric guard on multiplier: (CASE WHEN raw."multiplier"::text ~ '^-?[0-9]+(\.[0-9]+)?$' THEN raw."multiplier"::text::numeric END)
Size expression: (CASE WHEN raw."lot_size"::text ~ '^-?[0-9]+(\.[0-9]+)?$' THEN raw."lot_size"::text::numeric END)
Query:
SELECT m.instrument_id::text AS instrument_id, (CASE WHEN raw."lot_size"::text ~ '^-?[0-9]+(\.[0-9]+)?$' THEN raw."lot_size"::text::numeric END) AS units FROM unified.broker_mappings m JOIN unified.instruments i ON i.instrument_id = m.instrument_id JOIN dhan.instruments raw ON raw."security_id"::text = m.broker_token AND raw.download_date = m.mapping_date WHERE m.broker = :broker AND m.mapping_date = :mapping_date AND i.segment = ANY(:segments) AND (i.expiry_date IS NULL OR i.expiry_date >= :mapping_date) AND raw."exch_id"::text = ANY(:exchanges)
"""Reads a source's rows and shows which sizes it keeps, and what the bare base class does.
`read` runs the source's query on an open connection and gathers, for each contract, the set of distinct sizes its rows give. A row whose size is missing, zero or negative is left out, and a contract listed twice with two different sizes keeps both, which is how the resolver later notices a source contradicting itself.
The connection here is a small stand-in that returns five made-up rows and remembers the parameters it was given, so the program needs no database. The rows copy a case seen in Stoxkart's file on 2026-09-28, where one USDINR option was listed with lots of 1000 and 2000.
The base class names no expression of its own, so calling `units_expression` on it raises `NotImplementedError`; the program catches that to show it.
Run it from the project root:
python examples/stock_brokers/instruments/mapping/utilities/contract_sizes/ContractSizeSource/example_2_reading_rows_that_disagree.py
"""
import datetime
import types
from stock_brokers.instruments.mapping.utilities.contract_sizes import (
ContractSizeSource,
StoxkartLotSizeSource,
)
class StandInConnection:
"""A stand-in for a database connection that returns fixed rows and records the parameters of each query.
Attributes:
rows (list): The rows every query returns.
parameters (dict | None): The parameters of the last query, or None before any.
"""
def __init__(self, rows):
"""Holds the rows to return.
Args:
rows (list): The rows every query returns.
Returns:
None: This method returns nothing.
"""
self.rows = rows
self.parameters = None
def execute(self, statement, parameters):
"""Records the parameters and returns the fixed rows.
Args:
statement (sqlalchemy.sql.elements.TextClause): The query, which is not run.
parameters (dict): The query's parameters.
Returns:
list: The fixed rows.
"""
self.parameters = parameters
return self.rows
class ReadingRowsThatDisagreeExample:
"""Reads five stand-in rows through Stoxkart's source and tries the base class's expression.
Attributes:
source (StoxkartLotSizeSource): The source being read.
connection (StandInConnection): The stand-in connection.
"""
def __init__(self):
"""Builds the source and a connection holding five rows.
Returns:
None: This method returns nothing.
"""
self.source = StoxkartLotSizeSource()
self.connection = StandInConnection([
types.SimpleNamespace(
instrument_id='usdinr-option',
units='1000',
),
types.SimpleNamespace(
instrument_id='usdinr-option',
units='2000',
),
types.SimpleNamespace(
instrument_id='eurinr-future',
units='1000.00',
),
types.SimpleNamespace(
instrument_id='gbpinr-future',
units=None,
),
types.SimpleNamespace(
instrument_id='jpyinr-future',
units='0',
),
])
def run(self):
"""Prints the sizes read, the query parameters and the base class's refusal.
Returns:
None: This method returns nothing.
"""
sizes = self.source.read(
self.connection,
datetime.date(2026, 9, 28),
[
'nse_currency_options',
'nse_currency_futures',
],
)
for instrument_id in sorted(sizes):
texts = []
for size in sorted(sizes[instrument_id]):
texts.append(format(size, 'f'))
print(f'{instrument_id}: {texts}')
print(f'Broker queried: {self.connection.parameters["broker"]}')
print(f'Exchange labels queried: {self.connection.parameters["exchanges"]}')
base_source = ContractSizeSource()
try:
base_source.units_expression()
except NotImplementedError:
print('The base class has no size expression of its own.')
if __name__ == '__main__':
ReadingRowsThatDisagreeExample().run()
It prints:
Methods:
| Name | Description |
|---|---|
numeric |
An SQL expression reading a snapshot column as a number, or null when it does not hold one. |
units_expression |
The SQL expression giving the contract's size in quotation units. |
statement |
The query reading this source for every live contract in the given segments on a date. |
read |
Reads this source's size for every live contract it lists. |
numeric
¶
An SQL expression reading a snapshot column as a number, or null when it does not hold one.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
column
|
str
|
The snapshot column. |
required |
Returns:
| Type | Description |
|---|---|
str
|
The SQL expression. |
Example usage
print(f'Numeric guard on multiplier: {self.source.numeric("multiplier")}')
Source code in stock_brokers/instruments/mapping/utilities/contract_sizes.py
units_expression
¶
The SQL expression giving the contract's size in quotation units.
Returns:
| Type | Description |
|---|---|
str
|
The SQL expression. |
Raises:
| Type | Description |
|---|---|
NotImplementedError
|
Always, in the base class. |
Example usage
Source code in stock_brokers/instruments/mapping/utilities/contract_sizes.py
statement
¶
The query reading this source for every live contract in the given segments on a date.
Returns:
| Type | Description |
|---|---|
TextClause
|
The query, taking |
Example usage
Source code in stock_brokers/instruments/mapping/utilities/contract_sizes.py
read
¶
Reads this source's size for every live contract it lists.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
connection
|
Connection
|
An open connection. |
required |
mapping_date
|
date
|
The mapping date. |
required |
segments
|
list
|
The exchange-prefixed segments to read. |
required |
Returns:
| Type | Description |
|---|---|
dict
|
Instrument ids to the set of distinct sizes, as decimals, that the source's rows give for the contract, leaving out rows whose size is missing, zero or negative. A set holds more than one size when the source's file lists the contract twice with different sizes. |
Example usage
sizes = self.source.read(
self.connection,
datetime.date(2026, 9, 28),
[
'nse_currency_options',
'nse_currency_futures',
],
)
Source code in stock_brokers/instruments/mapping/utilities/contract_sizes.py
WisdomCapitalMultiplierSource
¶
Bases: ContractSizeSource
Wisdom Capital's XTS multiplier, which is the contract size in quotation units on MCX and NSE commodities.
Example programs
"""Prints the SQL expression Wisdom Capital's XTS `multiplier` column is read with, and the query around it.
On MCX and NSE commodities that column is already the contract size in quotation units, so the expression is only the numeric guard around it.
The program only builds the expression and the query text, so it needs no database. The query joins the date's broker mappings to the Wisdom Capital snapshot on the broker token and the download date, and keeps only the exchange labels this source is trusted on.
Run it from the project root:
python examples/stock_brokers/instruments/mapping/utilities/contract_sizes/WisdomCapitalMultiplierSource/example_1_the_size_expression.py
"""
from stock_brokers.instruments.mapping.utilities.contract_sizes import (
WisdomCapitalMultiplierSource,
)
class ReadingTheExpressionExample:
"""Prints the source's settings, its size expression and its query.
Attributes:
source (WisdomCapitalMultiplierSource): The source being shown.
"""
def __init__(self):
"""Builds the source.
Returns:
None: This method returns nothing.
"""
self.source = WisdomCapitalMultiplierSource()
def run(self):
"""Prints the name, the broker, the trusted exchange labels, the expression and the query.
Returns:
None: This method returns nothing.
"""
print(f'Name: {self.source.NAME}')
print(f'Broker: {self.source.BROKER}')
print(f'Token column: {self.source.TOKEN_COLUMN}')
print(f'Exchange column: {self.source.EXCHANGE_COLUMN}')
print(f'Trusted exchange labels: {self.source.EXCHANGES}')
print(f'Size expression: {self.source.units_expression()}')
print('Query:')
print(str(self.source.statement()))
if __name__ == '__main__':
ReadingTheExpressionExample().run()
It prints:
Name: wisdom_capital_multiplier
Broker: wisdom_capital
Token column: exchangeinstrumentid
Exchange column: exchangesegment
Trusted exchange labels: ['MCXFO', 'NSECO']
Size expression: (CASE WHEN raw."multiplier"::text ~ '^-?[0-9]+(\.[0-9]+)?$' THEN raw."multiplier"::text::numeric END)
Query:
SELECT m.instrument_id::text AS instrument_id, (CASE WHEN raw."multiplier"::text ~ '^-?[0-9]+(\.[0-9]+)?$' THEN raw."multiplier"::text::numeric END) AS units FROM unified.broker_mappings m JOIN unified.instruments i ON i.instrument_id = m.instrument_id JOIN wisdom_capital.instruments raw ON raw."exchangeinstrumentid"::text = m.broker_token AND raw.download_date = m.mapping_date WHERE m.broker = :broker AND m.mapping_date = :mapping_date AND i.segment = ANY(:segments) AND (i.expiry_date IS NULL OR i.expiry_date >= :mapping_date) AND raw."exchangesegment"::text = ANY(:exchanges)
"""Reads Wisdom Capital's XTS `multiplier` column for a few contracts through a stand-in connection.
`read` runs the source's query and returns, for each contract, the set of distinct sizes its rows give, as decimals with trailing zeros removed. The rows copy real MCX sizes: GOLD at 100 units of 10 grams, CRUDEOIL at 100 barrels and NATURALGAS at 1250 mmBtu. One option row has an empty multiplier, and the source leaves it out rather than guessing.
The connection is a small stand-in that returns the rows it was built with and records the parameters of the query, so the program needs no database. The parameters show that the query is limited to Wisdom Capital's rows on the trusted exchange labels.
Run it from the project root:
python examples/stock_brokers/instruments/mapping/utilities/contract_sizes/WisdomCapitalMultiplierSource/example_2_reading_sizes.py
"""
import datetime
import types
from stock_brokers.instruments.mapping.utilities.contract_sizes import (
WisdomCapitalMultiplierSource,
)
class StandInConnection:
"""A stand-in for a database connection that returns fixed rows and records the parameters of each query.
Attributes:
rows (list): The rows every query returns.
parameters (dict | None): The parameters of the last query, or None before any.
"""
def __init__(self, rows):
"""Holds the rows to return.
Args:
rows (list): The rows every query returns.
Returns:
None: This method returns nothing.
"""
self.rows = rows
self.parameters = None
def execute(self, statement, parameters):
"""Records the parameters and returns the fixed rows.
Args:
statement (sqlalchemy.sql.elements.TextClause): The query, which is not run.
parameters (dict): The query's parameters.
Returns:
list: The fixed rows.
"""
self.parameters = parameters
return self.rows
class ReadingSizesExample:
"""Reads the stand-in rows through the source and prints each contract's sizes.
Attributes:
source (WisdomCapitalMultiplierSource): The source being read.
connection (StandInConnection): The stand-in connection.
"""
def __init__(self):
"""Builds the source and a connection holding the rows.
Returns:
None: This method returns nothing.
"""
self.source = WisdomCapitalMultiplierSource()
self.connection = StandInConnection([
types.SimpleNamespace(
instrument_id='gold-future-oct',
units='100',
),
types.SimpleNamespace(
instrument_id='gold-future-dec',
units='100',
),
types.SimpleNamespace(
instrument_id='crudeoil-future-oct',
units='100',
),
types.SimpleNamespace(
instrument_id='naturalgas-future-oct',
units='1250',
),
types.SimpleNamespace(
instrument_id='silver-option',
units=None,
),
])
def run(self):
"""Prints the sizes read and the parameters the query was given.
Returns:
None: This method returns nothing.
"""
sizes = self.source.read(
self.connection,
datetime.date(2026, 9, 28),
[
'mcx_commodity_futures',
'mcx_commodity_options',
],
)
for instrument_id in sorted(sizes):
texts = []
for size in sorted(sizes[instrument_id]):
texts.append(format(size, 'f'))
print(f'{instrument_id}: {texts}')
print(f'Broker queried: {self.connection.parameters["broker"]}')
print(f'Mapping date: {self.connection.parameters["mapping_date"]}')
print(f'Segments: {self.connection.parameters["segments"]}')
print(f'Exchange labels: {self.connection.parameters["exchanges"]}')
if __name__ == '__main__':
ReadingSizesExample().run()
It prints:
Methods:
| Name | Description |
|---|---|
units_expression |
The |
numeric |
An SQL expression reading a snapshot column as a number, or null when it does not hold one. |
statement |
The query reading this source for every live contract in the given segments on a date. |
read |
Reads this source's size for every live contract it lists. |
units_expression
¶
The multiplier column.
Returns:
| Type | Description |
|---|---|
str
|
The SQL expression. |
Example usage
numeric
¶
An SQL expression reading a snapshot column as a number, or null when it does not hold one.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
column
|
str
|
The snapshot column. |
required |
Returns:
| Type | Description |
|---|---|
str
|
The SQL expression. |
Example usage
print(f'Numeric guard on multiplier: {self.source.numeric("multiplier")}')
Source code in stock_brokers/instruments/mapping/utilities/contract_sizes.py
statement
¶
The query reading this source for every live contract in the given segments on a date.
Returns:
| Type | Description |
|---|---|
TextClause
|
The query, taking |
Example usage
Source code in stock_brokers/instruments/mapping/utilities/contract_sizes.py
read
¶
Reads this source's size for every live contract it lists.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
connection
|
Connection
|
An open connection. |
required |
mapping_date
|
date
|
The mapping date. |
required |
segments
|
list
|
The exchange-prefixed segments to read. |
required |
Returns:
| Type | Description |
|---|---|
dict
|
Instrument ids to the set of distinct sizes, as decimals, that the source's rows give for the contract, leaving out rows whose size is missing, zero or negative. A set holds more than one size when the source's file lists the contract twice with different sizes. |
Example usage
sizes = self.source.read(
self.connection,
datetime.date(2026, 9, 28),
[
'nse_currency_options',
'nse_currency_futures',
],
)
Source code in stock_brokers/instruments/mapping/utilities/contract_sizes.py
KotakContractSizeSource
¶
Bases: ContractSizeSource
Kotak's lot size in its trading unit, converted to quotation units with its general numerator and denominator, and scaled by its multiplier where it gives one, as on currencies.
Example programs
"""Prints the SQL expression Kotak's lot size converted to quotation units is read with, and the query around it.
Kotak counts GOLD in kilograms, so its lot size of 1 is multiplied by its general numerator and divided by its general denominator to reach the 100 units of 10 grams the exchange quotes in; on currencies it gives a multiplier as well, which scales the lot size when it is positive.
The program only builds the expression and the query text, so it needs no database. The query joins the date's broker mappings to the Kotak snapshot on the broker token and the download date, and keeps only the exchange labels this source is trusted on.
Run it from the project root:
python examples/stock_brokers/instruments/mapping/utilities/contract_sizes/KotakContractSizeSource/example_1_the_size_expression.py
"""
from stock_brokers.instruments.mapping.utilities.contract_sizes import (
KotakContractSizeSource,
)
class ReadingTheExpressionExample:
"""Prints the source's settings, its size expression and its query.
Attributes:
source (KotakContractSizeSource): The source being shown.
"""
def __init__(self):
"""Builds the source.
Returns:
None: This method returns nothing.
"""
self.source = KotakContractSizeSource()
def run(self):
"""Prints the name, the broker, the trusted exchange labels, the expression and the query.
Returns:
None: This method returns nothing.
"""
print(f'Name: {self.source.NAME}')
print(f'Broker: {self.source.BROKER}')
print(f'Token column: {self.source.TOKEN_COLUMN}')
print(f'Exchange column: {self.source.EXCHANGE_COLUMN}')
print(f'Trusted exchange labels: {self.source.EXCHANGES}')
print(f'Size expression: {self.source.units_expression()}')
print('Query:')
print(str(self.source.statement()))
if __name__ == '__main__':
ReadingTheExpressionExample().run()
It prints:
Name: kotak_contract_size
Broker: kotak
Token column: psymbol
Exchange column: pexchseg
Trusted exchange labels: ['mcx_fo', 'nse_com', 'cde_fo']
Size expression: ((CASE WHEN raw."llotsize"::text ~ '^-?[0-9]+(\.[0-9]+)?$' THEN raw."llotsize"::text::numeric END) * (CASE WHEN (CASE WHEN raw."lmultiplier"::text ~ '^-?[0-9]+(\.[0-9]+)?$' THEN raw."lmultiplier"::text::numeric END) > 0 THEN (CASE WHEN raw."lmultiplier"::text ~ '^-?[0-9]+(\.[0-9]+)?$' THEN raw."lmultiplier"::text::numeric END) ELSE 1 END) * (CASE WHEN raw."dgennum"::text ~ '^-?[0-9]+(\.[0-9]+)?$' THEN raw."dgennum"::text::numeric END) / NULLIF((CASE WHEN raw."dgenden"::text ~ '^-?[0-9]+(\.[0-9]+)?$' THEN raw."dgenden"::text::numeric END), 0))
Query:
SELECT m.instrument_id::text AS instrument_id, ((CASE WHEN raw."llotsize"::text ~ '^-?[0-9]+(\.[0-9]+)?$' THEN raw."llotsize"::text::numeric END) * (CASE WHEN (CASE WHEN raw."lmultiplier"::text ~ '^-?[0-9]+(\.[0-9]+)?$' THEN raw."lmultiplier"::text::numeric END) > 0 THEN (CASE WHEN raw."lmultiplier"::text ~ '^-?[0-9]+(\.[0-9]+)?$' THEN raw."lmultiplier"::text::numeric END) ELSE 1 END) * (CASE WHEN raw."dgennum"::text ~ '^-?[0-9]+(\.[0-9]+)?$' THEN raw."dgennum"::text::numeric END) / NULLIF((CASE WHEN raw."dgenden"::text ~ '^-?[0-9]+(\.[0-9]+)?$' THEN raw."dgenden"::text::numeric END), 0)) AS units FROM unified.broker_mappings m JOIN unified.instruments i ON i.instrument_id = m.instrument_id JOIN kotak.instruments raw ON raw."psymbol"::text = m.broker_token AND raw.download_date = m.mapping_date WHERE m.broker = :broker AND m.mapping_date = :mapping_date AND i.segment = ANY(:segments) AND (i.expiry_date IS NULL OR i.expiry_date >= :mapping_date) AND raw."pexchseg"::text = ANY(:exchanges)
"""Reads Kotak's lot size converted to quotation units for a few contracts through a stand-in connection.
`read` runs the source's query and returns, for each contract, the set of distinct sizes its rows give, as decimals with trailing zeros removed. The stand-in rows hold the size as the database would compute it from that expression, including trailing zeros, which `read` normalises away. A negative figure, which a broken denominator could produce, is left out.
The connection is a small stand-in that returns the rows it was built with and records the parameters of the query, so the program needs no database. The parameters show that the query is limited to Kotak's rows on the trusted exchange labels.
Run it from the project root:
python examples/stock_brokers/instruments/mapping/utilities/contract_sizes/KotakContractSizeSource/example_2_reading_sizes.py
"""
import datetime
import types
from stock_brokers.instruments.mapping.utilities.contract_sizes import (
KotakContractSizeSource,
)
class StandInConnection:
"""A stand-in for a database connection that returns fixed rows and records the parameters of each query.
Attributes:
rows (list): The rows every query returns.
parameters (dict | None): The parameters of the last query, or None before any.
"""
def __init__(self, rows):
"""Holds the rows to return.
Args:
rows (list): The rows every query returns.
Returns:
None: This method returns nothing.
"""
self.rows = rows
self.parameters = None
def execute(self, statement, parameters):
"""Records the parameters and returns the fixed rows.
Args:
statement (sqlalchemy.sql.elements.TextClause): The query, which is not run.
parameters (dict): The query's parameters.
Returns:
list: The fixed rows.
"""
self.parameters = parameters
return self.rows
class ReadingSizesExample:
"""Reads the stand-in rows through the source and prints each contract's sizes.
Attributes:
source (KotakContractSizeSource): The source being read.
connection (StandInConnection): The stand-in connection.
"""
def __init__(self):
"""Builds the source and a connection holding the rows.
Returns:
None: This method returns nothing.
"""
self.source = KotakContractSizeSource()
self.connection = StandInConnection([
types.SimpleNamespace(
instrument_id='gold-future-oct',
units='100.0000',
),
types.SimpleNamespace(
instrument_id='usdinr-future-oct',
units='1000.00',
),
types.SimpleNamespace(
instrument_id='silver-future-dec',
units='30.000',
),
types.SimpleNamespace(
instrument_id='copper-future-oct',
units='-1',
),
])
def run(self):
"""Prints the sizes read and the parameters the query was given.
Returns:
None: This method returns nothing.
"""
sizes = self.source.read(
self.connection,
datetime.date(2026, 9, 28),
[
'mcx_commodity_futures',
'nse_currency_futures',
],
)
for instrument_id in sorted(sizes):
texts = []
for size in sorted(sizes[instrument_id]):
texts.append(format(size, 'f'))
print(f'{instrument_id}: {texts}')
print(f'Broker queried: {self.connection.parameters["broker"]}')
print(f'Mapping date: {self.connection.parameters["mapping_date"]}')
print(f'Segments: {self.connection.parameters["segments"]}')
print(f'Exchange labels: {self.connection.parameters["exchanges"]}')
if __name__ == '__main__':
ReadingSizesExample().run()
It prints:
Methods:
| Name | Description |
|---|---|
units_expression |
|
numeric |
An SQL expression reading a snapshot column as a number, or null when it does not hold one. |
statement |
The query reading this source for every live contract in the given segments on a date. |
read |
Reads this source's size for every live contract it lists. |
units_expression
¶
llotsize times lmultiplier when it is positive, times dgennum, over dgenden.
Returns:
| Type | Description |
|---|---|
str
|
The SQL expression. |
Example usage
Source code in stock_brokers/instruments/mapping/utilities/contract_sizes.py
numeric
¶
An SQL expression reading a snapshot column as a number, or null when it does not hold one.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
column
|
str
|
The snapshot column. |
required |
Returns:
| Type | Description |
|---|---|
str
|
The SQL expression. |
Example usage
print(f'Numeric guard on multiplier: {self.source.numeric("multiplier")}')
Source code in stock_brokers/instruments/mapping/utilities/contract_sizes.py
statement
¶
The query reading this source for every live contract in the given segments on a date.
Returns:
| Type | Description |
|---|---|
TextClause
|
The query, taking |
Example usage
Source code in stock_brokers/instruments/mapping/utilities/contract_sizes.py
read
¶
Reads this source's size for every live contract it lists.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
connection
|
Connection
|
An open connection. |
required |
mapping_date
|
date
|
The mapping date. |
required |
segments
|
list
|
The exchange-prefixed segments to read. |
required |
Returns:
| Type | Description |
|---|---|
dict
|
Instrument ids to the set of distinct sizes, as decimals, that the source's rows give for the contract, leaving out rows whose size is missing, zero or negative. A set holds more than one size when the source's file lists the contract twice with different sizes. |
Example usage
sizes = self.source.read(
self.connection,
datetime.date(2026, 9, 28),
[
'nse_currency_options',
'nse_currency_futures',
],
)
Source code in stock_brokers/instruments/mapping/utilities/contract_sizes.py
GrowwLotSizeSource
¶
Bases: ContractSizeSource
Groww's lot size on its commodity segment, which Groww gives in quotation units.
Example programs
"""Prints the SQL expression Groww's `lot_size` on its COMMODITY segment is read with, and the query around it.
Groww already states a commodity lot in quotation units, which is why its GOLD lot reads 100 where Zerodha says 1.
The program only builds the expression and the query text, so it needs no database. The query joins the date's broker mappings to the Groww snapshot on the broker token and the download date, and keeps only the exchange labels this source is trusted on.
Run it from the project root:
python examples/stock_brokers/instruments/mapping/utilities/contract_sizes/GrowwLotSizeSource/example_1_the_size_expression.py
"""
from stock_brokers.instruments.mapping.utilities.contract_sizes import (
GrowwLotSizeSource,
)
class ReadingTheExpressionExample:
"""Prints the source's settings, its size expression and its query.
Attributes:
source (GrowwLotSizeSource): The source being shown.
"""
def __init__(self):
"""Builds the source.
Returns:
None: This method returns nothing.
"""
self.source = GrowwLotSizeSource()
def run(self):
"""Prints the name, the broker, the trusted exchange labels, the expression and the query.
Returns:
None: This method returns nothing.
"""
print(f'Name: {self.source.NAME}')
print(f'Broker: {self.source.BROKER}')
print(f'Token column: {self.source.TOKEN_COLUMN}')
print(f'Exchange column: {self.source.EXCHANGE_COLUMN}')
print(f'Trusted exchange labels: {self.source.EXCHANGES}')
print(f'Size expression: {self.source.units_expression()}')
print('Query:')
print(str(self.source.statement()))
if __name__ == '__main__':
ReadingTheExpressionExample().run()
It prints:
Name: groww_lot_size
Broker: groww
Token column: exchange_token
Exchange column: segment
Trusted exchange labels: ['COMMODITY']
Size expression: (CASE WHEN raw."lot_size"::text ~ '^-?[0-9]+(\.[0-9]+)?$' THEN raw."lot_size"::text::numeric END)
Query:
SELECT m.instrument_id::text AS instrument_id, (CASE WHEN raw."lot_size"::text ~ '^-?[0-9]+(\.[0-9]+)?$' THEN raw."lot_size"::text::numeric END) AS units FROM unified.broker_mappings m JOIN unified.instruments i ON i.instrument_id = m.instrument_id JOIN groww.instruments raw ON raw."exchange_token"::text = m.broker_token AND raw.download_date = m.mapping_date WHERE m.broker = :broker AND m.mapping_date = :mapping_date AND i.segment = ANY(:segments) AND (i.expiry_date IS NULL OR i.expiry_date >= :mapping_date) AND raw."segment"::text = ANY(:exchanges)
"""Reads Groww's `lot_size` on its COMMODITY segment for a few contracts through a stand-in connection.
`read` runs the source's query and returns, for each contract, the set of distinct sizes its rows give, as decimals with trailing zeros removed. The rows give GOLD at 100, GOLDM at 10 and a CRUDEOIL option at 100. The last row stands for a column that held text; the real query's numeric guard turns such text into null, and the stand-in returns None for it to match.
The connection is a small stand-in that returns the rows it was built with and records the parameters of the query, so the program needs no database. The parameters show that the query is limited to Groww's rows on the trusted exchange labels.
Run it from the project root:
python examples/stock_brokers/instruments/mapping/utilities/contract_sizes/GrowwLotSizeSource/example_2_reading_sizes.py
"""
import datetime
import types
from stock_brokers.instruments.mapping.utilities.contract_sizes import (
GrowwLotSizeSource,
)
class StandInConnection:
"""A stand-in for a database connection that returns fixed rows and records the parameters of each query.
Attributes:
rows (list): The rows every query returns.
parameters (dict | None): The parameters of the last query, or None before any.
"""
def __init__(self, rows):
"""Holds the rows to return.
Args:
rows (list): The rows every query returns.
Returns:
None: This method returns nothing.
"""
self.rows = rows
self.parameters = None
def execute(self, statement, parameters):
"""Records the parameters and returns the fixed rows.
Args:
statement (sqlalchemy.sql.elements.TextClause): The query, which is not run.
parameters (dict): The query's parameters.
Returns:
list: The fixed rows.
"""
self.parameters = parameters
return self.rows
class ReadingSizesExample:
"""Reads the stand-in rows through the source and prints each contract's sizes.
Attributes:
source (GrowwLotSizeSource): The source being read.
connection (StandInConnection): The stand-in connection.
"""
def __init__(self):
"""Builds the source and a connection holding the rows.
Returns:
None: This method returns nothing.
"""
self.source = GrowwLotSizeSource()
self.connection = StandInConnection([
types.SimpleNamespace(
instrument_id='gold-future-oct',
units='100',
),
types.SimpleNamespace(
instrument_id='goldm-future-oct',
units='10',
),
types.SimpleNamespace(
instrument_id='crudeoil-option',
units='100',
),
types.SimpleNamespace(
instrument_id='zinc-future-oct',
units=None,
),
])
def run(self):
"""Prints the sizes read and the parameters the query was given.
Returns:
None: This method returns nothing.
"""
sizes = self.source.read(
self.connection,
datetime.date(2026, 9, 28),
[
'mcx_commodity_futures',
'mcx_commodity_options',
],
)
for instrument_id in sorted(sizes):
texts = []
for size in sorted(sizes[instrument_id]):
texts.append(format(size, 'f'))
print(f'{instrument_id}: {texts}')
print(f'Broker queried: {self.connection.parameters["broker"]}')
print(f'Mapping date: {self.connection.parameters["mapping_date"]}')
print(f'Segments: {self.connection.parameters["segments"]}')
print(f'Exchange labels: {self.connection.parameters["exchanges"]}')
if __name__ == '__main__':
ReadingSizesExample().run()
It prints:
Methods:
| Name | Description |
|---|---|
units_expression |
The |
numeric |
An SQL expression reading a snapshot column as a number, or null when it does not hold one. |
statement |
The query reading this source for every live contract in the given segments on a date. |
read |
Reads this source's size for every live contract it lists. |
units_expression
¶
The lot_size column.
Returns:
| Type | Description |
|---|---|
str
|
The SQL expression. |
Example usage
numeric
¶
An SQL expression reading a snapshot column as a number, or null when it does not hold one.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
column
|
str
|
The snapshot column. |
required |
Returns:
| Type | Description |
|---|---|
str
|
The SQL expression. |
Example usage
print(f'Numeric guard on multiplier: {self.source.numeric("multiplier")}')
Source code in stock_brokers/instruments/mapping/utilities/contract_sizes.py
statement
¶
The query reading this source for every live contract in the given segments on a date.
Returns:
| Type | Description |
|---|---|
TextClause
|
The query, taking |
Example usage
Source code in stock_brokers/instruments/mapping/utilities/contract_sizes.py
read
¶
Reads this source's size for every live contract it lists.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
connection
|
Connection
|
An open connection. |
required |
mapping_date
|
date
|
The mapping date. |
required |
segments
|
list
|
The exchange-prefixed segments to read. |
required |
Returns:
| Type | Description |
|---|---|
dict
|
Instrument ids to the set of distinct sizes, as decimals, that the source's rows give for the contract, leaving out rows whose size is missing, zero or negative. A set holds more than one size when the source's file lists the contract twice with different sizes. |
Example usage
sizes = self.source.read(
self.connection,
datetime.date(2026, 9, 28),
[
'nse_currency_options',
'nse_currency_futures',
],
)
Source code in stock_brokers/instruments/mapping/utilities/contract_sizes.py
ShoonyaCurrencySource
¶
Bases: ContractSizeSource
Shoonya's lot size times its multiplier on NSE currencies, where it gives a lot size of 1 and a multiplier of the contract size.
Example programs
"""Prints the SQL expression Shoonya's lot size times its multiplier on NSE currencies is read with, and the query around it.
Shoonya lists a currency lot as 1 and puts the contract size in its multiplier, so the product of the two is the size in quotation units.
The program only builds the expression and the query text, so it needs no database. The query joins the date's broker mappings to the Shoonya snapshot on the broker token and the download date, and keeps only the exchange labels this source is trusted on.
Run it from the project root:
python examples/stock_brokers/instruments/mapping/utilities/contract_sizes/ShoonyaCurrencySource/example_1_the_size_expression.py
"""
from stock_brokers.instruments.mapping.utilities.contract_sizes import (
ShoonyaCurrencySource,
)
class ReadingTheExpressionExample:
"""Prints the source's settings, its size expression and its query.
Attributes:
source (ShoonyaCurrencySource): The source being shown.
"""
def __init__(self):
"""Builds the source.
Returns:
None: This method returns nothing.
"""
self.source = ShoonyaCurrencySource()
def run(self):
"""Prints the name, the broker, the trusted exchange labels, the expression and the query.
Returns:
None: This method returns nothing.
"""
print(f'Name: {self.source.NAME}')
print(f'Broker: {self.source.BROKER}')
print(f'Token column: {self.source.TOKEN_COLUMN}')
print(f'Exchange column: {self.source.EXCHANGE_COLUMN}')
print(f'Trusted exchange labels: {self.source.EXCHANGES}')
print(f'Size expression: {self.source.units_expression()}')
print('Query:')
print(str(self.source.statement()))
if __name__ == '__main__':
ReadingTheExpressionExample().run()
It prints:
Name: shoonya_lot_size_times_multiplier
Broker: shoonya
Token column: token
Exchange column: exchange
Trusted exchange labels: ['CDS']
Size expression: ((CASE WHEN raw."lotsize"::text ~ '^-?[0-9]+(\.[0-9]+)?$' THEN raw."lotsize"::text::numeric END) * (CASE WHEN raw."multiplier"::text ~ '^-?[0-9]+(\.[0-9]+)?$' THEN raw."multiplier"::text::numeric END))
Query:
SELECT m.instrument_id::text AS instrument_id, ((CASE WHEN raw."lotsize"::text ~ '^-?[0-9]+(\.[0-9]+)?$' THEN raw."lotsize"::text::numeric END) * (CASE WHEN raw."multiplier"::text ~ '^-?[0-9]+(\.[0-9]+)?$' THEN raw."multiplier"::text::numeric END)) AS units FROM unified.broker_mappings m JOIN unified.instruments i ON i.instrument_id = m.instrument_id JOIN shoonya.instruments raw ON raw."token"::text = m.broker_token AND raw.download_date = m.mapping_date WHERE m.broker = :broker AND m.mapping_date = :mapping_date AND i.segment = ANY(:segments) AND (i.expiry_date IS NULL OR i.expiry_date >= :mapping_date) AND raw."exchange"::text = ANY(:exchanges)
"""Reads Shoonya's lot size times its multiplier on NSE currencies for a few contracts through a stand-in connection.
`read` runs the source's query and returns, for each contract, the set of distinct sizes its rows give, as decimals with trailing zeros removed. The rows give USDINR and EURINR at 1000 units and JPYINR at 100000 yen, which are the NSE contract sizes.
The connection is a small stand-in that returns the rows it was built with and records the parameters of the query, so the program needs no database. The parameters show that the query is limited to Shoonya's rows on the trusted exchange labels.
Run it from the project root:
python examples/stock_brokers/instruments/mapping/utilities/contract_sizes/ShoonyaCurrencySource/example_2_reading_sizes.py
"""
import datetime
import types
from stock_brokers.instruments.mapping.utilities.contract_sizes import (
ShoonyaCurrencySource,
)
class StandInConnection:
"""A stand-in for a database connection that returns fixed rows and records the parameters of each query.
Attributes:
rows (list): The rows every query returns.
parameters (dict | None): The parameters of the last query, or None before any.
"""
def __init__(self, rows):
"""Holds the rows to return.
Args:
rows (list): The rows every query returns.
Returns:
None: This method returns nothing.
"""
self.rows = rows
self.parameters = None
def execute(self, statement, parameters):
"""Records the parameters and returns the fixed rows.
Args:
statement (sqlalchemy.sql.elements.TextClause): The query, which is not run.
parameters (dict): The query's parameters.
Returns:
list: The fixed rows.
"""
self.parameters = parameters
return self.rows
class ReadingSizesExample:
"""Reads the stand-in rows through the source and prints each contract's sizes.
Attributes:
source (ShoonyaCurrencySource): The source being read.
connection (StandInConnection): The stand-in connection.
"""
def __init__(self):
"""Builds the source and a connection holding the rows.
Returns:
None: This method returns nothing.
"""
self.source = ShoonyaCurrencySource()
self.connection = StandInConnection([
types.SimpleNamespace(
instrument_id='usdinr-future-oct',
units='1000',
),
types.SimpleNamespace(
instrument_id='jpyinr-future-oct',
units='100000',
),
types.SimpleNamespace(
instrument_id='eurinr-option',
units='1000',
),
])
def run(self):
"""Prints the sizes read and the parameters the query was given.
Returns:
None: This method returns nothing.
"""
sizes = self.source.read(
self.connection,
datetime.date(2026, 9, 28),
[
'nse_currency_futures',
'nse_currency_options',
],
)
for instrument_id in sorted(sizes):
texts = []
for size in sorted(sizes[instrument_id]):
texts.append(format(size, 'f'))
print(f'{instrument_id}: {texts}')
print(f'Broker queried: {self.connection.parameters["broker"]}')
print(f'Mapping date: {self.connection.parameters["mapping_date"]}')
print(f'Segments: {self.connection.parameters["segments"]}')
print(f'Exchange labels: {self.connection.parameters["exchanges"]}')
if __name__ == '__main__':
ReadingSizesExample().run()
It prints:
Methods:
| Name | Description |
|---|---|
units_expression |
|
numeric |
An SQL expression reading a snapshot column as a number, or null when it does not hold one. |
statement |
The query reading this source for every live contract in the given segments on a date. |
read |
Reads this source's size for every live contract it lists. |
units_expression
¶
lotsize times multiplier.
Returns:
| Type | Description |
|---|---|
str
|
The SQL expression. |
Example usage
numeric
¶
An SQL expression reading a snapshot column as a number, or null when it does not hold one.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
column
|
str
|
The snapshot column. |
required |
Returns:
| Type | Description |
|---|---|
str
|
The SQL expression. |
Example usage
print(f'Numeric guard on multiplier: {self.source.numeric("multiplier")}')
Source code in stock_brokers/instruments/mapping/utilities/contract_sizes.py
statement
¶
The query reading this source for every live contract in the given segments on a date.
Returns:
| Type | Description |
|---|---|
TextClause
|
The query, taking |
Example usage
Source code in stock_brokers/instruments/mapping/utilities/contract_sizes.py
read
¶
Reads this source's size for every live contract it lists.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
connection
|
Connection
|
An open connection. |
required |
mapping_date
|
date
|
The mapping date. |
required |
segments
|
list
|
The exchange-prefixed segments to read. |
required |
Returns:
| Type | Description |
|---|---|
dict
|
Instrument ids to the set of distinct sizes, as decimals, that the source's rows give for the contract, leaving out rows whose size is missing, zero or negative. A set holds more than one size when the source's file lists the contract twice with different sizes. |
Example usage
sizes = self.source.read(
self.connection,
datetime.date(2026, 9, 28),
[
'nse_currency_options',
'nse_currency_futures',
],
)
Source code in stock_brokers/instruments/mapping/utilities/contract_sizes.py
StoxkartLotSizeSource
¶
Bases: ContractSizeSource
Stoxkart's lot size on the NSE and BSE currency segments and NCDEX.
Example programs
"""Prints the SQL expression Stoxkart's `lot_size` on NSE and BSE currencies and NCDEX is read with, and the query around it.
Stoxkart is the only broker that lists BSE currencies and NCDEX, so on those markets its figure alone is enough for the contract to be traded.
The program only builds the expression and the query text, so it needs no database. The query joins the date's broker mappings to the Stoxkart snapshot on the broker token and the download date, and keeps only the exchange labels this source is trusted on.
Run it from the project root:
python examples/stock_brokers/instruments/mapping/utilities/contract_sizes/StoxkartLotSizeSource/example_1_the_size_expression.py
"""
from stock_brokers.instruments.mapping.utilities.contract_sizes import (
StoxkartLotSizeSource,
)
class ReadingTheExpressionExample:
"""Prints the source's settings, its size expression and its query.
Attributes:
source (StoxkartLotSizeSource): The source being shown.
"""
def __init__(self):
"""Builds the source.
Returns:
None: This method returns nothing.
"""
self.source = StoxkartLotSizeSource()
def run(self):
"""Prints the name, the broker, the trusted exchange labels, the expression and the query.
Returns:
None: This method returns nothing.
"""
print(f'Name: {self.source.NAME}')
print(f'Broker: {self.source.BROKER}')
print(f'Token column: {self.source.TOKEN_COLUMN}')
print(f'Exchange column: {self.source.EXCHANGE_COLUMN}')
print(f'Trusted exchange labels: {self.source.EXCHANGES}')
print(f'Size expression: {self.source.units_expression()}')
print('Query:')
print(str(self.source.statement()))
if __name__ == '__main__':
ReadingTheExpressionExample().run()
It prints:
Name: stoxkart_lot_size
Broker: stoxkart
Token column: token
Exchange column: exchange
Trusted exchange labels: ['NSECD', 'BSECD', 'NCDEX']
Size expression: (CASE WHEN raw."lot_size"::text ~ '^-?[0-9]+(\.[0-9]+)?$' THEN raw."lot_size"::text::numeric END)
Query:
SELECT m.instrument_id::text AS instrument_id, (CASE WHEN raw."lot_size"::text ~ '^-?[0-9]+(\.[0-9]+)?$' THEN raw."lot_size"::text::numeric END) AS units FROM unified.broker_mappings m JOIN unified.instruments i ON i.instrument_id = m.instrument_id JOIN stoxkart.instruments raw ON raw."token"::text = m.broker_token AND raw.download_date = m.mapping_date WHERE m.broker = :broker AND m.mapping_date = :mapping_date AND i.segment = ANY(:segments) AND (i.expiry_date IS NULL OR i.expiry_date >= :mapping_date) AND raw."exchange"::text = ANY(:exchanges)
"""Reads Stoxkart's `lot_size` on NSE and BSE currencies and NCDEX for a few contracts through a stand-in connection.
`read` runs the source's query and returns, for each contract, the set of distinct sizes its rows give, as decimals with trailing zeros removed. The rows give a BSE USDINR future at 1000 units and two NCDEX futures, DHANIYA and JEERA, at 5 and 3 tonnes.
The connection is a small stand-in that returns the rows it was built with and records the parameters of the query, so the program needs no database. The parameters show that the query is limited to Stoxkart's rows on the trusted exchange labels.
Run it from the project root:
python examples/stock_brokers/instruments/mapping/utilities/contract_sizes/StoxkartLotSizeSource/example_2_reading_sizes.py
"""
import datetime
import types
from stock_brokers.instruments.mapping.utilities.contract_sizes import (
StoxkartLotSizeSource,
)
class StandInConnection:
"""A stand-in for a database connection that returns fixed rows and records the parameters of each query.
Attributes:
rows (list): The rows every query returns.
parameters (dict | None): The parameters of the last query, or None before any.
"""
def __init__(self, rows):
"""Holds the rows to return.
Args:
rows (list): The rows every query returns.
Returns:
None: This method returns nothing.
"""
self.rows = rows
self.parameters = None
def execute(self, statement, parameters):
"""Records the parameters and returns the fixed rows.
Args:
statement (sqlalchemy.sql.elements.TextClause): The query, which is not run.
parameters (dict): The query's parameters.
Returns:
list: The fixed rows.
"""
self.parameters = parameters
return self.rows
class ReadingSizesExample:
"""Reads the stand-in rows through the source and prints each contract's sizes.
Attributes:
source (StoxkartLotSizeSource): The source being read.
connection (StandInConnection): The stand-in connection.
"""
def __init__(self):
"""Builds the source and a connection holding the rows.
Returns:
None: This method returns nothing.
"""
self.source = StoxkartLotSizeSource()
self.connection = StandInConnection([
types.SimpleNamespace(
instrument_id='bse-usdinr-future-oct',
units='1000',
),
types.SimpleNamespace(
instrument_id='ncdex-dhaniya-future-nov',
units='5',
),
types.SimpleNamespace(
instrument_id='ncdex-jeera-future-nov',
units='3',
),
])
def run(self):
"""Prints the sizes read and the parameters the query was given.
Returns:
None: This method returns nothing.
"""
sizes = self.source.read(
self.connection,
datetime.date(2026, 9, 28),
[
'bse_currency_futures',
'ncdex_commodity_futures',
],
)
for instrument_id in sorted(sizes):
texts = []
for size in sorted(sizes[instrument_id]):
texts.append(format(size, 'f'))
print(f'{instrument_id}: {texts}')
print(f'Broker queried: {self.connection.parameters["broker"]}')
print(f'Mapping date: {self.connection.parameters["mapping_date"]}')
print(f'Segments: {self.connection.parameters["segments"]}')
print(f'Exchange labels: {self.connection.parameters["exchanges"]}')
if __name__ == '__main__':
ReadingSizesExample().run()
It prints:
Methods:
| Name | Description |
|---|---|
units_expression |
The |
numeric |
An SQL expression reading a snapshot column as a number, or null when it does not hold one. |
statement |
The query reading this source for every live contract in the given segments on a date. |
read |
Reads this source's size for every live contract it lists. |
units_expression
¶
The lot_size column.
Returns:
| Type | Description |
|---|---|
str
|
The SQL expression. |
Example usage
numeric
¶
An SQL expression reading a snapshot column as a number, or null when it does not hold one.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
column
|
str
|
The snapshot column. |
required |
Returns:
| Type | Description |
|---|---|
str
|
The SQL expression. |
Example usage
print(f'Numeric guard on multiplier: {self.source.numeric("multiplier")}')
Source code in stock_brokers/instruments/mapping/utilities/contract_sizes.py
statement
¶
The query reading this source for every live contract in the given segments on a date.
Returns:
| Type | Description |
|---|---|
TextClause
|
The query, taking |
Example usage
Source code in stock_brokers/instruments/mapping/utilities/contract_sizes.py
read
¶
Reads this source's size for every live contract it lists.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
connection
|
Connection
|
An open connection. |
required |
mapping_date
|
date
|
The mapping date. |
required |
segments
|
list
|
The exchange-prefixed segments to read. |
required |
Returns:
| Type | Description |
|---|---|
dict
|
Instrument ids to the set of distinct sizes, as decimals, that the source's rows give for the contract, leaving out rows whose size is missing, zero or negative. A set holds more than one size when the source's file lists the contract twice with different sizes. |
Example usage
sizes = self.source.read(
self.connection,
datetime.date(2026, 9, 28),
[
'nse_currency_options',
'nse_currency_futures',
],
)
Source code in stock_brokers/instruments/mapping/utilities/contract_sizes.py
ContractSizeDecision
¶
The rule that turns the sources' figures for one contract into a stored decision.
Attributes:
| Name | Type | Description |
|---|---|---|
SINGLE_SOURCE_MARKETS |
list
|
The |
SIBLING_MARKETS |
list
|
The |
Example programs
"""Decides the contract size of five contracts from what each source says about them.
`decide` looks only at the figures it is given. Two or more sources that agree make a contract `confirmed`; one source makes it `single_source`, which is tradeable only on BSE currencies and NCDEX, where Stoxkart is the only broker listing them; any disagreement, including one source giving two sizes for the same contract, makes it a `conflict`; and no figure at all makes it `no_source`.
The figures are made up but follow real contract sizes: 100 units for MCX GOLD, 1000 for a USDINR contract and 5 tonnes for an NCDEX DHANIYA future. `market` is printed beside each decision because the tradeable flag of a single-source contract depends on it. No database is needed.
Run it from the project root:
python examples/stock_brokers/instruments/mapping/utilities/contract_sizes/ContractSizeDecision/example_1_deciding_one_contract.py
"""
import decimal
from stock_brokers.instruments.mapping.utilities.contract_sizes import (
ContractSizeDecision,
)
class DecidingOneContractExample:
"""Runs the decision rule over five contracts and prints each answer.
Attributes:
decision (ContractSizeDecision): The rule being shown.
cases (list): Tuples of a label, a segment, the figures and the contradicting sources.
"""
def __init__(self):
"""Builds the rule and the five cases.
Returns:
None: This method returns nothing.
"""
self.decision = ContractSizeDecision()
self.cases = [
(
'MCX GOLD, three sources agree',
'mcx_commodity_futures',
{
'wisdom_capital_multiplier': decimal.Decimal('100'),
'kotak_contract_size': decimal.Decimal('100'),
'groww_lot_size': decimal.Decimal('100'),
},
None,
),
(
'NSE USDINR, one source disagrees',
'nse_currency_options',
{
'kotak_contract_size': decimal.Decimal('1000'),
'shoonya_lot_size_times_multiplier': decimal.Decimal('1000'),
'stoxkart_lot_size': decimal.Decimal('2000'),
},
None,
),
(
'NSE USDINR, Stoxkart lists it twice',
'nse_currency_options',
{
'kotak_contract_size': decimal.Decimal('1000'),
'shoonya_lot_size_times_multiplier': decimal.Decimal('1000'),
},
{
'stoxkart_lot_size',
},
),
(
'NCDEX DHANIYA, Stoxkart only',
'ncdex_commodity_futures',
{
'stoxkart_lot_size': decimal.Decimal('5'),
},
None,
),
(
'MCX ZINC, no source',
'mcx_commodity_futures',
{},
None,
),
]
def run(self):
"""Prints the market and the decision for each case.
Returns:
None: This method returns nothing.
"""
for label, segment, figures, contradicting_sources in self.cases:
market = self.decision.market(segment)
units_per_lot, status, tradeable = self.decision.decide(
segment,
figures,
contradicting_sources,
)
print(f'{label}')
print(f' market {market}: size {units_per_lot}, {status}, tradeable {tradeable}')
if __name__ == '__main__':
DecidingOneContractExample().run()
It prints:
MCX GOLD, three sources agree
market ('mcx', 'commodity'): size 100, confirmed, tradeable True
NSE USDINR, one source disagrees
market ('nse', 'currency'): size None, conflict, tradeable False
NSE USDINR, Stoxkart lists it twice
market ('nse', 'currency'): size None, conflict, tradeable False
NCDEX DHANIYA, Stoxkart only
market ('ncdex', 'commodity'): size 5, single_source, tradeable True
MCX ZINC, no source
market ('mcx', 'commodity'): size None, no_source, tradeable False
"""Upgrades a newly listed MCX contract that only one broker lists yet, by checking its already confirmed siblings.
Some brokers add a new far-month expiry later than others, so on MCX a new NATURALGAS option can have a single source for a few days. `decide` calls it `single_source` and untradeable. `settle_by_siblings` then looks at the sizes of the confirmed contracts of the same underlying in the same segment: when they all have one size and it is the single source's figure, the contract becomes `sibling_confirmed` and tradeable.
The program runs four such checks. The first is upgraded. The second is not, because the siblings come in two sizes, as happens after the exchange revises a lot. The third is not, because the single figure disagrees with the siblings. The fourth is on NSE commodities, where the sibling rule does not apply at all. The figures are made up; no database is needed.
Run it from the project root:
python examples/stock_brokers/instruments/mapping/utilities/contract_sizes/ContractSizeDecision/example_2_settling_by_siblings.py
"""
import decimal
from stock_brokers.instruments.mapping.utilities.contract_sizes import (
ContractSizeDecision,
)
class SettlingBySiblingsExample:
"""Decides four single-source contracts and then settles each against its siblings.
Attributes:
decision (ContractSizeDecision): The rule being shown.
cases (list): Tuples of a label, a segment, the single source's figure and the siblings' confirmed sizes.
"""
def __init__(self):
"""Builds the rule and the four cases.
Returns:
None: This method returns nothing.
"""
self.decision = ContractSizeDecision()
self.cases = [
(
'MCX NATURALGAS far-month option',
'mcx_commodity_options',
decimal.Decimal('1250'),
{
decimal.Decimal('1250'),
},
),
(
'MCX index option after a lot revision',
'mcx_commodity_index_options',
decimal.Decimal('15'),
{
decimal.Decimal('15'),
decimal.Decimal('30'),
},
),
(
'MCX option disagreeing with its siblings',
'mcx_commodity_options',
decimal.Decimal('1250'),
{
decimal.Decimal('250'),
},
),
(
'NSE commodity future',
'nse_commodity_futures',
decimal.Decimal('1'),
{
decimal.Decimal('1'),
},
),
]
def run(self):
"""Prints the first decision and the settled status for each case.
Returns:
None: This method returns nothing.
"""
print(f'Markets where one source is enough: {ContractSizeDecision.SINGLE_SOURCE_MARKETS}')
print(f'Markets where siblings can confirm: {ContractSizeDecision.SIBLING_MARKETS}')
for label, segment, figure, sibling_sizes in self.cases:
units_per_lot, status, tradeable = self.decision.decide(
segment,
{
'kotak_contract_size': figure,
},
)
settled_status, settled_tradeable = self.decision.settle_by_siblings(
segment,
units_per_lot,
status,
tradeable,
sibling_sizes,
)
print(f'{label}: {status} {tradeable} -> {settled_status} {settled_tradeable}')
if __name__ == '__main__':
SettlingBySiblingsExample().run()
It prints:
Markets where one source is enough: [('bse', 'currency'), ('ncdex', 'commodity')]
Markets where siblings can confirm: [('mcx', 'commodity')]
MCX NATURALGAS far-month option: single_source False -> sibling_confirmed True
MCX index option after a lot revision: single_source False -> single_source False
MCX option disagreeing with its siblings: single_source False -> single_source False
NSE commodity future: single_source False -> single_source False
Methods:
| Name | Description |
|---|---|
market |
The exchange and asset class of an exchange-prefixed segment. |
decide |
Decides one contract's size from what each source says. |
settle_by_siblings |
Upgrades an untradeable single-source decision when the underlying's confirmed contracts all agree with it. |
market
¶
The exchange and asset class of an exchange-prefixed segment.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
segment
|
str
|
The segment, such as |
required |
Returns:
| Type | Description |
|---|---|
tuple
|
|
Example usage
Source code in stock_brokers/instruments/mapping/utilities/contract_sizes.py
decide
¶
Decides one contract's size from what each source says.
A source that gives two different sizes for the same contract contradicts itself, and makes the contract a conflict just as two disagreeing sources do. Keeping either of its sizes would make the decision depend on the order the database happens to return the rows in.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
segment
|
str
|
The contract's exchange-prefixed segment. |
required |
figures
|
dict
|
Source names to sizes as decimals, for the sources that give exactly one. |
required |
contradicting_sources
|
set | None
|
The names of the sources that give more than one size for the contract, or None for none. |
None
|
Returns:
| Type | Description |
|---|---|
tuple
|
|
Example usage
units_per_lot, status, tradeable = self.decision.decide(
segment,
figures,
contradicting_sources,
)
units_per_lot, status, tradeable = self.decision.decide(
segment,
{
'kotak_contract_size': figure,
},
)
Source code in stock_brokers/instruments/mapping/utilities/contract_sizes.py
settle_by_siblings
¶
Upgrades an untradeable single-source decision when the underlying's confirmed contracts all agree with it.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
segment
|
str
|
The contract's exchange-prefixed segment. |
required |
units_per_lot
|
Decimal | None
|
The size |
required |
status
|
str
|
The status |
required |
tradeable
|
bool
|
Whether |
required |
sibling_sizes
|
set
|
The distinct sizes of the confirmed contracts of the same underlying in the same segment on the date. |
required |
Returns:
| Type | Description |
|---|---|
tuple
|
|
Example usage
settled_status, settled_tradeable = self.decision.settle_by_siblings(
segment,
units_per_lot,
status,
tradeable,
sibling_sizes,
)
Source code in stock_brokers/instruments/mapping/utilities/contract_sizes.py
ContractSizeResolver
¶
Reads every source for a mapping date, decides each contract's size and writes the decisions.
Attributes:
| Name | Type | Description |
|---|---|---|
WRITE_BATCH_ROWS |
int
|
How many rows are inserted per statement. |
SOURCES |
list
|
The source classes read, in the order their figures are recorded. |
engine |
Engine
|
The engine over TimescaleDB. |
decision |
ContractSizeDecision
|
The rule applied to each contract. |
Builds the resolver.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
engine
|
Engine | None
|
The engine to use. None builds one from the environment. |
None
|
Returns:
| Type | Description |
|---|---|
None
|
This method returns nothing. |
Example programs
"""Decides and writes one mapping date's contract sizes through a stand-in database.
`run` reads the date's live currency and commodity contracts, reads every source's figure for them, decides each contract, writes the decisions to `unified.contract_sizes` in one transaction and returns a count per segment, status and tradeable flag.
The engine here is a stand-in that answers the resolver's queries from a few rows held in memory and records what the resolver writes, so no PostgreSQL is needed. The contracts are made up but realistic: three MCX GOLD futures, of which the newest far month is listed by Groww only; an NCDEX DHANIYA future that only Stoxkart lists; and an MCX ZINC future no source gives a size for.
Notice that the far-month GOLD future becomes `sibling_confirmed`, because the two nearer GOLD futures are confirmed at the same 100 units, and that DHANIYA is tradeable on a single source because NCDEX is one of the single-source markets. Each written row keeps every source's figure, so the reason for the decision can be read back later.
Run it from the project root:
python examples/stock_brokers/instruments/mapping/utilities/contract_sizes/ContractSizeResolver/example_1_deciding_and_writing_a_date.py
"""
import datetime
import types
from stock_brokers.instruments.mapping.utilities.contract_sizes import (
ContractSizeResolver,
)
class StandInConnection:
"""A stand-in database connection that answers the resolver's queries from rows in memory.
Attributes:
database (StandInDatabase): The rows and the record of writes.
"""
def __init__(self, database):
"""Holds the stand-in database.
Args:
database (StandInDatabase): The rows and the record of writes.
Returns:
None: This method returns nothing.
"""
self.database = database
def __enter__(self):
"""Opens the connection.
Returns:
StandInConnection: This connection.
"""
return self
def __exit__(self, error_type, error, traceback):
"""Closes the connection.
Args:
error_type (type | None): The exception type raised inside the block, if any.
error (BaseException | None): The exception raised inside the block, if any.
traceback (types.TracebackType | None): The traceback, if any.
Returns:
bool: False, so an exception is never swallowed.
"""
return False
def execute(self, statement, parameters):
"""Answers one query from the rows in memory, or records one write.
Args:
statement (sqlalchemy.sql.elements.TextClause): The statement.
parameters (dict | list): The statement's parameters, or a list of them for a batch insert.
Returns:
list: The rows the query returns, empty for a write.
"""
sql = str(statement)
if sql.startswith('DELETE'):
self.database.deleted_dates.append(parameters['mapping_date'])
return []
if sql.startswith('INSERT'):
for row in parameters:
self.database.written_rows.append(row)
return []
if 'AS units' in sql:
return self.database.source_rows.get(parameters['broker'], [])
return self.database.contract_rows
class StandInDatabase:
"""The rows the stand-in answers with, and the writes it has received.
Attributes:
contract_rows (list): The live contracts, as rows with `instrument_id`, `segment` and `underlying_symbol`.
source_rows (dict): Broker name to the rows of `instrument_id` and `units` its source returns.
deleted_dates (list): The mapping dates whose decisions were deleted.
written_rows (list): The decision rows inserted.
"""
def __init__(self, contract_rows, source_rows):
"""Holds the rows.
Args:
contract_rows (list): The live contracts.
source_rows (dict): Broker name to its source's rows.
Returns:
None: This method returns nothing.
"""
self.contract_rows = contract_rows
self.source_rows = source_rows
self.deleted_dates = []
self.written_rows = []
class StandInEngine:
"""A stand-in SQLAlchemy engine whose connections all share one stand-in database.
Attributes:
database (StandInDatabase): The shared rows.
"""
def __init__(self, database):
"""Holds the database.
Args:
database (StandInDatabase): The shared rows.
Returns:
None: This method returns nothing.
"""
self.database = database
def connect(self):
"""Opens a connection for reading.
Returns:
StandInConnection: A connection over the shared rows.
"""
return StandInConnection(self.database)
def begin(self):
"""Opens a connection inside a transaction.
Returns:
StandInConnection: A connection over the shared rows.
"""
return StandInConnection(self.database)
class DecidingAndWritingADateExample:
"""Runs the resolver for 2026-09-28 over five contracts and prints the summary and the written rows.
Attributes:
database (StandInDatabase): The stand-in rows.
resolver (ContractSizeResolver): The resolver being shown.
"""
def __init__(self):
"""Builds the stand-in database and the resolver over it.
Returns:
None: This method returns nothing.
"""
self.database = StandInDatabase(
[
self.contract('gold-2026-10', 'mcx_commodity_futures', 'GOLD'),
self.contract('gold-2026-12', 'mcx_commodity_futures', 'GOLD'),
self.contract('gold-2027-02', 'mcx_commodity_futures', 'GOLD'),
self.contract('dhaniya-2026-11', 'ncdex_commodity_futures', 'DHANIYA'),
self.contract('zinc-2026-10', 'mcx_commodity_futures', 'ZINC'),
],
{
'wisdom_capital': [
self.figure('gold-2026-10', '100'),
self.figure('gold-2026-12', '100'),
],
'kotak': [
self.figure('gold-2026-10', '100.000'),
],
'groww': [
self.figure('gold-2026-10', '100'),
self.figure('gold-2026-12', '100'),
self.figure('gold-2027-02', '100'),
],
'stoxkart': [
self.figure('dhaniya-2026-11', '5'),
],
},
)
self.resolver = ContractSizeResolver(StandInEngine(self.database))
def contract(self, instrument_id, segment, underlying_symbol):
"""Builds one live contract row.
Args:
instrument_id (str): The contract's instrument id.
segment (str): Its exchange-prefixed segment.
underlying_symbol (str): Its underlying's symbol.
Returns:
types.SimpleNamespace: The row.
"""
return types.SimpleNamespace(
instrument_id=instrument_id,
segment=segment,
underlying_symbol=underlying_symbol,
)
def figure(self, instrument_id, units):
"""Builds one source row.
Args:
instrument_id (str): The contract's instrument id.
units (str): The size the source gives.
Returns:
types.SimpleNamespace: The row.
"""
return types.SimpleNamespace(
instrument_id=instrument_id,
units=units,
)
def run(self):
"""Runs the resolver and prints the summary and every written row.
Returns:
None: This method returns nothing.
"""
summary = self.resolver.run(datetime.date(2026, 9, 28))
print('Summary:')
for segment, status, tradeable in sorted(summary):
count = summary[(segment, status, tradeable)]
print(f' {segment} {status} tradeable={tradeable}: {count}')
for deleted_date in self.database.deleted_dates:
print(f'Decisions deleted first for: {deleted_date.isoformat()}')
print('Rows written:')
for row in self.database.written_rows:
units_text = 'none'
if row['units_per_lot'] is not None:
units_text = format(row['units_per_lot'], 'f')
print(f' {row["instrument_id"]}: {units_text} {row["status"]} tradeable={row["tradeable"]} sources={row["sources"]}')
if __name__ == '__main__':
DecidingAndWritingADateExample().run()
It prints:
Summary:
mcx_commodity_futures confirmed tradeable=True: 2
mcx_commodity_futures no_source tradeable=False: 1
mcx_commodity_futures sibling_confirmed tradeable=True: 1
ncdex_commodity_futures single_source tradeable=True: 1
Decisions deleted first for: 2026-09-28
Rows written:
gold-2026-10: 100 confirmed tradeable=True sources={"groww_lot_size": "100", "kotak_contract_size": "100", "wisdom_capital_multiplier": "100"}
gold-2026-12: 100 confirmed tradeable=True sources={"groww_lot_size": "100", "wisdom_capital_multiplier": "100"}
gold-2027-02: 100 sibling_confirmed tradeable=True sources={"groww_lot_size": "100"}
dhaniya-2026-11: 5 single_source tradeable=True sources={"stoxkart_lot_size": "5"}
zinc-2026-10: none no_source tradeable=False sources={}
"""Decides a date's contract sizes without writing them, to inspect the conflicts first, and then writes them.
`segments` lists every exchange-prefixed currency and commodity futures and options segment the resolver reads. `live_contracts` reads the contracts mapped on the date in those segments, and `decisions` reads every source and decides each contract without writing anything, which is useful to look at the conflicts before `write` replaces the date's stored decisions.
The engine is the same kind of in-memory stand-in as in the first program, so no PostgreSQL is needed. Two troubles are staged here. Stoxkart's file lists one USDINR option twice, with lots of 1000 and 2000, so the contract is a conflict and its recorded figure reads `1000|2000`, even though Kotak and Shoonya agree on 1000. And the MCX SILVERMIC contracts come in two confirmed sizes, as after a lot revision, so the far-month contract that only Groww lists stays `single_source` and untradeable instead of being settled by its siblings.
Run it from the project root:
python examples/stock_brokers/instruments/mapping/utilities/contract_sizes/ContractSizeResolver/example_2_inspecting_conflicts_before_writing.py
"""
import datetime
import types
from stock_brokers.instruments.mapping.utilities.contract_sizes import (
ContractSizeResolver,
)
class StandInConnection:
"""A stand-in database connection that answers the resolver's queries from rows in memory.
Attributes:
database (StandInDatabase): The rows and the record of writes.
"""
def __init__(self, database):
"""Holds the stand-in database.
Args:
database (StandInDatabase): The rows and the record of writes.
Returns:
None: This method returns nothing.
"""
self.database = database
def __enter__(self):
"""Opens the connection.
Returns:
StandInConnection: This connection.
"""
return self
def __exit__(self, error_type, error, traceback):
"""Closes the connection.
Args:
error_type (type | None): The exception type raised inside the block, if any.
error (BaseException | None): The exception raised inside the block, if any.
traceback (types.TracebackType | None): The traceback, if any.
Returns:
bool: False, so an exception is never swallowed.
"""
return False
def execute(self, statement, parameters):
"""Answers one query from the rows in memory, or records one write.
Args:
statement (sqlalchemy.sql.elements.TextClause): The statement.
parameters (dict | list): The statement's parameters, or a list of them for a batch insert.
Returns:
list: The rows the query returns, empty for a write.
"""
sql = str(statement)
if sql.startswith('DELETE'):
self.database.deleted_dates.append(parameters['mapping_date'])
return []
if sql.startswith('INSERT'):
for row in parameters:
self.database.written_rows.append(row)
return []
if 'AS units' in sql:
return self.database.source_rows.get(parameters['broker'], [])
return self.database.contract_rows
class StandInDatabase:
"""The rows the stand-in answers with, and the writes it has received.
Attributes:
contract_rows (list): The live contracts, as rows with `instrument_id`, `segment` and `underlying_symbol`.
source_rows (dict): Broker name to the rows of `instrument_id` and `units` its source returns.
deleted_dates (list): The mapping dates whose decisions were deleted.
written_rows (list): The decision rows inserted.
"""
def __init__(self, contract_rows, source_rows):
"""Holds the rows.
Args:
contract_rows (list): The live contracts.
source_rows (dict): Broker name to its source's rows.
Returns:
None: This method returns nothing.
"""
self.contract_rows = contract_rows
self.source_rows = source_rows
self.deleted_dates = []
self.written_rows = []
class StandInEngine:
"""A stand-in SQLAlchemy engine whose connections all share one stand-in database.
Attributes:
database (StandInDatabase): The shared rows.
"""
def __init__(self, database):
"""Holds the database.
Args:
database (StandInDatabase): The shared rows.
Returns:
None: This method returns nothing.
"""
self.database = database
def connect(self):
"""Opens a connection for reading.
Returns:
StandInConnection: A connection over the shared rows.
"""
return StandInConnection(self.database)
def begin(self):
"""Opens a connection inside a transaction.
Returns:
StandInConnection: A connection over the shared rows.
"""
return StandInConnection(self.database)
class InspectingConflictsBeforeWritingExample:
"""Lists the segments, reads the live contracts, decides them and then writes the decisions.
Attributes:
database (StandInDatabase): The stand-in rows.
engine (StandInEngine): The stand-in engine.
resolver (ContractSizeResolver): The resolver being shown.
mapping_date (datetime.date): The date being decided.
"""
def __init__(self):
"""Builds the stand-in database and the resolver over it.
Returns:
None: This method returns nothing.
"""
self.database = StandInDatabase(
[
self.contract('usdinr-option-83', 'nse_currency_options', 'USDINR'),
self.contract('silvermic-2026-11', 'mcx_commodity_futures', 'SILVERMIC'),
self.contract('silvermic-2027-02', 'mcx_commodity_futures', 'SILVERMIC'),
self.contract('silvermic-2027-04', 'mcx_commodity_futures', 'SILVERMIC'),
],
{
'kotak': [
self.figure('usdinr-option-83', '1000'),
self.figure('silvermic-2026-11', '1'),
self.figure('silvermic-2027-02', '5'),
],
'shoonya': [
self.figure('usdinr-option-83', '1000'),
],
'stoxkart': [
self.figure('usdinr-option-83', '1000'),
self.figure('usdinr-option-83', '2000'),
],
'groww': [
self.figure('silvermic-2026-11', '1'),
self.figure('silvermic-2027-02', '5'),
self.figure('silvermic-2027-04', '5'),
],
},
)
self.engine = StandInEngine(self.database)
self.resolver = ContractSizeResolver(self.engine)
self.mapping_date = datetime.date(2026, 9, 28)
def contract(self, instrument_id, segment, underlying_symbol):
"""Builds one live contract row.
Args:
instrument_id (str): The contract's instrument id.
segment (str): Its exchange-prefixed segment.
underlying_symbol (str): Its underlying's symbol.
Returns:
types.SimpleNamespace: The row.
"""
return types.SimpleNamespace(
instrument_id=instrument_id,
segment=segment,
underlying_symbol=underlying_symbol,
)
def figure(self, instrument_id, units):
"""Builds one source row.
Args:
instrument_id (str): The contract's instrument id.
units (str): The size the source gives.
Returns:
types.SimpleNamespace: The row.
"""
return types.SimpleNamespace(
instrument_id=instrument_id,
units=units,
)
def run(self):
"""Prints the segments, the live contracts and the decisions, then writes them.
Returns:
None: This method returns nothing.
"""
segments = self.resolver.segments()
print(f'{len(segments)} segments are read, starting with {segments[:4]}')
with self.engine.connect() as connection:
contracts = self.resolver.live_contracts(connection, self.mapping_date, segments)
for instrument_id in contracts:
segment, underlying_symbol = contracts[instrument_id]
print(f'Live: {instrument_id} in {segment} on {underlying_symbol}')
rows = self.resolver.decisions(self.mapping_date)
print(f'Rows written before writing: {len(self.database.written_rows)}')
for row in rows:
print(f' {row["instrument_id"]}: {row["status"]} tradeable={row["tradeable"]} sources={row["sources"]}')
written = self.resolver.write(self.mapping_date, rows)
print(f'Rows written: {written}')
if __name__ == '__main__':
InspectingConflictsBeforeWritingExample().run()
It prints:
32 segments are read, starting with ['nse_currency_futures', 'bse_currency_futures', 'mcx_currency_futures', 'ncdex_currency_futures']
Live: usdinr-option-83 in nse_currency_options on USDINR
Live: silvermic-2026-11 in mcx_commodity_futures on SILVERMIC
Live: silvermic-2027-02 in mcx_commodity_futures on SILVERMIC
Live: silvermic-2027-04 in mcx_commodity_futures on SILVERMIC
Rows written before writing: 0
usdinr-option-83: conflict tradeable=False sources={"kotak_contract_size": "1000", "shoonya_lot_size_times_multiplier": "1000", "stoxkart_lot_size": "1000|2000"}
silvermic-2026-11: confirmed tradeable=True sources={"groww_lot_size": "1", "kotak_contract_size": "1"}
silvermic-2027-02: confirmed tradeable=True sources={"groww_lot_size": "5", "kotak_contract_size": "5"}
silvermic-2027-04: single_source tradeable=False sources={"groww_lot_size": "5"}
Rows written: 4
Methods:
| Name | Description |
|---|---|
segments |
Every exchange-prefixed currency and commodity futures and options segment. |
live_contracts |
Every contract mapped on the date in the segments that has not expired. |
decisions |
Decides every live contract's size for the date. |
write |
Replaces the date's decisions with the given rows, in one transaction. |
run |
Decides and writes every live contract's size for the date. |
Source code in stock_brokers/instruments/mapping/utilities/contract_sizes.py
segments
¶
Every exchange-prefixed currency and commodity futures and options segment.
Returns:
| Type | Description |
|---|---|
list
|
The segments. |
Example usage
Source code in stock_brokers/instruments/mapping/utilities/contract_sizes.py
live_contracts
¶
Every contract mapped on the date in the segments that has not expired.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
connection
|
Connection
|
An open connection. |
required |
mapping_date
|
date
|
The mapping date. |
required |
segments
|
list
|
The exchange-prefixed segments. |
required |
Returns:
| Type | Description |
|---|---|
dict
|
Instrument ids to |
Example usage
contracts = self.resolver.live_contracts(connection, self.mapping_date, segments)
Source code in stock_brokers/instruments/mapping/utilities/contract_sizes.py
decisions
¶
Decides every live contract's size for the date.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
mapping_date
|
date
|
The mapping date. |
required |
Returns:
| Type | Description |
|---|---|
list
|
One dictionary per contract with |
Example usage
rows = self.resolver.decisions(self.mapping_date)
Source code in stock_brokers/instruments/mapping/utilities/contract_sizes.py
write
¶
Replaces the date's decisions with the given rows, in one transaction.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
mapping_date
|
date
|
The mapping date. |
required |
rows
|
list
|
The decisions, as |
required |
Returns:
| Type | Description |
|---|---|
int
|
The number of rows written. |
Example usage
written = self.resolver.write(self.mapping_date, rows)
Source code in stock_brokers/instruments/mapping/utilities/contract_sizes.py
run
¶
Decides and writes every live contract's size for the date.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
mapping_date
|
date
|
The mapping date. |
required |
Returns:
| Type | Description |
|---|---|
Counter
|
|
Example usage
summary = self.resolver.run(datetime.date(2026, 9, 28))
Source code in stock_brokers/instruments/mapping/utilities/contract_sizes.py
ContractSizeCommand
¶
The command line: decide a date's contract sizes and print how many contracts each segment has in each status.
Example programs
"""Runs the contract size command line with a date it cannot read, and shows that it stops before touching the database.
`ContractSizeCommand.run` reads its arguments from `sys.argv`, as `python -m stock_brokers.instruments.mapping.utilities.contract_sizes --date 2026-09-15` would pass them. A `--date` that is not a real `YYYY-MM-DD` date is refused with a message and exit code 2, which the project uses for a bad argument, and no resolver or database engine is built.
The program sets `sys.argv` itself to the date `2026-09-31`, a day September does not have, so it needs no database and no shell arguments.
Run it from the project root:
python examples/stock_brokers/instruments/mapping/utilities/contract_sizes/ContractSizeCommand/example_1_a_malformed_date.py
"""
import sys
from stock_brokers.instruments.mapping.utilities.contract_sizes import (
ContractSizeCommand,
)
class MalformedDateExample:
"""Runs the command with an impossible date and prints its exit code.
Attributes:
command (ContractSizeCommand): The command being shown.
"""
def __init__(self):
"""Builds the command and sets the arguments it will read.
Returns:
None: This method returns nothing.
"""
sys.argv = [
'contract_sizes',
'--date',
'2026-09-31',
]
self.command = ContractSizeCommand()
def run(self):
"""Runs the command and prints the exit code it returns.
Returns:
None: This method returns nothing.
"""
exit_code = self.command.run()
print(f'Exit code: {exit_code}')
if __name__ == '__main__':
MalformedDateExample().run()
It prints:
"""Runs the contract size command line for one date, the way the daily mapping run does, against a stand-in database.
`ContractSizeCommand.run` reads `--date` from `sys.argv`, builds a `ContractSizeResolver` over the configured PostgreSQL engine, decides and writes the date's contract sizes, and prints how many contracts each segment has in each status.
The command builds its engine through `get_postgres_engine`, so this program replaces that function in the `contract_sizes` module with one returning an in-memory stand-in engine. That keeps the real command and the real resolver running while no database is reached. The stand-in holds three MCX CRUDEOIL futures, two confirmed at 100 barrels by Wisdom Capital and Kotak and one far month listed by Wisdom Capital only, and one BSE USDINR future that only Stoxkart lists.
Notice in the printed table that the far-month CRUDEOIL future is `sibling_confirmed` and that the BSE currency future is tradeable on its single source.
Run it from the project root:
python examples/stock_brokers/instruments/mapping/utilities/contract_sizes/ContractSizeCommand/example_2_running_for_a_date.py
"""
import sys
import types
from stock_brokers.instruments.mapping.utilities import contract_sizes
from stock_brokers.instruments.mapping.utilities.contract_sizes import (
ContractSizeCommand,
)
class StandInConnection:
"""A stand-in database connection that answers the resolver's queries from rows in memory.
Attributes:
database (StandInDatabase): The rows and the record of writes.
"""
def __init__(self, database):
"""Holds the stand-in database.
Args:
database (StandInDatabase): The rows and the record of writes.
Returns:
None: This method returns nothing.
"""
self.database = database
def __enter__(self):
"""Opens the connection.
Returns:
StandInConnection: This connection.
"""
return self
def __exit__(self, error_type, error, traceback):
"""Closes the connection.
Args:
error_type (type | None): The exception type raised inside the block, if any.
error (BaseException | None): The exception raised inside the block, if any.
traceback (types.TracebackType | None): The traceback, if any.
Returns:
bool: False, so an exception is never swallowed.
"""
return False
def execute(self, statement, parameters):
"""Answers one query from the rows in memory, or records one write.
Args:
statement (sqlalchemy.sql.elements.TextClause): The statement.
parameters (dict | list): The statement's parameters, or a list of them for a batch insert.
Returns:
list: The rows the query returns, empty for a write.
"""
sql = str(statement)
if sql.startswith('DELETE'):
self.database.deleted_dates.append(parameters['mapping_date'])
return []
if sql.startswith('INSERT'):
for row in parameters:
self.database.written_rows.append(row)
return []
if 'AS units' in sql:
return self.database.source_rows.get(parameters['broker'], [])
return self.database.contract_rows
class StandInDatabase:
"""The rows the stand-in answers with, and the writes it has received.
Attributes:
contract_rows (list): The live contracts, as rows with `instrument_id`, `segment` and `underlying_symbol`.
source_rows (dict): Broker name to the rows of `instrument_id` and `units` its source returns.
deleted_dates (list): The mapping dates whose decisions were deleted.
written_rows (list): The decision rows inserted.
"""
def __init__(self, contract_rows, source_rows):
"""Holds the rows.
Args:
contract_rows (list): The live contracts.
source_rows (dict): Broker name to its source's rows.
Returns:
None: This method returns nothing.
"""
self.contract_rows = contract_rows
self.source_rows = source_rows
self.deleted_dates = []
self.written_rows = []
class StandInEngine:
"""A stand-in SQLAlchemy engine whose connections all share one stand-in database.
Attributes:
database (StandInDatabase): The shared rows.
"""
def __init__(self, database):
"""Holds the database.
Args:
database (StandInDatabase): The shared rows.
Returns:
None: This method returns nothing.
"""
self.database = database
def connect(self):
"""Opens a connection for reading.
Returns:
StandInConnection: A connection over the shared rows.
"""
return StandInConnection(self.database)
def begin(self):
"""Opens a connection inside a transaction.
Returns:
StandInConnection: A connection over the shared rows.
"""
return StandInConnection(self.database)
class RunningForADateExample:
"""Runs the command for 2026-09-28 over a stand-in database and prints its exit code and what it wrote.
Attributes:
database (StandInDatabase): The stand-in rows.
command (ContractSizeCommand): The command being shown.
"""
def __init__(self):
"""Builds the stand-in database, points the command's engine at it and sets the arguments.
Returns:
None: This method returns nothing.
"""
self.database = StandInDatabase(
[
self.contract('crudeoil-2026-10', 'mcx_commodity_futures', 'CRUDEOIL'),
self.contract('crudeoil-2026-11', 'mcx_commodity_futures', 'CRUDEOIL'),
self.contract('crudeoil-2027-03', 'mcx_commodity_futures', 'CRUDEOIL'),
self.contract('bse-usdinr-2026-10', 'bse_currency_futures', 'USDINR'),
],
{
'wisdom_capital': [
self.figure('crudeoil-2026-10', '100'),
self.figure('crudeoil-2026-11', '100'),
self.figure('crudeoil-2027-03', '100'),
],
'kotak': [
self.figure('crudeoil-2026-10', '100'),
self.figure('crudeoil-2026-11', '100'),
],
'stoxkart': [
self.figure('bse-usdinr-2026-10', '1000'),
],
},
)
contract_sizes.get_postgres_engine = self.stand_in_engine
sys.argv = [
'contract_sizes',
'--date',
'2026-09-28',
]
self.command = ContractSizeCommand()
def stand_in_engine(self):
"""Builds the stand-in engine the command uses in place of the configured one.
Returns:
StandInEngine: An engine over the stand-in database.
"""
return StandInEngine(self.database)
def contract(self, instrument_id, segment, underlying_symbol):
"""Builds one live contract row.
Args:
instrument_id (str): The contract's instrument id.
segment (str): Its exchange-prefixed segment.
underlying_symbol (str): Its underlying's symbol.
Returns:
types.SimpleNamespace: The row.
"""
return types.SimpleNamespace(
instrument_id=instrument_id,
segment=segment,
underlying_symbol=underlying_symbol,
)
def figure(self, instrument_id, units):
"""Builds one source row.
Args:
instrument_id (str): The contract's instrument id.
units (str): The size the source gives.
Returns:
types.SimpleNamespace: The row.
"""
return types.SimpleNamespace(
instrument_id=instrument_id,
units=units,
)
def run(self):
"""Runs the command and prints its exit code and how many rows it wrote.
Returns:
None: This method returns nothing.
"""
exit_code = self.command.run()
print(f'Exit code: {exit_code}')
print(f'Rows written: {len(self.database.written_rows)}')
if __name__ == '__main__':
RunningForADateExample().run()
It prints:
Methods:
| Name | Description |
|---|---|
run |
Parses the arguments, runs the resolver and prints the summary. |
run
¶
Parses the arguments, runs the resolver and prints the summary.
Returns:
| Type | Description |
|---|---|
int
|
The exit code: 0 on success, 2 for a malformed date. |
Example usage