Add M5 pullback entry archetype (XAU_PB): +140R combined on 12mo
Second entry pattern alongside the Keltner-breakout: enter on the retrace to EMA21/KC-mid in an established trend (EMA >=3/4 aligned, ADX>=20, retrace 0.8-6.0 ATR, resumption bar closed back in trend direction, RSI in the pullback zone not the extended zone). Detector lives in the aggregator so live and GPU-backtest paths share one implementation. Own gates (composite floor 15, confirmation-TF agreement, MTF opposition, vol regimes) with a diagnostic funnel. PB stops anchor to the pullback swing (~$8 vs ~$25 breakout), so PB trades get their own trail arm (0.8R vs 0.6R global) via per-trade trail overrides in the backtester and strategy-aware Telegram alerts. 12mo M5 validation: PB alone +38.3R/254 trades, positive both halves and all four full quarters; core breakout trades undisturbed (+101.9R vs +105.0 baseline). Combined book: 549 trades (~10.6/wk), +140.1R, PF 1.78, DD 11.3R vs 311 trades/+105.0R before.
This commit is contained in:
+26
-13
@@ -1094,7 +1094,10 @@ class Trade:
|
||||
final_confidence: float
|
||||
market_regime: str
|
||||
volatility_regime: str = ""
|
||||
strategy_tag: str = "" # "XAU", "EUR_PB", "GBP_PB", "GENERIC"
|
||||
strategy_tag: str = "" # "XAU", "XAU_PB", "EUR_PB", "GBP_PB", "GENERIC"
|
||||
# Per-trade trailing overrides (0 = use the run-level params).
|
||||
trail_arm_r: float = 0.0
|
||||
trail_dist_r: float = 0.0
|
||||
|
||||
# Per-instructions: entry reasoning and indicator breakdown ─────
|
||||
entry_reasons: List[str] = field(default_factory=list)
|
||||
@@ -1194,6 +1197,9 @@ def _simulate_trailing(
|
||||
t.exit_reason = "NO_DATA"
|
||||
continue
|
||||
|
||||
arm = t.trail_arm_r if t.trail_arm_r > 0 else trail_arm_r
|
||||
dist = t.trail_dist_r if t.trail_dist_r > 0 else trail_dist_r
|
||||
|
||||
cur_sl = t.sl
|
||||
peak = t.entry_price
|
||||
exit_price = None
|
||||
@@ -1217,9 +1223,9 @@ def _simulate_trailing(
|
||||
peak = max(peak, fav) if buy else min(peak, fav)
|
||||
exc = ((peak - t.entry_price) if buy
|
||||
else (t.entry_price - peak)) / risk
|
||||
if exc >= trail_arm_r:
|
||||
lvl = (peak - trail_dist_r * risk if buy
|
||||
else peak + trail_dist_r * risk)
|
||||
if exc >= arm:
|
||||
lvl = (peak - dist * risk if buy
|
||||
else peak + dist * risk)
|
||||
cur_sl = max(cur_sl, lvl) if buy else min(cur_sl, lvl)
|
||||
|
||||
if exit_price is None:
|
||||
@@ -1617,16 +1623,19 @@ def run_backtest(
|
||||
if report.signal_quality not in bt_cfg.quality_filter:
|
||||
continue
|
||||
|
||||
# Strategy tag (from bullish_reasons[0] which the aggregator pre-pends).
|
||||
# Strategy tag (from reasons[0] which the aggregator pre-pends).
|
||||
# Pullback SELLs mark bearish_reasons, so check both lists.
|
||||
tag = "GENERIC"
|
||||
if report.bullish_reasons:
|
||||
first = report.bullish_reasons[0]
|
||||
if "XAU STRATEGY" in first:
|
||||
tag = "XAU"
|
||||
elif "EUR STRATEGY" in first:
|
||||
tag = "EUR_PB"
|
||||
elif "GBP STRATEGY" in first:
|
||||
tag = "GBP_PB"
|
||||
first_bull = report.bullish_reasons[0] if report.bullish_reasons else ""
|
||||
first_bear = report.bearish_reasons[0] if report.bearish_reasons else ""
|
||||
if "XAU PULLBACK" in first_bull or "XAU PULLBACK" in first_bear:
|
||||
tag = "XAU_PB"
|
||||
elif "XAU STRATEGY" in first_bull:
|
||||
tag = "XAU"
|
||||
elif "EUR STRATEGY" in first_bull:
|
||||
tag = "EUR_PB"
|
||||
elif "GBP STRATEGY" in first_bull:
|
||||
tag = "GBP_PB"
|
||||
|
||||
if bt_cfg.strategies_only and tag not in bt_cfg.strategies_only:
|
||||
continue
|
||||
@@ -1684,6 +1693,10 @@ def run_backtest(
|
||||
indicators_opposing=oppose,
|
||||
indicators_neutral=neutral,
|
||||
indicators_total=total,
|
||||
trail_arm_r=(platform_cfg.signals.pb_trail_arm_r
|
||||
if tag == "XAU_PB" else 0.0),
|
||||
trail_dist_r=(platform_cfg.signals.pb_trail_dist_r
|
||||
if tag == "XAU_PB" else 0.0),
|
||||
))
|
||||
last_entry_pos = pos
|
||||
|
||||
|
||||
Reference in New Issue
Block a user