81 lines
2.8 KiB
Python
81 lines
2.8 KiB
Python
"""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())
|