"""Phase 5+6 entry point: optimize GoldScalperPro on real XAUUSD bars. Assembles ObjectiveConfig (engine + signals + search space + constraints), runs Optuna, applies the diverse top-N selector, then runs the robustness layers on the finalists and prints a report. This is the cycle that Phase 7 will feed into MT5 for verification. Usage: python run.py [--trials 200] [--top-n 3] [--deposit 10000] """ from __future__ import annotations import argparse import sys import time from pathlib import Path PROJECT = Path(__file__).resolve().parent sys.path.insert(0, str(PROJECT)) import optuna from shared.core.engine import SizingInputs from shared.data.loaders import load_bars from shared.optimizer.objective import ( Constraints, ObjectiveConfig, build_objective, ) from shared.optimizer.selector import select_diverse_topn from shared.robustness.layers import stability_region from strategies.gold_scalper_pro.instruments import XAUUSD_REAL from strategies.gold_scalper_pro.scalper_engine import ( ScalperConfig, ScalperEngine, config_from_params, engine_kwargs_from_params, ) from strategies.gold_scalper_pro.search_space import ( FROZEN_BASELINE, INT_PARAMS, SEARCH_SPACE, ) from strategies.gold_scalper_pro.signals import build_signals def find_bars_file() -> Path: """Auto-find the latest XAUUSD M5 parquet in data/.""" data_dir = PROJECT / "data" candidates = sorted(data_dir.glob("XAUUSD_M5_*.parquet")) if not candidates: raise FileNotFoundError(f"no XAUUSD M5 parquet in {data_dir}") return candidates[-1] def main() -> int: ap = argparse.ArgumentParser(description="Optimize GoldScalperPro.") ap.add_argument("--trials", type=int, default=100, help="Optuna trials (default 100)") ap.add_argument("--top-n", type=int, default=3, help="diverse finalists to select (default 3)") ap.add_argument("--deposit", type=float, default=10000.0, help="initial deposit (default 10000)") ap.add_argument("--bars", type=str, default="", help="path to parquet bars (blank = auto-find)") args = ap.parse_args() bars_path = Path(args.bars) if args.bars else find_bars_file() print(f"=== GoldScalperPro optimization ===") print(f"bars : {bars_path.name}") print(f"trials : {args.trials}") print(f"top-n : {args.top_n}") print(f"deposit : {args.deposit:,.0f} USD") print() bars = load_bars(bars_path) print(f"loaded {len(bars):,} bars {bars['timestamp'].iloc[0]} → {bars['timestamp'].iloc[-1]}") # ── Assemble the objective ──────────────────────────────────────────── constraints = Constraints( min_trades=25, min_profit_factor=1.2, # relaxed for first pass; tightened later max_equity_dd_pct=0.40, # 40% hard cap ) obj_cfg = ObjectiveConfig( engine=ScalperEngine(), bars=bars, instrument=XAUUSD_REAL, sizing=SizingInputs(), initial_deposit=args.deposit, search_space=SEARCH_SPACE, int_params=INT_PARAMS, frozen_baseline=FROZEN_BASELINE, constraints=constraints, dd_weight=1.0, build_signals=build_signals, build_engine_kwargs=engine_kwargs_from_params, ) objective = build_objective(obj_cfg) # ── Run Optuna ───────────────────────────────────────────────────────── optuna.logging.set_verbosity(optuna.logging.WARNING) study = optuna.create_study(direction="maximize", sampler=optuna.samplers.TPESampler(seed=42)) print(f"\nrunning {args.trials} trials ...") t0 = time.time() study.optimize(objective, n_trials=args.trials, show_progress_bar=False) elapsed = time.time() - t0 print(f"done in {elapsed:.1f}s ({elapsed/args.trials:.2f}s/trial)") # ── Report ──────────────────────────────────────────────────────────── best = study.best_trial print(f"\n=== best trial #{best.number} ===") print(f" score : {best.value:+,.2f}") print(f" net profit : {best.user_attrs['net_profit']:+,.2f}") print(f" profit factor : {best.user_attrs['profit_factor']:.2f}") print(f" trades : {best.user_attrs['total_trades']}") print(f" win rate : {best.user_attrs['win_rate']:.2%}") print(f" equity DD : {best.user_attrs['max_equity_dd']:,.2f} " f"({best.user_attrs['max_equity_dd_pct']:.2%})") print(f" sharpe : {best.user_attrs['sharpe']:.2f}") if best.user_attrs.get("violations"): print(f" violations : {best.user_attrs['violations']}") print(" params:") for k, v in best.user_attrs["params"].items(): if k in SEARCH_SPACE: print(f" {k:24s} = {v}") # ── Diverse top-N ───────────────────────────────────────────────────── print(f"\n=== diverse top-{args.top_n} finalists ===") finalists = select_diverse_topn(study, args.top_n, SEARCH_SPACE) for i, t in enumerate(finalists, 1): print(f" #{i} trial {t.number}: score={t.value:+,.2f} " f"net={t.user_attrs['net_profit']:+,.2f} " f"PF={t.user_attrs['profit_factor']:.2f} " f"trades={t.user_attrs['total_trades']}") # ── Stability region (is the best on a plateau?) ───────────────────── print(f"\n=== stability region ===") sr = stability_region(study, SEARCH_SPACE) print(f" passed : {sr.get('passed')}") print(f" cluster_size : {sr.get('cluster_size')}") print(f" best_in_cluster : {sr.get('best_in_cluster')}") if sr.get("reason"): print(f" reason : {sr['reason']}") print("\n=== done ===") print("Next: Phase 7 — generate .set/.ini for each finalist, run MT5, compare.") return 0 if __name__ == "__main__": raise SystemExit(main())