605faf5310
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>
198 lines
6.6 KiB
Python
198 lines
6.6 KiB
Python
"""
|
|
Batch MT5 genetic optimization per symbol → regenerate RSIScalpingSuperParams.mqh
|
|
|
|
Usage:
|
|
python run_mt5_cluster.py optimize --symbols EURUSD,GBPUSD,USDJPY
|
|
python run_mt5_cluster.py optimize --all-forex
|
|
python run_mt5_cluster.py backtest-portfolio
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import re
|
|
import subprocess
|
|
import sys
|
|
import xml.etree.ElementTree as ET
|
|
from datetime import datetime
|
|
from pathlib import Path
|
|
|
|
LAB = Path(__file__).resolve().parent
|
|
TESTER = LAB / "run_mt5_tester.py"
|
|
OPT_SET = LAB / "XAUUSD_Genetic_Optimization.set"
|
|
PARAMS_MQH = LAB / "RSIScalpingSuperParams.mqh"
|
|
MAGIC_MQH = LAB / "RSIScalpingSuperMagic.mqh"
|
|
|
|
FOREX_MAJORS = [
|
|
"EURUSD", "GBPUSD", "USDJPY", "AUDUSD", "USDCHF", "USDCAD", "NZDUSD", "EURJPY", "XAUUSD"
|
|
]
|
|
|
|
|
|
def parse_best_from_xml(xml_path: Path) -> dict | None:
|
|
if not xml_path.exists():
|
|
return None
|
|
ns = {"ss": "urn:schemas-microsoft-com:office:spreadsheet"}
|
|
root = ET.parse(xml_path).getroot()
|
|
rows = root.findall(".//ss:Worksheet/ss:Table/ss:Row", ns)
|
|
if len(rows) < 2:
|
|
return None
|
|
headers = [c.find("ss:Data", ns).text for c in rows[0].findall("ss:Cell", ns)]
|
|
best = None
|
|
best_score = float("-inf")
|
|
for row in rows[1:]:
|
|
cells = [c.find("ss:Data", ns).text for c in row.findall("ss:Cell", ns)]
|
|
if len(cells) < len(headers):
|
|
continue
|
|
d = dict(zip(headers, cells))
|
|
try:
|
|
profit = float(d.get("Profit", 0))
|
|
pf = float(d.get("Profit Factor", 0))
|
|
dd = float(d.get("Equity DD %", 100))
|
|
sharpe = float(d.get("Sharpe Ratio", 0))
|
|
except (TypeError, ValueError):
|
|
continue
|
|
if profit <= 0 or pf < 1.05 or dd > 20:
|
|
continue
|
|
score = profit * pf / max(dd, 1.0) + sharpe * 100
|
|
if score > best_score:
|
|
best_score = score
|
|
best = {
|
|
"profit": profit,
|
|
"pf": pf,
|
|
"dd": dd,
|
|
"sharpe": sharpe,
|
|
"trades": int(float(d.get("Trades", 0))),
|
|
"rsi_period": int(float(d["RSI_Period"])),
|
|
"rsi_overbought": float(d["RSI_Overbought"]),
|
|
"rsi_oversold": float(d["RSI_Oversold"]),
|
|
"rsi_target_buy": float(d["RSI_Target_Buy"]),
|
|
"rsi_target_sell": float(d["RSI_Target_Sell"]),
|
|
"bars_to_wait": int(float(d["BarsToWait"])),
|
|
}
|
|
return best
|
|
|
|
|
|
def run_optimize_symbol(symbol: str, from_date: str, to_date: str, timeout: int) -> dict | None:
|
|
cmd = [
|
|
sys.executable,
|
|
str(TESTER),
|
|
"optimize",
|
|
"--symbol",
|
|
symbol,
|
|
"--from",
|
|
from_date,
|
|
"--to",
|
|
to_date,
|
|
"--set",
|
|
str(OPT_SET),
|
|
"--timeout",
|
|
str(timeout),
|
|
]
|
|
print(f"\n=== MT5 genetic optimize {symbol} ===")
|
|
subprocess.run(cmd, check=False)
|
|
import MetaTrader5 as mt5
|
|
|
|
if not mt5.initialize():
|
|
return None
|
|
data = Path(mt5.terminal_info().data_path)
|
|
mt5.shutdown()
|
|
xml = data / f"RSIScalpingAdaptive_{symbol}_optimize.xml"
|
|
return parse_best_from_xml(xml)
|
|
|
|
|
|
def write_params_mqh(results: dict[str, dict]) -> None:
|
|
lines = [
|
|
"// RSIScalpingSuperParams.mqh — auto-generated from MT5 genetic optimization",
|
|
f"// Generated: {datetime.now().isoformat(timespec='seconds')}",
|
|
"#ifndef RSI_SCALPING_SUPER_PARAMS_MQH",
|
|
"#define RSI_SCALPING_SUPER_PARAMS_MQH",
|
|
"",
|
|
'#include "RSIScalpingSuperMagic.mqh"',
|
|
"",
|
|
f"#define RS_SUPER_SLOT_COUNT {len(results)}",
|
|
"",
|
|
"struct RSSlotParams",
|
|
"{",
|
|
" int rsiPeriod;",
|
|
" double rsiOverbought;",
|
|
" double rsiOversold;",
|
|
" double rsiTargetBuy;",
|
|
" double rsiTargetSell;",
|
|
" int barsToWait;",
|
|
" double lotSize;",
|
|
"};",
|
|
"",
|
|
"struct RSSlotConfig",
|
|
"{",
|
|
" string symbol;",
|
|
" int magic;",
|
|
" bool enabled;",
|
|
" RSSlotParams p;",
|
|
"};",
|
|
"",
|
|
"const RSSlotConfig RS_SUPER_SLOTS[RS_SUPER_SLOT_COUNT] =",
|
|
"{",
|
|
]
|
|
for i, (sym, r) in enumerate(results.items(), start=1):
|
|
comment = f"// {sym} MT5 genetic profit=${r['profit']:.0f} PF={r['pf']:.2f} DD={r['dd']:.1f}%"
|
|
lines.append(f" {comment}")
|
|
lines.append(
|
|
f' {{ "{sym}", RS_SUPER_MAGIC_BASE + {i}, true,'
|
|
)
|
|
lines.append(
|
|
f" {{ {r['rsi_period']}, {r['rsi_overbought']:.1f}, {r['rsi_oversold']:.1f}, "
|
|
f"{r['rsi_target_buy']:.1f}, {r['rsi_target_sell']:.1f}, {r['bars_to_wait']}, 0.10 }} }},"
|
|
)
|
|
lines += ["};", "", "#endif", ""]
|
|
PARAMS_MQH.write_text("\n".join(lines), encoding="utf-8")
|
|
print(f"Wrote {PARAMS_MQH}")
|
|
|
|
|
|
def main() -> None:
|
|
p = argparse.ArgumentParser()
|
|
p.add_argument("mode", choices=["optimize", "backtest-portfolio"])
|
|
p.add_argument("--symbols", default=",".join(FOREX_MAJORS))
|
|
p.add_argument("--all-forex", action="store_true")
|
|
p.add_argument("--from", dest="from_date", default="2004.01.01")
|
|
p.add_argument("--to", dest="to_date", default="2026.01.01")
|
|
p.add_argument("--timeout", type=int, default=7200)
|
|
args = p.parse_args()
|
|
|
|
syms = FOREX_MAJORS if args.all_forex else [s.strip() for s in args.symbols.split(",") if s.strip()]
|
|
|
|
if args.mode == "optimize":
|
|
results: dict[str, dict] = {}
|
|
for sym in syms:
|
|
best = run_optimize_symbol(sym, args.from_date, args.to_date, args.timeout)
|
|
if best:
|
|
results[sym] = best
|
|
print(f" {sym}: profit=${best['profit']:.0f} PF={best['pf']:.2f} DD={best['dd']:.1f}%")
|
|
else:
|
|
print(f" {sym}: no stable candidate — skipped")
|
|
if not results:
|
|
raise SystemExit("No symbols passed optimization gates")
|
|
if len(results) < len(syms):
|
|
print(f"WARNING: only {len(results)}/{len(syms)} symbols optimized — merge manually into RSIScalpingSuperParams.mqh")
|
|
return
|
|
write_params_mqh(results)
|
|
else:
|
|
cmd = [
|
|
sys.executable,
|
|
str(LAB / "run_mt5_tester.py"),
|
|
"backtest",
|
|
"--symbol",
|
|
"EURUSD",
|
|
"--from",
|
|
args.from_date,
|
|
"--to",
|
|
args.to_date,
|
|
"--set",
|
|
str(LAB / "SuperEA_portfolio.set"),
|
|
]
|
|
# portfolio backtest uses SuperEA — extend run_mt5_tester for SuperEA
|
|
print("Use MT5 Tester manually: Expert=RSIScalpingSuper.ex5 on EURUSD H1, load SuperEA_portfolio.set")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|