Files
zhutoutoutousan 605faf5310 Prepare source-only public release for develop.
Add cluster audit pipeline, united EA updates, brochure generators, and publication hygiene (gitignore, MT5 path desensitization, pre-upload scan). Remove tracked reports, models, and binary artifacts from the repo.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-02 15:03:43 +02:00

134 lines
5.2 KiB
Python

#!/usr/bin/env python3
"""Optimize SimpleEMA v5 (trend-leg cross + pullback)."""
from __future__ import annotations
import argparse
import json
import random
import sys
from dataclasses import asdict
from datetime import datetime
from pathlib import Path
import MetaTrader5 as mt5
import pandas as pd
ROOT = Path(__file__).resolve().parents[3]
LAB = Path(__file__).resolve().parent
sys.path.insert(0, str(ROOT / "backtesting" / "MT5"))
sys.path.insert(0, str(LAB))
from cluster_audit.backtest_core import CostModel, load_bars, resolve_symbol # noqa: E402
from run_backtest import pip_size # noqa: E402
from strategy_v5 import V5Params, load_v5_cache, market_from_cache, sample_v5, simulate_v5, write_v5_set # noqa: E402
def seed_params() -> list[V5Params]:
"""Known-good cross preset + selective pullback variants."""
return [
V5Params(fast_ema=10, slow_ema=36, cross_cooldown=2, htf_ema_period=100, use_pullback=False),
V5Params(fast_ema=10, slow_ema=46, cross_cooldown=8, htf_ema_period=200, use_pullback=False),
V5Params(fast_ema=7, slow_ema=46, cross_cooldown=5, use_pullback=False),
V5Params(
fast_ema=10, slow_ema=46, cross_cooldown=8, htf_ema_period=200,
use_pullback=True, pullback_touch=1, pullback_adx_min=22,
pullback_min_gap_pips=2.0, trend_leg_bars=56, pullback_cooldown=6,
max_pullbacks_per_leg=1,
),
]
def main() -> None:
ap = argparse.ArgumentParser()
ap.add_argument("--trials", type=int, default=2000)
ap.add_argument("--min-trades", type=int, default=150)
ap.add_argument("--max-trades", type=int, default=2500)
ap.add_argument("--seed", type=int, default=42)
args = ap.parse_args()
out = LAB
rng = random.Random(args.seed)
if not mt5.initialize():
raise SystemExit("MT5 init failed")
try:
sym = resolve_symbol("EURUSD")
df = load_bars(sym, mt5.TIMEFRAME_M15, datetime(2020, 1, 1), datetime(2026, 1, 1))
costs = CostModel.for_symbol(sym)
pip = pip_size(sym)
point = float(mt5.symbol_info(sym).point)
print(f"v5 optimize {sym} M15 trials={args.trials} trades={args.min_trades}-{args.max_trades}")
cache = load_v5_cache(df)
best_profit = None
best_balanced = None
target = None
trial_rows = []
def eval_params(p: V5Params, n: int) -> None:
nonlocal best_profit, best_balanced, target
md = market_from_cache(cache, p)
r = simulate_v5(md, sym, p, costs, pip, point)
trial_rows.append({"trial": n, "net": r.net_profit, "trades": r.total_trades, "pf": r.profit_factor, **asdict(p)})
if args.min_trades <= r.total_trades <= args.max_trades and r.net_profit > 0 and r.profit_factor >= 1.05:
if target is None or r.net_profit > target[0].net_profit:
target = (r, p)
print(f" HIT {n}: net=${r.net_profit:,.0f} t={r.total_trades} PF={r.profit_factor:.2f}")
if r.net_profit > 0:
if best_balanced is None or r.total_trades > best_balanced[0].total_trades or (
r.total_trades == best_balanced[0].total_trades and r.net_profit > best_balanced[0].net_profit
):
best_balanced = (r, p)
if best_profit is None or r.net_profit > best_profit[0].net_profit:
best_profit = (r, p)
for i, p in enumerate(seed_params(), 1):
eval_params(p, i)
print(f" seed {i}: net=${trial_rows[-1]['net']:,.0f} t={trial_rows[-1]['trades']} PF={trial_rows[-1]['pf']:.2f}")
for n in range(len(seed_params()) + 1, args.trials + 1):
eval_params(sample_v5(rng), n)
if n % 500 == 0:
b = best_balanced or best_profit
print(f" ... {n}/{args.trials} best_bal t={b[0].total_trades} net=${b[0].net_profit:,.0f} hit={'yes' if target else 'no'}")
final_r, final_p = target or best_balanced or best_profit
assert final_r and final_p
write_v5_set(final_p, out / "SimpleEMA_optimized.set")
payload = {
"version": 5,
"target_met": target is not None,
"params": asdict(final_p),
"metrics": {k: v for k, v in asdict(final_r).items() if k != "trades"},
}
(out / "best_params.json").write_text(json.dumps(payload, indent=2), encoding="utf-8")
pd.DataFrame(trial_rows).to_csv(out / "optimize_trials.csv", index=False)
(out / "best_run").mkdir(exist_ok=True)
rows = [
{
"side": t["side"],
"open_time": df.index[t["open_i"]],
"close_time": df.index[t["close_i"]],
"profit": t["profit"],
"exit_reason": t["exit_reason"],
}
for t in final_r.trades
]
pd.DataFrame(rows).to_csv(out / "best_run" / "trades.csv", index=False)
print(
f"\n{'TARGET' if target else 'BEST'}: net=${final_r.net_profit:,.2f} "
f"trades={final_r.total_trades} PF={final_r.profit_factor:.2f} WR={final_r.win_rate:.1f}% DD={final_r.max_drawdown_pct:.1f}%"
)
finally:
mt5.shutdown()
if __name__ == "__main__":
main()