Files
mymt5opp/scripts/diag_size_mismatch.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

114 lines
4.5 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.
"""Diagnose the IS sizing mismatch: Python avg net/trade = $0.38 vs MT5 $2.97.
If gross P/L scales proportionally to MT5 (factor ~1/7.8) and trade count
matches, it's pure sizing. If PF also shifts, the BE/trailing logic differs.
"""
from __future__ import annotations
import json
import sys
from pathlib import Path
PROJECT = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(PROJECT))
import pandas as pd
from shared.core.engine import SizingInputs
from shared.core.metrics import compute_metrics
from shared.data.loaders import load_bars
from strategies.gold_scalper_pro.instruments import XAUUSD_REAL
from strategies.gold_scalper_pro.scalper_engine import (
ScalperEngine,
engine_kwargs_from_params,
)
from strategies.gold_scalper_pro.search_space import FROZEN_BASELINE, SEARCH_SPACE
from strategies.gold_scalper_pro.signals import build_signals
import optuna
from shared.optimizer.selector import select_diverse_topn
IS_START = pd.Timestamp("2025-01-01 00:00:00")
IS_END = pd.Timestamp("2026-01-01 00:00:00")
def main() -> int:
db = PROJECT / "studies" / "optuna" / "gold_scalper_pro_is2025.db"
study = optuna.load_study(
study_name="gold_scalper_pro_is2025",
storage=f"sqlite:///{db}",
)
finalists = select_diverse_topn(study, n=3, ranges=SEARCH_SPACE)
t = finalists[0]
merged = {**FROZEN_BASELINE, **t.params}
print(f"finalist #1 (trial #{t.number})")
print(f" InpRiskPercent = {merged['InpRiskPercent']}")
print(f" InpAtrSLMult = {merged['InpAtrSLMult']}")
print(f" InpAtrTPMult = {merged['InpAtrTPMult']}")
m5 = load_bars(PROJECT / "data" / "XAUUSD_M5_2024-06-26_2026-06-26.parquet")
m1 = load_bars(PROJECT / "data" / "XAUUSD_M1_2024-06-26_2026-06-26.parquet")
is_bars = m5[(m5["timestamp"] >= IS_START) & (m5["timestamp"] < IS_END)].reset_index(drop=True)
is_m1 = m1[(m1["timestamp"] >= IS_START) & (m1["timestamp"] < IS_END)].reset_index(drop=True)
print(f" IS bars: {len(is_bars):,} IS M1: {len(is_m1):,}")
pack = build_signals(merged, is_bars, XAUUSD_REAL)
engine = ScalperEngine()
result = engine.run(
is_bars, pack.signals_long, pack.signals_short,
pack.sl_prices, pack.tp_prices,
XAUUSD_REAL, SizingInputs(), 1000.0,
m1_bars=is_m1,
**engine_kwargs_from_params(merged),
)
m = compute_metrics(result, periods_per_year=252 * 24 * 12)
gross_profit = sum(t.pnl for t in result.trades if t.pnl > 0)
gross_loss = sum(t.pnl for t in result.trades if t.pnl < 0)
print(f"\nPython IS:")
print(f" trades = {m.total_trades}")
print(f" gross profit = {gross_profit:.2f}")
print(f" gross loss = {gross_loss:.2f}")
print(f" net = {gross_profit + gross_loss:.2f}")
print(f" PF = {gross_profit / -gross_loss:.4f}" if gross_loss < 0 else " PF = inf")
print(f" avg net/trade = {(gross_profit + gross_loss) / m.total_trades:.4f}")
# Sample first 5 trades — check lot sizes & prices.
print(f"\nFirst 5 trades:")
print(f" {'time':<21} {'dir':<5} {'entry':>10} {'exit':>10} {'lots':>8} {'pnl':>9} {'reason'}")
for t in result.trades[:5]:
d = "LONG" if t.direction.name == "LONG" else "SHRT"
print(f" {str(t.entry_time):<21} {d:<5} {t.entry_price:>10.2f} "
f"{t.exit_price:>10.2f} {t.lots:>8.4f} {t.pnl:>9.4f} {t.exit_reason}")
# Distribution of lots.
import numpy as np
lots_arr = np.array([t.lots for t in result.trades])
print(f"\n lots: min={lots_arr.min():.4f} max={lots_arr.max():.4f} "
f"mean={lots_arr.mean():.4f} median={np.median(lots_arr):.4f}")
print(f" lots unique count: {len(np.unique(lots_arr))}")
print(f" lots histogram (top 5):")
vals, counts = np.unique(lots_arr, return_counts=True)
for v, c in sorted(zip(vals, counts), key=lambda x: -x[1])[:5]:
print(f" {v:.4f} ×{c}")
# MT5 comparison.
print(f"\nMT5 IS (from report):")
print(f" gross profit = 23416.75")
print(f" gross loss = -16429.41")
print(f" net = 6987.34")
print(f" PF = 1.43")
print(f" trades = 2348")
print(f" avg net/trade = {6987.34/2348:.4f}")
# Scaling check: if Python lots were 7.8x larger, would P/L match?
py_gross = gross_profit
mt5_gross = 23416.75
print(f"\n scaling factor (MT5 gross profit / Python gross profit): "
f"{mt5_gross/py_gross:.2f}x")
return 0
if __name__ == "__main__":
raise SystemExit(main())