Files

118 lines
4.3 KiB
Python
Raw Permalink Normal View History

"""Phase 7 — Compare Python vs MT5 for finalist #1.
Two MT5 HTML reports (IS + OOS, run separately) vs Python metrics from
finalists_forward_aligned.json. Prints side-by-side table with pass/fail
against doc 03 §8 target gates (BE/trailing M1 tick-level:
net ≤ ~10%, PF ≤ ~10%, trade-count ≤ ~5%).
Usage:
python scripts/compare_finalist.py 1
"""
from __future__ import annotations
import json
import sys
from pathlib import Path
PROJECT = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(PROJECT))
from shared.data.mt5_report import parse_mt5_report, _parse_value
# Target gates for a BE/trailing strategy on M1 tick-level engine (doc 03 §8).
GATES = {
"net_profit": 0.10,
"profit_factor": 0.10,
"total_trades": 0.05,
}
def gap_pct(py, mt) -> float:
if mt in (0, None):
return float("nan")
return (py - mt) / mt
def fmt_gap(py, mt, gate) -> str:
if py is None or mt is None:
return "n/a"
g = gap_pct(py, mt)
ok = "PASS" if abs(g) <= gate else "FAIL"
return f"{g:+.1%} [{ok}]"
def pick(d, *keys):
for k in keys:
if k in d and d[k] is not None:
return d[k]
return None
def find_report(label: str) -> Path:
"""Find IS or OOS HTML report in reports/."""
rdir = PROJECT / "reports"
# Prefer explicit IS-/OOS- prefixed files.
cands = sorted(rdir.glob(f"{label}-ReportTester*.html"))
if cands:
return cands[-1]
# Fall back to label anywhere in the name.
cands = sorted(rdir.glob(f"*{label}*.html"))
if cands:
return cands[-1]
sys.exit(f"no {label} HTML report in {rdir}")
def main() -> int:
finalist_idx = int(sys.argv[1]) if len(sys.argv) > 1 else 1
fwd_json = PROJECT / "studies" / "finalists" / "gold_scalper_pro_is2025-2026.json"
if not fwd_json.exists():
sys.exit(f"missing: {fwd_json} — run scripts/reeval_finalist_forward.py")
fwd = json.loads(fwd_json.read_text(encoding="utf-8"))
if finalist_idx < 1 or finalist_idx > len(fwd["finalists"]):
sys.exit(f"finalist index must be 1..{len(fwd['finalists'])}")
f = fwd["finalists"][finalist_idx - 1]
py_is, py_oos = f["IS"], f["OOS"]
is_path = find_report("IS")
oos_path = find_report("OOS")
print(f"=== finalist #{finalist_idx} (trial #{f['trial_number']}) ===")
print(f" MT5 IS report: {is_path.name}")
print(f" MT5 OOS report: {oos_path.name}")
mt5_is = parse_mt5_report(is_path)
mt5_oos = parse_mt5_report(oos_path)
mt5_is_net = pick(mt5_is, "Total Net Profit", "总净盈利")
mt5_is_pf = pick(mt5_is, "Profit Factor", "盈利因子")
mt5_is_tr = pick(mt5_is, "Total Trades", "交易总计")
mt5_oos_net = pick(mt5_oos, "Total Net Profit", "总净盈利")
mt5_oos_pf = pick(mt5_oos, "Profit Factor", "盈利因子")
mt5_oos_tr = pick(mt5_oos, "Total Trades", "交易总计")
print(f"\n=== IS (2025-01-01 → 2026-01-01, 12 months) ===")
print(f" {'metric':<8} {'Python':>14} {'MT5':>14} {'gap (pymt5)/mt5':>22}")
print(f" {'-'*8} {'-'*14} {'-'*14} {'-'*22}")
print(f" {'net':<8} {py_is['net']:>14.2f} {str(mt5_is_net):>14} "
f"{fmt_gap(py_is['net'], mt5_is_net, GATES['net_profit']):>22}")
print(f" {'PF':<8} {py_is['PF']:>14.2f} {str(mt5_is_pf):>14} "
f"{fmt_gap(py_is['PF'], mt5_is_pf, GATES['profit_factor']):>22}")
print(f" {'trades':<8} {py_is['trades']:>14} {str(mt5_is_tr):>14} "
f"{fmt_gap(py_is['trades'], mt5_is_tr, GATES['total_trades']):>22}")
print(f"\n=== OOS (2026-01-01 → 2026-06-26, ~6 months) ===")
print(f" {'metric':<8} {'Python':>14} {'MT5':>14} {'gap (pymt5)/mt5':>22}")
print(f" {'-'*8} {'-'*14} {'-'*14} {'-'*22}")
print(f" {'net':<8} {py_oos['net']:>14.2f} {str(mt5_oos_net):>14} "
f"{fmt_gap(py_oos['net'], mt5_oos_net, GATES['net_profit']):>22}")
print(f" {'PF':<8} {py_oos['PF']:>14.2f} {str(mt5_oos_pf):>14} "
f"{fmt_gap(py_oos['PF'], mt5_oos_pf, GATES['profit_factor']):>22}")
print(f" {'trades':<8} {py_oos['trades']:>14} {str(mt5_oos_tr):>14} "
f"{fmt_gap(py_oos['trades'], mt5_oos_tr, GATES['total_trades']):>22}")
print(f"\n target gate (doc 03 §8, BE/trailing M1 tick-level): "
f"net ≤10% PF ≤10% trades ≤5%")
return 0
if __name__ == "__main__":
raise SystemExit(main())