"""Download XAUUSD M1 history to Parquet for tick-level simulation. M1 bars are the finest granularity MT5 exposes via copy_rates_range. We use each M1 bar's OHLC as 4 synthetic ticks (open→high→low→close for longs, open→low→high→close for shorts) to drive BE/trailing precisely inside each M5 bar. Downloads the same 2-year window as the M5 file so the two align. """ from __future__ import annotations import sys import time from datetime import datetime, timedelta from pathlib import Path PROJECT = Path(__file__).resolve().parent.parent sys.path.insert(0, str(PROJECT)) from shared.config import get_secret, load_env load_env(PROJECT) import MetaTrader5 as mt5 # type: ignore import pandas as pd SYMBOL = "XAUUSD" TIMEFRAME = "M1" START = (datetime.now() - timedelta(days=730)).strftime("%Y-%m-%d") END = datetime.now().strftime("%Y-%m-%d") OUT = PROJECT / "data" / f"{SYMBOL}_{TIMEFRAME}_{START}_{END}.parquet" def connect(retries: int = 5, sleep_s: float = 5.0) -> bool: for attempt in range(1, retries + 1): if not mt5.initialize(): print(f" initialize() attempt {attempt}/{retries} failed: {mt5.last_error()}") time.sleep(sleep_s) continue login = int(get_secret("MT5_DEMO_LOGIN") or 0) password = get_secret("MT5_DEMO_PASSWORD") server = get_secret("MT5_DEMO_SERVER") if not mt5.login(login, password=password, server=server): print(f" login() attempt {attempt}/{retries} failed: {mt5.last_error()}") mt5.shutdown() time.sleep(sleep_s) continue print(f" connected: login={login} server={server}") return True return False def main() -> int: if not connect(): print("could not connect — is the terminal running and logged in?") return 1 try: tf = getattr(mt5, f"TIMEFRAME_{TIMEFRAME}") print(f"downloading {SYMBOL} {TIMEFRAME} {START} → {END} ...") rates = mt5.copy_rates_range( SYMBOL, tf, pd.Timestamp(START), pd.Timestamp(END) ) if rates is None or len(rates) == 0: print(f" no bars returned: {mt5.last_error()}") return 2 df = pd.DataFrame(rates) df["timestamp"] = pd.to_datetime(df["time"], unit="s", utc=False) df = df[["timestamp", "open", "high", "low", "close", "spread"]] df = df.sort_values("timestamp").reset_index(drop=True) df = df.drop_duplicates(subset=["timestamp"]).reset_index(drop=True) OUT.parent.mkdir(parents=True, exist_ok=True) df.to_parquet(OUT, index=False) print(f"saved {len(df):,} bars → {OUT.relative_to(PROJECT)}") print(f"range: {df['timestamp'].iloc[0]} → {df['timestamp'].iloc[-1]}") return 0 finally: mt5.shutdown() if __name__ == "__main__": raise SystemExit(main())