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>
318 lines
13 KiB
Python
318 lines
13 KiB
Python
"""
|
|
Build cluster-latest SuperEA audit params from sequential JSON reports.
|
|
|
|
Usage:
|
|
python -m cluster_audit.sync_cluster
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
from datetime import datetime
|
|
from pathlib import Path
|
|
|
|
from cluster_audit.scoring import DEFAULT_TRADES_PER_DAY, acceptance, period_days, trades_per_day
|
|
from cluster_audit.strategy_registry import PERIODS, STRATEGIES
|
|
|
|
REPORTS = Path(__file__).parent / "reports" / "sequential"
|
|
OUT_MQH = Path(__file__).resolve().parents[3] / "frontline" / "cluster-latest" / "SuperEA_AuditParams.mqh"
|
|
OUT_JSON = Path(__file__).parent / "reports" / "cluster_manifest.json"
|
|
|
|
RSI_SCALP_IDS = [
|
|
"rsi_scalp_appl_unit", "rsi_scalp_appl_trail", "rsi_scalp_adbe_trail",
|
|
"rsi_scalp_btc_unit", "rsi_scalp_btc_trail", "rsi_scalp_mu",
|
|
"rsi_scalp_nvda_unit", "rsi_scalp_nvda_trail", "rsi_scalp_nvda_trail_v2",
|
|
"rsi_scalp_tsla_unit", "rsi_scalp_tsla_trail", "rsi_scalp_xau_trail",
|
|
]
|
|
RSI_INDEX = {sid: i for i, sid in enumerate(RSI_SCALP_IDS)}
|
|
|
|
MAGIC_MAP = {s["id"]: 401000 + i for i, s in enumerate(STRATEGIES, 1)}
|
|
|
|
|
|
def load_report(sid: str) -> dict | None:
|
|
p = REPORTS / f"{sid}_2021-2026.json"
|
|
if not p.exists():
|
|
return None
|
|
return json.loads(p.read_text(encoding="utf-8"))
|
|
|
|
|
|
def spec_defaults(sid: str) -> dict:
|
|
for s in STRATEGIES:
|
|
if s["id"] == sid:
|
|
return dict(s["defaults"])
|
|
return {}
|
|
|
|
|
|
def evaluate_report(r: dict, days: int) -> tuple[bool, list[str]]:
|
|
from cluster_audit.backtest_core import BacktestReport
|
|
|
|
o = r.get("optimized", {})
|
|
rep = BacktestReport(
|
|
strategy_id=r["id"],
|
|
symbol=r.get("symbol", ""),
|
|
timeframe=r.get("timeframe", "H1"),
|
|
period_label="2021-2026",
|
|
net_profit=float(o.get("net_profit", 0)),
|
|
total_trades=int(o.get("total_trades", 0)),
|
|
win_rate=float(o.get("win_rate", 0)),
|
|
profit_factor=float(o.get("profit_factor", 0)),
|
|
sharpe=float(o.get("sharpe", 0)),
|
|
max_drawdown_pct=float(o.get("max_drawdown_pct", 0)),
|
|
avg_win=float(o.get("avg_win", 0)),
|
|
avg_loss=float(o.get("avg_loss", 0)),
|
|
worst_trades=o.get("worst_trades", []),
|
|
losing_trades=o.get("losing_trades", []),
|
|
exit_reason_breakdown=o.get("exit_reason_breakdown", {}),
|
|
monthly_returns=o.get("monthly_returns", {}),
|
|
params=o.get("params", {}),
|
|
)
|
|
if r.get("passed") is True:
|
|
ok, issues = acceptance(rep, days, DEFAULT_TRADES_PER_DAY)
|
|
if ok:
|
|
return True, []
|
|
return acceptance(rep, days, DEFAULT_TRADES_PER_DAY)
|
|
|
|
|
|
def _lit_bool(v) -> str:
|
|
return "true" if v else "false"
|
|
|
|
|
|
def emit_darvas(params: dict, ok: bool) -> list[str]:
|
|
d = {**spec_defaults("darvas_xau"), **params}
|
|
return [
|
|
"static void SE_AuditDarvas(DarvasBoxConfig &c)",
|
|
"{",
|
|
f" c.box_period = {int(d['box_period'])};",
|
|
f" c.box_deviation = {float(d['box_deviation'])};",
|
|
f" c.ma_period = {int(d['ma_period'])};",
|
|
f" c.trend_threshold = {float(d['trend_threshold'])};",
|
|
f" c.stop_loss_pts = {float(d['stop_loss_pts'])};",
|
|
f" c.take_profit_pts = {float(d['take_profit_pts'])};",
|
|
" c.box_timeframe = PERIOD_M15;",
|
|
" c.trend_timeframe = PERIOD_M15;",
|
|
" c.use_close_breakout = true;",
|
|
" c.require_volume_ma = false;",
|
|
"}",
|
|
f"static bool SE_AuditDarvasEnabled() {{ return {_lit_bool(ok)}; }}",
|
|
"",
|
|
]
|
|
|
|
|
|
def emit_ema_slope(fn: str, params: dict, ok: bool) -> list[str]:
|
|
d = params
|
|
lines = [f"static void SE_Audit{fn}(EmaSlopeConfig &c)", "{"]
|
|
for key, cast in [
|
|
("ema_period", int), ("price_threshold_pips", float), ("slope_threshold_pips", float),
|
|
("monitor_timeout_sec", int), ("trailing_stop_pips", float),
|
|
("max_trades_per_crossover", int), ("profit_check_bars", int),
|
|
("weekly_adx_period", int), ("weekly_adx_min", float), ("weekly_adx_bar_shift", int),
|
|
]:
|
|
if key in d:
|
|
lines.append(f" c.{key} = {cast(d[key])};")
|
|
if "use_trailing_stop" in d:
|
|
lines.append(f" c.use_trailing_stop = {_lit_bool(d['use_trailing_stop'])};")
|
|
lines += ["}", f"static bool SE_Audit{fn}Enabled() {{ return {_lit_bool(ok)}; }}", ""]
|
|
return lines
|
|
|
|
|
|
def emit_mean_rev(params: dict, ok: bool) -> list[str]:
|
|
d = {**spec_defaults("mean_rev_btc"), **params}
|
|
return [
|
|
"static void SE_AuditMeanRev(MeanReversionConfig &c)",
|
|
"{",
|
|
f" c.ema_period = {int(d.get('ema_period', 250))};",
|
|
f" c.min_ema_distance_pts = {float(d.get('min_ema_distance_pts', 3650))};",
|
|
f" c.rsi_period = {int(d.get('rsi_period', 28))};",
|
|
f" c.rsi_oversold = {float(d.get('rsi_oversold', 40))};",
|
|
f" c.rsi_overbought = {float(d.get('rsi_overbought', 83))};",
|
|
f" c.adx_period = {int(d.get('adx_period', 14))};",
|
|
f" c.adx_max_for_entry = {float(d.get('adx_max_for_entry', 17))};",
|
|
f" c.adx_escape = {float(d.get('adx_escape', 34))};",
|
|
f" c.use_rsi_cross = {_lit_bool(d.get('use_rsi_cross', True))};",
|
|
f" c.use_hard_sltp = {_lit_bool(d.get('use_hard_sltp', False))};",
|
|
f" c.sl_points = {float(d.get('sl_points', 1300))};",
|
|
f" c.tp_points = {float(d.get('tp_points', 13400))};",
|
|
"}",
|
|
f"static bool SE_AuditMeanRevEnabled() {{ return {_lit_bool(ok)}; }}",
|
|
"",
|
|
]
|
|
|
|
|
|
def emit_rsi_cross(params: dict, ok: bool) -> list[str]:
|
|
d = {**spec_defaults("rsi_cross_xau"), **params}
|
|
return [
|
|
"static void SE_AuditRsiCross(RsiCrossOverConfig &c)",
|
|
"{",
|
|
f" c.rsi_period = {int(d.get('rsi_period', 19))};",
|
|
f" c.overbought_level = {float(d.get('overbought_level', 93))};",
|
|
f" c.oversold_level = {float(d.get('oversold_level', 22))};",
|
|
f" c.ema_period = {int(d.get('ema_period', 140))};",
|
|
f" c.ema_slope_threshold = {float(d.get('ema_slope_threshold', 105))};",
|
|
f" c.ema_distance_threshold = {float(d.get('ema_distance_threshold', 165))};",
|
|
f" c.exit_buy_rsi = {float(d.get('exit_buy_rsi', 86))};",
|
|
f" c.exit_sell_rsi = {float(d.get('exit_sell_rsi', 10))};",
|
|
f" c.trailing_stop_pts = {float(d.get('trailing_stop_pts', 295))};",
|
|
f" c.cooldown_seconds = {int(d.get('cooldown_seconds', 209))};",
|
|
"}",
|
|
f"static bool SE_AuditRsiCrossEnabled() {{ return {_lit_bool(ok)}; }}",
|
|
"",
|
|
]
|
|
|
|
|
|
def emit_rsi_asian(fn: str, params: dict, ok: bool) -> list[str]:
|
|
d = params
|
|
return [
|
|
f"static void SE_Audit{fn}(RsiAsianConfig &c)",
|
|
"{",
|
|
f" c.rsi_period = {int(d.get('rsi_period', 28))};",
|
|
f" c.overbought_level = {float(d.get('overbought_level', 60))};",
|
|
f" c.oversold_level = {float(d.get('oversold_level', 8))};",
|
|
f" c.asian_session_start = {int(d.get('asian_session_start', 0))};",
|
|
f" c.asian_session_end = {int(d.get('asian_session_end', 8))};",
|
|
f" c.use_rsi_exit = {_lit_bool(d.get('use_rsi_exit', True))};",
|
|
f" c.rsi_exit_level = {float(d.get('rsi_exit_level', 55))};",
|
|
"}",
|
|
f"static bool SE_Audit{fn}Enabled() {{ return {_lit_bool(ok)}; }}",
|
|
"",
|
|
]
|
|
|
|
|
|
def emit_rsi_secret(params: dict, ok: bool) -> list[str]:
|
|
d = {**spec_defaults("rsi_secret_xau"), **params}
|
|
return [
|
|
"static void SE_AuditRsiSecret(RsiSecretSauceConfig &c)",
|
|
"{",
|
|
f" c.rsi_period = {int(d.get('rsi_period', 16))};",
|
|
f" c.rsi_overbought = {float(d.get('rsi_overbought', 72.5))};",
|
|
f" c.rsi_oversold = {float(d.get('rsi_oversold', 32.5))};",
|
|
f" c.stop_loss_atr = {float(d.get('stop_loss_atr', 2.75))};",
|
|
f" c.take_profit_atr = {float(d.get('take_profit_atr', 5.0))};",
|
|
f" c.min_bars_between_trades = {int(d.get('min_bars_between_trades', 7))};",
|
|
"}",
|
|
f"static bool SE_AuditRsiSecretEnabled() {{ return {_lit_bool(ok)}; }}",
|
|
"",
|
|
]
|
|
|
|
|
|
def emit_rsi_scalp(idx: int, sid: str, params: dict, ok: bool) -> list[str]:
|
|
d = {**spec_defaults(sid), **params}
|
|
return [
|
|
f"static void SE_AuditRsi{idx}(RsiScalpConfig &c)",
|
|
"{",
|
|
f" c.rsi_period = {int(d.get('rsi_period', 14))};",
|
|
f" c.rsi_overbought = {float(d.get('rsi_overbought', 70))};",
|
|
f" c.rsi_oversold = {float(d.get('rsi_oversold', 30))};",
|
|
f" c.rsi_target_buy = {float(d.get('rsi_target_buy', 80))};",
|
|
f" c.rsi_target_sell = {float(d.get('rsi_target_sell', 50))};",
|
|
f" c.bars_to_wait = {int(d.get('bars_to_wait', 5))};",
|
|
f" c.use_trailing = {_lit_bool(d.get('use_trailing', False))};",
|
|
f" c.trail_distance_pts = {float(d.get('trail_distance_pts', 0))};",
|
|
f" c.trail_activation_pts = {float(d.get('trail_activation_pts', 0))};",
|
|
"}",
|
|
f"static bool SE_AuditRsi{idx}Enabled() {{ return {_lit_bool(ok)}; }}",
|
|
"",
|
|
]
|
|
|
|
|
|
def stub_enabled(name: str, ok: bool = False) -> list[str]:
|
|
return [f"static bool SE_Audit{name}Enabled() {{ return {_lit_bool(ok)}; }}", ""]
|
|
|
|
|
|
def main() -> None:
|
|
start, end = PERIODS["2021-2026"]
|
|
days = period_days(start, end)
|
|
manifest: dict = {
|
|
"generated": datetime.now().isoformat(),
|
|
"period_days": days,
|
|
"trades_per_day_target": DEFAULT_TRADES_PER_DAY,
|
|
"strategies": {},
|
|
}
|
|
|
|
reports: dict[str, dict] = {}
|
|
status: dict[str, bool] = {}
|
|
params_map: dict[str, dict] = {}
|
|
|
|
for spec in STRATEGIES:
|
|
sid = spec["id"]
|
|
r = load_report(sid)
|
|
if not r:
|
|
manifest["strategies"][sid] = {"status": "no_report", "passed": False}
|
|
status[sid] = False
|
|
params_map[sid] = spec_defaults(sid)
|
|
continue
|
|
reports[sid] = r
|
|
params = r.get("optimized_params") or r.get("optimized", {}).get("params", spec_defaults(sid))
|
|
params_map[sid] = params
|
|
ok, issues = evaluate_report(r, days)
|
|
o = r.get("optimized", {})
|
|
tpd = trades_per_day(
|
|
type("R", (), {"total_trades": int(o.get("total_trades", 0))})(),
|
|
days,
|
|
)
|
|
status[sid] = ok
|
|
manifest["strategies"][sid] = {
|
|
"passed": ok,
|
|
"magic": MAGIC_MAP.get(sid),
|
|
"trades": o.get("total_trades"),
|
|
"trades_per_day": round(tpd, 3),
|
|
"net_profit": o.get("net_profit"),
|
|
"sharpe": o.get("sharpe"),
|
|
"profit_factor": o.get("profit_factor"),
|
|
"issues": issues,
|
|
"params": params,
|
|
}
|
|
|
|
lines = [
|
|
"//+------------------------------------------------------------------+",
|
|
"//| SuperEA_AuditParams.mqh - optimized params from cluster audit |",
|
|
f"//| Generated: {datetime.now().isoformat()}",
|
|
"//+------------------------------------------------------------------+",
|
|
"#ifndef SUPER_EA_AUDIT_PARAMS_MQH",
|
|
"#define SUPER_EA_AUDIT_PARAMS_MQH",
|
|
"",
|
|
]
|
|
|
|
lines += [f"// darvas_xau: {'PASS' if status.get('darvas_xau') else 'DISABLED'}"]
|
|
lines += emit_darvas(params_map.get("darvas_xau", {}), status.get("darvas_xau", False))
|
|
|
|
lines += [f"// ema_slope_unit: {'PASS' if status.get('ema_slope_unit') else 'DISABLED'}"]
|
|
lines += emit_ema_slope("EmaUnit", params_map.get("ema_slope_unit", {}), status.get("ema_slope_unit", False))
|
|
|
|
lines += [f"// ema_slope_trail: {'PASS' if status.get('ema_slope_trail') else 'DISABLED'}"]
|
|
lines += emit_ema_slope("EmaTrail", params_map.get("ema_slope_trail", {}), status.get("ema_slope_trail", False))
|
|
|
|
lines += [f"// mean_rev_btc: {'PASS' if status.get('mean_rev_btc') else 'DISABLED'}"]
|
|
lines += emit_mean_rev(params_map.get("mean_rev_btc", {}), status.get("mean_rev_btc", False))
|
|
|
|
lines += [f"// rsi_cross_xau: {'PASS' if status.get('rsi_cross_xau') else 'DISABLED'}"]
|
|
lines += emit_rsi_cross(params_map.get("rsi_cross_xau", {}), status.get("rsi_cross_xau", False))
|
|
|
|
for sid, fn in [
|
|
("rsi_asian_eur", "RsiAsianEur"),
|
|
("rsi_asian_aud", "RsiAsianAud"),
|
|
("rsi_asian_gbp", "RsiAsianGbp"),
|
|
]:
|
|
lines += [f"// {sid}: {'PASS' if status.get(sid) else 'DISABLED'}"]
|
|
lines += emit_rsi_asian(fn, params_map.get(sid, {}), status.get(sid, False))
|
|
|
|
lines += [f"// rsi_secret_xau: {'PASS' if status.get('rsi_secret_xau') else 'DISABLED'}"]
|
|
lines += emit_rsi_secret(params_map.get("rsi_secret_xau", {}), status.get("rsi_secret_xau", False))
|
|
|
|
for sid in RSI_SCALP_IDS:
|
|
idx = RSI_INDEX[sid]
|
|
lines += [f"// {sid}: {'PASS' if status.get(sid) else 'DISABLED'}"]
|
|
lines += emit_rsi_scalp(idx, sid, params_map.get(sid, {}), status.get(sid, False))
|
|
|
|
lines += ["#endif", ""]
|
|
OUT_MQH.parent.mkdir(parents=True, exist_ok=True)
|
|
OUT_MQH.write_text("\n".join(lines), encoding="utf-8")
|
|
OUT_JSON.write_text(json.dumps(manifest, indent=2), encoding="utf-8")
|
|
passed = [k for k, v in status.items() if v]
|
|
print(f"Wrote {OUT_MQH}")
|
|
print(f"Wrote {OUT_JSON}")
|
|
print(f"Passed {len(passed)}/{len(STRATEGIES)}: {', '.join(passed) if passed else '(none)'}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|