Backtest reporting checklist โ€” ForexBot2 (XAU/USD) ================================================== Every backtest report must record: - The exact command line (window, --primary-tf, --spread, and any --set / --quality / --strategies / --confirm-tfs overrides) and the "Backend:" log line. - Trades, total R, win rate, profit factor and max drawdown for the whole window AND for both halves, at both cost conventions ($0.65 measured-Oanda headline, $0.30 cross-book comparability) โ€” CLAUDE.md rule 1. - Per trade (the exported CSV already carries these): entry/exit time and price, exit reason, R multiple, MFE/MAE, entry reasons, and every indicator's signal / value / agreement at decision time. - Breakdowns by session/hour, weekday, regime and volatility regime, and, for any candidate change, the removed/added/shifted trade diff against the control book (the 5-bar cooldown displaces entries, so judge run-level numbers, never the added-trade bucket alone). - The md5 of the exported book whenever the run is a parity or regression check. Replays go through the real signal code (analyze_from_dataframes) โ€” never a re-implementation. --- 2026-09-06: decision-time feature export --- - `--export-features` appends stake_multiplier, r_sized and the 65 `f_*` decision-time features (position_sizing.FEATURE_ORDER) AFTER the 63 pinned columns; default export is byte-identical to the pinned books. - Any sizing report must show the FLAT book and the SIZED book side by side and the label-shuffle null; the 2026-09-06 campaign verdict (nothing ships, backtest/research/sizing_campaign_2026-09.md) is the reference. --- 2026-09-09: replay recording (--record DIR [--record-bars]) --- - `--record DIR` writes manifest.json + trades/indicators/events parquet for every ACCEPTED trade: every indicator's signal / value / confidence / BASE weight / regime multiplier / EFFECTIVE weight / contribution on the primary AND both confirmation timeframes, penalties, the setup, the outcome, and the Forex Factory calendar events around the trade (backtest/cache/calendar.parquet, refreshed with scripts/ff_calendar_scraper.py). `--record-bars` adds one row per ANALYZED primary bar with the same votes plus forward moves (1h/4h/24h in ATR units). - Recording is read-only w.r.t. the signal path: any recorded run must be paired with a control run (same CLI without --record) and the report must show the two books' MD5s equal (manifest.book_csv_md5 is the md5 of the 63-column book). - Scorecards and "what-if" tables from these files (backtest/replay_scorecard.py) are IN-BOOK descriptive statistics: they cannot add trades a change would create and ignore the gates. Any weight / indicator change they suggest is a hypothesis for a full rule-1 rerun, never a decision. --- 2026-09-18: GPU backend (backtest/gpu_kernels.py) --- - The "Backend:" line now names the CUDA device ("CuPy GPU (, cupy )") or the reason for the NumPy fallback; quote it verbatim. `--backend cpu` forces the NumPy twins, `gpu` refuses to fall back. Books are byte-identical on both backends (reference MD5s, CLAUDE.md ยง9). - Any change to a kernel or to its CPU twin is a parity event: re-run Module 36, regenerate the five reference books on BOTH backends and show the ten MD5s unchanged. Never replace a kernel with a differently ordered algorithm (parallel scan, two-pass variance) even if it is faster. --- 2026-09-18: CUDA research twin (backtest/cudaBasedBacktesting.py) --- - Exemption from "replays go through the real signal code" above, granted by the owner for this ONE research script: its per-bar decision is a CUDA re-implementation of engine.py, signal_aggregator.py and chart_patterns.py (backtest/cuda_signal_kernels.py). gpu_backtest.py stays the base reference and live scans never use the twin. - Its numbers count as evidence only while parity holds. After ANY change to engine.py, signal_aggregator.py, chart_patterns.py, position_sizing.py, the recorder's row layout or the GPUTechnicalEngine overrides: mirror the change in cuda_signal_kernels.py in the same commit, keep test_all.py Module 37 green, and regenerate at least the M15 OOS book (2024-06-01 -> 2025-03-30, $0.65) with BOTH scripts and show equal MD5s before quoting a CUDA-script result. - A report built from the twin quotes both scripts' "Backend:" lines (the twin's necessarily starts with "CuPy GPU (") and the paired MD5 of at least one book from the reference script on the same code and cache. Whole-window and half-window figures, costs and breakdowns are then read from the twin's book exactly as from the reference's; the CSVs are identical. - The twin has no CPU path (--backend cpu / FB2_BACKEND=cpu are refused), --workers is ignored, and it refuses configurations beyond its compile-time limits (more than 4 confirmation timeframes, divergence lookback >= 64, swing lookback >= 256, cp_lookback >= 128) with a message that points back to gpu_backtest.py. --- 2026-09-30: daily-trend veto (M15) and the D1 frame --- - The M15 factory config carries xau_strategy.daily_trend_veto = "both" (M15 override 7). Both backtesters then load the D1 frame as an EXTRA frame next to the primary and confirmation frames (the "Confirm TFs:" line does not list it), cached under its own pad: backtest/cache/oanda_XAU_USD_1d__.parquet. A new M15 window therefore needs that file (one network fetch the first time; "GPU cache fallbacks: 0" is unaffected). - The veto reads CLOSED daily bars only (a bar labelled D counts from D + 1 day <= the primary bar's close), drops the provider's Sunday stub bars, and removes a finished setup that goes against a non-zero state. Definition and constants: signal_aggregator.daily_trend_series. - Research switches, both default-neutral elsewhere: --set xau_strategy.daily_trend_veto= off|both|buy|sell and --set xau_strategy.wide_stop_cap_atr=K (K = 0 is off; the cap FAILED its rule on 2026-09-30 and ships nowhere). A control for an M15 candidate is the factory config, i.e. WITH the veto; to reproduce a pre-2026-09-30 M15 book pass --set xau_strategy.daily_trend_veto=off. - A candidate that is a FINAL veto (it only removes finished setups) may be screened on a research harness (K1 + K2 of the twin, a mask on the final candidates before the cooldown, the shared resolver) once that harness is shown to reproduce a shipped book byte for byte; the formal numbers still come from the real code path with the paired gpu_backtest.py MD5. - The 2026-09-30 report (backtest/research/daily_trend_campaign_2026-09-30.md) is the template for a risk-first rule: rules, windows and neighbours written before the runs; the long window (2019-01-01 -> 2026-08-07) and an unseen holdout as clauses; the rising-market cost reported, not hidden.