Fix M5 signal quality: ATR ordering bug, noise gates, blocked-hour leak (+41R)
ATR was computed after EMAs so ribbon compression detection was dead code on all timeframes — moved ATR first. Extended M5 swing S/R lookback from 50 to 120 bars. Added four M5-specific filters: min score, MTF agreement, confirmation TF conviction, and SELL quality cap (A-grade M5 shorts enter at dip extremes and lose). Fixed backtester blocked-hour check to use bar close time matching the live bot. Added trade geometry config (spread gate, SL floor, TP floor RR) and primary_tf mismatch guard in backtester. Backtest: 1133 trades/+11.5R → 419 trades/+41.0R, PF 1.03→1.37, DD 21→11R. Train/val split positive in both halves (+19.8R/+21.3R).
This commit is contained in:
@@ -1383,6 +1383,14 @@ def run_backtest(
|
||||
primary_timeframe=bt_cfg.primary_tf,
|
||||
confirmation_timeframes=list(bt_cfg.confirmation_tfs),
|
||||
)
|
||||
elif platform_cfg.primary_timeframe != bt_cfg.primary_tf:
|
||||
# A silent mismatch yields 0 trades: the replayer walks
|
||||
# bt_cfg.primary_tf bars while the aggregator looks for
|
||||
# platform_cfg.primary_timeframe in the slices and never finds it.
|
||||
raise ValueError(
|
||||
f"primary_tf mismatch: BacktestConfig={bt_cfg.primary_tf.value} "
|
||||
f"vs PlatformConfig={platform_cfg.primary_timeframe.value}"
|
||||
)
|
||||
|
||||
provider = ForexDataProvider(platform_cfg)
|
||||
|
||||
@@ -1464,6 +1472,15 @@ def run_backtest(
|
||||
if not (bt_filter.session_start_hour <= bar_hour < bt_filter.session_end_hour):
|
||||
skipped_reasons["SESSION_WINDOW"] = skipped_reasons.get("SESSION_WINDOW", 0) + 1
|
||||
continue
|
||||
# Blocked hours (live checks datetime.now().hour ≈ bar close).
|
||||
# analyze_from_dataframes step 6b checks bar-open timestamp,
|
||||
# which is one bar behind — misses the last 5min of each hour.
|
||||
_is_xau = PAIR_INFO[bt_cfg.pair]["display"] == "XAU/USD"
|
||||
_blocked = (platform_cfg.xau_strategy.blocked_hours_utc
|
||||
if _is_xau else bt_filter.blocked_hours_utc)
|
||||
if bar_hour in _blocked:
|
||||
skipped_reasons["SESSION_WINDOW"] = skipped_reasons.get("SESSION_WINDOW", 0) + 1
|
||||
continue
|
||||
|
||||
# Cooldown.
|
||||
if pos - last_entry_pos < bt_cfg.cooldown_bars:
|
||||
|
||||
Reference in New Issue
Block a user