"""Open a broker demo account, connect, read it, and disconnect in finally. Local: python mt5_demo.py (Python 3, no packages required). Optionally set MRPC_API_KEY to your own user key; otherwise TRIAL is used. The website supplies USER_KEY for signed-in visitors. Never print credentials. """ import asyncio import json import os from urllib.parse import urlencode from urllib.request import Request, urlopen try: from pyodide.http import pyfetch except ImportError: pyfetch = None API_KEY = globals().get("USER_KEY") or os.environ.get("MRPC_API_KEY") or "TRIAL" API_BASE = globals().get("DEMO_API_BASE") or os.environ.get("MRPC_API_BASE", "https://mt5.mrpc.pro") async def request(path, parameters=None, terminal_id=None): url = API_BASE + path if parameters: url += "?" + urlencode(parameters) headers = {"APIKey": API_KEY} if terminal_id: headers["id"] = terminal_id if pyfetch: response = await pyfetch(url, headers=headers, credentials="omit", cache="no-store") if not response.ok: raise RuntimeError(f"{path}: HTTP {response.status}") body = await response.json() else: with urlopen(Request(url, headers=headers), timeout=110) as response: body = json.load(response) if body.get("error"): raise RuntimeError(f"{path}: API request failed") return body.get("data", body) def check_cancelled(): callback = globals().get("DEMO_CANCELLED") if callback and callback(): raise RuntimeError("Cancelled") async def main(): result = {"steps": [], "success": False, "disconnected": False} terminal_id = None step = "Open demo account" try: check_cancelled() demo = await request("/DemoAccount/Open", { "company": "MetaQuotes Ltd.", "server": "MetaQuotes-Demo", "firstName": "Test", "lastName": "User", "email": "test@test.com", "phone": "+1234567890", "timeoutSeconds": "60", }) if demo.get("resultCode", 0) != 0 or not demo.get("login") or not demo.get("password") or demo.get("server") != "MetaQuotes-Demo": raise RuntimeError("Demo registration did not return usable credentials") result["demoAccount"] = {"login": demo["login"], "server": demo["server"]} result["steps"].append("Demo account opened") check_cancelled() step = "Connect" connected = await request("/ConnectEx", { "user": str(demo["login"]), "password": demo["password"], "mtClusterName": demo["server"], "timeoutSeconds": "90", "expiration": "5", "name": "Website demo", }) # ConnectEx returns the ID used by subsequent requests and Disconnect. terminal_id = connected.get("terminalInstanceGuid") if not terminal_id: raise RuntimeError("ConnectEx did not return a terminal ID") result["steps"].append("Connected to MT5") check_cancelled() step = "Read account" result["account"] = await request("/AccountSummary", terminal_id=terminal_id) if "accountBalance" not in result["account"]: raise RuntimeError("No account summary returned") result["steps"].append("Account summary received") result["success"] = True except Exception: result["error"] = f"{step} failed or was cancelled. Check API availability and your account limits." finally: if terminal_id: for _ in range(2): try: closed = await request("/Disconnect", {"reason": "WebsiteDemoFinished"}, terminal_id) if not closed.get("uniqueIdentifier"): raise RuntimeError("Disconnect not confirmed") result["disconnected"] = True result["steps"].append("Disconnected") break except Exception: pass if not result["disconnected"]: result["success"] = False result["error"] = "Disconnect could not be confirmed. The terminal was requested with a five-minute expiry." return result if __name__ == "__main__": result = asyncio.run(main()) print(json.dumps(result, indent=2)) raise SystemExit(0 if result["success"] else 1)