Files
ForexBot2/backtest/research/cycle_2026-09-10.md
T
Harwinder Sekhon 1c293f7d2e research: improvement cycle 1 ledger (baseline, cost ladder, C1 reject, C2 plateau, seeding parity)
backtest/research/cycle_2026-09-10.md: pre-registered rules, HEAD baseline
on the 3-year window (three pinned MD5s reproduced), cost ladder and
concentration facts, bucket screen (one cycle-2 candidate: M15 grade-A
SELLs), C1 H1 divergence->0 REJECTED, C2 M15 RSI-multiplier ladder =
stable plateau, seeding-depth measurement and the two shipped parity
fixes. CLAUDE.md: live fetch depth, backtester pad, 501 tests, the
2026-09-10 state entry; corrected the docs note (no PDF builds exist)
and recorded pyarrow as a dev-venv-only dependency.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NQDjW8rXYrJR4JDWMWKUij
2026-09-10 08:29:14 -04:00

20 KiB
Raw Blame History

Improvement cycle 1 — 2026-09-10 (HEAD baseline, robustness challenge, seeding parity)

Purpose

First cycle under the owner's standing brief (evidence-driven incremental improvement of the existing XAU/USD system). No new strategy idea is tested. The cycle (1) re-establishes the baseline at HEAD and proves the harness reproduces the pinned books, (2) challenges the current edge — cost sensitivity, concentration, and the parameter neighbourhood of the M15 RSI × 0.5 change shipped 2026-09-09, (3) reruns the one parked candidate with prior rule-1 evidence (H1 divergence off) through the production vehicle it now has, and (4) measures and fixes a live-vs-backtest indicator-seeding gap (H4 fetches 380 bars, D1 300, while the backtester seeds from its whole cached history). Everything under "Pre-registered" was written before the corresponding run.

Harness

venv/bin/python backtest/gpu_backtest.py --pair XAUUSD --spread 0.65 --workers 12 --primary-tf {5min|15min|1h|4h}, pair-factory config (no --quality/--strategies/ --confirm-tfs). Windows: W3 2023-09-05→2026-09-05 (halves at 2025-03-05; last-12-months slice from 2025-09-05 reported descriptively), W2 2024-09-05→2026-09-05 (halves at 2025-09-05), OOS 2024-06-01→2025-03-30. Code: git 2ac7301 (branch sql-integration). Backend: NumPy CPU (CuPy imports but has no CUDA toolkit on this box). Bars: cached backtest/cache/oanda_XAU_USD_{5min,15min,1h,4h}_20230705_20260905.parquet, _1d_20221110_20260905, _{5min,15min,1h}_20240705_20260905, _{…}_20240331_20250330. Outputs: backtest/research/cycle_2026-09-10/ (gitignored). Every candidate is compared with a CONTROL produced on this harness in this session. --set technical.regime_weights_override=<JSON> replaces the WHOLE table, so each candidate passes the complete table with one indicator's multipliers changed.

Pre-registered rules (written 2026-09-10 before any run)

Step 0 — reproducibility gate

The HEAD factory run must reproduce the ledger's MD5s: M5 W2 29120a1ee32979431d062f210d41a9d1, M15 W2 d185f099482a07f29502cb5a371a9bbf, M15 OOS 6f8f24b49597ced28f50a19e08c3472f. A mismatch stops the cycle until explained.

Step 1 — diagnostics (descriptive; flags fixed in advance)

  • Cost ladder $0.65 / 0.80 / 1.00 / 1.30 on W3 per TF. Red flag for a TF: at $0.80 PF < 1.3 or either W3 half ≤ 0.
  • Concentration/stability: share of total R in the top 5% of trades, % of calendar months positive, longest losing streak, max DD and R per calendar year. No action threshold.
  • Bucket screen over the recorded W3 trades (direction, quality, regime, strategy, entry bar-close hour, weekday, us_holiday, ev_entry_within_30m_hi_usd, ev_hi_usd_during_hold, year/quarter; plus M15/H4 SELL-in-transitioning and holiday trades). A bucket becomes a cycle-2 candidate ONLY if n ≥ 30, R < 0 in each of the three 12-month slices AND in OOS, and a mechanism can be stated. No gate is built this cycle.

C1 — H1 divergence multiplier → 0 in every regime (H1-only)

Vehicle: --set technical.regime_weights_override=<default table with divergence 0.0 in all five regimes>, primary 1h, W3 + OOS, control = the Step-0 HEAD run. PASS only if ALL: (a) ΔR ≥ +5R on W3; (b) ΔR > 0 in both W3 halves; (c) PF ≥ 1.3 and not more than 0.05 below control; (d) max DD ≤ control + 2R; (e) trade count within ±25%; (f) OOS ΔR ≥ 0. Last-12-months slice reported, not a clause. No rescue, no regime-family fine-tuning after seeing the result. A pass is a shipping PROPOSAL for the owner.

C2 — M15 RSI multiplier neighbourhood (stability of the live change)

Ladder k ∈ {0, 0.25, 0.5 (HEAD), 0.75, 1.0 (pre-2026-09-09), 1.5}, full table with rsi × k, on W2, OOS and W3. Reading, with G(k) = R(k) − R(1.0) on W2: stable plateau if G(0.25) ≥ 0.5·G(0.5) and G(0.75) ≥ 0.5·G(0.5) and the OOS deltas at 0.25 and 0.75 are ≥ 0; spike if either neighbour retains < 50% of G(0.5). Outcome is a report to the owner; no different k is proposed whatever the result.

Step 3 — seeding-depth parity

3a: on the deepest cached H4/D1 frames (H1 as control), sampled bars over the last two years, compare TechnicalEngine.analyze() on the trailing N bars with the full-history result; MIN_SEED_BARS = smallest N with zero indicator-signal flips on both H4 and D1. 3b: live per-TF fetch floor raised to that depth (no book changes; the backtester is the reference). 3c: backtester slow-TF pad raised to the same depth, shipped only if the M5 and M15 W3 books stay byte-identical and the H1/H4 differences are confined to the window-start seeding zone; the H1/H4 re-pin is the owner's decision.

Results

(filled in as runs complete)

Step 0 — HEAD baseline (W3, $0.65, factory config; recorded with --record)

TF trades total R WR PF max DD half 1 (→2025-03-05) half 2 last 12 mo SELL bucket book MD5
M5 870 +153.64 53.3% 1.50 10.1 136 / +16.73 / PF 1.36 734 / +136.91 / 1.52 593 / +119.65 / 1.57 100 / +8.95 / 1.25 c2722f9830528658b53854f6bae61b8c
M15 1273 +186.61 59.4% 1.39 19.3 556 / +75.87 / 1.37 717 / +110.74 / 1.41 474 / +81.04 / 1.47 404 / −15.18 / 0.91 b2cfa9e2e6cd3997f4d5ca9b49cab90e
H1 253 +70.02 64.4% 1.98 4.4 115 / +26.26 / 1.79 138 / +43.76 / 2.15 89 / +24.62 / 1.96 0 (all shorts blocked) b99111132d9ecb80c2998ed5aee2f3ea
H4 114 +21.68 57.9% 1.59 5.4 52 / +4.91 / 1.26 62 / +16.77 / 1.94 46 / +12.34 / 1.79 20 / −0.97 / 0.89 4508e7a3402e008ba1d93805094cd981

These equal the numbers CLAUDE.md records for HEAD (M5 870/+153.6/1.50, M15 1273/+186.61/1.39, H1 253/+70.0/1.98, H4 114/+21.7/1.59). Engine / gate fingerprints from the manifests: M5 f28b1a8c409d/bc0991492340, M15 536f84340ea0/b2920991cdb8 (new since the RSI override), H1 6efb6d365f01/0b67d738dc15, H4 ed96f258b10e/0c3b51f063f9. replay_scorecard.py --check on the four recorded runs: see step0.out. Per calendar year (R / PF): M5 2023 +2.4/1.47, 2024 +1.0/1.02 (102 trades — a flat year), 2025 +40.5/1.34, 2026 +109.7/1.77; M15 +13.2/1.40, +52.9/1.36, +77.3/1.43, +43.2/1.37; H1 +5.5/2.60, +14.7/1.58, +33.7/2.11, +16.1/2.35; H4 +1.2/1.28, +4.6/1.39, +7.7/1.74, +8.2/1.78.

Step 3a — seeding-depth measurement (seed_depth.out)

Deepest Oanda caches (H4 12,419 bars, D1 2,617, H1 45,914; 2018→2026-08-06), bars sampled every 12th (H4, 267 bars) / 2nd (D1, 313) / 48th (H1, 247) over the last two years; TechnicalEngine.analyze() on the trailing N bars vs on the full history; identical results with raw frames and with frames cleaned by validate_and_clean (the frame-wide σ of the outlier filter did not add a single flip).

TF N = live depth today bars with ≥1 indicator flip which max composite Δ N = 600 … 5000
H4 380 2 / 267 ema_trend 4.66 0 flips at every depth
D1 300 1 / 313 ema_trend 9.47 0 flips at every depth
H1 1460 (control) 0 / 247 — 0.00 0

Only the 200-bar EMA trend reading is affected, as the adjust=False seeding predicts; composite-signal and regime never flipped in the sample. Pre-registered pick = smallest zero-flip depth = 600; shipped floor = 1000 (seed weight e^-10, a ×40 margin on a sample of a few hundred bars; costs nothing — one request either way). The 2026-08-04 audit's "~2% of H4 bars" was measured on a different sample; this sample says 0.75% (H4) and 0.3% (D1) of bars, with composite errors up to 9.5 points on the ones that flip.

Step 1 — concentration, stability, bucket screen (W3 recorded books, diag_w3_head.txt)

TF top 5% of trades carry months positive longest losing streak
M5 94% of total R (44 trades, +144R of +154R) 62% (23/37) 8
M15 96% (64 trades, +179R of +187R) 68% (25/37) 9
H1 48% (13 trades) 71% (25/35) 4
H4 80% (6 trades) 50% (15/30) 6

The intraday edge is a fat right tail: trailing exits let a few dozen runners pay for everything else (M5 2024 was flat over 102 trades). H1 is the only timeframe whose edge is spread across its book.

Bucket screen (pre-registered criterion: n ≥ 30 and R < 0 in each 12-month slice y1/y2/y3 AND in the OOS slice of the same book). Exactly one bucket qualifies (besides the trivial exit_reason = SL):

  • M15 SELL grade A: 230 trades, −14.4R, PF 0.86, y1 −7.4 (67) / y2 −0.4 (80) / y3 −6.5 (83) / OOS −6.9 (62). Context: M15 SELL overall 404 / −15.2R / PF 0.91 (SELL max DD 36R on its own curve); SELL|B 160 / −5.5R (y3 +4.6 — not qualifying); SELL|A+ 14 / +4.7R; SELL|trending_down 301 / −10.9R (y2 +0.9 — not qualifying). This is the same bucket the 2026-09-03 campaign tested as sell_min_quality=["B"] (allow only B-grade M15 shorts) and REJECTED: 12mo +0.7R, 5-yr −8.9R with max DD 35.8 → 41.4R, OOS +8.3R — A-grade M15 shorts paid in 2021–2023. Under the owner's ≤3-year window they lose in every slice. Recorded as the ONE cycle-2 candidate from this screen (M15 sell_min_quality excluding "A" via the factory), to be run against a control with the 3-yr DD clause; the 5-yr verdict is the fact on the other side of the scale. Not built this cycle (rule: no gate from the screen).
  • Not qualifying / no evidence: holiday-day trades are POSITIVE on M15 (27 / +10.6R) and small everywhere; entries within 30 min after a high-impact USD release are positive (M15 114 / +9.3R); M15 SELL-in-transitioning (the gate-6c bucket) 75 / −2.2R with y2 and y3 positive; M15 vol_regime = high 291 / +4.1R / PF 1.03 (y3 +12.6); hour 7 UTC −4.0R (OOS +2.4). M5/H1/H4 produced no qualifying bucket.

Step 0 — reproducibility gate: PASS

book MD5 at HEAD ledger MD5 result
M5 W2 29120a1ee32979431d062f210d41a9d1 same equal
M15 W2 d185f099482a07f29502cb5a371a9bbf same equal
M15 OOS 6f8f24b49597ced28f50a19e08c3472f same equal
H1 OOS (new control, no prior MD5) c02eebc9066916d7637bbf5796993f87 — recorded

replay_scorecard.py --check: all eight invariants hold on every recorded W3 run.

Step 3b — live floor shipped (commit 9942736) and verified on the live feed

BaseDataProvider.MIN_LIVE_BARS = 1000, applied in a new pure helper _live_outputsize(timeframe, lookback_days); requested depths now M5 5000, M15 5000, H1 1460 (unchanged), H4 1000 (was 380), D1 1000 (was 300). The backtester never calls fetch_ohlcv, so no book changes. Tests: new Module 16 depth test, Module 21 cache test updated from 300 to 1000; suite 500 passed. Live verification 2026-09-10 (real Oanda fetch, same latest closed bar, engine on the frame cleaned exactly as live does):

  • H4 (bar 2026-09-10 04:00 UTC): 380 / 1000 / 5000 bars → identical readings (composite +15.03, ranging) — no flip on today's bar.
  • D1 (bar 2026-09-09): 300 bars → ema_trend SELL, composite −9.53; 1000 and 5000 bars → ema_trend NEUTRAL, composite −8.87. The old depth was mis-reading the daily trend today; the new floor agrees with the converged value.

Step 1 — cost ladder (W3, same trade lists; --spread only re-prices; mechanics check: the H1 $1.00 total predicted from the $0.65 book (−0.35 $/trade ÷ risk) equals the rerun to the cent)

TF $0.65 R / PF / DD $0.80 R / PF / DD (halves) $1.00 R / PF / DD $1.30 R / PF / DD red flag ($0.80: PF < 1.3 or a half ≤ 0)?
M5 +153.6 / 1.50 / 10.1 +145.6 / 1.46 / 11.0 (+14.9 / +130.7) +134.9 / 1.42 / 12.2 +118.8 / 1.36 / 14.1 no
M15 +186.6 / 1.39 / 19.3 +176.4 / 1.37 / 19.9 (+70.0 / +106.4) +162.8 / 1.34 / 20.8 +142.4 / 1.29 / 22.6 no (PF dips under 1.3 only at $1.30)
H1 +70.0 / 1.98 / 4.4 +68.9 / 1.96 / 4.5 (+25.6 / +43.2) +67.3 / 1.93 / 4.5 +65.0 / 1.89 / 4.7 no
H4 +21.7 / 1.59 / 5.4 +21.3 / 1.57 / 5.5 (+4.7 / +16.6) +20.8 / 1.56 / 5.7 +20.1 / 1.53 / 6.0 no

Doubling the assumed round-trip cost ($0.65 → $1.30) costs M5 23% and M15 24% of total R and H1/H4 about 7%; the H1 book is the least cost-sensitive because its stops are the widest in dollars. The M5 first half is thin at every price (+8.7R at $1.30 over 136 trades), which is the 2024 flat year again, not a cost effect.

C1 — H1 divergence multiplier → 0 in every regime: FAIL (a, b, c) — closed

book trades total R ΔR half 1 half 2 PF max DD last 12 mo OOS
H1 control (HEAD, W3) 253 +70.02 +26.26 +43.76 1.98 4.4 +24.62 (89) 78 / +16.68 / PF 1.74
H1 divergence 0 (W3) 280 +71.01 +0.99 +28.70 (+2.4) +42.30 (−1.5) 1.89 4.8 +23.09 (102) 88 / +21.91 / PF 1.92 (+5.2)

Clauses: (a) ΔR +1.0 < +5 FAIL; (b) second half negative FAIL; (c) PF 1.98 → 1.89, drop 0.09 > 0.05 FAIL; (d) DD +0.4 pass; (e) trades +10.7% pass; (f) OOS +5.2 pass. Calendar years: 2024 +14.7 → +19.1, 2025 +33.7 → +34.1, 2026 +16.1 → +12.4 — the gain sits in 2024, the most recent year gives it back. The OOS candidate book (88 / +21.91 / PF 1.92) is exactly the 2026-09-03 campaign's b12_h1_nosell_nodiv_oos under disabled_indicators — so a 0.0 regime multiplier is mechanically the same as disabling the indicator for the composite (engine skips only base weight 0, but an effective weight of 0 adds nothing to score, weight sum or confidence); the only residual difference is display (reasons text still lists the vote). The parked candidate's earlier evidence (5-yr +9.6R over no-shorts) was carried by 2021–2023; on the owner's 3-year window it is noise with a worse profit factor. Not shipped; do not re-propose without new years.

C2 — M15 RSI multiplier neighbourhood: STABLE PLATEAU (the 2026-09-09 ship stands)

Full regime table with the RSI multiplier × k (k = 1.0 is the pre-2026-09-09 table, k = 0.5 is HEAD; every other point is a --set run on the same harness).

k W2 trades / R / PF / DD (halves) G(k) = R − R(1.0) on W2 OOS trades / R / PF / DD ΔOOS W3 trades / R / PF / DD (halves)
0 (RSI off) 1091 / +145.46 / 1.35 / 18.5 (+74.1 / +71.3) +27.8 418 / +51.89 / 1.34 / 19.5 +11.3 1490 / +188.14 / 1.33 / 22.7 (+69.3 / +118.8)
0.25 1008 / +139.93 / 1.37 / 20.1 (+66.1 / +73.9) +22.3 (78% of G(0.5)) 390 / +59.55 / 1.44 / 17.3 +19.0 1374 / +187.58 / 1.36 / 21.4 (+77.7 / +109.9)
0.5 (HEAD) 931 / +146.14 / 1.43 / 16.1 (+66.1 / +80.0) +28.5 355 / +60.67 / 1.49 / 17.1 +20.1 1273 / +186.61 / 1.39 / 19.3 (+75.9 / +110.7)
0.75 854 / +137.83 / 1.45 / 14.9 (+47.6 / +90.3) +20.2 (71%) 326 / +55.56 / 1.49 / 15.6 +15.0 1176 / +178.81 / 1.42 / 17.2 (+66.9 / +111.9)
1.0 (old) 793 / +117.62 / 1.41 / 14.8 (+38.2 / +79.4) 0 300 / +40.59 / 1.38 / 15.5 0 1092 / +151.90 / 1.38 / 15.5 (+55.7 / +96.2)
1.5 676 / +110.17 / 1.46 / 14.2 (+39.4 / +70.7) −7.5 266 / +37.27 / 1.39 / 18.8 −3.3 949 / +136.20 / 1.39 / 18.8 (+42.6 / +93.6)

Reading (rule fixed in advance): G(0.25) = 78% and G(0.75) = 71% of G(0.5), both ≥ 50%; OOS deltas +19.0 and +15.0, both ≥ 0 → stable plateau, not a spike. The response is monotone in k on every window (R falls as RSI's weight rises; trade count falls ~90 per 0.25 step), PF peaks in the 0.5–0.75 band, and drawdown grows as k → 0 (RSI off has the same R as HEAD with PF 1.35 and DD 22.7 on W3). The shipped 0.5 sits inside a broad region, on its PF/DD-favourable side. Under this rule no other k is proposed.

Step 3c — backtester history pad targets the same 1000-bar seed (_history_pad_days)

Change: the per-TF warm-up pad now targets BaseDataProvider.MIN_LIVE_BARS (1000 bars, converted to calendar days with 7/5) instead of 210 bars, so a window's first bars are seeded like the rest of the window and like live. Intraday pads are unchanged (62 days already hold ≥ 1488 bars), so M5/M15/H1 keep their cache windows; H4 now pads 234 days (was 62 → 372 bars) and D1 1400 days (was 299 → ~210 bars for a 200-bar EMA). New caches oanda_XAU_USD_4h_20230114_20260905.parquet (5,826 bars) and _1d_20191105_20260905.parquet (2,124 bars); the first H4 chunk (2023-01-14, a Saturday) is legitimately empty. The raw-path fetch for trade resolution now reuses the primary timeframe's own padded start (one cache file per TF, as before). Test: Module 21 "history pad seeds slow TFs like live"; suite 501 passed.

W3 books before → after, trade-by-trade (bookdiff.py with the recorded runs):

TF trades / R before trades / R after trades added / removed / R changed what actually differs
H1 253 / +70.02 253 / +70.02 0 / 0 / 0 ONE confirmation reading: 2023-09-20 18:00 (15 days into the window), H4 ema_trend STRONG_BUY → BUY (value 1.0 → 0.5, confidence 90 → 65); that trade's blended score moves 0.21; nothing else in 5,761 recorded indicator rows
H4 114 / +21.68 114 / +21.68 0 / 0 / 0 no signal flips; 20 D1 rows (ADX/RSI/MACD Wilder recursions) differ at the 1e-6…1e-9 level, all Oct–Dec 2023; exported floats differ by ≤ 1.7e-10 (score ≤ 1.4e-9, R ≤ 1e-15)

Both differences sit in the window-start seeding zone, exactly where the deeper seed was meant to act, and neither moves a trade. The canonical H1 book therefore needs NO re-pin (trade content identical); only the CSV bytes differ (H1 3b45d12bade823178b267c13bc698458 vs b99111132d9ecb80c2998ed5aee2f3ea, H4 e75adae13c839b15736f69c0114e6aa5 vs 4508e7a3402e008ba1d93805094cd981), which the parity recipe must state.

M5 and M15 on the patched harness: byte-identical to Step 0 (M5 c2722f9830528658b53854f6bae61b8c, M15 b2cfa9e2e6cd3997f4d5ca9b49cab90e) — the pad change did not leak into the intraday books. Shipped (commit below); no re-pin needed.

Verdicts

item decision one-line reason
Step 0 reproducibility PASS three ledger MD5s reproduced at HEAD; scorecard invariants hold
Cost ladder no red flag every TF keeps PF ≥ 1.3 with positive halves at $0.80; edge survives $1.00 on all four
Concentration fact recorded M5/M15: top 5% of trades carry 94–96% of R; H1 48%
Bucket screen one cycle-2 candidate M15 grade-A SELLs (230 / −14.4R, negative in y1, y2, y3 and OOS); 5-yr history says the opposite — run it against a control with the DD clause before believing it
C1 H1 divergence → 0 REJECT ΔR +1.0 (< +5), second half −1.5R, PF 1.98 → 1.89; OOS-only gain
C2 M15 RSI ladder stable plateau neighbours keep 78% / 71% of the shipped gain, OOS positive at both; ship stands
3a/3b live seeding floor ACCEPT, shipped (9942736) measured flips at 380/300 bars, none at ≥ 600; live D1 flipped on 2026-09-10 at the old depth; books untouched
3c backtester pad ACCEPT, shipped no trade moved in any book; M5/M15 byte-identical; H1/H4 differ only in window-start numerics

What NOT to re-propose (added to the ledger's closed list)

  • H1 divergence weight 0 (any vehicle) without new years of data.
  • M15 RSI multiplier other than 0.5 on the strength of a single-point rerun; the ladder is on record.
  • Cost-driven gates: the books are not cost-fragile at $0.80–$1.00.

Shipped

  • 9942736 providers: MIN_LIVE_BARS = 1000 live floor + _live_outputsize (Module 16 test; Module 21 expectation 300 → 1000). Suite 500.
  • backtester: _history_pad_days (62 / 234 / 1400 days for intraday / H4 / D1), raw-path fetch reuses the primary's start (Module 21 test). Suite 501. Live H1/H4 scans and the books now share the same seeding depth; pinned H1/H4 CSVs are trade-identical but no longer byte-reproducible (float noise in exported indicator columns — same status the M5/M15 pins have had since 2026-09-07).
  • No signal-core change, no docs change (nothing user-visible moved), both news gating flags untouched.