"""Smoke test: run the scalper engine end-to-end on real XAUUSD bars. Uses the frozen baseline params (the saved .set config). Verifies the engine + signals + metrics produce a sane result (trades, PnL, drawdown) before we wire the optimizer. This is the Phase 4 validation gate — not yet the MT5 fidelity check (that's Phase 7). """ from __future__ import annotations import sys from pathlib import Path PROJECT = Path(__file__).resolve().parent.parent sys.path.insert(0, str(PROJECT)) import numpy as np import pandas as pd from shared.core.engine import SizingInputs from shared.core.metrics import compute_metrics from shared.data.loaders import load_bars from strategies.gold_scalper_pro.instruments import XAUUSD_REAL from strategies.gold_scalper_pro.scalper_engine import ScalperConfig, ScalperEngine from strategies.gold_scalper_pro.search_space import FROZEN_BASELINE from strategies.gold_scalper_pro.signals import build_signals def main() -> int: bars_path = PROJECT / "data" / "XAUUSD_M5_2024-06-26_2026-06-26.parquet" print(f"loading {bars_path.name} ...") bars = load_bars(bars_path) print(f" {len(bars):,} bars {bars['timestamp'].iloc[0]} → {bars['timestamp'].iloc[-1]}") print("\nbuilding signals (frozen baseline params) ...") pack = build_signals(FROZEN_BASELINE, bars, XAUUSD_REAL) n_long = int(pack.signals_long.sum()) n_short = int(pack.signals_short.sum()) print(f" long signals : {n_long}") print(f" short signals: {n_short}") # Build ScalperConfig from frozen baseline (mirrors EA inputs). cfg = ScalperConfig( use_break_even=FROZEN_BASELINE["InpUseBreakEven"], use_trailing=FROZEN_BASELINE["InpUseTrailing"], use_session=FROZEN_BASELINE["InpUseSession"], session_start_hour=FROZEN_BASELINE["InpSessionStartHour"], session_end_hour=FROZEN_BASELINE["InpSessionEndHour"], max_positions=FROZEN_BASELINE["InpMaxPositions"], max_trades_per_day=FROZEN_BASELINE["InpMaxTradesPerDay"], daily_loss_limit_pct=FROZEN_BASELINE["InpDailyLossLimit"], daily_profit_target_pct=FROZEN_BASELINE["InpDailyProfitTarget"], min_seconds_between=FROZEN_BASELINE["InpMinSecondsBetween"], sizing_mode=FROZEN_BASELINE["InpSizingMode"], fixed_lots=FROZEN_BASELINE["InpFixedLots"], risk_percent=FROZEN_BASELINE["InpRiskPercent"], break_even_points=FROZEN_BASELINE["InpBreakEvenPoints"], break_even_lock=FROZEN_BASELINE["InpBreakEvenLock"], trail_start_points=FROZEN_BASELINE["InpTrailStartPoints"], trail_step_points=FROZEN_BASELINE["InpTrailStepPoints"], ) sizing = SizingInputs() # unused — sizing lives in ScalperConfig for this EA print("\nrunning engine ...") engine = ScalperEngine() result = engine.run( bars, pack.signals_long, pack.signals_short, pack.sl_prices, pack.tp_prices, XAUUSD_REAL, sizing, initial_deposit=10000.0, scalper_cfg=cfg, ) metrics = compute_metrics(result, periods_per_year=252 * 24 * 12) # M5 → ~72/year print("\n=== result (frozen baseline) ===") print(f" trades : {metrics.total_trades}") print(f" net profit : {metrics.net_profit:,.2f}") print(f" profit factor : {metrics.profit_factor:.2f}") print(f" win rate : {metrics.win_rate:.2%}") print(f" equity DD max : {metrics.max_equity_dd:,.2f} ({metrics.max_equity_dd_pct:.2%})") print(f" sharpe : {metrics.sharpe:.2f}") if result.trades: reasons = {} for t in result.trades: reasons[t.exit_reason] = reasons.get(t.exit_reason, 0) + 1 print(f" exit reasons : {reasons}") print(f" final balance : {result.final_balance:,.2f}") return 0 if __name__ == "__main__": raise SystemExit(main())