Building a Robust Prop Trading Execution Engine for Gold (XAUUSD) Using Python and MetaTrader 5 (MQL5)

Passing modern proprietary trading evaluations requires strict discipline, particularly regarding Maximum Daily Drawdown and Overall Account Drawdown. While manual traders often fail due to emotional slippage and poor position sizing, algorithmic execution engines provide deterministic risk control.

Gold (XAUUSD) is known for extreme intraday volatility, institutional liquidity sweeps, and sudden expansion phases. This article details an end-to-end quantitative architecture integrating Python (for high-level risk modeling and volatility calculations) with MetaTrader 5 / MQL5 (for sub-millisecond execution and order tracking).

1. System Architecture

The engine operates on a decoupled client-terminal model:

  1. MetaTrader 5 API Bridge: Streams tick and bar data directly into a Python runtime.
  2. Quantitative Risk Filter: Calculates dynamic lot sizing based on real-time Average True Range (ATR) and equity curves.
  3. Execution & Order Telemetry: Dispatches buy/sell limit orders and manages trailing protective stops.

2. Python Risk Controller & Bridge Implementation

Below is the production-ready Python controller script. It validates broker connectivity, assesses the daily drawdown limit, and calculates optimal position sizes for XAUUSD.

 python import MetaTrader5 as mt5 import pandas as pd import numpy as np from datetime import datetime class GoldRiskEngine: def __init__(self, symbol="XAUUSD", max_daily_loss_pct=0.04, risk_per_trade_pct=0.01): self.symbol = symbol self.max_daily_loss_pct = max_daily_loss_pct self.risk_per_trade_pct = risk_per_trade_pct if not mt5.initialize(): raise RuntimeError(f"MetaTrader5 initialization failed: {mt5.last_error()}") def get_account_health(self): account_info = mt5.account_info() if account_info is None: raise ValueError("Failed to fetch account info.") equity = account_info.equity balance = account_info.balance daily_pnl = equity - balance return { "balance": balance, "equity": equity, "daily_pnl": daily_pnl, "daily_drawdown_pct": abs(daily_pnl / balance) if daily_pnl < 0 else 0.0 } def calculate_lot_size(self, stop_loss_pips): health = self.get_account_health() if health["daily_drawdown_pct"] >= self.max_daily_loss_pct: print("🚨 Max daily drawdown reached! Halting new execution.") return 0.0 risk_amount = health["equity"] * self.risk_per_trade_pct # For XAUUSD, 1 pip = 0.1 USD per 0.01 lot roughly depending on contract specs pip_value = 1.0 lot_size = risk_amount / (stop_loss_pips * pip_value) return round(max(0.01, min(lot_size, 5.0)), 2) def shutdown(self): mt5.shutdown() if __name__ == "__main__": engine = GoldRiskEngine() print("Account Health:", engine.get_account_health()) print("Calculated Lot Size:", engine.calculate_lot_size(stop_loss_pips=150)) engine.shutdown()