154 lines
6.3 KiB
Python
154 lines
6.3 KiB
Python
"""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())
|