From fcf379c759e890a98264b378669f605e5ed12238 Mon Sep 17 00:00:00 2001 From: I Luk Kim Date: Tue, 28 Apr 2026 01:43:55 -0700 Subject: [PATCH] Fix snapshot refresh: enable auto-rebuild, fix date-range edge cases, log incremental failures - registry.json: change _ftb_fix_v2 from manual_only to auto_full_rebuild so backtest auto-refreshes when snapshot doesn't cover the requested period - run.py: return [] (not all_trading_days fallback) when parking cap pushes requested_end before requested_start, preventing silent wrong-date-range runs - run.py: allow 1-trading-day lag tolerance in parking cap so a single lagging symbol (e.g. QQQM shortly after close) doesn't cap the whole simulation - backtest_sim.py: log incremental_update_failed_falling_back warning so silent fallback to full rebuild is visible in direct-mode logs Co-Authored-By: Claude Sonnet 4.6 --- apps/backtester/run.py | 20 ++++++++++++++++++++ apps/paper_trader/backtest_sim.py | 5 +++++ configs/snapshots/registry.json | 4 ++-- 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/apps/backtester/run.py b/apps/backtester/run.py index 1e352a4..1713f4e 100644 --- a/apps/backtester/run.py +++ b/apps/backtester/run.py @@ -1788,6 +1788,16 @@ class BacktestRunner: if requested_start <= requested_end: return get_trading_days(requested_start, requested_end) + # requested_start > requested_end: parking cap pushed end before start + # (e.g. user requested start=2026-04-27 but data only covers to 2026-04-24). + # Do NOT fall through to all_trading_days() — that silently runs on the + # lookback-extended store (e.g. March 17–April 24 due to lookback_entry_enabled). + logger.warning( + "backtest_start_beyond_available_data", + requested_start=requested_start.isoformat(), + effective_end=requested_end.isoformat(), + ) + return [] if not self.config.get_strategy_engines(): return self.store.all_trading_days() include_reaction_dates = any( @@ -8338,6 +8348,16 @@ def _extend_store_to_requested_window( _parking_last_dates.append(max(_sym_dates)) if _parking_last_dates: _parking_cap = min(_parking_last_dates) + _parking_best = max(_parking_last_dates) + # Allow up to 1 trading-day lag per symbol (extras like QQQM can lag Oracle + # by a few minutes after market close, leaving a None for the most recent bar). + # If the straggler is only 1 trading day behind the best-covered symbol, use + # the best date so the simulation isn't unnecessarily capped. + if _parking_cap < _parking_best: + from libs.backtest.calendar import get_trading_days as _gtd + _lag_days = max(0, len(_gtd(_parking_cap, _parking_best)) - 1) + if _lag_days <= 1: + _parking_cap = _parking_best current_end = getattr(store, "_requested_end_date", end_date) if _parking_cap < current_end: setattr(store, "_requested_end_date", _parking_cap) diff --git a/apps/paper_trader/backtest_sim.py b/apps/paper_trader/backtest_sim.py index 0da2123..0c03ffc 100644 --- a/apps/paper_trader/backtest_sim.py +++ b/apps/paper_trader/backtest_sim.py @@ -447,6 +447,11 @@ async def _refresh_snapshot( try: await incremental_update_canonical_snapshot(snapshot_id) except Exception as inc_exc: + logger.warning( + "incremental_update_failed_falling_back", + snapshot_id=snapshot_id, + error=str(inc_exc), + ) if console: console.print(f" [yellow]Incremental failed ({inc_exc}), falling back to full rebuild...[/]") await build_canonical_snapshot(snapshot_id, manual=manual) diff --git a/configs/snapshots/registry.json b/configs/snapshots/registry.json index 38ab2a9..b6d4792 100644 --- a/configs/snapshots/registry.json +++ b/configs/snapshots/registry.json @@ -97,8 +97,8 @@ ] }, "midlarge-liquid-long-v1_bucketfix_full_audit_canonical_ftb_fix_v2": { - "purpose": "test_ftb_fix", - "refresh_policy": "manual_only", + "purpose": "main_ftb_fix", + "refresh_policy": "auto_full_rebuild", "universe_profile": "midlarge-liquid-long-v1", "label_version": "label-2.0.0", "start_date": "2022-03-01",