"""Diagnostic: print the ATR value Python computes at the first signal bar (2025-01-02 01:50) and derive what MT5's ATR must have been (from the observed 0.16 lots). If they differ, the gap is in the bar data, not in the ATR math. Also dump the OHLC of the M5 bars around 2025-01-02 01:50 so we can compare against what MT5 sees. """ from __future__ import annotations import sys from pathlib import Path PROJECT = Path(__file__).resolve().parent.parent sys.path.insert(0, str(PROJECT)) import optuna import pandas as pd from shared.data.loaders import load_bars from shared.indicators.base import atr from shared.optimizer.selector import select_diverse_topn from strategies.gold_scalper_pro.instruments import XAUUSD_REAL from strategies.gold_scalper_pro.search_space import FROZEN_BASELINE, SEARCH_SPACE 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) f1 = finalists[0] merged = {**FROZEN_BASELINE, **f1.params} atr_period = int(merged["InpAtrPeriod"]) sl_mult = float(merged["InpAtrSLMult"]) risk_pct = float(merged["InpRiskPercent"]) init_deposit = 1000.0 print(f"finalist #1 atr_period={atr_period} sl_mult={sl_mult} risk={risk_pct}%") full_m5 = load_bars(PROJECT / "data" / "XAUUSD_M5_2024-06-26_2026-06-26.parquet") close = full_m5["close"].to_numpy(dtype=float) high = full_m5["high"].to_numpy(dtype=float) low = full_m5["low"].to_numpy(dtype=float) atr_arr = atr(high, low, close, atr_period) # Find the 2025-01-02 01:50 bar (signal bar for first trade). target = pd.Timestamp("2025-01-02 01:50:00") ts = pd.to_datetime(full_m5["timestamp"].to_numpy()) idx = int(ts.searchsorted(target, side="left")) print(f"\n first-signal bar @ {ts[idx]} (idx {idx})") print(f" OHLC = O={full_m5['open'].iloc[idx]:.2f} H={full_m5['high'].iloc[idx]:.2f} " f"L={full_m5['low'].iloc[idx]:.2f} C={full_m5['close'].iloc[idx]:.2f} " f"spread={full_m5['spread'].iloc[idx]}") print(f" ATR({atr_period}) at this bar = {atr_arr[idx]:.6f}") print(f" sl_dist = {sl_mult} × ATR = {sl_mult * atr_arr[idx]:.6f}") print(f" loss_per_lot = sl_dist / tick_size × tick_value = " f"{sl_mult * atr_arr[idx] / XAUUSD_REAL.tick_size * XAUUSD_REAL.tick_value:.4f}") risk_money = init_deposit * risk_pct / 100.0 sl_dist = sl_mult * atr_arr[idx] loss_per_lot = sl_dist / XAUUSD_REAL.tick_size * XAUUSD_REAL.tick_value lots = risk_money / loss_per_lot print(f" risk_money = ${risk_money:.4f}") print(f" Python computed lots = {lots:.6f} → rounded to {XAUUSD_REAL.round_volume(lots):.4f}") # What would MT5's ATR have to be to produce 0.16 lots? mt5_lots = 0.16 mt5_loss_per_lot = risk_money / mt5_lots mt5_sl_dist = mt5_loss_per_lot * XAUUSD_REAL.tick_size / XAUUSD_REAL.tick_value mt5_atr = mt5_sl_dist / sl_mult print(f"\n MT5 first trade: {mt5_lots} lots → sl_dist={mt5_sl_dist:.6f} → ATR={mt5_atr:.6f}") print(f" ratio Python/MT5 ATR = {atr_arr[idx] / mt5_atr:.4f} ({(atr_arr[idx]/mt5_atr - 1)*100:+.1f}%)") # Dump 30 bars around the signal to inspect the recent volatility. print(f"\n last {atr_period + 5} bars before signal (for ATR warmup):") print(f" {'ts':<22} {'open':>9} {'high':>9} {'low':>9} {'close':>9} {'TR':>9}") for j in range(max(0, idx - atr_period - 5), idx + 1): tr = max( high[j] - low[j], abs(high[j] - close[j-1]) if j > 0 else high[j] - low[j], abs(low[j] - close[j-1]) if j > 0 else high[j] - low[j], ) print(f" {str(ts[j]):<22} {full_m5['open'].iloc[j]:>9.2f} {full_m5['high'].iloc[j]:>9.2f} " f"{full_m5['low'].iloc[j]:>9.2f} {full_m5['close'].iloc[j]:>9.2f} {tr:>9.4f}") return 0 if __name__ == "__main__": raise SystemExit(main())