111 lines
4.5 KiB
Python
111 lines
4.5 KiB
Python
"""Query XAUUSD symbol spec from MT5 and dump it as an InstrumentConfig sketch.
|
|
|
|
Run with the venv python. Uses the MetaTrader5 package (Topology A) to read
|
|
the symbol specification (digits, point, tick value, contract size, volume
|
|
steps) — these come from the broker, not guesses (doc 05 §1). Also downloads
|
|
a short M5 history window for the first validation pass (doc 03 §8).
|
|
|
|
IPC-timeout workarounds (Stack Overflow #66492735):
|
|
- Use forward slashes in the terminal path (backslashes trigger -10005).
|
|
- Split initialize(path) and login() into two steps (one-shot initialize with
|
|
login/password/server is fragile).
|
|
- Allow reusing an already-running terminal instance (same path = same IPC).
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import sys
|
|
import time
|
|
from pathlib import Path
|
|
|
|
# Make the project importable when run as a script.
|
|
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
|
|
|
|
# Forward slashes — backslashes in this path trigger IPC timeout (-10005).
|
|
TERMINAL = "C:/Program Files/MetaTrader 5 IC Markets Global/terminal64.exe"
|
|
SYMBOL = "XAUUSD"
|
|
|
|
|
|
def connect(retries: int = 3, sleep_s: float = 5.0) -> bool:
|
|
"""Connect in two steps: initialize() → login(login,password,server).
|
|
|
|
IMPORTANT: call initialize() with NO path argument. Passing a path makes
|
|
the package spawn a NEW headless terminal at that path, which then sits
|
|
waiting for an interactive login it can never complete (IPC timeout -10005).
|
|
Calling initialize() with no args CONNECTS to the already-running terminal
|
|
(the one with the UI you logged into manually).
|
|
"""
|
|
for attempt in range(1, retries + 1):
|
|
# Step 1: connect to the running terminal (no path → reuse existing).
|
|
if not mt5.initialize():
|
|
err = mt5.last_error()
|
|
print(f" initialize() attempt {attempt}/{retries} failed: {err}")
|
|
time.sleep(sleep_s)
|
|
continue
|
|
# Step 2: explicit login with the demo account (re-auth is safe).
|
|
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):
|
|
err = mt5.last_error()
|
|
print(f" login() attempt {attempt}/{retries} failed: {err}")
|
|
mt5.shutdown()
|
|
time.sleep(sleep_s)
|
|
continue
|
|
print(f" connected: login={login} server={server}")
|
|
return True
|
|
return False
|
|
|
|
|
|
def main() -> int:
|
|
if not connect(retries=5, sleep_s=10.0):
|
|
print("could not connect to MT5 after retries")
|
|
print("hint: confirm the demo account logs in manually in the terminal first;")
|
|
print(" if it does, the IPC issue is path/instance related, not account.")
|
|
return 1
|
|
try:
|
|
info = mt5.symbol_info(SYMBOL)
|
|
if info is None:
|
|
print(f"symbol_info({SYMBOL}) returned None — is the symbol in Market Watch?")
|
|
return 2
|
|
print(f"=== {SYMBOL} specification (IC Markets) ===")
|
|
fields = [
|
|
"name", "digits", "point", "trade_tick_size", "trade_tick_value",
|
|
"trade_contract_size", "volume_min", "volume_step", "volume_max",
|
|
"spread", "trade_stops_level", "swap_mode", "swap_long", "swap_short",
|
|
"swap_rollover3days",
|
|
]
|
|
for f in fields:
|
|
print(f" {f:24s} = {getattr(info, f, '<n/a>')}")
|
|
# Triple-swap weekday: MT5 swap_rollover3days is 0=Mon..6=Sun.
|
|
|
|
print("\n=== short M5 history probe (last 5 bars) ===")
|
|
rates = mt5.copy_rates_from_pos(SYMBOL, mt5.TIMEFRAME_M5, 0, 5)
|
|
if rates is None or len(rates) == 0:
|
|
print(" no rates returned")
|
|
else:
|
|
df = pd.DataFrame(rates)
|
|
df["timestamp"] = pd.to_datetime(df["time"], unit="s")
|
|
print(df[["timestamp", "open", "high", "low", "close", "spread"]].to_string(index=False))
|
|
|
|
print("\n=== available history depth (count bars in last 2 years) ===")
|
|
from datetime import datetime, timedelta
|
|
end = datetime.now()
|
|
start = end - timedelta(days=730)
|
|
n = mt5.copy_rates_range(SYMBOL, mt5.TIMEFRAME_M5, start, end)
|
|
print(f" M5 bars {start.date()} → {end.date()}: {0 if n is None else len(n)}")
|
|
return 0
|
|
finally:
|
|
mt5.shutdown()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|