64 lines
2.8 KiB
Python
64 lines
2.8 KiB
Python
"""Diagnose the exit-side PnL gap between Python and MT5.
|
|
|
|
MT5: gross profit 185.01, gross loss -123.78, 92 trades, net 61.23.
|
|
Python: 92 trades, net 118.79. So Python's gross profit is much higher
|
|
OR its gross loss is much smaller. Find out which by dumping Python's
|
|
gross profit / gross loss + per-reason breakdown.
|
|
"""
|
|
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
|
|
from strategies.gold_scalper_pro.signals import build_signals
|
|
import collections
|
|
|
|
bars = load_bars(PROJECT / "data" / "XAUUSD_M5_2024-06-26_2026-06-26.parquet")
|
|
start = pd.Timestamp("2026-04-16 00:00:00")
|
|
end = pd.Timestamp("2026-05-08 00:00:00")
|
|
window = bars[(bars["timestamp"] >= start) & (bars["timestamp"] < end)].reset_index(drop=True)
|
|
|
|
params = dict(FROZEN_BASELINE)
|
|
params["InpAtrPeriod"] = 15
|
|
pack = build_signals(params, window, XAUUSD_REAL)
|
|
engine = ScalperEngine()
|
|
result = engine.run(window, pack.signals_long, pack.signals_short,
|
|
pack.sl_prices, pack.tp_prices, XAUUSD_REAL, SizingInputs(),
|
|
1000.0, **engine_kwargs_from_params(params))
|
|
|
|
wins = [t.pnl for t in result.trades if t.pnl > 0]
|
|
losses = [t.pnl for t in result.trades if t.pnl < 0]
|
|
print(f"=== Python exit-side breakdown ({len(result.trades)} trades) ===")
|
|
print(f" gross profit : {sum(wins):+.2f} ({len(wins)} trades)")
|
|
print(f" gross loss : {sum(losses):+.2f} ({len(losses)} trades)")
|
|
print(f" net : {sum(t.pnl for t in result.trades):+.2f}")
|
|
print()
|
|
print(f"=== MT5 (from report) ===")
|
|
print(f" gross profit : +185.01")
|
|
print(f" gross loss : -123.78")
|
|
print(f" net : +61.23")
|
|
print()
|
|
|
|
by_reason = collections.defaultdict(list)
|
|
for t in result.trades:
|
|
by_reason[t.exit_reason].append(t.pnl)
|
|
print("=== Python PnL by exit reason ===")
|
|
for reason, pnls in sorted(by_reason.items()):
|
|
arr = __import__("numpy").array(pnls)
|
|
print(f" {reason:14s} n={len(arr):3d} sum={arr.sum():+8.2f} "
|
|
f"mean={arr.mean():+6.2f} min={arr.min():+7.2f} max={arr.max():+7.2f}")
|
|
|
|
# Distribution of win sizes — MT5's avg win = 185.01 / n_wins; need n_wins from MT5.
|
|
# MT5 win rate unknown, but PF = GP/|GL| = 185.01/123.78 = 1.494 → matches.
|
|
print(f"\n=== Python win/loss sizes ===")
|
|
print(f" avg win : {sum(wins)/len(wins):+.2f} (n={len(wins)})")
|
|
print(f" avg loss : {sum(losses)/len(losses):+.2f} (n={len(losses)})")
|
|
print(f" Python PF: {sum(wins)/abs(sum(losses)):.2f}")
|