loader
¶
Copy the brokers' stored bars into unified.price_history.
One run, for one interval, goes:
- Discover. Every series with bars in each source broker's
price_history_progress. - Resolve. Each series to its instrument, through
resolution, and each (series, instrument) to a row in unified.price_history_sources, kept when the source policy allows that broker for that instrument's segment. A row whose series now resolves elsewhere is marked superseded. - Choose work. An instrument is rebuilt when one of its sources is new, superseded, or has bars its source row has not seen: the broker's earliest bar moved earlier, or its latest later.
- Stitch. For each instrument, the primary broker's series are laid over each other by time. Where two have the same bar, the series with the most recent bars wins, since that is the token the instrument trades under now. INDIAGLYCO's EQ series owns its days up to 2026-09-01 and its BE series from 2026-09-02; ORICONENT's BE series fills the six weeks of 2026 when EQ had no bars.
- Gap-fill. A gap-fill broker supplies only the trading days the primary has no bar for, and only when its bars agree with the primary's on the days both have.
- Filter. Bars on days the exchange did not trade, off the interval's grid, or with a high below the open or close or a low above them, are dropped and counted.
- Write. Upserted, with a row only rewritten when a value actually differs so that a re-run does not decompress chunks for nothing; bars no source provides any more are deleted; and the source rows' watermarks moved - all in one transaction per instrument.
An instrument is rebuilt over its whole history for daily bars, which is at most a few thousand rows. Nothing is ever averaged between brokers.
Classes:
| Name | Description |
|---|---|
UnifiedLoader |
Loads one interval of bars into unified.price_history. |
IntradayLoader |
Loads intraday bars, set-based in SQL. |
Functions:
| Name | Description |
|---|---|
corrected_bar |
A bar with a correction applied. |
UnifiedLoader
¶
Loads one interval of bars into unified.price_history.
Attributes:
| Name | Type | Description |
|---|---|---|
interval |
str
|
The stored interval name being loaded. |
connection |
The psycopg2 connection everything is read and written through. |
|
resolver |
SeriesResolver
|
Finds each series' instrument. |
calendar |
TradingCalendar
|
Trading days per exchange. |
counts |
Counter
|
What the run did, for its summary. |
Prepare a load.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
interval
|
str
|
The stored interval name, for example "day". |
required |
instrument_ids
|
set[str] | None
|
Restrict the load to these instruments, or None for all. |
None
|
resolver
|
SeriesResolver | None
|
A resolver to share, or None to build one. |
None
|
calendar
|
TradingCalendar | None
|
A calendar to share, or None to build one. |
None
|
Returns:
| Type | Description |
|---|---|
None
|
This function returns nothing. |
Example programs
"""Loads INDIAGLYCO's daily bars into the unified price history from Flattrade's EQ and BE series, filtering the bars that cannot be right.
`UnifiedLoader.run` does one interval's whole load: it finds every series with bars in each source broker's progress table, resolves each series to its instrument, records the pairing in `unified.price_history_sources`, and rebuilds every instrument one of whose sources is new or has grown. A rebuild lays the primary broker's series over each other with the most recent series on top: INDIAGLYCO traded on its EQ token until 2026-09-01 and on its BE token from that day, so both have a bar on 2026-09-01 and the BE series wins it. Bars on a day the exchange did not trade, off the day grid, or with a high below the open or close are dropped and counted, a bar already stored that no source provides any more is deleted, and each source row's watermarks are moved, all in one transaction.
The loader opens PostgreSQL through `get_postgres` in its constructor, and the example runner points PostgreSQL at a closed port, so the program replaces `get_postgres` in the loader module with a stand-in returning an in-memory connection. It answers the loader's queries with recorded-looking rows for two weeks of Flattrade bars and records every row written. The resolver and the trading calendar are passed in, as the constructor allows: a small stand-in resolver that knows both series, and a real `TradingCalendar` whose days are filled in by hand so it reads nothing. What to notice: the Saturday bar, the bar stamped at 09:15 and the bar with an impossible range are each dropped; the BE series' close of 2026-09-01 is the one kept; and the bar of 2026-08-21, which no source provides, is deleted.
Run it from the project root:
python examples/stock_brokers/instruments/historical/utilities/unified/loader/UnifiedLoader/example_1_loading_daily_bars.py
"""
import datetime
import decimal
import zoneinfo
from stock_brokers.instruments.historical.utilities.unified import loader
from stock_brokers.instruments.historical.utilities.unified.calendar import (
TradingCalendar,
)
from stock_brokers.instruments.historical.utilities.unified.loader import (
UnifiedLoader,
)
from stock_brokers.instruments.historical.utilities.unified.resolution import (
Resolution,
)
INDIA = zoneinfo.ZoneInfo('Asia/Kolkata')
EQ_SERIES = 'NSE|7000|INDIAGLYCO-EQ'
BE_SERIES = 'NSE|7001|INDIAGLYCO-BE'
class StandInSeriesResolver:
"""A stand-in for `SeriesResolver` that resolves INDIAGLYCO's two Flattrade series."""
def resolve(self, broker, identifiers):
"""Resolves each identifier to INDIAGLYCO on NSE.
Args:
broker (str): The broker.
identifiers (list): The broker's series identifiers.
Returns:
dict: Identifier to a list holding one Resolution.
"""
resolved = {}
for identifier in identifiers:
resolution = Resolution(
identifier,
'INDIAGLYCO-NSE',
'nse',
'nse_equities',
'INDIAGLYCO',
'broker_mapping',
'active',
None,
datetime.date(2026, 8, 12),
datetime.date(2026, 9, 29),
None,
None,
)
resolved[identifier] = [
resolution,
]
return resolved
class StandInCursor:
"""A stand-in for a psycopg2 cursor that hands every statement to its connection.
Attributes:
connection (StandInConnection): The connection that answers the statements.
result (list): The rows the last statement produced.
rowcount (int): How many rows the last statement changed.
"""
def __init__(self, connection):
"""Builds a cursor over the stand-in connection.
Args:
connection (StandInConnection): The connection that answers the statements.
Returns:
None: This method returns nothing.
"""
self.connection = connection
self.result = []
self.rowcount = 0
def __enter__(self):
"""Opens the cursor as a context manager.
Returns:
StandInCursor: This cursor.
"""
return self
def __exit__(self, exception_type, exception, traceback):
"""Closes the cursor, 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 mogrify(self, template, arguments):
"""Stages one row of a multi-row insert, as `execute_values` asks a cursor to.
Args:
template (bytes): The row template.
arguments (tuple): The row's values.
Returns:
bytes: A placeholder for the rendered row.
"""
self.connection.staged_rows.append(arguments)
return b'(...)'
def execute(self, statement, parameters=None):
"""Runs one statement against the stand-in connection.
Args:
statement (str | bytes): The SQL text.
parameters (tuple | list | None): The statement's parameters.
Returns:
None: This method returns nothing.
"""
if isinstance(statement, bytes):
statement = statement.decode()
self.result, self.rowcount = self.connection.answer(statement, parameters)
def fetchone(self):
"""The first row the last statement produced.
Returns:
tuple | None: The row, or None when there was none.
"""
if not self.result:
return None
return self.result[0]
def fetchall(self):
"""Every row the last statement produced.
Returns:
list: The rows.
"""
return self.result
class StandInConnection:
"""A stand-in for a psycopg2 connection over Flattrade's INDIAGLYCO bars.
Attributes:
encoding (str): The client encoding, which `execute_values` reads.
bars (dict): Series identifier to its (time, open, high, low, close, volume, oi) rows.
staged_rows (list): Rows staged by `mogrify` for the next insert.
written (list): Every row inserted into the unified price history.
deleted (list): The bar times deleted from it.
source_updates (list): (source id, owned from, owned to) for each source row updated.
next_source_id (int): The id the next new source row gets.
"""
encoding = 'UTF8'
def __init__(self):
"""Fills in the two series' bars.
Returns:
None: This method returns nothing.
"""
self.bars = {
EQ_SERIES: [],
BE_SERIES: [],
}
self.staged_rows = []
self.written = []
self.deleted = []
self.source_updates = []
self.next_source_id = 11
self.add_bar(EQ_SERIES, datetime.datetime(2026, 8, 27, tzinfo=INDIA), '480.00')
self.add_bar(EQ_SERIES, datetime.datetime(2026, 8, 28, tzinfo=INDIA), '482.50')
self.add_bar(EQ_SERIES, datetime.datetime(2026, 8, 29, tzinfo=INDIA), '482.50')
self.add_bar(EQ_SERIES, datetime.datetime(2026, 8, 31, tzinfo=INDIA), '485.00')
self.add_bar(EQ_SERIES, datetime.datetime(2026, 9, 1, tzinfo=INDIA), '484.00')
self.add_bar(BE_SERIES, datetime.datetime(2026, 9, 1, tzinfo=INDIA), '484.20')
self.add_bar(BE_SERIES, datetime.datetime(2026, 9, 2, tzinfo=INDIA), '242.50')
self.add_bar(BE_SERIES, datetime.datetime(2026, 9, 3, 9, 15, tzinfo=INDIA), '243.00')
self.add_bar(BE_SERIES, datetime.datetime(2026, 9, 3, tzinfo=INDIA), '244.00')
self.add_bar(BE_SERIES, datetime.datetime(2026, 9, 4, tzinfo=INDIA), '0')
def add_bar(self, series, moment, close_text):
"""Adds one daily bar whose open, high and low sit around its close.
A close of 0 stands for a bar whose high was served below its open.
Args:
series (str): The series identifier.
moment (datetime.datetime): The bar time.
close_text (str): The close, as text so it becomes an exact Decimal.
Returns:
None: This method returns nothing.
"""
close = decimal.Decimal(close_text)
if close == 0:
row = self.row(moment, decimal.Decimal('245.00'), decimal.Decimal('244.00'), decimal.Decimal('243.00'), decimal.Decimal('244.50'), 90000, None)
else:
row = self.row(moment, close - 1, close + 2, close - 3, close, 120000, None)
self.bars[series].append(row)
def row(self, *columns):
"""Builds one result row.
Args:
*columns (object): The row's columns, in order.
Returns:
tuple: The row.
"""
return columns
def cursor(self):
"""Opens a stand-in cursor.
Returns:
StandInCursor: The cursor.
"""
return StandInCursor(self)
def answer(self, statement, parameters):
"""Answers one of the loader's statements.
Args:
statement (str): The SQL text.
parameters (tuple | list | None): The statement's parameters.
Returns:
tuple: (rows, rows changed).
"""
if 'from unified.price_history_sources where' in statement:
return [], 0
if 'price_history_progress' in statement:
if 'flattrade.' not in statement:
return [], 0
return [
self.row(EQ_SERIES, self.bars[EQ_SERIES][0][0], self.bars[EQ_SERIES][-1][0]),
self.row(BE_SERIES, self.bars[BE_SERIES][0][0], self.bars[BE_SERIES][-1][0]),
], 0
if 'insert into unified.price_history_sources' in statement:
source_id = self.next_source_id
self.next_source_id += 1
return [
self.row(source_id),
], 1
if 'from flattrade.price_history' in statement:
return self.bars[parameters[0]], 0
if 'from unified.correction_ranges' in statement:
return [], 0
if 'select "time" from unified.price_history' in statement:
return [
self.row(datetime.datetime(2026, 8, 21, tzinfo=INDIA)),
self.row(datetime.datetime(2026, 8, 27, tzinfo=INDIA)),
], 0
if 'delete from unified.price_history' in statement:
self.deleted.extend(parameters[2])
return [], len(parameters[2])
if 'insert into unified.price_history' in statement:
self.written.extend(self.staged_rows)
changed = len(self.staged_rows)
self.staged_rows = []
return [], changed
if 'update unified.price_history_sources set' in statement:
self.source_updates.append(self.row(parameters[6], parameters[2], parameters[3]))
return [], 1
raise ValueError(f'The stand-in does not know this statement: {statement[:60]}')
def commit(self):
"""Accepts a commit.
Returns:
None: This method returns nothing.
"""
def rollback(self):
"""Accepts a rollback.
Returns:
None: This method returns nothing.
"""
class StandInPostgres:
"""Hands out the stand-in connection in the place of `get_postgres`.
Attributes:
connection (StandInConnection): The connection handed out.
"""
def __init__(self, connection):
"""Remembers the connection to hand out.
Args:
connection (StandInConnection): The connection handed out.
Returns:
None: This method returns nothing.
"""
self.connection = connection
def get_postgres(self):
"""Returns the stand-in connection.
Returns:
StandInConnection: The connection.
"""
return self.connection
class LoadingDailyBarsExample:
"""Runs a daily load for INDIAGLYCO and prints what was written, deleted and counted.
Attributes:
connection (StandInConnection): The in-memory connection.
loader (UnifiedLoader): The loader being shown.
"""
def __init__(self):
"""Installs the stand-in connection and builds the loader with a hand-filled calendar.
Returns:
None: This method returns nothing.
"""
self.connection = StandInConnection()
loader.get_postgres = StandInPostgres(self.connection).get_postgres
calendar = TradingCalendar()
calendar.days['nse'] = set()
day = datetime.date(2026, 8, 17)
while day <= datetime.date(2026, 9, 11):
if day.weekday() < 5:
calendar.days['nse'].add(day)
day += datetime.timedelta(days=1)
self.loader = UnifiedLoader('day', resolver=StandInSeriesResolver(), calendar=calendar)
def run(self):
"""Runs the load and prints the outcome.
Returns:
None: This method returns nothing.
"""
counts = self.loader.run()
for name in sorted(counts):
print(f'{name}: {counts[name]}')
print('Rows written (time, close, source id):')
for row in self.connection.written:
print(f' {row[0].date()} {row[6]} {row[9]}')
deleted = []
for moment in self.connection.deleted:
deleted.append(moment.date().isoformat())
print(f'Deleted: {deleted}')
for source_id, owned_from, owned_to in self.connection.source_updates:
print(f'Source {source_id} owns {owned_from.date()} to {owned_to.date()}')
if __name__ == '__main__':
LoadingDailyBarsExample().run()
It prints:
bars_deleted: 1
bars_kept: 6
bars_written: 6
filtered_bad_range: 1
filtered_non_trading_day: 1
filtered_off_grid: 1
instruments_rebuilt: 1
series_dhan: 0
series_flattrade: 2
series_zerodha: 0
sources_superseded: 0
Rows written (time, close, source id):
2026-08-27 480.00 11
2026-08-28 482.50 11
2026-08-31 485.00 11
2026-09-01 484.20 12
2026-09-02 242.50 12
2026-09-03 244.00 12
Deleted: ['2026-08-21']
Source 11 owns 2026-08-27 to 2026-08-31
Source 12 owns 2026-09-01 to 2026-09-03
"""Calls each step of `UnifiedLoader` by hand: refreshing the sources table, reading a corrected series, checking bars, and gap-filling an index from a second broker.
`run` strings the loader's steps together; this program calls them one at a time so each answer is visible.
- `refresh_sources` resolves every series in the source brokers' progress tables. A series that now resolves to a different instrument than its active source row says gets a new row, the old row is marked superseded, and the instrument it no longer feeds is returned as a placeholder so its bars are rebuilt; a series that resolves to nothing is kept in `unresolved`. `upsert_source` writes one source row and returns its id.
- `read_series` reads one source's bars and applies any confirmed correction: PIDILITIND's NSE bars before 2025-09-23 were served at half their price, and come back doubled, rounded to the paisa, with the volume untouched.
- `valid` passes or drops single bars, counting why.
- `stitch` lays one broker's series over each other, and `agreement` measures how often two brokers' closes agree on the days both have.
- `rebuild` builds NIFTY 50's bars: Zerodha is the primary and lacks 2026-09-10; Dhan agrees with Zerodha on all 22 shared days and fills that day; Flattrade's closes are 1% away and it is refused as a gap-fill broker.
The loader opens PostgreSQL through `get_postgres` in its constructor, which the example runner points at a closed port, so the program replaces `get_postgres` in the loader module with a stand-in returning an in-memory connection that answers the loader's queries with canned rows and records what is written. The resolver is a small stand-in passed to the constructor, and the calendar is a real `TradingCalendar` whose days are filled in by hand.
Run it from the project root:
python examples/stock_brokers/instruments/historical/utilities/unified/loader/UnifiedLoader/example_2_each_step_by_hand.py
"""
import datetime
import decimal
import zoneinfo
from stock_brokers.instruments.historical.utilities.unified import loader
from stock_brokers.instruments.historical.utilities.unified.calendar import (
TradingCalendar,
)
from stock_brokers.instruments.historical.utilities.unified.loader import (
Bar,
Source,
UnifiedLoader,
)
from stock_brokers.instruments.historical.utilities.unified.resolution import (
Resolution,
)
INDIA = zoneinfo.ZoneInfo('Asia/Kolkata')
MISSING_DAY = datetime.date(2026, 9, 10)
CORRECTION_DATE = datetime.datetime(2025, 9, 23, tzinfo=INDIA)
class StandInSeriesResolver:
"""A stand-in for `SeriesResolver` that resolves a renamed series to its new instrument and cannot place another."""
def resolve(self, broker, identifiers):
"""Resolves each identifier, or reports it rejected.
Args:
broker (str): The broker.
identifiers (list): The broker's series identifiers.
Returns:
dict: Identifier to a list holding one Resolution.
"""
resolved = {}
for identifier in identifiers:
if identifier == 'NSE|9999|NEWCO-EQ':
resolution = Resolution(
identifier,
'NEWCO-NSE',
'nse',
'nse_equities',
'NEWCO',
'broker_mapping',
'active',
None,
datetime.date(2026, 9, 3),
datetime.date(2026, 9, 29),
None,
None,
)
else:
resolution = Resolution(
identifier,
None,
None,
None,
None,
None,
'rejected',
'no mapping, exchange token or symbol match',
None,
None,
None,
None,
)
resolved[identifier] = [
resolution,
]
return resolved
class StandInCursor:
"""A stand-in for a psycopg2 cursor that hands every statement to its connection.
Attributes:
connection (StandInConnection): The connection that answers the statements.
result (list): The rows the last statement produced.
rowcount (int): How many rows the last statement changed.
"""
def __init__(self, connection):
"""Builds a cursor over the stand-in connection.
Args:
connection (StandInConnection): The connection that answers the statements.
Returns:
None: This method returns nothing.
"""
self.connection = connection
self.result = []
self.rowcount = 0
def __enter__(self):
"""Opens the cursor as a context manager.
Returns:
StandInCursor: This cursor.
"""
return self
def __exit__(self, exception_type, exception, traceback):
"""Closes the cursor, 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 mogrify(self, template, arguments):
"""Stages one row of a multi-row insert, as `execute_values` asks a cursor to.
Args:
template (bytes): The row template.
arguments (tuple): The row's values.
Returns:
bytes: A placeholder for the rendered row.
"""
self.connection.staged_rows.append(arguments)
return b'(...)'
def execute(self, statement, parameters=None):
"""Runs one statement against the stand-in connection.
Args:
statement (str | bytes): The SQL text.
parameters (tuple | list | None): The statement's parameters.
Returns:
None: This method returns nothing.
"""
if isinstance(statement, bytes):
statement = statement.decode()
self.result, self.rowcount = self.connection.answer(statement, parameters)
def fetchone(self):
"""The first row the last statement produced.
Returns:
tuple | None: The row, or None when there was none.
"""
if not self.result:
return None
return self.result[0]
def fetchall(self):
"""Every row the last statement produced.
Returns:
list: The rows.
"""
return self.result
class StandInConnection:
"""A stand-in for a psycopg2 connection over a few brokers' stored bars.
Attributes:
encoding (str): The client encoding, which `execute_values` reads.
bars (dict): (broker, series identifier) to its (time, open, high, low, close, volume, oi) rows.
staged_rows (list): Rows staged by `mogrify` for the next insert.
written (list): Every row inserted into the unified price history.
superseded (list): The source ids marked superseded.
"""
encoding = 'UTF8'
def __init__(self):
"""Fills in NIFTY 50's bars at three brokers and PIDILITIND's at Flattrade.
Returns:
None: This method returns nothing.
"""
self.bars = {
('zerodha', '256265'): [],
('dhan', '13|IDX_I|NIFTY'): [],
('flattrade', 'NSE|26000|Nifty 50'): [],
('flattrade', 'NSE|10575|PIDILITIND-EQ'): [],
}
self.staged_rows = []
self.written = []
self.superseded = []
day = datetime.date(2026, 8, 31)
position = 0
while day <= datetime.date(2026, 9, 30):
if day.weekday() < 5:
moment = datetime.datetime(day.year, day.month, day.day, tzinfo=INDIA)
close = decimal.Decimal(25000 + position * 10)
if day != MISSING_DAY:
self.bars[('zerodha', '256265')].append(self.bar(moment, close))
self.bars[('dhan', '13|IDX_I|NIFTY')].append(self.bar(moment, close))
self.bars[('flattrade', 'NSE|26000|Nifty 50')].append(self.bar(moment, close * decimal.Decimal('1.01')))
position += 1
day += datetime.timedelta(days=1)
self.bars[('flattrade', 'NSE|10575|PIDILITIND-EQ')].append(self.bar(datetime.datetime(2025, 9, 22, tzinfo=INDIA), decimal.Decimal('1519.05')))
self.bars[('flattrade', 'NSE|10575|PIDILITIND-EQ')].append(self.bar(CORRECTION_DATE, decimal.Decimal('1523.00')))
def bar(self, moment, close):
"""Builds one daily bar whose open, high and low sit around its close.
Args:
moment (datetime.datetime): The bar time.
close (decimal.Decimal): The close.
Returns:
tuple: The bar's columns.
"""
return self.row(moment, close - 5, close + 10, close - 12, close, 250000, None)
def row(self, *columns):
"""Builds one result row.
Args:
*columns (object): The row's columns, in order.
Returns:
tuple: The row.
"""
return columns
def cursor(self):
"""Opens a stand-in cursor.
Returns:
StandInCursor: The cursor.
"""
return StandInCursor(self)
def answer(self, statement, parameters):
"""Answers one of the loader's statements.
Args:
statement (str): The SQL text.
parameters (tuple | list | None): The statement's parameters.
Returns:
tuple: (rows, rows changed).
"""
if 'from unified.price_history_sources where' in statement:
return [
self.row(7, 'flattrade', 'NSE|9999|NEWCO-EQ', 'OLDCO-NSE', 'active', None, None, None, None),
], 0
if 'price_history_progress' in statement:
if 'flattrade.' not in statement:
return [], 0
return [
self.row('NSE|9999|NEWCO-EQ', datetime.datetime(2026, 9, 3, tzinfo=INDIA), datetime.datetime(2026, 9, 29, tzinfo=INDIA)),
self.row('NSE|8888|MYSTERY-EQ', datetime.datetime(2026, 9, 3, tzinfo=INDIA), datetime.datetime(2026, 9, 29, tzinfo=INDIA)),
], 0
if 'insert into unified.price_history_sources' in statement:
return [
self.row(31),
], 1
if "set status = 'superseded'" in statement:
self.superseded.append(parameters[0])
return [], 1
brokers = [
'zerodha',
'dhan',
'flattrade',
]
for broker in brokers:
if f'from {broker}.price_history' in statement:
return self.bars[(broker, parameters[0])], 0
if 'from unified.correction_ranges' in statement:
if parameters[1] != 'NSE|10575|PIDILITIND-EQ':
return [], 0
return [
self.row(datetime.datetime(2020, 1, 1, tzinfo=INDIA), CORRECTION_DATE, decimal.Decimal('2'), decimal.Decimal('1')),
], 0
if 'select "time" from unified.price_history' in statement:
return [], 0
if 'insert into unified.price_history' in statement:
self.written.extend(self.staged_rows)
changed = len(self.staged_rows)
self.staged_rows = []
return [], changed
if 'update unified.price_history_sources set' in statement:
return [], 1
raise ValueError(f'The stand-in does not know this statement: {statement[:60]}')
def commit(self):
"""Accepts a commit.
Returns:
None: This method returns nothing.
"""
def rollback(self):
"""Accepts a rollback.
Returns:
None: This method returns nothing.
"""
class StandInPostgres:
"""Hands out the stand-in connection in the place of `get_postgres`.
Attributes:
connection (StandInConnection): The connection handed out.
"""
def __init__(self, connection):
"""Remembers the connection to hand out.
Args:
connection (StandInConnection): The connection handed out.
Returns:
None: This method returns nothing.
"""
self.connection = connection
def get_postgres(self):
"""Returns the stand-in connection.
Returns:
StandInConnection: The connection.
"""
return self.connection
class EachStepByHandExample:
"""Calls the loader's steps one at a time and prints each answer.
Attributes:
connection (StandInConnection): The in-memory connection.
loader (UnifiedLoader): The loader being shown.
"""
def __init__(self):
"""Installs the stand-in connection and builds the loader with a hand-filled calendar.
Returns:
None: This method returns nothing.
"""
self.connection = StandInConnection()
loader.get_postgres = StandInPostgres(self.connection).get_postgres
calendar = TradingCalendar()
calendar.days['nse'] = set()
day = datetime.date(2025, 9, 1)
while day <= datetime.date(2026, 9, 30):
if day.weekday() < 5:
calendar.days['nse'].add(day)
day += datetime.timedelta(days=1)
self.loader = UnifiedLoader('day', resolver=StandInSeriesResolver(), calendar=calendar)
def source(self, source_id, broker, series, role):
"""Builds one of NIFTY 50's source rows.
Args:
source_id (int): The source row's id.
broker (str): The broker.
series (str): The broker's series identifier.
role (str): "primary" or "gap_fill".
Returns:
Source: The source.
"""
return Source(
source_id,
broker,
series,
'NIFTY50-NSE',
role,
'nse',
None,
None,
datetime.datetime(2026, 8, 31, tzinfo=INDIA),
datetime.datetime(2026, 9, 30, tzinfo=INDIA),
True,
)
def check(self, label, bar):
"""Prints whether one bar passes the loader's checks.
Args:
label (str): What is odd about the bar.
bar (Bar): The bar.
Returns:
None: This method returns nothing.
"""
print(f'valid, {label}: {self.loader.valid(bar, "nse")}')
def run(self):
"""Walks through each step and prints what it did.
Returns:
None: This method returns nothing.
"""
sources = self.loader.refresh_sources()
for source in sources:
print(f'refresh_sources: {source.instrument_id} from {source.broker} {source.broker_series}, source {source.source_id}, changed {source.changed}')
print(f'Superseded source rows: {self.connection.superseded}; unresolved: {self.loader.unresolved}')
identifiers = [
'NSE|9999|NEWCO-EQ',
]
resolution = StandInSeriesResolver().resolve('flattrade', identifiers)['NSE|9999|NEWCO-EQ'][0]
print(f"upsert_source returns id {self.loader.upsert_source('flattrade', 'NSE|9999|NEWCO-EQ', resolution, 'primary')}")
corrected = Source(41, 'flattrade', 'NSE|10575|PIDILITIND-EQ', 'PIDILITIND-NSE', 'primary', 'nse', None, None, None, None, True)
for bar in self.loader.read_series(corrected):
print(f'read_series PIDILITIND {bar.time.date()}: close {bar.close}, high {bar.high}, volume {bar.volume}')
moment = datetime.datetime(2026, 9, 15, tzinfo=INDIA)
good = Bar(moment, decimal.Decimal('100'), decimal.Decimal('104'), decimal.Decimal('99'), decimal.Decimal('102'), 10, None)
self.check('an ordinary bar', good)
self.check('a zero close', good._replace(close=decimal.Decimal('0')))
self.check('on a Sunday', good._replace(time=datetime.datetime(2026, 9, 13, tzinfo=INDIA)))
self.check('stamped 09:15', good._replace(time=datetime.datetime(2026, 9, 15, 9, 15, tzinfo=INDIA)))
self.check('high below the close', good._replace(high=decimal.Decimal('101')))
zerodha = self.source(21, 'zerodha', '256265', 'primary')
dhan = self.source(22, 'dhan', '13|IDX_I|NIFTY', 'gap_fill')
flattrade = self.source(23, 'flattrade', 'NSE|26000|Nifty 50', 'gap_fill')
primary_bars = self.loader.stitch([zerodha])
print(f'stitch Zerodha: {len(primary_bars)} bars')
print(f'agreement with Dhan: {self.loader.agreement(primary_bars, self.loader.stitch([dhan]))}')
print(f'agreement with Flattrade: {self.loader.agreement(primary_bars, self.loader.stitch([flattrade]))}')
members = [
zerodha,
dhan,
flattrade,
]
self.loader.rebuild('NIFTY50-NSE', members)
filled = []
for row in self.connection.written:
if row[9] != 21:
filled.append(f'{row[0].date()} from source {row[9]}')
print(f'rebuild wrote {len(self.connection.written)} bars; filled from another broker: {filled}')
for name in sorted(self.loader.counts):
print(f'{name}: {self.loader.counts[name]}')
if __name__ == '__main__':
EachStepByHandExample().run()
It prints:
refresh_sources: NEWCO-NSE from flattrade NSE|9999|NEWCO-EQ, source 31, changed True
refresh_sources: OLDCO-NSE from None None, source None, changed True
Superseded source rows: [7]; unresolved: [('flattrade', 'NSE|8888|MYSTERY-EQ', 'rejected', 'no mapping, exchange token or symbol match')]
upsert_source returns id 31
read_series PIDILITIND 2025-09-22: close 3038.10, high 3058.10, volume 250000
read_series PIDILITIND 2025-09-23: close 1523.00, high 1533.00, volume 250000
valid, an ordinary bar: True
valid, a zero close: False
valid, on a Sunday: False
valid, stamped 09:15: False
valid, high below the close: False
stitch Zerodha: 22 bars
agreement with Dhan: (1.0, 22)
agreement with Flattrade: (0.0, 22)
rebuild wrote 23 bars; filled from another broker: ['2026-09-10 from source 22']
bars_corrected: 1
bars_deleted: 0
bars_gap_filled: 1
bars_kept: 23
bars_written: 23
filtered_bad_range: 1
filtered_missing_price: 1
filtered_non_trading_day: 1
filtered_off_grid: 1
gap_fill_refused: 1
series_dhan: 0
series_flattrade: 2
series_zerodha: 0
sources_superseded: 1
unresolved_flattrade: 1
Methods:
| Name | Description |
|---|---|
run |
Discover, resolve and load. |
refresh_sources |
Resolve every series of every source broker and bring the sources table up to date. |
upsert_source |
Insert or refresh one source row, returning its id. |
read_series |
One source's bars, restricted to the part of the series that belongs to its instrument, with |
stitch |
Lay one broker's series for an instrument over each other, the most recent series on top. |
valid |
Whether a bar passes the calendar, grid and range checks, counting why when it does not. |
agreement |
The share of shared bar times on which two brokers' closes agree. |
rebuild |
Rebuild one instrument's bars from its sources, in one transaction. |
Source code in stock_brokers/instruments/historical/utilities/unified/loader.py
run
¶
Discover, resolve and load.
Returns:
| Type | Description |
|---|---|
Counter
|
What was done - series seen, instruments rebuilt, bars written, deleted and filtered. |
Example usage
Source code in stock_brokers/instruments/historical/utilities/unified/loader.py
refresh_sources
¶
Resolve every series of every source broker and bring the sources table up to date.
Returns:
| Type | Description |
|---|---|
list[Source]
|
The active sources, restricted to |
Example usage
Source code in stock_brokers/instruments/historical/utilities/unified/loader.py
137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 | |
upsert_source
¶
Insert or refresh one source row, returning its id.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
broker
|
str
|
The broker. |
required |
identifier
|
str
|
The broker's series identifier. |
required |
resolution
|
Resolution
|
The instrument it resolved to. |
required |
role
|
str
|
"primary" or "gap_fill". |
required |
Returns:
| Type | Description |
|---|---|
int
|
The source_id. |
Example usage
print(f"upsert_source returns id {self.loader.upsert_source('flattrade', 'NSE|9999|NEWCO-EQ', resolution, 'primary')}")
Source code in stock_brokers/instruments/historical/utilities/unified/loader.py
read_series
¶
One source's bars, restricted to the part of the series that belongs to its instrument, with any confirmed correction from unified.price_history_corrections applied.
A corrected price is rounded to the paisa: the broker rounded when it divided, so multiplying back restores the raw price to within that rounding and no closer.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
source
|
Source
|
The source. |
required |
Returns:
| Type | Description |
|---|---|
list[Bar]
|
The bars, oldest first. |
Example usage
Source code in stock_brokers/instruments/historical/utilities/unified/loader.py
stitch
¶
Lay one broker's series for an instrument over each other, the most recent series on top.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
members
|
list[Source]
|
The instrument's sources at one broker. |
required |
Returns:
| Type | Description |
|---|---|
dict
|
Bar time to (Bar, source_id). |
Example usage
print(f'agreement with Dhan: {self.loader.agreement(primary_bars, self.loader.stitch([dhan]))}')
print(f'agreement with Flattrade: {self.loader.agreement(primary_bars, self.loader.stitch([flattrade]))}')
Source code in stock_brokers/instruments/historical/utilities/unified/loader.py
valid
¶
Whether a bar passes the calendar, grid and range checks, counting why when it does not.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
bar
|
Bar
|
The bar. |
required |
exchange
|
str
|
The instrument's exchange. |
required |
Returns:
| Type | Description |
|---|---|
bool
|
True if the bar is kept. |
Example usage
Source code in stock_brokers/instruments/historical/utilities/unified/loader.py
agreement
staticmethod
¶
The share of shared bar times on which two brokers' closes agree.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
primary
|
dict
|
Bar time to (Bar, source_id). |
required |
other
|
dict
|
Bar time to (Bar, source_id). |
required |
Returns:
| Type | Description |
|---|---|
tuple[float, int]
|
The share agreeing, and how many bars were compared. |
Example usage
print(f'agreement with Dhan: {self.loader.agreement(primary_bars, self.loader.stitch([dhan]))}')
print(f'agreement with Flattrade: {self.loader.agreement(primary_bars, self.loader.stitch([flattrade]))}')
Source code in stock_brokers/instruments/historical/utilities/unified/loader.py
rebuild
¶
Rebuild one instrument's bars from its sources, in one transaction.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
instrument_id
|
str
|
The instrument. |
required |
members
|
list[Source]
|
Its sources; a list holding only a placeholder with no broker means every source is gone and its bars are deleted. |
required |
Returns:
| Type | Description |
|---|---|
None
|
This function returns nothing. |
Example usage
Source code in stock_brokers/instruments/historical/utilities/unified/loader.py
365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 | |
IntradayLoader
¶
Bases: UnifiedLoader
Loads intraday bars, set-based in SQL.
Intraday history is two orders of magnitude larger than daily - flattrade alone stores some 300 million intraday bars - so bars are never brought into Python. Each instrument is rebuilt only from the point its sources changed: from three days before the newest bar its source rows had seen, or from the beginning for a new source or one whose history grew backwards.
The rebuild for one instrument deletes its bars in that window and inserts the stitched ones in
a single statement, DISTINCT ON the bar time with the most recent series first; then inserts a
gap-fill broker's bars for whole trading days the primary does not have, when the gap-fill broker
agrees with the primary on the days both have. Trading days come from a temporary table filled
once from the calendar; the grid and range checks are expressions in the same statement.
Prepare an intraday load.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
interval
|
str
|
The stored intraday interval name, for example "15minute". |
required |
instrument_ids
|
set[str] | None
|
Restrict the load to these instruments, or None for all. |
None
|
resolver
|
SeriesResolver | None
|
A resolver to share, or None to build one. |
None
|
calendar
|
TradingCalendar | None
|
A calendar to share, or None to build one. |
None
|
Returns:
| Type | Description |
|---|---|
None
|
This function returns nothing. |
Example programs
"""Runs an incremental 15 minute load for a BSE share, with Flattrade as the primary broker and Wisdom Capital filling whole missing days.
Intraday history is far too large to bring into Python, so `IntradayLoader` does the stitching in SQL. When it is built it copies the calendar's trading days into a temporary table and reads how far each source row had been seen. `run` then refreshes the sources as the daily loader does, and rebuilds each changed instrument only from where its sources changed: three days before the newest bar a changed source had already been seen, so a window the downloader re-fetched and corrected is picked up. The rebuild deletes the instrument's bars from that point, inserts the primary broker's bars in one statement, checks that the gap-fill broker's closes agree with them within 1% on at least 95% of the bars both have, and only then inserts the gap-fill broker's bars for whole days the primary lacks.
The loader opens PostgreSQL through `get_postgres` in its constructor, which the example runner points at a closed port, so the program replaces `get_postgres` in the loader module with a stand-in returning an in-memory connection. Because the real work happens inside PostgreSQL, the stand-in cannot compute it: it answers each statement with recorded-looking row counts, 118 bars deleted, 125 inserted from Flattrade, 121 of 125 shared bars agreeing and 25 filled from Wisdom Capital, and records the parameters each statement was sent with. What the program shows is therefore the loader's own decisions: the window start, the statements' parameters, the agreement test and the counts. The resolver is a stand-in passed to the constructor and the calendar is a real `TradingCalendar` with its days filled in by hand.
Run it from the project root:
python examples/stock_brokers/instruments/historical/utilities/unified/loader/IntradayLoader/example_1_an_incremental_15_minute_load.py
"""
import datetime
import zoneinfo
from stock_brokers.instruments.historical.utilities.unified import loader
from stock_brokers.instruments.historical.utilities.unified.calendar import (
TradingCalendar,
)
from stock_brokers.instruments.historical.utilities.unified.loader import (
IntradayLoader,
)
from stock_brokers.instruments.historical.utilities.unified.resolution import (
Resolution,
)
INDIA = zoneinfo.ZoneInfo('Asia/Kolkata')
FLATTRADE_SERIES = 'BSE|500331|PIDILITIND'
WISDOM_SERIES = '12|500331'
LAST_SEEN = datetime.datetime(2026, 9, 25, 15, 15, tzinfo=INDIA)
NEWEST = datetime.datetime(2026, 9, 29, 15, 15, tzinfo=INDIA)
EARLIEST = datetime.datetime(2025, 9, 1, 9, 15, tzinfo=INDIA)
class StandInSeriesResolver:
"""A stand-in for `SeriesResolver` that resolves both brokers' series to PIDILITIND on BSE."""
def resolve(self, broker, identifiers):
"""Resolves each identifier to PIDILITIND on BSE.
Args:
broker (str): The broker.
identifiers (list): The broker's series identifiers.
Returns:
dict: Identifier to a list holding one Resolution.
"""
resolved = {}
for identifier in identifiers:
resolution = Resolution(
identifier,
'PIDILITIND-BSE',
'bse',
'bse_equities',
'PIDILITIND',
'broker_mapping',
'active',
None,
datetime.date(2026, 8, 12),
datetime.date(2026, 9, 29),
None,
None,
)
resolved[identifier] = [
resolution,
]
return resolved
class StandInCursor:
"""A stand-in for a psycopg2 cursor that hands every statement to its connection.
Attributes:
connection (StandInConnection): The connection that answers the statements.
result (list): The rows the last statement produced.
rowcount (int): How many rows the last statement changed.
"""
def __init__(self, connection):
"""Builds a cursor over the stand-in connection.
Args:
connection (StandInConnection): The connection that answers the statements.
Returns:
None: This method returns nothing.
"""
self.connection = connection
self.result = []
self.rowcount = 0
def __enter__(self):
"""Opens the cursor as a context manager.
Returns:
StandInCursor: This cursor.
"""
return self
def __exit__(self, exception_type, exception, traceback):
"""Closes the cursor, 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 mogrify(self, template, arguments):
"""Renders one row, as `execute_values` and the loader's series list ask a cursor to.
Args:
template (bytes): The row template.
arguments (tuple): The row's values.
Returns:
bytes: The row's values as text, standing in for the rendered row.
"""
self.connection.staged_rows.append(arguments)
return str(arguments).encode()
def execute(self, statement, parameters=None):
"""Runs one statement against the stand-in connection.
Args:
statement (str | bytes): The SQL text.
parameters (tuple | list | None): The statement's parameters.
Returns:
None: This method returns nothing.
"""
if isinstance(statement, bytes):
statement = statement.decode()
self.result, self.rowcount = self.connection.answer(statement, parameters)
def fetchone(self):
"""The first row the last statement produced.
Returns:
tuple | None: The row, or None when there was none.
"""
if not self.result:
return None
return self.result[0]
def fetchall(self):
"""Every row the last statement produced.
Returns:
list: The rows.
"""
return self.result
class StandInConnection:
"""A stand-in for a psycopg2 connection that answers the intraday loader's statements with row counts.
Attributes:
encoding (str): The client encoding, which `execute_values` reads.
staged_rows (list): Rows rendered by `mogrify` since the last statement.
trading_days_copied (int): How many trading days were copied into the temporary table.
agreement (tuple): The (bars compared, bars agreeing) the agreement query answers.
statements (list): A short description of each statement that changes bars.
"""
encoding = 'UTF8'
def __init__(self, agreement):
"""Starts with nothing executed.
Args:
agreement (tuple): The (bars compared, bars agreeing) the agreement query answers.
Returns:
None: This method returns nothing.
"""
self.staged_rows = []
self.trading_days_copied = 0
self.agreement = agreement
self.statements = []
def row(self, *columns):
"""Builds one result row.
Args:
*columns (object): The row's columns, in order.
Returns:
tuple: The row.
"""
return columns
def cursor(self):
"""Opens a stand-in cursor.
Returns:
StandInCursor: The cursor.
"""
return StandInCursor(self)
def answer(self, statement, parameters):
"""Answers one of the loader's statements.
Args:
statement (str): The SQL text.
parameters (tuple | list | dict | None): The statement's parameters.
Returns:
tuple: (rows, rows changed).
"""
if 'unified_trading_days values' in statement:
self.trading_days_copied += len(self.staged_rows)
self.staged_rows = []
return [], 0
if 'temporary table' in statement or 'truncate' in statement:
return [], 0
if 'select source_id, broker_earliest_seen, broker_latest_seen' in statement:
return [
self.row(51, EARLIEST, LAST_SEEN),
self.row(52, EARLIEST, LAST_SEEN),
], 0
if 'from unified.price_history_sources where' in statement:
return [
self.row(51, 'flattrade', FLATTRADE_SERIES, 'PIDILITIND-BSE', 'active', EARLIEST, LAST_SEEN, EARLIEST, LAST_SEEN),
self.row(52, 'wisdom_capital', WISDOM_SERIES, 'PIDILITIND-BSE', 'active', EARLIEST, LAST_SEEN, EARLIEST, LAST_SEEN),
], 0
if 'price_history_progress' in statement:
if 'flattrade.' in statement:
return [
self.row(FLATTRADE_SERIES, EARLIEST, NEWEST),
], 0
if 'wisdom_capital.' in statement:
return [
self.row(WISDOM_SERIES, EARLIEST, NEWEST),
], 0
return [], 0
if 'insert into unified.price_history_sources' in statement:
source_id = 52
if parameters[0] == 'flattrade':
source_id = 51
return [
self.row(source_id),
], 1
if 'delete from unified.price_history' in statement:
self.statements.append(f'delete bars of {parameters[0]} from {parameters[2].isoformat()}')
return [], 118
if 'select count(*), count(*) filter' in statement:
return [
self.agreement,
], 0
if 'insert into unified.price_history' in statement:
self.staged_rows = []
broker = statement.split(' b\n')[0].split('from ')[-1].split('.')[0]
self.statements.append(f"insert from {broker}: start {parameters['start'].isoformat()}, every {parameters['minutes']} minutes, exchange {parameters['exchange']}, series {parameters['series']}")
if broker == 'flattrade':
return [], 125
return [], 25
if 'update unified.price_history_sources' in statement:
return [], 1
raise ValueError(f'The stand-in does not know this statement: {statement[:60]}')
def commit(self):
"""Accepts a commit.
Returns:
None: This method returns nothing.
"""
def rollback(self):
"""Accepts a rollback.
Returns:
None: This method returns nothing.
"""
class StandInPostgres:
"""Hands out the stand-in connection in the place of `get_postgres`.
Attributes:
connection (StandInConnection): The connection handed out.
"""
def __init__(self, connection):
"""Remembers the connection to hand out.
Args:
connection (StandInConnection): The connection handed out.
Returns:
None: This method returns nothing.
"""
self.connection = connection
def get_postgres(self):
"""Returns the stand-in connection.
Returns:
StandInConnection: The connection.
"""
return self.connection
class IncrementalLoadExample:
"""Runs a 15 minute load for PIDILITIND on BSE and prints the loader's decisions.
Attributes:
connection (StandInConnection): The in-memory connection.
loader (IntradayLoader): The loader being shown.
"""
def __init__(self):
"""Installs the stand-in connection and builds the loader with a hand-filled calendar.
Returns:
None: This method returns nothing.
"""
agreement = (
125,
121,
)
self.connection = StandInConnection(agreement)
loader.get_postgres = StandInPostgres(self.connection).get_postgres
calendar = TradingCalendar()
calendar.days['nse'] = set()
calendar.days['bse'] = set()
day = datetime.date(2026, 9, 1)
while day <= datetime.date(2026, 9, 30):
if day.weekday() < 5:
calendar.days['nse'].add(day)
calendar.days['bse'].add(day)
day += datetime.timedelta(days=1)
self.loader = IntradayLoader('15minute', resolver=StandInSeriesResolver(), calendar=calendar)
def run(self):
"""Runs the load and prints what was decided and sent.
Returns:
None: This method returns nothing.
"""
print(f'Minutes per bar: {self.loader.minutes}; trading days copied: {self.connection.trading_days_copied}')
for source_id, seen in self.loader.previously_seen.items():
print(f'Source {source_id} previously seen from {seen[0].isoformat()} to {seen[1].isoformat()}')
counts = self.loader.run()
for statement in self.connection.statements:
print(statement)
for name in sorted(counts):
print(f'{name}: {counts[name]}')
if __name__ == '__main__':
IncrementalLoadExample().run()
It prints:
Minutes per bar: 15; trading days copied: 44
Source 51 previously seen from 2025-09-01T09:15:00+05:30 to 2026-09-25T15:15:00+05:30
Source 52 previously seen from 2025-09-01T09:15:00+05:30 to 2026-09-25T15:15:00+05:30
delete bars of PIDILITIND-BSE from 2026-09-22T15:15:00+05:30
insert from flattrade: start 2026-09-22T15:15:00+05:30, every 15 minutes, exchange bse, series ('BSE|500331|PIDILITIND', 51, 0, None, None)
insert from wisdom_capital: start 2026-09-22T15:15:00+05:30, every 15 minutes, exchange bse, series ('12|500331', 52, 0, None, None)
bars_deleted: 118
bars_gap_filled: 25
bars_written: 125
instruments_rebuilt: 1
series_dhan: 0
series_flattrade: 1
series_wisdom_capital: 1
series_zerodha: 0
sources_superseded: 0
"""Calls `IntradayLoader`'s steps by hand: choosing where a rebuild starts, testing a gap-fill broker, and rebuilding with that broker refused.
`window_start` decides how much of an instrument's intraday history is rebuilt. It starts three days before the newest bar a changed source had already been seen, so a recently corrected window is picked up; but it rebuilds from the beginning, 1990-01-01, when any source is new, when a source's history grew backwards, or when the list holds only a placeholder for an instrument that lost its sources. The program asks it about each of those cases.
`intraday_agreement` counts, in one query, the bars on which a gap-fill broker's close is within 1% of the bars already inserted, and `insert_bars` inserts one broker's stitched bars from a start time, restricted to whole missing days for a gap-fill broker. `rebuild` combines them: here Wisdom Capital agrees on only 20 of 30 shared bars, below the 95% the loader requires, so it is refused and only Flattrade's bars are inserted.
The loader opens PostgreSQL through `get_postgres` in its constructor, which the example runner points at a closed port, so the program replaces `get_postgres` in the loader module with a stand-in returning an in-memory connection. The real stitching runs inside PostgreSQL, so the stand-in answers with recorded-looking row counts and records the parameters each statement was sent with; what the program shows is the loader's own decisions around those statements. The resolver is a stand-in and the calendar is a real `TradingCalendar` with its days filled in by hand.
Run it from the project root:
python examples/stock_brokers/instruments/historical/utilities/unified/loader/IntradayLoader/example_2_window_start_and_a_refused_gap_fill.py
"""
import datetime
import zoneinfo
from stock_brokers.instruments.historical.utilities.unified import loader
from stock_brokers.instruments.historical.utilities.unified.calendar import (
TradingCalendar,
)
from stock_brokers.instruments.historical.utilities.unified.loader import (
IntradayLoader,
Source,
)
from stock_brokers.instruments.historical.utilities.unified.resolution import (
Resolution,
)
INDIA = zoneinfo.ZoneInfo('Asia/Kolkata')
FLATTRADE_SERIES = 'BSE|500331|PIDILITIND'
WISDOM_SERIES = '12|500331'
LAST_SEEN = datetime.datetime(2026, 9, 25, 15, 15, tzinfo=INDIA)
NEWEST = datetime.datetime(2026, 9, 29, 15, 15, tzinfo=INDIA)
EARLIEST = datetime.datetime(2025, 9, 1, 9, 15, tzinfo=INDIA)
class StandInSeriesResolver:
"""A stand-in for `SeriesResolver` that resolves both brokers' series to PIDILITIND on BSE."""
def resolve(self, broker, identifiers):
"""Resolves each identifier to PIDILITIND on BSE.
Args:
broker (str): The broker.
identifiers (list): The broker's series identifiers.
Returns:
dict: Identifier to a list holding one Resolution.
"""
resolved = {}
for identifier in identifiers:
resolution = Resolution(
identifier,
'PIDILITIND-BSE',
'bse',
'bse_equities',
'PIDILITIND',
'broker_mapping',
'active',
None,
datetime.date(2026, 8, 12),
datetime.date(2026, 9, 29),
None,
None,
)
resolved[identifier] = [
resolution,
]
return resolved
class StandInCursor:
"""A stand-in for a psycopg2 cursor that hands every statement to its connection.
Attributes:
connection (StandInConnection): The connection that answers the statements.
result (list): The rows the last statement produced.
rowcount (int): How many rows the last statement changed.
"""
def __init__(self, connection):
"""Builds a cursor over the stand-in connection.
Args:
connection (StandInConnection): The connection that answers the statements.
Returns:
None: This method returns nothing.
"""
self.connection = connection
self.result = []
self.rowcount = 0
def __enter__(self):
"""Opens the cursor as a context manager.
Returns:
StandInCursor: This cursor.
"""
return self
def __exit__(self, exception_type, exception, traceback):
"""Closes the cursor, 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 mogrify(self, template, arguments):
"""Renders one row, as `execute_values` and the loader's series list ask a cursor to.
Args:
template (bytes): The row template.
arguments (tuple): The row's values.
Returns:
bytes: The row's values as text, standing in for the rendered row.
"""
self.connection.staged_rows.append(arguments)
return str(arguments).encode()
def execute(self, statement, parameters=None):
"""Runs one statement against the stand-in connection.
Args:
statement (str | bytes): The SQL text.
parameters (tuple | list | None): The statement's parameters.
Returns:
None: This method returns nothing.
"""
if isinstance(statement, bytes):
statement = statement.decode()
self.result, self.rowcount = self.connection.answer(statement, parameters)
def fetchone(self):
"""The first row the last statement produced.
Returns:
tuple | None: The row, or None when there was none.
"""
if not self.result:
return None
return self.result[0]
def fetchall(self):
"""Every row the last statement produced.
Returns:
list: The rows.
"""
return self.result
class StandInConnection:
"""A stand-in for a psycopg2 connection that answers the intraday loader's statements with row counts.
Attributes:
encoding (str): The client encoding, which `execute_values` reads.
staged_rows (list): Rows rendered by `mogrify` since the last statement.
trading_days_copied (int): How many trading days were copied into the temporary table.
agreement (tuple): The (bars compared, bars agreeing) the agreement query answers.
statements (list): A short description of each statement that changes bars.
"""
encoding = 'UTF8'
def __init__(self, agreement):
"""Starts with nothing executed.
Args:
agreement (tuple): The (bars compared, bars agreeing) the agreement query answers.
Returns:
None: This method returns nothing.
"""
self.staged_rows = []
self.trading_days_copied = 0
self.agreement = agreement
self.statements = []
def row(self, *columns):
"""Builds one result row.
Args:
*columns (object): The row's columns, in order.
Returns:
tuple: The row.
"""
return columns
def cursor(self):
"""Opens a stand-in cursor.
Returns:
StandInCursor: The cursor.
"""
return StandInCursor(self)
def answer(self, statement, parameters):
"""Answers one of the loader's statements.
Args:
statement (str): The SQL text.
parameters (tuple | list | dict | None): The statement's parameters.
Returns:
tuple: (rows, rows changed).
"""
if 'unified_trading_days values' in statement:
self.trading_days_copied += len(self.staged_rows)
self.staged_rows = []
return [], 0
if 'temporary table' in statement or 'truncate' in statement:
return [], 0
if 'select source_id, broker_earliest_seen, broker_latest_seen' in statement:
return [
self.row(51, EARLIEST, LAST_SEEN),
self.row(52, EARLIEST, LAST_SEEN),
], 0
if 'from unified.price_history_sources where' in statement:
return [
self.row(51, 'flattrade', FLATTRADE_SERIES, 'PIDILITIND-BSE', 'active', EARLIEST, LAST_SEEN, EARLIEST, LAST_SEEN),
self.row(52, 'wisdom_capital', WISDOM_SERIES, 'PIDILITIND-BSE', 'active', EARLIEST, LAST_SEEN, EARLIEST, LAST_SEEN),
], 0
if 'price_history_progress' in statement:
if 'flattrade.' in statement:
return [
self.row(FLATTRADE_SERIES, EARLIEST, NEWEST),
], 0
if 'wisdom_capital.' in statement:
return [
self.row(WISDOM_SERIES, EARLIEST, NEWEST),
], 0
return [], 0
if 'insert into unified.price_history_sources' in statement:
source_id = 52
if parameters[0] == 'flattrade':
source_id = 51
return [
self.row(source_id),
], 1
if 'delete from unified.price_history' in statement:
self.statements.append(f'delete bars of {parameters[0]} from {parameters[2].isoformat()}')
return [], 118
if 'select count(*), count(*) filter' in statement:
return [
self.agreement,
], 0
if 'insert into unified.price_history' in statement:
self.staged_rows = []
broker = statement.split(' b\n')[0].split('from ')[-1].split('.')[0]
self.statements.append(f"insert from {broker}: start {parameters['start'].isoformat()}, every {parameters['minutes']} minutes, exchange {parameters['exchange']}, series {parameters['series']}")
if broker == 'flattrade':
return [], 125
return [], 25
if 'update unified.price_history_sources' in statement:
return [], 1
raise ValueError(f'The stand-in does not know this statement: {statement[:60]}')
def commit(self):
"""Accepts a commit.
Returns:
None: This method returns nothing.
"""
def rollback(self):
"""Accepts a rollback.
Returns:
None: This method returns nothing.
"""
class StandInPostgres:
"""Hands out the stand-in connection in the place of `get_postgres`.
Attributes:
connection (StandInConnection): The connection handed out.
"""
def __init__(self, connection):
"""Remembers the connection to hand out.
Args:
connection (StandInConnection): The connection handed out.
Returns:
None: This method returns nothing.
"""
self.connection = connection
def get_postgres(self):
"""Returns the stand-in connection.
Returns:
StandInConnection: The connection.
"""
return self.connection
class WindowStartExample:
"""Asks the intraday loader where rebuilds start, then rebuilds with a gap-fill broker it refuses.
Attributes:
connection (StandInConnection): The in-memory connection.
loader (IntradayLoader): The loader being shown.
"""
def __init__(self):
"""Installs the stand-in connection and builds the loader with a hand-filled calendar.
Returns:
None: This method returns nothing.
"""
agreement = (
30,
20,
)
self.connection = StandInConnection(agreement)
loader.get_postgres = StandInPostgres(self.connection).get_postgres
calendar = TradingCalendar()
calendar.days['nse'] = set()
calendar.days['bse'] = set()
day = datetime.date(2026, 9, 1)
while day <= datetime.date(2026, 9, 30):
if day.weekday() < 5:
calendar.days['bse'].add(day)
day += datetime.timedelta(days=1)
self.loader = IntradayLoader('15minute', resolver=StandInSeriesResolver(), calendar=calendar)
def source(self, source_id, broker, series, role, earliest, changed):
"""Builds one of PIDILITIND's source rows.
Args:
source_id (int | None): The source row's id, or None for a placeholder.
broker (str | None): The broker, or None for a placeholder.
series (str | None): The broker's series identifier.
role (str | None): "primary" or "gap_fill".
earliest (datetime.datetime | None): The broker's earliest bar.
changed (bool): Whether the source has changed since it was last loaded.
Returns:
Source: The source.
"""
return Source(
source_id,
broker,
series,
'PIDILITIND-BSE',
role,
'bse',
None,
None,
earliest,
NEWEST,
changed,
)
def ask(self, label, *members):
"""Prints where a rebuild of these sources would start.
Args:
label (str): What is special about the sources.
*members (Source): The instrument's sources.
Returns:
None: This method returns nothing.
"""
print(f'window_start, {label}: {self.loader.window_start(list(members)).isoformat()}')
def run(self):
"""Asks about each case, tests the gap-fill broker, and rebuilds.
Returns:
None: This method returns nothing.
"""
flattrade = self.source(51, 'flattrade', FLATTRADE_SERIES, 'primary', EARLIEST, True)
wisdom = self.source(52, 'wisdom_capital', WISDOM_SERIES, 'gap_fill', EARLIEST, False)
self.ask('one source grew forwards', flattrade, wisdom)
self.ask('a new source', flattrade, self.source(99, 'wisdom_capital', WISDOM_SERIES, 'gap_fill', EARLIEST, True))
grew_backwards = self.source(51, 'flattrade', FLATTRADE_SERIES, 'primary', datetime.datetime(2025, 6, 2, 9, 15, tzinfo=INDIA), True)
self.ask('a source grew backwards', grew_backwards, wisdom)
self.ask('only a placeholder', self.source(None, None, None, None, None, True))
cursor = self.connection.cursor()
gap_fill_sources = [
wisdom,
]
share, compared = self.loader.intraday_agreement(cursor, 'PIDILITIND-BSE', gap_fill_sources)
print(f'intraday_agreement with Wisdom Capital: {share:.3f} over {compared} bars')
start = datetime.datetime(2026, 9, 22, 15, 15, tzinfo=INDIA)
primary_sources = [
flattrade,
]
self.loader.insert_bars(cursor, 'PIDILITIND-BSE', 'bse', primary_sources, start, whole_days_only=False)
print(f'insert_bars by hand: {self.connection.statements[-1]}')
self.connection.statements = []
self.loader.counts.clear()
members = [
flattrade,
wisdom,
]
self.loader.rebuild('PIDILITIND-BSE', members)
print('rebuild sent:')
for statement in self.connection.statements:
print(f' {statement}')
for name in sorted(self.loader.counts):
print(f'{name}: {self.loader.counts[name]}')
if __name__ == '__main__':
WindowStartExample().run()
It prints:
window_start, one source grew forwards: 2026-09-22T15:15:00+05:30
window_start, a new source: 1990-01-01T00:00:00+00:00
window_start, a source grew backwards: 1990-01-01T00:00:00+00:00
window_start, only a placeholder: 1990-01-01T00:00:00+00:00
intraday_agreement with Wisdom Capital: 0.667 over 30 bars
insert_bars by hand: insert from flattrade: start 2026-09-22T15:15:00+05:30, every 15 minutes, exchange bse, series ('BSE|500331|PIDILITIND', 51, 0, None, None)
rebuild sent:
delete bars of PIDILITIND-BSE from 2026-09-22T15:15:00+05:30
insert from flattrade: start 2026-09-22T15:15:00+05:30, every 15 minutes, exchange bse, series ('BSE|500331|PIDILITIND', 51, 0, None, None)
bars_deleted: 118
bars_written: 125
gap_fill_refused: 1
Methods:
| Name | Description |
|---|---|
window_start |
Where an instrument's rebuild starts. |
rebuild |
Rebuild one instrument's intraday bars from where its sources changed, in one transaction. |
insert_bars |
Insert one broker's stitched bars for an instrument from |
intraday_agreement |
The share of bars on which a gap-fill broker's close agrees with the bars already inserted. |
run |
Discover, resolve and load. |
refresh_sources |
Resolve every series of every source broker and bring the sources table up to date. |
upsert_source |
Insert or refresh one source row, returning its id. |
read_series |
One source's bars, restricted to the part of the series that belongs to its instrument, with |
stitch |
Lay one broker's series for an instrument over each other, the most recent series on top. |
valid |
Whether a bar passes the calendar, grid and range checks, counting why when it does not. |
agreement |
The share of shared bar times on which two brokers' closes agree. |
Source code in stock_brokers/instruments/historical/utilities/unified/loader.py
window_start
¶
Where an instrument's rebuild starts.
The whole history is rebuilt when a source is new, when a source lost, or when one's history grew backwards; otherwise from a little before the newest bar any changed source had seen.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
members
|
list[Source]
|
The instrument's sources. |
required |
Returns:
| Type | Description |
|---|---|
datetime
|
The first bar time to rebuild. |
Example usage
print(f'window_start, {label}: {self.loader.window_start(list(members)).isoformat()}')
Source code in stock_brokers/instruments/historical/utilities/unified/loader.py
rebuild
¶
Rebuild one instrument's intraday bars from where its sources changed, in one transaction.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
instrument_id
|
str
|
The instrument. |
required |
members
|
list[Source]
|
Its sources. |
required |
Returns:
| Type | Description |
|---|---|
None
|
This function returns nothing. |
Example usage
Source code in stock_brokers/instruments/historical/utilities/unified/loader.py
insert_bars
¶
Insert one broker's stitched bars for an instrument from start.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
cursor
|
cursor
|
An open cursor inside the instrument's transaction. |
required |
instrument_id
|
str
|
The instrument. |
required |
exchange
|
str
|
Its exchange, for the trading day check. |
required |
sources
|
list[Source]
|
The broker's series for the instrument. |
required |
start
|
datetime
|
The first bar time to insert. |
required |
whole_days_only
|
bool
|
Insert only on India dates the instrument has no bar for yet. |
required |
Returns:
| Type | Description |
|---|---|
None
|
This function returns nothing. |
Example usage
self.loader.insert_bars(cursor, 'PIDILITIND-BSE', 'bse', primary_sources, start, whole_days_only=False)
Source code in stock_brokers/instruments/historical/utilities/unified/loader.py
intraday_agreement
¶
The share of bars on which a gap-fill broker's close agrees with the bars already inserted.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
cursor
|
cursor
|
An open cursor. |
required |
instrument_id
|
str
|
The instrument. |
required |
sources
|
list[Source]
|
The gap-fill broker's series. |
required |
Returns:
| Type | Description |
|---|---|
tuple[float, int]
|
The share agreeing, and how many bars were compared. |
Example usage
share, compared = self.loader.intraday_agreement(cursor, 'PIDILITIND-BSE', gap_fill_sources)
Source code in stock_brokers/instruments/historical/utilities/unified/loader.py
run
¶
Discover, resolve and load.
Returns:
| Type | Description |
|---|---|
Counter
|
What was done - series seen, instruments rebuilt, bars written, deleted and filtered. |
Example usage
Source code in stock_brokers/instruments/historical/utilities/unified/loader.py
refresh_sources
¶
Resolve every series of every source broker and bring the sources table up to date.
Returns:
| Type | Description |
|---|---|
list[Source]
|
The active sources, restricted to |
Example usage
Source code in stock_brokers/instruments/historical/utilities/unified/loader.py
137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 | |
upsert_source
¶
Insert or refresh one source row, returning its id.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
broker
|
str
|
The broker. |
required |
identifier
|
str
|
The broker's series identifier. |
required |
resolution
|
Resolution
|
The instrument it resolved to. |
required |
role
|
str
|
"primary" or "gap_fill". |
required |
Returns:
| Type | Description |
|---|---|
int
|
The source_id. |
Example usage
print(f"upsert_source returns id {self.loader.upsert_source('flattrade', 'NSE|9999|NEWCO-EQ', resolution, 'primary')}")
Source code in stock_brokers/instruments/historical/utilities/unified/loader.py
read_series
¶
One source's bars, restricted to the part of the series that belongs to its instrument, with any confirmed correction from unified.price_history_corrections applied.
A corrected price is rounded to the paisa: the broker rounded when it divided, so multiplying back restores the raw price to within that rounding and no closer.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
source
|
Source
|
The source. |
required |
Returns:
| Type | Description |
|---|---|
list[Bar]
|
The bars, oldest first. |
Example usage
Source code in stock_brokers/instruments/historical/utilities/unified/loader.py
stitch
¶
Lay one broker's series for an instrument over each other, the most recent series on top.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
members
|
list[Source]
|
The instrument's sources at one broker. |
required |
Returns:
| Type | Description |
|---|---|
dict
|
Bar time to (Bar, source_id). |
Example usage
print(f'agreement with Dhan: {self.loader.agreement(primary_bars, self.loader.stitch([dhan]))}')
print(f'agreement with Flattrade: {self.loader.agreement(primary_bars, self.loader.stitch([flattrade]))}')
Source code in stock_brokers/instruments/historical/utilities/unified/loader.py
valid
¶
Whether a bar passes the calendar, grid and range checks, counting why when it does not.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
bar
|
Bar
|
The bar. |
required |
exchange
|
str
|
The instrument's exchange. |
required |
Returns:
| Type | Description |
|---|---|
bool
|
True if the bar is kept. |
Example usage
Source code in stock_brokers/instruments/historical/utilities/unified/loader.py
agreement
staticmethod
¶
The share of shared bar times on which two brokers' closes agree.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
primary
|
dict
|
Bar time to (Bar, source_id). |
required |
other
|
dict
|
Bar time to (Bar, source_id). |
required |
Returns:
| Type | Description |
|---|---|
tuple[float, int]
|
The share agreeing, and how many bars were compared. |
Example usage
print(f'agreement with Dhan: {self.loader.agreement(primary_bars, self.loader.stitch([dhan]))}')
print(f'agreement with Flattrade: {self.loader.agreement(primary_bars, self.loader.stitch([flattrade]))}')
Source code in stock_brokers/instruments/historical/utilities/unified/loader.py
corrected_bar
¶
A bar with a correction applied.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
bar
|
Bar
|
The bar as served. |
required |
price_multiplier
|
Decimal
|
What the prices were divided by. |
required |
volume_multiplier
|
Decimal
|
What the volume was divided by. |
required |
Returns:
| Type | Description |
|---|---|
Bar
|
The corrected bar. |