Big-candle gate (default-OFF) + M5 relaxation campaign + --set tooling
Gate 6h (big-candle entry gate): blocks all XAU entries (core, XAU_PB, XAU_CP) when the primary TF's last 24h contains a bar with range >= K x prior-bar ATR. Ships default-OFF: validation rejected every threshold (K 3-6 on M5/M15 12mo) — removed trades were net-POSITIVE at every cell; M5 quiet-window trades are the only losing bucket. Config knobs big_candle_atr_multiple/lookback_hours, --big-candle backtest flag, Module 17 tests. M5 relaxation SHIPPED (validated 2026-07-30, Oanda bars, $0.30 spread): m5_min_abs_score 30->25, m5_min_confirmation_score 10->5, hour-21 UTC unblocked M5-only. Beats control in ALL 6 calendar years of the 5yr replay (+230.9R/2319 PF 1.26 DD 21.3 vs +168.0R/2048 PF 1.21 DD 27.0), 12mo +107.4R PF 1.36 vs +84.6R PF 1.33, OOS +26.1R vs +17.2R, $0.70 spread +86.9R vs +66.4R. M15: every relaxation retested negative — config unchanged. Declined-but-open: M5 hour-22 block, M15 hour-16 unblock (both fail 5yr/OOS). Evidence in the factory docstring. Tooling: generic --set PATH=JSON research override in gpu_backtest (typo-proof, logged); diagnose_filters filter_tags refreshed for gates 6d-6i with a staleness-guard test (Module 18); research scripts hour_analysis/burst_profile/book_diff. Suite: 268 tests. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014jqHpmNYmwYMowF6bPZyeD
This commit is contained in:
@@ -30,6 +30,7 @@ Usage:
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import logging
|
||||
import multiprocessing as mp
|
||||
import os
|
||||
@@ -2117,6 +2118,19 @@ def _parse_args(argv: Optional[List[str]] = None) -> argparse.Namespace:
|
||||
p.add_argument("--cp-patterns", default=None,
|
||||
help="Comma-separated pattern subset for --cp (e.g. "
|
||||
"double_bottom,bull_flag). Default: all detectors.")
|
||||
p.add_argument("--big-candle", type=float, default=None,
|
||||
help="Force xau_strategy.big_candle_atr_multiple=K for this "
|
||||
"run (no entries while the lookback window contains a "
|
||||
"bar with range >= K x prior ATR) — for validating the "
|
||||
"candidate.")
|
||||
p.add_argument("--big-candle-hours", type=float, default=None,
|
||||
help="Override big_candle_lookback_hours for --big-candle "
|
||||
"runs (default 24).")
|
||||
p.add_argument("--set", action="append", default=[], metavar="PATH=JSON",
|
||||
help="Override any PlatformConfig leaf by dotted path; the "
|
||||
"value is JSON-parsed for typing (research knob; "
|
||||
"repeatable). e.g. --set xau_strategy.m5_min_abs_score=25 "
|
||||
"--set 'xau_strategy.sell_min_quality=[\"A+\",\"A\",\"B\",\"C\"]'")
|
||||
p.add_argument("--no-cache", action="store_true", help="Skip parquet cache (force API fetch)")
|
||||
p.add_argument("--spread", type=float, default=None,
|
||||
help="Round-trip cost in price units per trade (default: pair's "
|
||||
@@ -2148,6 +2162,39 @@ def _resolve_tf(s: str) -> Timeframe:
|
||||
raise SystemExit(f"Unknown timeframe '{s}'. Valid: {[t.value for t in Timeframe]}")
|
||||
|
||||
|
||||
def _apply_config_overrides(cfg: PlatformConfig, overrides: List[str]) -> None:
|
||||
"""Apply repeatable --set PATH=JSON research overrides onto the config.
|
||||
|
||||
Unknown paths are a hard error: dataclass instances silently accept
|
||||
setattr of any name, so a typo'd path would otherwise produce a
|
||||
no-op run that looks like 'the relaxation had no effect'. Every
|
||||
applied override is logged — the run log is the config audit trail
|
||||
required by instructions_for_backtest.txt.
|
||||
"""
|
||||
for item in overrides:
|
||||
path, sep, raw = item.partition("=")
|
||||
if not sep:
|
||||
raise SystemExit(f"--set expects PATH=VALUE, got '{item}'")
|
||||
try:
|
||||
value = json.loads(raw)
|
||||
except json.JSONDecodeError:
|
||||
value = raw # bare-string convenience ("oanda" etc.)
|
||||
obj = cfg
|
||||
parts = path.strip().split(".")
|
||||
for attr in parts[:-1]:
|
||||
if not hasattr(obj, attr):
|
||||
raise SystemExit(f"--set: unknown config path '{path}'")
|
||||
obj = getattr(obj, attr)
|
||||
leaf = parts[-1]
|
||||
if not hasattr(obj, leaf):
|
||||
raise SystemExit(f"--set: unknown config path '{path}'")
|
||||
old = getattr(obj, leaf)
|
||||
if hasattr(old, "__dataclass_fields__"):
|
||||
raise SystemExit(f"--set: '{path}' is a config section, not a leaf")
|
||||
setattr(obj, leaf, value)
|
||||
logger.info(f"--set override: {path} = {value!r} (was {old!r})")
|
||||
|
||||
|
||||
def main(argv: Optional[List[str]] = None) -> int:
|
||||
args = _parse_args(argv)
|
||||
|
||||
@@ -2197,6 +2244,12 @@ def main(argv: Optional[List[str]] = None) -> int:
|
||||
platform_cfg.xau_strategy.cp_patterns = [
|
||||
s.strip() for s in args.cp_patterns.split(",") if s.strip()
|
||||
]
|
||||
if args.big_candle is not None:
|
||||
platform_cfg.xau_strategy.big_candle_atr_multiple = args.big_candle
|
||||
if args.big_candle_hours is not None:
|
||||
platform_cfg.xau_strategy.big_candle_lookback_hours = args.big_candle_hours
|
||||
if args.set:
|
||||
_apply_config_overrides(platform_cfg, args.set)
|
||||
confirm_tfs = list(platform_cfg.confirmation_timeframes)
|
||||
|
||||
end = (
|
||||
|
||||
Reference in New Issue
Block a user