# 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=` 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=`, 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.