backtest: seed slow-TF history to the live 1000-bar floor (_history_pad_days)

The per-TF warm-up pad before --start targeted 210 bars, so a 3-year H4
book started with 372 H4 bars and ~210 D1 bars of seeding while the
live scanner now fetches at least MIN_LIVE_BARS (1000) on every
timeframe. _history_pad_days keeps the 62-day intraday pad (already
>= 1488 bars, so M5/M15/H1 cache windows are untouched) and pads H4 by
234 and D1 by 1400 calendar days; the raw-path fetch for trade
resolution reuses the primary timeframe's own start (one cache file
per TF, as before).

Verified on the 3-year books at $0.65: M5 and M15 byte-identical
(c2722f98…, b2cfa9e2…); H1 and H4 trade-for-trade identical (253 /
+70.02R, 114 / +21.68R) — the only differences are one H4
confirmation reading 15 days into the H1 window and 1e-6…1e-9 numerics
in D1 Wilder recursions during Oct–Dec 2023, i.e. the window-start
seeding zone. Evidence: backtest/research/cycle_2026-09-10.md.

Tests: Module 21 "history pad seeds slow TFs like live". Suite 501 passed.

Claude-Session: https://claude.ai/code/session_01NQDjW8rXYrJR4JDWMWKUij
This commit is contained in:
Harwinder Sekhon
2026-09-10 08:29:04 -04:00
parent 1bc6a27445
commit c742c65ef6
2 changed files with 44 additions and 13 deletions
+15
View File
@@ -5849,6 +5849,21 @@ def _():
"no 0.0-defaulted prices may appear"
@test("backtester: history pad seeds slow TFs like live (H4/D1 deeper, intraday unchanged)")
def _():
import backtest.gpu_backtest as _gb
pad = {tf: _gb._history_pad_days(tf, 250) for tf in
(Timeframe.M5, Timeframe.M15, Timeframe.H1, Timeframe.H4, Timeframe.D1)}
# intraday pads keep the 62-day base -> cache windows and books unchanged
assert pad[Timeframe.M5] == 62 and pad[Timeframe.M15] == 62 and pad[Timeframe.H1] == 62, pad
# slow TFs fetch enough history for MIN_LIVE_BARS (1000) bars: H4 6/day, D1 1/day
assert pad[Timeframe.H4] == 234 and pad[Timeframe.D1] == 1400, pad
# base pad still follows --warmup-bars when that is the larger requirement
assert _gb._history_pad_days(Timeframe.M5, 1000) == 250
# the floor is the provider's constant, not a second copy of the number
assert _gb.BaseDataProvider.MIN_LIVE_BARS == 1000
@test("backtester: NaN bar inside a trailing window cannot poison R/metrics")
def _():
import gpu_backtest as _gb