# Risk-reward floor and M15 grade-A SELL block — pre-registered campaign, 2026-09-26 Owner questions (2026-09-26): plans sometimes show a target smaller than the stop (RR 0.75 / 0.5); and "we still lose money". The live review of `logs/signals.db` (2026-09-04 → 2026-09-25, 100 unique setups replayed on the scans' own stored candles) found: no stop-hunt signature (52 stop-outs, 71% ran ≥ 1R past the stop, the same as the books' 70–72%); −25.1R of the −37.8R raw live book were consecutive-bar re-entries the backtester's 5-bar cooldown never takes; the cooldown view (−12.7R / 57) sits at the 3rd–5th percentile of three-week windows of the two-year books. This file was written BEFORE any candidate run. Rules first, results appended below them. ## Harness `venv/bin/python backtest/cudaBasedBacktesting.py --pair XAUUSD --primary-tf --start --end --spread <0.65|0.30> [--set ...] --export-csv ` (CUDA research twin; its parity with `gpu_backtest.py` was re-proven today: the M15 and M5 two-year controls reproduce the pinned MD5s `b4aa07e33e17e23774c486a6900d673b` and `49f8e602fc6ae769713c94ef7b257810`). Factory configuration; `--set` is the only override. A passing candidate is re-run once on `gpu_backtest.py` before it ships and the two MD5s must be equal. Windows: W2 2024-09-05 → 2026-09-05 (halves split at 2025-09-05); OOS 2024-06-01 → 2025-03-30 (halves at the midpoint 2024-10-30); W3 2023-09-05 → 2026-09-05 (the owner's 3-year maximum, halves at 2025-03-05; used for the max-DD clause). Costs $0.65 (headline) and $0.30. ## Candidates | # | TF | `--set` | Hypothesis | |---|---|---|---| | B1 | M15 | `signals.min_rr_ratio=1.0` | Vetoes the grade-A XAU_ATR plan (RR 0.75 / 0.5); the signal falls through to the GENERIC swing setup whose M15 floor is already 1R. Every M15 plan then shows RR ≥ 1. Two-year book: XAU_ATR 174 / +5.7R / 24% wrong-direction vs GENERIC 757 / +140.5R / 16% | | B2 | M5 | `signals.tp_floor_rr=1.0` | Retest of the 1R target floor on M5 (failed 2026-07-11 on Twelve Data bars at $0.30). Expected to fail again: it lifts low-ATR targets past the $9 spread gate | | B3 | M15 | `xau_strategy.sell_min_quality=["A+","B"]` | Parked cycle-2 candidate: M15 grade-A SELLs 230 / −14.4R, negative in y1, y2, y3 and OOS (cycle_2026-09-10.md). Counter-fact on record: those shorts paid in 2021–22 | | B4 | M15 | B1 + B3 | The shipped combination if both pass alone | ## Acceptance rule (CLAUDE.md rule 1, written before the runs) A candidate PASSES only if, at $0.65, on W2: (a) total R ≥ control + 5R; (b) both halves ≥ the control's halves − 1R (no half may fund the other); (c) PF ≥ 1.3 and ≥ control − 0.02; (d) OOS total R ≥ control − 1R; (e) W3 max drawdown ≤ control + 10%; (f) the same sign of ΔR at $0.30. A candidate that changes fewer than 20 trades on W2 is "no evidence", not a pass. B4 must pass the rule on its own numbers, not by inheritance. Judge run-level numbers, never the added/removed bucket alone (the 5-bar cooldown displaces entries). Trade diff keyed on (entry time, direction). ## Results (all runs on the CUDA twin, `Backend: CuPy GPU (NVIDIA GeForce RTX 4070 SUPER, cupy 14.0.1)`, 0 cache fallbacks; controls reproduce the pinned MD5s) | TF | Window | Cost | Candidate | Trades | Total R | H1 | H2 | WR | PF | Max DD | MD5 (8) | |---|---|---|---|---|---|---|---|---|---|---|---| | M15 | W2 | $0.65 | control | 931 | +146.14 | +66.14 | +80.00 | 60.5% | 1.43 | 16.1 | b4aa07e3 | | M15 | W2 | $0.65 | B1 | 931 | +144.16 | +65.09 | +79.07 | 60.3% | 1.42 | 16.1 | 80428e98 | | M15 | W2 | $0.65 | **B3** | 813 | **+154.15** | +70.10 | +84.04 | 62.2% | 1.53 | 15.6 | 497520ef | | M15 | W2 | $0.65 | B4 | 813 | +153.41 | +70.73 | +82.68 | 62.1% | 1.53 | 15.6 | 177b716d | | M15 | W2 | $0.30 | control | 931 | +161.44 | +75.63 | +85.81 | 61.0% | 1.48 | 14.9 | 6bdea010 | | M15 | W2 | $0.30 | B1 | 931 | +158.17 | +74.41 | +83.75 | 60.7% | 1.47 | 14.9 | 21dcfc9e | | M15 | W2 | $0.30 | B3 | 813 | +167.53 | +78.61 | +88.91 | 62.6% | 1.59 | 14.5 | aff050f7 | | M15 | W2 | $0.30 | B4 | 813 | +166.06 | +79.12 | +86.95 | 62.5% | 1.58 | 14.8 | 03a412b8 | | M15 | OOS | $0.65 | control | 355 | +60.67 | +9.14 | +51.52 | 60.8% | 1.49 | 17.1 | 9fd624fb | | M15 | OOS | $0.65 | B1 | 355 | +60.67 | +9.14 | +51.52 | 60.8% | 1.49 | 17.1 | 9fd624fb (identical) | | M15 | OOS | $0.65 | B3 = B4 | 313 | +68.50 | +18.87 | +49.63 | 63.6% | 1.67 | 12.4 | 09b8a8e7 | | M15 | OOS | $0.30 | control | 355 | +69.18 | +13.48 | +55.70 | 61.7% | 1.57 | 14.9 | 30a82817 | | M15 | OOS | $0.30 | B3 = B4 | 313 | +76.21 | +22.92 | +53.29 | 64.5% | 1.77 | 11.1 | 5fc34cef | | M15 | W3 | $0.65 | control | 1273 | +186.61 | +75.87 | +110.74 | 59.4% | 1.39 | 19.3 | 5194e5d4 | | M15 | W3 | $0.65 | B1 | 1273 | +183.78 | +75.83 | +107.95 | 59.2% | 1.39 | 19.3 | 2d5cfc31 | | M15 | W3 | $0.65 | B3 | 1105 | +195.79 | +76.87 | +118.92 | 61.1% | 1.49 | 20.9 | 8bed7199 | | M15 | W3 | $0.65 | B4 | 1105 | +194.19 | +76.00 | +118.18 | 61.0% | 1.48 | 20.9 | 05201726 | | M15 | W3 | $0.30 | control / B1 / B3 / B4 | 1273 / 1273 / 1105 / 1105 | +210.42 / +206.25 / +216.53 / +214.19 | | | | 1.45 / 1.44 / 1.55 / 1.54 | 17.7 / 17.7 / 19.6 / 19.6 | | | M5 | W2 | $0.65 | control | 781 | +146.07 | +26.42 | +119.65 | 52.9% | 1.52 | 10.1 | 49f8e602 | | M5 | W2 | $0.65 | B2 | 1105 | +194.94 | +54.11 | +140.83 | 53.6% | 1.50 | 15.9 | 351f9eee | | M5 | W2 | $0.30 | control / B2 | 781 / 1105 | +162.09 / +220.32 | +31.71 / +65.22 | +130.37 / +155.10 | | 1.60 / 1.58 | 8.9 / 13.7 | | | M5 | OOS | $0.65 | control | 86 | +9.47 | +0.01 | +9.46 | 53.5% | 1.33 | 8.2 | da497d74 | | M5 | OOS | $0.65 | B2 | 235 | +13.17 | −6.01 | +19.18 | 52.3% | 1.17 | 12.3 | 3cece33b | | M5 | OOS | $0.30 | control / B2 | 86 / 235 | +12.42 / +20.66 | +1.65 / −2.04 | +10.77 / +22.69 | | 1.46 / 1.28 | 6.7 / 9.5 | | | M5 | W3 | $0.65 | control | 870 | +153.64 | +16.73 | +136.91 | 53.3% | 1.50 | 10.1 | 408de695 | | M5 | W3 | $0.65 | B2 | 1333 | +223.87 | +47.49 | +176.38 | 53.4% | 1.48 | 15.9 | f89176d6 | | M5 | W3 | $0.30 | control / B2 | 870 / 1333 | +172.40 / +256.20 | | | | 1.57 / 1.56 | 8.9 / 13.7 | | Trade diffs on W2 at $0.65 (key = entry time + direction): - B1: 0 added, 0 removed, 931 common. The 158 grade-A XAU_ATR setups become GENERIC (strategy mix 915 / 16 vs 757 / 174); same bars, same direction, a swing-anchored stop instead of 2 × ATR. - B3: 162 removed (−8.11R, all grade-A SELLs), 44 added (−0.11R, cooldown displacement), 769 common. Direction mix 635 BUY / 178 SELL vs 635 / 296. - B2: 392 added (+63.63R), 68 removed (+17.23R), 713 common. ### Verdicts against the pre-registered rule | # | (a) ΔR ≥ +5 W2 | (b) halves | (c) PF | (d) OOS | (e) W3 DD ≤ +10% | (f) $0.30 sign | Verdict | |---|---|---|---|---|---|---|---| | B1 | −2.0 ✗ | −1.05 / −0.93 ✓ | 1.42 ✓ | 0 ✓ | 19.3 ✓ | −3.3 ✓ | **FAIL (a)**. Trade-identical; the GENERIC stop is not better than the 2 × ATR stop on the same grade-A signals (W3 −2.8R). Not shipped | | B2 | +48.9 ✓ | +27.7 / +21.2 ✓ | 1.50 ✓ (−0.02) | +3.7 ✓ (but OOS PF 1.17, OOS H1 −6.0) | 15.9 vs 10.1 = +57% ✗ | +58.2 ✓ | **FAIL (e)**. Owner's call, see the ladder below | | B3 | +8.0 ✓ | +4.0 / +4.0 ✓ | 1.53 ✓ | +7.8 ✓ | 20.9 vs 19.3 = +8% ✓ | +6.1 ✓ | **PASS a–f — SHIPPED** | | B4 | +7.3 ✓ | ✓ | 1.53 ✓ | +7.8 ✓ | +8% ✓ | +4.6 ✓ | Passes, but 0.7R below B3 alone on W2 and 1.6R on W3; B1's part adds nothing. Not shipped | B3 per year on W3 ($0.65, control → B3): 2023 (Sep–Dec) +13.2 → +10.8; 2024 +52.9 → +53.3; 2025 +77.3 → +81.2; 2026 (to Sep 5) +43.2 → +50.5. Removed grade-A SELLs by year: 2023 15 / +1.4R, 2024 76 / −1.3R, 2025 79 / −8.6R, 2026 60 / −5.8R. The max-DD episode is the same one in both books (2023-12-01 → 2024-02-23), 19.3R → 20.9R. M15 SELLs remaining under B3 on W3: A+ 19 / +2.6R, B 217 / −8.6R. The 5-year fact on the other side of the scale stands (shorts paid in 2021–22). ### M5 target-floor ladder (informational, run after B2's verdict; NOT pre-registered, NOT shipped) `--set signals.tp_floor_rr=` on M5, $0.65: | Floor | W2 trades / R / PF / DD | OOS trades / R / PF / DD | W3 trades / R / PF / DD | |---|---|---|---| | 0.5 (shipped) | 781 / +146.1 / 1.52 / 10.1 | 86 / +9.5 / 1.33 / 8.2 | 870 / +153.6 / 1.50 / 10.1 | | 0.6 | 868 / +159.1 / 1.51 / 10.2 | 105 / +1.5 / 1.04 / 12.5 | 979 / +164.5 / 1.46 / 17.1 | | 0.75 | 975 / +192.4 / 1.57 / 13.4 | 147 / +13.5 / 1.31 / 9.4 | 1133 / +211.6 / 1.54 / 13.4 | | 0.9 | 1053 / +209.3 / 1.58 / 13.5 | 194 / +22.8 / 1.41 / 7.5 | 1252 / +238.7 / 1.55 / 13.5 | | 1.0 (B2) | 1105 / +194.9 / 1.50 / 15.9 | 235 / +13.2 / 1.17 / 12.3 | 1333 / +223.9 / 1.48 / 15.9 | Reading: the 2026-07-11 verdict ("a 1R floor hurts M5") was made on Twelve Data bars at $0.30 with the pre-relaxation gates and the pullback entries on; on Oanda bars at $0.65 with today's gates the floor lifts small-ATR targets past the $9 spread gate and those trades earn (B2 added trades on W3: 554 / +90.8R, 53% winners, median stop $12 vs $20, 962 of 1,105 plans at exactly RR 1.0; positive in every year; by volatility regime extreme +44.6R, low +32.0R, normal +14.2R). The cost is a deeper drawdown in the same May–July 2026 episode (10.1R → 13.4–15.9R) and a non-monotone ladder (0.9 beats 1.0 on every window, 0.6 is worse than 0.5 out of sample), which is why nothing from it ships now. If the owner wants M5 plans at RR ≥ 1 (the original request), the honest candidate is a fresh pre-registered run at ONE value chosen before looking (0.75 or 1.0), with the DD clause waived explicitly as on 2026-07-21, plus the cost ladder ($0.80, $1.00) because the added trades carry $12 stops. Until then M5 plans keep 0.5R guides and the alert says so. ## Shipped (2026-09-26) 1. **A1 — alert cooldown** (`telegram_bot.py`): no new alert on a timeframe within 5 bars of the last alerted setup's bar, `_ALERT_COOLDOWN_BARS = 5` = `backtest.cooldown_bars`. Book-neutral. 2. **A2 — alert wording** (`telegram_bot.py`, `main.py`, `docs/user_guide.html`): the trail is printed first as the tested exit; TP1/TP2 are guides with their R; "Planned TP1 RR"; one validated-book line per setup class from `agent_panel_base_rates.json`. Book-neutral. 3. **B3 — M15 grade-A SELL block** (`pair_signals/xauusd.py`, M15 override 6). Re-pins the M15 books (below). 4. **XAU_ATR target floor** (`signal_aggregator._generate_xau_atr_trade_setup`, mirrored in `cuda_signal_kernels.py` K2): the gold ATR generator now honours `tp_floor_rr` like the GENERIC one, compared as multipliers so the non-binding case is bit-identical. Binds only on M15 (floor 1R): grade-A plans print TP1 at 2 × ATR = 1R instead of 1.5 × ATR = 0.75R. TP1 is advisory in the backtester, so the only book effect is through the $9 spread gate (parity MD5s below). ## Addendum (same day): printed-target floor extended to every generator Owner rule: "make sure TP1 is at least 1:1". Shipped as `signals.tp_print_floor_rr = 1.0` (effective floor `max(tp_floor_rr, tp_print_floor_rr)`), applied after the min-RR and spread gates in the gold ATR, GENERIC and chart-pattern generators (Python and CUDA K2). Label-only by construction and by measurement (twin books vs the previous pins, same code otherwise): | Book | Rows | R before → after | Columns that differ | Rows with a new `tp1` | min `rr_planned` before → after | |---|---|---|---|---|---| | M5 W2 | 781 | +146.07 → +146.07 | `tp1`, `rr_planned` | 544 (GENERIC 408, XAU_CP 105, XAU_ATR 31) | 0.5 → 1.0 | | M15 W2 | 813 | +154.15 → +154.15 | none | 0 | 1.0 → 1.0 | | H1 W3 | 253 | +70.02 → +70.02 | `tp1`, `rr_planned` | 252 | 0.5 → 1.0 | | H4 W3 | 114 | +21.68 → +21.68 | `tp1`, `rr_planned` | 108 | 0.5 → 1.0 | New pins: M5 W2 `e599c8a8652f95b7fcdf5a7ef2b564f5`, H1 W3 `a44fd19b4067f46e5bd98d50cc3eff92`, H4 W3 `f3587bae3c5dc3eda468ea3588b17dcb`; M15 W2 `5b65ecf6…` and OOS `09b8a8e7…` unchanged. Reference script MD5s: see the parity lines appended below. The gated floor (`tp_floor_rr`, 0.5 on M5 / 1.0 on M15) is untouched: raising it is the B2 question above, a book change, and remains the owner's call. ## Addendum 2 (same day): consistency checks on `signals.db` — two delivery bugs fixed Read-only checks over the 8,453 rows (2026-09-04 → 09-25): scan latency (median 10–15 s after the candle close on every timeframe; 2–3% of rows read the previous bar, all in 18–22 UTC around the Labor Day gap and the settlement pause), `actionable` / setup consistency (clean), gate reasons for actionable grades without a setup (all `SPREAD GATE`, small-ATR M5 bars), outcome states (97 resolved, 36 duplicates, 1 unresolvable for a candle gap), scan-outcome labelling (7,054 of 7,634 complete), 8 `failed` scans (Oanda "primary timeframe data unavailable", notably 06:11–06:17 UTC on both Sep 15 and Sep 16), dashboard-vs-bot disagreement on the same bar (4 bars of 3,300). Two bugs, both fixed and tested, books untouched: 1. **Panel probability never stored under `verdict_source: "macro"`** (the live setting): all 33 v2 rows had `panel_probability` NULL although `panel_probs_json` carried the macro persona's `p_win`. Cause: the macro-source `PanelVerdict` carried no `probability`, `_write_fields` wrote 0.0 and the logger's `probability if probability else None` dropped it. Fix: the macro verdict's probability is the macro take's `p_win` (`agent_panel.run_panel`). Without it the planned re-evaluation at 100 / 300 outcomes would have had no live probabilities to score. 2. **Breach rescans re-log the last pre-pause M15 bar.** Every evening the 20:45 UTC bar was logged again at 22:01, 22:06 and 22:11 (Sep 17–24) by the tick-driven level-breach rescan: the first post-pause bar closes at 22:15, so the frame still ends at 20:45. The candle loop already skips that window (`_closed_bar_in_settlement`); `_handle_breach` now applies the same guard. Cosmetic for alerts (dedup + cooldown), but it polluted the log's per-bar dedup and the parity reports. Not bugs, recorded: 380 `blocked` weekend rows on Sep 20 carry no `bar_ts` by design; the 43× repeat of the Sep 7 18:25 M5 bar is the known holiday gap (no holiday calendar); the H4 loop produced no setup in three weeks (its two tradeable close hours).