91 lines
3.8 KiB
Python
91 lines
3.8 KiB
Python
"""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())
|