data.wrangler¶
Turn candles into validated Bar streams. Makes you declare whether your timestamps mean the bar's open or its close.
wrangler
¶
Wrangle user-supplied OHLCV rows into validated, time-ordered Bar tuples.
The single most dangerous bug in bar-based backtesting is the silent
off-by-one-bar look-ahead: treating a bar's OPEN timestamp as if it were its
CLOSE (or vice versa) hands the strategy one bar of the future. This module
kills that bug at the API level — the caller MUST declare what the source
timestamp means via stamp:
stamp="open":ts_event = tsandts_init = ts + stepstamp="close":ts_init = tsandts_event = ts - step
where step = unit x unit_number in nanoseconds. There is no default.
Other hard guarantees
- NAIVE timestamps are rejected with an error naming the fix — never guessed.
- Prices convert via
str() -> Decimal(neverfloat -> Decimal) and must land exactly on the instrument's tick grid (RowOffGridErrorwith the offending row index otherwise). NaN/Infinity prices and negative volumes are rejected with the row context. - Only fixed-span intraday units (SECOND/MINUTE/HOUR) are accepted: a Globex trading day is 23 hours, so DAY-and-above bars belong to the session-aware calendar-resampling layer, not a fixed nanosecond step.
- Output is sorted ascending by
ts_init(stable), ready for a feed.
pandas is imported lazily — only bars_from_dataframe needs it.
RowOffGridError
¶
Bases: OffGridError
An input price is off the tick grid; carries the offending row index.
Source code in src/topstep_backtest/data/wrangler.py
step_ns
¶
The bar step (open -> close span) in nanoseconds.
Supports SECOND / MINUTE / HOUR only. TICK bars have no fixed time span. DAY / WEEK / MONTH bars are session-scoped, not fixed-span: a Globex trading day runs 23 hours (18:00 ET -> 17:00 ET), so a fixed 86,400s step would mis-stamp every daily bar and mis-attribute its trading day. Session-aware daily bars arrive with the calendar-resampling layer — supply intraday bars here.
Source code in src/topstep_backtest/data/wrangler.py
bars_from_records
¶
bars_from_records(rows: Iterable[tuple[object, ...]], *, contract_id: str, spec: InstrumentSpec, unit: AggregateBarUnit, unit_number: int, stamp: Literal['open', 'close']) -> tuple[Bar, ...]
Build tick-grid-validated Bar objects from (ts, o, h, l, c, v) rows.
stamp declares what the source timestamp means — see module docstring.
Rows are sorted ascending by the resulting ts_init (stable sort), so
the output is feed-ready regardless of input order.
Source code in src/topstep_backtest/data/wrangler.py
bars_from_dataframe
¶
bars_from_dataframe(df: Any, *, contract_id: str, spec: InstrumentSpec, unit: AggregateBarUnit, unit_number: int, stamp: Literal['open', 'close']) -> tuple[Bar, ...]
Build Bar objects from a pandas DataFrame of OHLCV candles.
The timestamp may be a column (case-insensitive: timestamp / ts / time /
datetime / ts_event) or the index — but the index is used ONLY when it is
a DatetimeIndex or is named (case-insensitively) after one of those
timestamp columns. A default RangeIndex (or any anonymous integer
index) is rejected: its 0, 1, 2, ... would otherwise be read as epoch
nanoseconds and every bar would silently land in 1970. OHLCV column names
are matched case-insensitively. Everything else — stamping,
naive-timestamp rejection, tick-grid enforcement, sorting — is delegated
to bars_from_records.