Files
mymt5opp/scripts/compare_finalist.py
gavindiaz 63a829cc46 phase 7-8 完成 + warmup 修复 + 产物结构化重组
主要内容:
- Phase 8 PROMOTE: finalist #1 (trial #324) registry 条目,自动生成
- Optuna objective warmup bug 修复 (shared/optimizer/objective.py)
- studies/ 目录按用途重组为 optuna/ + finalists/ + features/ 三层
- reports/ 加入 Optuna 中文 dashboard (5 主图 + 18 slice + 15 contour)
- 新增 PROJECT_GUIDE.md 项目说明文档
- 新增 build_registry_entry.py / build_optuna_dashboard.py / build_feature_datasets.py
- .gitignore: 允许提交 studies/*.db (Optuna DB) 和 reports/*.html (MT5 + dashboard)
2026-06-27 00:28:07 +08:00

118 lines
4.3 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""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())