resolution
¶
Find the unified instrument behind each of a broker's stored price series.
A series is resolved once, as a whole, rather than bar by bar. Its bars go back years before the first mapping date, so no date-by-date lookup could place them; what can be known is which instruments the series' token has pointed at over the mapping dates there are, and the choice between those is made here.
The evidence is tried in order of strength:
-
The broker's own mapping (
broker_mapping). The token's spans in unified.broker_mappings, restricted to the segments the identifier allows. One candidate settles it. Several are narrowed in three ways, in turn: -
A transient is dropped: a candidate seen on fewer dates, entirely inside another's span. On 2026-08-12 only zerodha's file was mapped, and without other brokers to cross-reference, its exchange traded funds and bonds were filed as uncategorised for that one day, under identities that exist on no other date.
- A consensus picks between candidates seen side by side: the one other brokers map the same exchange token to. flattrade's file keeps a stale row for DSP's IT ETF under its old symbol ITETFADD beside the current ITADD, both on token 17207, while every other broker maps 17207 to ITADD alone.
- Sequential candidates, one ending before the next begins, split the series in time at the boundary; bars from before the first mapping date belong to the earliest.
Anything still unresolved is ambiguous, and reported rather than guessed.
-
Other brokers' mappings of the exchange token (
exchange_token), when the broker never mapped the token at all. flattrade maps a symbol's EQ row and not its BE row, so its BE series - 566 of them in daily bars - have no mapping of their own, while the exchange token, shared by every broker's NSE and BSE files, names the instrument. -
The trading symbol (
raw_symbol), matched against unified.instruments in the allowed segments, only when it matches exactly one instrument - counting an uncategorised twin of a classified instrument as no match, since that is a row a broker could not classify.
Resolution follows unified.instruments's idea of identity, in which the symbol is part of what an instrument is. A company that changes its symbol is therefore two instruments, and its series is split at the rename: MANBRO before 2026-09-03, KDGREEN from then on.
Classes:
| Name | Description |
|---|---|
SeriesResolver |
Resolves broker price series to unified instruments. |
Functions:
| Name | Description |
|---|---|
india_midnight |
Midnight India time at the start of a date. |
family_of |
The family a SeriesContext's segments describe, for rebuilding Kite tokens. |
SeriesResolver
¶
Resolves broker price series to unified instruments.
Attributes:
| Name | Type | Description |
|---|---|---|
mapping_resolver |
MappingResolver
|
The look-ups over the mapped tables. |
engine |
Engine
|
The engine those look-ups use. |
Build the resolver.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
mapping_resolver
|
MappingResolver | None
|
An existing resolver to share, or None to build one. |
None
|
Returns:
| Type | Description |
|---|---|
None
|
This function returns nothing. |
Example programs
"""Resolves six of Flattrade's stored daily series to unified instruments, one for each rule the resolver applies.
A broker's price series is resolved once, as a whole: its bars reach back years before the first mapping date, so the question is which instruments its token has pointed at across the mapping dates there are, and which of them owns the series. `SeriesResolver.resolve` reads the identifier with the broker's own `series_context`, asks the mapping resolver for every instrument the token has been mapped to with the span of dates it was seen on, and chooses:
- one candidate settles it (RELIANCE);
- a candidate seen on fewer dates, entirely inside another's span, is a transient and is dropped (NIFTYBEES, filed as uncategorised for one day on 2026-08-12);
- between candidates seen side by side, the one other brokers map the same exchange token to wins, when at least two back it (ITADD over the stale ITETFADD row on token 17207);
- sequential candidates split the series in time at the boundary (MANBRO renamed KDGREEN on 2026-09-03);
- anything else is reported as ambiguous rather than guessed;
- a token the broker never mapped falls back on other brokers' mappings of its exchange token (INDIAGLYCO's BE series, backed by Dhan, Kotak and Zerodha, whose Kite token is rebuilt from the exchange token as 7001 shifted left eight bits plus NSE's segment code 1).
The mapping resolver and its database are replaced by small stand-ins holding those spans and other brokers' mappings, so no data store is reached; the stand-in engine answers the two queries `resolve` sends, and the names and dates follow the cases in the module's own documentation. The program finally calls `choose` by hand to show that a consensus of one broker is not enough.
Run it from the project root:
python examples/stock_brokers/instruments/historical/utilities/unified/resolution/SeriesResolver/example_1_resolving_flattrade_series.py
"""
import datetime
from stock_brokers.instruments.historical.utilities.unified.resolution import (
SeriesResolver,
)
class VoteRow:
"""A stand-in for one result row of the exchange token vote query.
Attributes:
broker (str): The broker that maps the token.
broker_token (str): Its token.
instrument_id (str): The instrument it maps the token to.
"""
def __init__(self, broker, broker_token, instrument_id):
"""Holds the row's columns.
Args:
broker (str): The broker that maps the token.
broker_token (str): Its token.
instrument_id (str): The instrument it maps the token to.
Returns:
None: This method returns nothing.
"""
self.broker = broker
self.broker_token = broker_token
self.instrument_id = instrument_id
class InstrumentRow:
"""A stand-in for one result row of an instrument master query, read through `_mapping`.
Attributes:
instrument_id (str): The instrument's id.
_mapping (dict): Every column by name, as SQLAlchemy rows offer them.
"""
def __init__(self, columns):
"""Holds the row's columns.
Args:
columns (dict): Every column by name.
Returns:
None: This method returns nothing.
"""
self.instrument_id = columns['instrument_id']
self._mapping = columns
class StandInResult:
"""A stand-in for a SQLAlchemy result.
Attributes:
rows (list): The rows.
"""
def __init__(self, rows):
"""Holds the rows.
Args:
rows (list): The rows.
Returns:
None: This method returns nothing.
"""
self.rows = rows
def all(self):
"""Every row.
Returns:
list: The rows.
"""
return self.rows
def one(self):
"""The only row.
Returns:
InstrumentRow: The row.
"""
return self.rows[0]
class StandInConnection:
"""A stand-in for a SQLAlchemy connection over the stand-in mapped tables.
Attributes:
engine (StandInEngine): The engine holding the tables.
"""
def __init__(self, engine):
"""Remembers the engine.
Args:
engine (StandInEngine): The engine holding the tables.
Returns:
None: This method returns nothing.
"""
self.engine = engine
def __enter__(self):
"""Opens the connection.
Returns:
StandInConnection: This connection.
"""
return self
def __exit__(self, exception_type, exception, traceback):
"""Closes the connection, letting any exception carry on.
Args:
exception_type (type | None): The class of an exception raised inside the block.
exception (BaseException | None): The exception raised inside the block.
traceback (types.TracebackType | None): Where it was raised.
Returns:
bool: Always False, so an exception is not swallowed.
"""
return False
def execute(self, statement, parameters):
"""Answers the exchange token vote query or the one-instrument query.
Args:
statement (sqlalchemy.TextClause): The SQL.
parameters (dict): The bound parameters.
Returns:
StandInResult: The rows.
"""
if 'GROUP BY' in str(statement):
return StandInResult(self.engine.votes(parameters))
instrument = self.engine.master[parameters['instrument_id']]
rows = [
InstrumentRow(instrument),
]
return StandInResult(rows)
class StandInEngine:
"""A stand-in for the SQLAlchemy engine, holding other brokers' mappings and the instrument master.
Attributes:
mappings (list): (broker, broker_token, instrument_id, segment) rows of other brokers' mappings.
master (dict): Instrument id to its row in the instrument master.
"""
def __init__(self):
"""Fills in the mappings and the master.
Returns:
None: This method returns nothing.
"""
self.mappings = []
self.master = {}
self.add_mapping('dhan', '17207', 'ITADD')
self.add_mapping('kotak', '17207', 'ITADD')
self.add_mapping('groww', '17207', 'ITADD')
self.add_mapping('stoxkart', '17207', 'ITADD')
self.add_mapping('dhan', '7001', 'INDIAGLYCO')
self.add_mapping('kotak', '7001', 'INDIAGLYCO')
self.add_mapping('zerodha', str(7001 << 8 | 1), 'INDIAGLYCO')
self.master['INDIAGLYCO'] = {
'instrument_id': 'INDIAGLYCO',
'exchange': 'nse',
'segment': 'nse_equities',
'shape': 'security',
'symbol': 'INDIAGLYCO',
'first_mapping_date': datetime.date(2026, 8, 12),
'last_mapping_date': datetime.date(2026, 9, 29),
}
def add_mapping(self, broker, broker_token, instrument_id):
"""Adds one other broker's mapping of an NSE share.
Args:
broker (str): The broker.
broker_token (str): Its token.
instrument_id (str): The instrument it maps to.
Returns:
None: This method returns nothing.
"""
row = (
broker,
broker_token,
instrument_id,
'nse_equities',
)
self.mappings.append(row)
def votes(self, parameters):
"""Selects the mappings the vote query asks for.
Args:
parameters (dict): The query's bound parameters.
Returns:
list: One VoteRow per matching (broker, token, instrument).
"""
rows = []
for broker, broker_token, instrument_id, segment in self.mappings:
if segment not in parameters['segments']:
continue
voter = broker in parameters['voters'] and broker_token in parameters['exchange_tokens']
zerodha = broker == 'zerodha' and parameters['include_zerodha'] and broker_token in parameters['zerodha_tokens']
if voter or zerodha:
rows.append(VoteRow(broker, broker_token, instrument_id))
return rows
def connect(self):
"""Opens a stand-in connection.
Returns:
StandInConnection: The connection.
"""
return StandInConnection(self)
class StandInMappingResolver:
"""A stand-in for `MappingResolver` that knows Flattrade's token spans.
Attributes:
engine (StandInEngine): The engine the series resolver queries directly.
spans (dict): Flattrade token to the spans it has been mapped over.
"""
def __init__(self):
"""Fills in the spans for five tokens.
Returns:
None: This method returns nothing.
"""
self.engine = StandInEngine()
self.spans = {}
self.add_span('2885', 'RELIANCE', 'RELIANCE', 'nse_equities', '2026-08-12', '2026-09-29', 35)
self.add_span('10576', 'NIFTYBEES', 'NIFTYBEES', 'nse_exchange_traded_funds', '2026-08-12', '2026-09-29', 35)
self.add_span('10576', 'NIFTYBEES-UNCATEGORISED', 'NIFTYBEES', 'nse_uncategorised', '2026-08-12', '2026-08-12', 1)
self.add_span('17207', 'ITETFADD', 'ITETFADD', 'nse_exchange_traded_funds', '2026-08-12', '2026-09-25', 32)
self.add_span('17207', 'ITADD', 'ITADD', 'nse_exchange_traded_funds', '2026-08-14', '2026-09-29', 34)
self.add_span('4617', 'MANBRO', 'MANBRO', 'nse_equities', '2026-08-12', '2026-09-02', 16)
self.add_span('4617', 'KDGREEN', 'KDGREEN', 'nse_equities', '2026-09-03', '2026-09-29', 19)
self.add_span('9999', 'TWINSA', 'TWINSA', 'nse_equities', '2026-08-12', '2026-09-25', 32)
self.add_span('9999', 'TWINSB', 'TWINSB', 'nse_equities', '2026-08-14', '2026-09-29', 34)
def add_span(self, token, instrument_id, symbol, segment, first_date, last_date, mapping_dates):
"""Adds one instrument a token has been mapped to, with its span.
Args:
token (str): Flattrade's token.
instrument_id (str): The instrument's id, a readable name in this program.
symbol (str): The instrument's symbol.
segment (str): The instrument's segment.
first_date (str): The first mapping date it was seen on.
last_date (str): The last mapping date it was seen on.
mapping_dates (int): How many mapping dates it was seen on.
Returns:
None: This method returns nothing.
"""
span = {
'instrument_id': instrument_id,
'exchange': 'nse',
'segment': segment,
'shape': 'security',
'symbol': symbol,
'first_mapping_date': datetime.date.fromisoformat(first_date),
'last_mapping_date': datetime.date.fromisoformat(last_date),
'mapping_dates': mapping_dates,
}
self.spans.setdefault(token, []).append(span)
def identity_spans(self, broker, broker_tokens, segments=None, match_on='broker_token'):
"""Answers every instrument each token has been mapped to, oldest first.
Args:
broker (str): The broker.
broker_tokens (list): The tokens to look up.
segments (tuple | None): The allowed segments, or None for any.
match_on (str): The mapping column compared, which this stand-in ignores.
Returns:
dict: Token to a list of spans; tokens with none are absent.
"""
found = {}
for token in broker_tokens:
for span in self.spans.get(token, []):
if segments is None or span['segment'] in segments:
found.setdefault(token, []).append(span)
return found
class ResolvingFlattradeSeriesExample:
"""Resolves six Flattrade series and prints each resolution.
Attributes:
resolver (SeriesResolver): The resolver being shown.
"""
def __init__(self):
"""Builds the resolver over the stand-in mapping resolver.
Returns:
None: This method returns nothing.
"""
self.resolver = SeriesResolver(StandInMappingResolver())
def describe(self, resolution):
"""Prints one resolution on one line.
Args:
resolution (Resolution): The resolution.
Returns:
None: This method returns nothing.
"""
valid_from = resolution.valid_from.isoformat() if resolution.valid_from else None
valid_to = resolution.valid_to.isoformat() if resolution.valid_to else None
print(f' -> {resolution.symbol} {resolution.status} by {resolution.resolved_by}, from {valid_from} to {valid_to}; {resolution.status_reason}')
def run(self):
"""Resolves the series, then shows `choose` refusing a consensus of one.
Returns:
None: This method returns nothing.
"""
identifiers = [
'NSE|2885|RELIANCE-EQ',
'NSE|10576|NIFTYBEES-EQ',
'NSE|17207|ITETFADD-EQ',
'NSE|4617|MANBRO-EQ',
'NSE|9999|TWINS-EQ',
'NSE|7001|INDIAGLYCO-BE',
]
resolved = self.resolver.resolve('flattrade', identifiers)
for identifier in identifiers:
print(identifier)
for resolution in resolved[identifier]:
self.describe(resolution)
spans = self.resolver.mapping_resolver.spans['9999']
votes = {
'TWINSA': 1,
}
print('choose between TWINSA and TWINSB with one broker behind TWINSA:')
for resolution in self.resolver.choose('NSE|9999|TWINS-EQ', spans, votes):
self.describe(resolution)
if __name__ == '__main__':
ResolvingFlattradeSeriesExample().run()
It prints:
NSE|2885|RELIANCE-EQ
-> RELIANCE active by broker_mapping, from None to None; None
NSE|10576|NIFTYBEES-EQ
-> NIFTYBEES active by broker_mapping, from None to None; 1 transient mapping(s) ignored
NSE|17207|ITETFADD-EQ
-> ITADD active by broker_mapping, from None to None; chosen by 4 other brokers over 0
NSE|4617|MANBRO-EQ
-> MANBRO active by broker_mapping, from None to 2026-09-03T00:00:00+05:30; split across 2 sequential instruments
-> KDGREEN active by broker_mapping, from 2026-09-03T00:00:00+05:30 to None; split across 2 sequential instruments
NSE|9999|TWINS-EQ
-> None ambiguous by broker_mapping, from None to None; token mapped to 2 instruments at once: nse_equities:TWINSA, nse_equities:TWINSB
NSE|7001|INDIAGLYCO-BE
-> INDIAGLYCO active by exchange_token, from None to None; exchange token 7001 mapped by 3 brokers
choose between TWINSA and TWINSB with one broker behind TWINSA:
-> None ambiguous by broker_mapping, from None to None; token mapped to 2 instruments at once: nse_equities:TWINSA, nse_equities:TWINSB
"""Falls back on other brokers and on the trading symbol for series whose own token was never mapped, calling each step of `SeriesResolver` by hand.
Flattrade maps a share's EQ row and not its BE row, so its BE series have no mapping of their own. `SeriesResolver.fall_back` handles such a series in two steps. First, other brokers' mappings of the same exchange token vote, and an instrument backed by at least two brokers, and by more than any other, wins; `exchange_token_votes` gathers those votes in one query, rebuilding Zerodha's Kite token from the exchange token so its vote counts too. Failing that, the trading symbol is matched against the instrument master in the allowed segments, where a classified instrument outranks an uncategorised twin, several classified matches are ambiguous, and no match at all rejects the series.
This program calls `exchange_token_votes`, `fall_back`, `symbol_matches`, `instrument` and `resolution` directly, with contexts read by Flattrade's own `series_context`, so each step's answer is visible. The database behind the resolver is a stand-in engine holding a few mappings and instrument master rows, so no data store is reached; ORICONENT's temporary BE token 10163, filed as uncategorised by one broker, is the case the module's documentation describes. Notice that one broker's vote for the uncategorised twin is not enough to decide, so the symbol decides instead.
Run it from the project root:
python examples/stock_brokers/instruments/historical/utilities/unified/resolution/SeriesResolver/example_2_falling_back_on_symbols.py
"""
import datetime
from stock_brokers.instruments.historical.flattrade import (
FlattradeCandles,
)
from stock_brokers.instruments.historical.utilities.unified.resolution import (
SeriesResolver,
)
class VoteRow:
"""A stand-in for one result row of the exchange token vote query.
Attributes:
broker (str): The broker that maps the token.
broker_token (str): Its token.
instrument_id (str): The instrument it maps the token to.
"""
def __init__(self, broker, broker_token, instrument_id):
"""Holds the row's columns.
Args:
broker (str): The broker that maps the token.
broker_token (str): Its token.
instrument_id (str): The instrument it maps the token to.
Returns:
None: This method returns nothing.
"""
self.broker = broker
self.broker_token = broker_token
self.instrument_id = instrument_id
class InstrumentRow:
"""A stand-in for one result row of an instrument master query, read through `_mapping`.
Attributes:
instrument_id (str): The instrument's id.
_mapping (dict): Every column by name, as SQLAlchemy rows offer them.
"""
def __init__(self, columns):
"""Holds the row's columns.
Args:
columns (dict): Every column by name.
Returns:
None: This method returns nothing.
"""
self.instrument_id = columns['instrument_id']
self._mapping = columns
class StandInResult:
"""A stand-in for a SQLAlchemy result.
Attributes:
rows (list): The rows.
"""
def __init__(self, rows):
"""Holds the rows.
Args:
rows (list): The rows.
Returns:
None: This method returns nothing.
"""
self.rows = rows
def all(self):
"""Every row.
Returns:
list: The rows.
"""
return self.rows
def one(self):
"""The only row.
Returns:
InstrumentRow: The row.
"""
return self.rows[0]
class StandInConnection:
"""A stand-in for a SQLAlchemy connection over the stand-in mapped tables.
Attributes:
engine (StandInEngine): The engine holding the tables.
"""
def __init__(self, engine):
"""Remembers the engine.
Args:
engine (StandInEngine): The engine holding the tables.
Returns:
None: This method returns nothing.
"""
self.engine = engine
def __enter__(self):
"""Opens the connection.
Returns:
StandInConnection: This connection.
"""
return self
def __exit__(self, exception_type, exception, traceback):
"""Closes the connection, letting any exception carry on.
Args:
exception_type (type | None): The class of an exception raised inside the block.
exception (BaseException | None): The exception raised inside the block.
traceback (types.TracebackType | None): Where it was raised.
Returns:
bool: Always False, so an exception is not swallowed.
"""
return False
def execute(self, statement, parameters):
"""Answers the exchange token vote query, the symbol query or the one-instrument query.
Args:
statement (sqlalchemy.TextClause): The SQL.
parameters (dict): The bound parameters.
Returns:
StandInResult: The rows.
"""
if 'GROUP BY' in str(statement):
return StandInResult(self.engine.votes(parameters))
if 'upper(m.symbol)' in str(statement):
return StandInResult(self.engine.symbol_rows(parameters))
instrument = self.engine.master[parameters['instrument_id']]
rows = [
InstrumentRow(instrument),
]
return StandInResult(rows)
class StandInEngine:
"""A stand-in for the SQLAlchemy engine, holding other brokers' mappings and the instrument master.
Attributes:
mappings (list): (broker, broker_token, instrument_id, segment) rows of other brokers' mappings.
master (dict): Instrument id to its row in the instrument master.
"""
def __init__(self):
"""Fills in the mappings and the master rows.
Returns:
None: This method returns nothing.
"""
self.mappings = []
self.master = {}
self.add_mapping('dhan', '7001', 'INDIAGLYCO')
self.add_mapping('kotak', '7001', 'INDIAGLYCO')
self.add_mapping('zerodha', str(7001 << 8 | 1), 'INDIAGLYCO')
self.add_mapping('stoxkart', '10163', 'ORICONENT-UNCATEGORISED')
self.add_instrument('INDIAGLYCO', 'INDIAGLYCO', 'nse_equities')
self.add_instrument('ORICONENT', 'ORICONENT', 'nse_equities')
self.add_instrument('ORICONENT-UNCATEGORISED', 'ORICONENT', 'nse_uncategorised')
self.add_instrument('DUPL', 'DUPL', 'nse_equities')
self.add_instrument('DUPL-ETF', 'DUPL', 'nse_exchange_traded_funds')
def add_instrument(self, instrument_id, symbol, segment):
"""Adds one NSE instrument to the master.
Args:
instrument_id (str): The instrument's id, a readable name in this program.
symbol (str): Its trading symbol.
segment (str): Its segment.
Returns:
None: This method returns nothing.
"""
self.master[instrument_id] = {
'instrument_id': instrument_id,
'exchange': 'nse',
'segment': segment,
'shape': 'security',
'symbol': symbol,
'first_mapping_date': datetime.date(2026, 8, 12),
'last_mapping_date': datetime.date(2026, 9, 29),
}
def symbol_rows(self, parameters):
"""Selects the master rows the symbol query asks for.
Args:
parameters (dict): The query's bound parameters.
Returns:
list: One InstrumentRow per match.
"""
rows = []
for columns in self.master.values():
if columns['exchange'] != parameters['exchange']:
continue
if columns['segment'] not in parameters['segments']:
continue
if columns['symbol'].upper() == parameters['symbol']:
rows.append(InstrumentRow(columns))
return rows
def add_mapping(self, broker, broker_token, instrument_id):
"""Adds one other broker's mapping of an NSE share.
Args:
broker (str): The broker.
broker_token (str): Its token.
instrument_id (str): The instrument it maps to.
Returns:
None: This method returns nothing.
"""
row = (
broker,
broker_token,
instrument_id,
'nse_equities',
)
self.mappings.append(row)
def votes(self, parameters):
"""Selects the mappings the vote query asks for.
Args:
parameters (dict): The query's bound parameters.
Returns:
list: One VoteRow per matching (broker, token, instrument).
"""
rows = []
for broker, broker_token, instrument_id, segment in self.mappings:
if segment not in parameters['segments']:
continue
voter = broker in parameters['voters'] and broker_token in parameters['exchange_tokens']
zerodha = broker == 'zerodha' and parameters['include_zerodha'] and broker_token in parameters['zerodha_tokens']
if voter or zerodha:
rows.append(VoteRow(broker, broker_token, instrument_id))
return rows
def connect(self):
"""Opens a stand-in connection.
Returns:
StandInConnection: The connection.
"""
return StandInConnection(self)
class StandInMappingResolver:
"""A stand-in for `MappingResolver` for series Flattrade never mapped.
Attributes:
engine (StandInEngine): The engine the series resolver queries directly.
"""
def __init__(self):
"""Builds the engine.
Returns:
None: This method returns nothing.
"""
self.engine = StandInEngine()
def identity_spans(self, broker, broker_tokens, segments=None, match_on='broker_token'):
"""Answers that none of the tokens was ever mapped.
Args:
broker (str): The broker.
broker_tokens (list): The tokens to look up.
segments (tuple | None): The allowed segments, or None for any.
match_on (str): The mapping column compared.
Returns:
dict: Always empty.
"""
return {}
class FallingBackOnSymbolsExample:
"""Resolves series without mappings of their own, one step at a time.
Attributes:
resolver (SeriesResolver): The resolver being shown.
"""
def __init__(self):
"""Builds the resolver over the stand-in mapping resolver.
Returns:
None: This method returns nothing.
"""
self.resolver = SeriesResolver(StandInMappingResolver())
def describe(self, resolution):
"""Prints one resolution on one line.
Args:
resolution (Resolution): The resolution.
Returns:
None: This method returns nothing.
"""
print(f' -> {resolution.instrument_id} {resolution.status} by {resolution.resolved_by}; {resolution.status_reason}')
def fall_back(self, identifier):
"""Gathers the votes for one series and falls back on them and its symbol.
Args:
identifier (str): Flattrade's series identifier.
Returns:
None: This method returns nothing.
"""
context = FlattradeCandles.series_context(identifier)
contexts = [
context,
]
votes = self.resolver.exchange_token_votes('flattrade', contexts)
print(f'{identifier}: votes {votes}')
for resolution in self.resolver.fall_back(identifier, context, votes.get(context.exchange_token, {})):
self.describe(resolution)
def run(self):
"""Falls back for five series, then calls the look-ups directly.
Returns:
None: This method returns nothing.
"""
self.fall_back('NSE|7001|INDIAGLYCO-BE')
self.fall_back('NSE|10163|ORICONENT-BE')
self.fall_back('NSE|20001|DUPL-BE')
self.fall_back('NSE|30001|GONE-BE')
self.fall_back('NFO|35003|NIFTY26SEPFUT')
context = FlattradeCandles.series_context('NSE|10163|ORICONENT-BE')
matches = self.resolver.symbol_matches('nse', context.segments, 'oriconent')
for match in matches:
print(f"symbol_matches ORICONENT: {match['instrument_id']} in {match['segment']}")
instrument = self.resolver.instrument('INDIAGLYCO')
print(f"instrument INDIAGLYCO: {instrument['segment']} seen {instrument['first_mapping_date']} to {instrument['last_mapping_date']}")
resolution = self.resolver.resolution('NSE|7001|INDIAGLYCO-BE', instrument, 'manual', reason='set by hand')
print(f'resolution built by hand: {resolution.instrument_id} {resolution.status} by {resolution.resolved_by}; {resolution.status_reason}')
if __name__ == '__main__':
FallingBackOnSymbolsExample().run()
It prints:
NSE|7001|INDIAGLYCO-BE: votes {'7001': {'INDIAGLYCO': 3}}
-> INDIAGLYCO active by exchange_token; exchange token 7001 mapped by 3 brokers
NSE|10163|ORICONENT-BE: votes {'10163': {'ORICONENT-UNCATEGORISED': 1}}
-> ORICONENT active by raw_symbol; symbol ORICONENT
NSE|20001|DUPL-BE: votes {}
-> None ambiguous by raw_symbol; symbol DUPL matches 2 instruments
NSE|30001|GONE-BE: votes {}
-> None rejected by None; no mapping, exchange token or symbol match
NFO|35003|NIFTY26SEPFUT: votes {}
-> None rejected by None; no mapping, exchange token or symbol match
symbol_matches ORICONENT: ORICONENT in nse_equities
symbol_matches ORICONENT: ORICONENT-UNCATEGORISED in nse_uncategorised
instrument INDIAGLYCO: nse_equities seen 2026-08-12 to 2026-09-29
resolution built by hand: INDIAGLYCO active by manual; set by hand
Methods:
| Name | Description |
|---|---|
resolve |
Resolve a broker's series. |
choose |
Pick between the instruments a series' own token has been mapped to. |
fall_back |
Resolve a series whose own token was never mapped. |
exchange_token_votes |
Count, for each exchange token, how many other brokers map it to each instrument. |
instrument |
One instrument's identity and its span across every broker's mappings. |
symbol_matches |
Every instrument with this exact symbol in the allowed segments, on any date. |
resolution |
An active Resolution for a chosen candidate. |
Source code in stock_brokers/instruments/historical/utilities/unified/resolution.py
resolve
¶
Resolve a broker's series.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
broker
|
str
|
The broker name, for example "flattrade". |
required |
identifiers
|
list[str]
|
Values of |
required |
Returns:
| Type | Description |
|---|---|
dict
|
Mapping of identifier to a list of Resolution, never empty. |
Example usage
resolved = self.resolver.resolve('flattrade', identifiers)
Source code in stock_brokers/instruments/historical/utilities/unified/resolution.py
choose
¶
Pick between the instruments a series' own token has been mapped to.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
identifier
|
str
|
The series identifier. |
required |
candidates
|
list[dict]
|
The token's spans, as |
required |
votes
|
dict
|
Other brokers' support per instrument_id for the exchange token. |
required |
Returns:
| Type | Description |
|---|---|
list[Resolution]
|
One Resolution, or several for a sequential split. |
Example usage
Source code in stock_brokers/instruments/historical/utilities/unified/resolution.py
fall_back
¶
Resolve a series whose own token was never mapped.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
identifier
|
str
|
The series identifier. |
required |
context
|
SeriesContext
|
What the identifier says about itself. |
required |
votes
|
dict
|
Other brokers' support per instrument_id for the exchange token. |
required |
Returns:
| Type | Description |
|---|---|
list[Resolution]
|
One Resolution, unresolved if nothing matched. |
Example usage
self.resolver.fall_back(identifier, context, votes.get(context.exchange_token, {}))
Source code in stock_brokers/instruments/historical/utilities/unified/resolution.py
exchange_token_votes
¶
Count, for each exchange token, how many other brokers map it to each instrument.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
broker
|
str
|
The broker whose series are being resolved, which does not vote. |
required |
contexts
|
list[SeriesContext]
|
Series sharing one set of segments. |
required |
Returns:
| Type | Description |
|---|---|
dict
|
exchange_token to {instrument_id: number of distinct brokers}. |
Example usage
votes = self.resolver.exchange_token_votes('flattrade', contexts)
Source code in stock_brokers/instruments/historical/utilities/unified/resolution.py
instrument
¶
One instrument's identity and its span across every broker's mappings.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
instrument_id
|
str
|
The unified id. |
required |
Returns:
| Type | Description |
|---|---|
dict
|
The keys |
Example usage
Source code in stock_brokers/instruments/historical/utilities/unified/resolution.py
symbol_matches
¶
Every instrument with this exact symbol in the allowed segments, on any date.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
exchange
|
str
|
The canonical exchange. |
required |
segments
|
tuple[str]
|
The allowed prefixed segments. |
required |
symbol
|
str
|
The trading symbol without its series suffix. |
required |
Returns:
| Type | Description |
|---|---|
list[dict]
|
The keys |
Example usage
matches = self.resolver.symbol_matches('nse', context.segments, 'oriconent')
Source code in stock_brokers/instruments/historical/utilities/unified/resolution.py
resolution
staticmethod
¶
An active Resolution for a chosen candidate.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
identifier
|
str
|
The series identifier. |
required |
candidate
|
dict
|
The chosen instrument, as |
required |
resolved_by
|
str
|
How it was found. |
required |
reason
|
str | None
|
Why, when the choice was not the only candidate. |
None
|
valid_from
|
datetime | None
|
The start of the part of the series it owns. |
None
|
valid_to
|
datetime | None
|
The end of that part, exclusive. |
None
|
Returns:
| Type | Description |
|---|---|
Resolution
|
The resolution. |
Example usage
resolution = self.resolver.resolution('NSE|7001|INDIAGLYCO-BE', instrument, 'manual', reason='set by hand')
Source code in stock_brokers/instruments/historical/utilities/unified/resolution.py
india_midnight
¶
Midnight India time at the start of a date.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
day
|
date
|
The date. |
required |
Returns:
| Type | Description |
|---|---|
datetime
|
A timezone aware timestamp. |
Source code in stock_brokers/instruments/historical/utilities/unified/resolution.py
family_of
¶
The family a SeriesContext's segments describe, for rebuilding Kite tokens.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
segments
|
tuple[str] | None
|
Prefixed segments. |
required |
Returns:
| Type | Description |
|---|---|
str | None
|
"cash" or "derivative", or None when the segments are neither. |