xauusd M15: halve the RSI regime multiplier in every regime (M15 override 5, rule-1 validated)

Owner-approved 2026-09-09 after the pre-registered weight test
(backtest/research/weight_test_2026-09-09.md). M15-only
technical.regime_weights_override = the shipped table with rsi x 0.5
(0.5 trending family / 0.75 ranging / 0.5 transitioning), derived in the
factory and pinned literally by the Module 9 equivalence test; a Module 33
test locks that M5/H1/H4 carry no override.

Evidence, 2-year Oanda window 2024-09-05..2026-09-05 at $0.65: 793/+117.62R
PF 1.41 -> 931/+146.14R PF 1.43 (fixed halves +27.9/+0.6R vs control); OOS
2024-06-01..2025-03-30 300/+40.59R PF 1.38 -> 355/+60.67R PF 1.49; 3-year
+151.90R -> +186.61R (max DD 15.5 -> 19.3R, 2026 -4R). Parity: the factory
run with no --set flag reproduces the validated candidate books byte-for-
byte (2-yr MD5 d185f099..., OOS 6f8f24b4...). M15 canonical book re-pinned
to backtest/oanda_m15_2yr_065_20260909.csv. Docs carry a one-line note.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Nc9Ninzyi18QkWezrxeBvB
This commit is contained in:
Harwinder Sekhon
2026-09-09 17:36:41 -04:00
parent f6c7635c59
commit 2ac73017b8
6 changed files with 115 additions and 7 deletions
+4 -4
View File
File diff suppressed because one or more lines are too long
@@ -171,3 +171,17 @@ recent one does not, and the 3-year max drawdown is 3.8R deeper (all of it in th
(931 / +146.14R / PF 1.43 at $0.65). Not shipped in this campaign.
Books and logs: `backtest/research/replay_2026-09/candidates/` and `.../oos/` (gitignored).
## Shipped (owner decision 2026-09-09, same day)
M15 override 5 in `pair_signals/xauusd.py`: `technical.regime_weights_override` = the shipped
table with the RSI multiplier halved in every regime (0.5 trending family / 0.75 ranging /
0.5 transitioning), derived from the default table and pinned literally by the Module 9
factory-equivalence test; one more Module 33 test locks that M5/H1/H4 carry no override.
Parity proof — the factory run with NO `--set` flag reproduces the validated candidate books
byte-for-byte: 2-year MD5 `d185f099482a07f29502cb5a371a9bbf` (931 / +146.14R / PF 1.43) and
OOS MD5 `6f8f24b49597ced28f50a19e08c3472f` (355 / +60.67R / PF 1.49). Canonical M15 book
re-pinned to `backtest/oanda_m15_2yr_065_20260909.csv` (copy of the 2-year factory book).
Docs: `docs/user_guide.html` and `docs/beginner_guide.html` carry a one-line note (PDF builds
remain behind, as before). Suite 499 tests. Live M15 scans get a new `engine_fingerprint`
from the next deploy; signal-log comparisons across it must split at the deploy time.
+1
View File
@@ -138,6 +138,7 @@ The message says: <i>"once price hits 4,159, trail SL $33 behind best price."</i
<li><b>H1 trades are held longer</b> — often overnight, sometimes over days. That is normal. Your stop loss is working for you while you sleep.</li>
<li><b>H1 is BUY-only.</b> All H1 SELL signals are blocked (since 2026-09-03) because they were a losing side on this timeframe — expect to see BUY signals here, never SELL.</li>
<li><b>M5 also holds back some SELL signals</b> — specifically while the market is in a choppy "transitioning" phase, since those shorts tested poorly.</li>
<li><b>M15 got a small tune-up on 2026-09-09</b> — one of its nine indicators (RSI) now counts for half as much, which means a few more M15 signals per week. Nothing changes in how you act on a signal.</li>
<li><b>Never take the same signal on two timeframes.</b> They are usually the same gold move seen at different zoom levels. Taking both doubles your risk, not your profit.</li>
</ul>
+1 -1
View File
@@ -98,7 +98,7 @@
</table>
<ul>
<li><b>M5 — intraday, execution-critical.</b> Highest total edge, but stops are tight: a couple of dollars of slippage or a late entry eats a meaningful share of every trade. Suitable only if you can act within a minute or two of the alert (or automate). Trades open and close within a session; you sleep flat. SELL signals are held back while the market regime reads "transitioning" — a validated filter, not a bug if you notice fewer shorts there.</li>
<li><b>M15 — the middle path.</b> Same frequency as M5, wider stops, up to 15 minutes to act. About half your trades will still be open overnight.</li>
<li><b>M15 — the middle path.</b> Same frequency as M5, wider stops, up to 15 minutes to act. About half your trades will still be open overnight. Since 2026-09-09 the M15 scoring gives its RSI reading half the weight it used to (RSI mostly voted against the trend on this timeframe), so expect roughly 9 signals a week instead of 7–8; in the two-year test (Sep 2024 – Sep 2026) that change lifted M15 from +117.6R to +146.1R with the same win rate and a slightly better profit factor, and it also improved the separate out-of-sample period. The M15 column above predates it.</li>
<li><b>H1 — swing trading around a normal life.</b> Best quality per trade and very forgiving of slow manual execution (you have up to an hour). The costs: positions held for days, through news and overnight sessions, and a <b>lumpy</b> equity curve — one quarter of the test year was negative and most of the profit came in a single quarter. Judge it on quarters, not weeks. H1 no longer issues SELL signals at all (blocked since 2026-09-03) — it is BUY-only on this timeframe.</li>
</ul>
<div class="callout"><b>If you trade manually from the Telegram alerts, H1 (or M15) is the honest choice.</b> M5's backtested edge assumes near-instant fills that a human copying signals rarely achieves.</div>
+35 -2
View File
@@ -90,6 +90,23 @@ metric. One clause waived BY OWNER DECISION 2026-07-21: 5yr max DD is
accepted in exchange for +20% total R. M5 keeps the global 0.6 arm
(same per-TF divergence pattern as tp_floor_rr and the hour-9 block).
M15 override 5 — RSI regime multiplier x 0.5 in every regime
(validated 2026-09-09, 2-year Oanda window 2024-09-05 -> 2026-09-05 at
$0.65, pre-registered rule in backtest/research/
weight_test_2026-09-09.md): 793/+117.62R PF 1.41 DD 14.8R ->
931/+146.14R PF 1.43 DD 16.1R, fixed halves +27.9/+0.6R vs control;
OOS 2024-06-01 -> 2025-03-30 300/+40.59R PF 1.38 -> 355/+60.67R
PF 1.49; 3-year 1092/+151.90R PF 1.38 -> 1273/+186.61R PF 1.39 (max DD
15.5 -> 19.3R, all in the 2023-24 year; 2026 -4R is the one calendar
year that does not improve). Mechanism: RSI is neutral on ~73% of M15
entries and votes against the trend when it fires; halving it lets more
trend-following entries clear the score band (210 added trades +26.8R,
mostly B-grade BUYs; 72 removed -7.1R). Candidate came from the
recorded replay screen (replay_recording_2026-09-09.md); the same
screen's M5 picks (MACD-down) all FAILED the replay, and ema_trend x 2
/ divergence -> 0 on M15 failed too. Canonical M15 book re-pinned to
backtest/oanda_m15_2yr_065_20260909.csv.
M5 override (validated 2026-07-11, full-history replay 2020-03-17 ->
2026-07-11 + 12mo book, $0.30 spread): block hour 9 UTC (post-London-
open whipsaw). Hour-9 entries lost -28.9R/143 over 6 years, negative
@@ -210,8 +227,8 @@ bollinger all look mis-weighted in-book and all replay worse when
oppose vote is a load-bearing quality bar, like ROC).
"""
from typing import Optional, List
from config import PlatformConfig, Timeframe
from typing import Dict, Optional, List
from config import PlatformConfig, TechnicalConfig, Timeframe
from app_config import apply_pair_overrides
_CONFIRMATIONS = {
@@ -222,6 +239,19 @@ _CONFIRMATIONS = {
}
def _m15_regime_weights() -> Dict[str, Dict[str, float]]:
"""Shipped regime weight table with the RSI multiplier halved in
every regime — the M15-only override validated 2026-09-09 (see the
module docstring, M15 override 5). Derived from the default table so
the intent ("RSI x 0.5, everything else as shipped") stays explicit;
the Module 9 equivalence test pins the resulting numbers."""
from engine import regime_weight_table # local import: keep the factory import-light
table = regime_weight_table(TechnicalConfig()) # a fresh TechnicalConfig has no override -> the shipped table
return {regime: {name: (mult * 0.5 if name == "rsi" else mult)
for name, mult in weights.items()}
for regime, weights in table.items()}
def make_config(
primary_tf: Timeframe,
confirmation_tfs: Optional[List[Timeframe]] = None,
@@ -272,6 +302,9 @@ def make_config(
# Later trailing-stop arm on M15 only — see module docstring.
config.signals.trail_arm_r = 0.70
config.signals.trail_dist_r = 0.75
# RSI regime multiplier × 0.5 in every regime on M15 only — see
# module docstring (M15 override 5, validated 2026-09-09).
config.technical.regime_weights_override = _m15_regime_weights()
if primary_tf == Timeframe.H1:
# Block every H1 SELL — the SELL quality gate (6e in
# signal_aggregator.py) downgrades any actionable-grade short
+60
View File
@@ -2078,6 +2078,35 @@ def _():
inline_cfg.xau_strategy.sell_blocked_hours_utc = [15, 17]
inline_cfg.signals.trail_arm_r = 0.70
inline_cfg.signals.trail_dist_r = 0.75
# M15 override 5 (2026-09-09): RSI x 0.5 in every regime —
# LITERAL so a silent default-table change fails loudly here.
inline_cfg.technical.regime_weights_override = {
"trending_up": {
"ema_trend": 2.0, "rsi": 0.5, "macd": 2.0,
"bollinger": 0.5, "keltner": 1.5, "roc": 1.5,
"candlestick": 0.8, "divergence": 1.0, "adx": 0.8,
},
"trending_down": {
"ema_trend": 2.0, "rsi": 0.5, "macd": 2.0,
"bollinger": 0.5, "keltner": 1.5, "roc": 1.5,
"candlestick": 0.8, "divergence": 1.0, "adx": 0.8,
},
"trending": {
"ema_trend": 2.0, "rsi": 0.5, "macd": 2.0,
"bollinger": 0.5, "keltner": 1.5, "roc": 1.5,
"candlestick": 0.8, "divergence": 1.0, "adx": 0.8,
},
"ranging": {
"ema_trend": 0.3, "rsi": 0.75, "macd": 1.5,
"bollinger": 1.5, "keltner": 2.0, "roc": 0.5,
"candlestick": 1.0, "divergence": 2.5, "adx": 0.5,
},
"transitioning": {
"ema_trend": 1.5, "rsi": 0.5, "macd": 2.5,
"bollinger": 1.0, "keltner": 2.5, "roc": 2.0,
"candlestick": 1.0, "divergence": 2.0, "adx": 1.0,
},
}
if tf == Timeframe.M5:
# Documented per-TF overrides (see xauusd.py docstring):
# hour 9 UTC blocked, hour 21 UNBLOCKED (2026-07-30
@@ -11861,6 +11890,37 @@ def _():
"the GPU engine must inherit the live composite, never shadow it"
@test("replay record follow-up: M15 factory halves RSI in every regime, other TFs carry no override")
def _():
# M15 override 5 (validated 2026-09-09): the M15 factory ships a
# regime_weights_override that is the shipped table with the RSI
# multiplier halved everywhere and every other cell untouched.
import engine as _eng
from pair_signals import PAIR_PRODUCERS
default = _eng.regime_weight_table(TechnicalConfig())
m15 = PAIR_PRODUCERS[XAUUSD](Timeframe.M15, [Timeframe.M5, Timeframe.H1])
override = m15.technical.regime_weights_override
assert set(override) == set(default), \
f"M15 override must cover exactly the shipped regimes: {sorted(override)}"
for regime, weights in default.items():
got = override[regime]
assert set(got) == set(weights), \
f"{regime}: indicator set changed: {sorted(got)}"
for name, mult in weights.items():
want = mult * 0.5 if name == "rsi" else mult
assert got[name] == want, \
f"{regime}.{name}: {got[name]} != {want} (default {mult})"
# The engine really runs with it (regime_weight_table prefers the override).
assert _eng.regime_weight_table(m15.technical) == override
# ...and it is M15-only — same per-TF pattern as every other override.
for tf, ctfs in [(Timeframe.M5, [Timeframe.M15, Timeframe.H1]),
(Timeframe.H1, [Timeframe.M15, Timeframe.H4]),
(Timeframe.H4, [Timeframe.H1, Timeframe.D1])]:
cfg = PAIR_PRODUCERS[XAUUSD](tf, ctfs)
assert cfg.technical.regime_weights_override == {}, \
f"{tf} must carry no regime weight override"
# ══════════════════════════════════════════════════════════════════════
# SUMMARY
# ══════════════════════════════════════════════════════════════════════