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:
@@ -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.
|
(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).
|
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.
|
||||||
|
|||||||
@@ -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 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>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>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>
|
<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>
|
</ul>
|
||||||
|
|
||||||
|
|||||||
@@ -98,7 +98,7 @@
|
|||||||
</table>
|
</table>
|
||||||
<ul>
|
<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>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>
|
<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>
|
</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>
|
<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
@@ -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
|
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).
|
(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 ->
|
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-
|
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
|
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).
|
oppose vote is a load-bearing quality bar, like ROC).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from typing import Optional, List
|
from typing import Dict, Optional, List
|
||||||
from config import PlatformConfig, Timeframe
|
from config import PlatformConfig, TechnicalConfig, Timeframe
|
||||||
from app_config import apply_pair_overrides
|
from app_config import apply_pair_overrides
|
||||||
|
|
||||||
_CONFIRMATIONS = {
|
_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(
|
def make_config(
|
||||||
primary_tf: Timeframe,
|
primary_tf: Timeframe,
|
||||||
confirmation_tfs: Optional[List[Timeframe]] = None,
|
confirmation_tfs: Optional[List[Timeframe]] = None,
|
||||||
@@ -272,6 +302,9 @@ def make_config(
|
|||||||
# Later trailing-stop arm on M15 only — see module docstring.
|
# Later trailing-stop arm on M15 only — see module docstring.
|
||||||
config.signals.trail_arm_r = 0.70
|
config.signals.trail_arm_r = 0.70
|
||||||
config.signals.trail_dist_r = 0.75
|
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:
|
if primary_tf == Timeframe.H1:
|
||||||
# Block every H1 SELL — the SELL quality gate (6e in
|
# Block every H1 SELL — the SELL quality gate (6e in
|
||||||
# signal_aggregator.py) downgrades any actionable-grade short
|
# signal_aggregator.py) downgrades any actionable-grade short
|
||||||
|
|||||||
+60
@@ -2078,6 +2078,35 @@ def _():
|
|||||||
inline_cfg.xau_strategy.sell_blocked_hours_utc = [15, 17]
|
inline_cfg.xau_strategy.sell_blocked_hours_utc = [15, 17]
|
||||||
inline_cfg.signals.trail_arm_r = 0.70
|
inline_cfg.signals.trail_arm_r = 0.70
|
||||||
inline_cfg.signals.trail_dist_r = 0.75
|
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:
|
if tf == Timeframe.M5:
|
||||||
# Documented per-TF overrides (see xauusd.py docstring):
|
# Documented per-TF overrides (see xauusd.py docstring):
|
||||||
# hour 9 UTC blocked, hour 21 UNBLOCKED (2026-07-30
|
# 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"
|
"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
|
# SUMMARY
|
||||||
# ══════════════════════════════════════════════════════════════════════
|
# ══════════════════════════════════════════════════════════════════════
|
||||||
|
|||||||
Reference in New Issue
Block a user