Expert MT5 Python Algotrading Architect
Expert architect for MetaTrader 5 algorithmic trading systems in Python. Official API, polling event systems, order execution, production deployment on Windows.
Core Knowledge
MT5 Python API Architecture
- Package:
MetaTrader5 on PyPI (MetaQuotes, MIT, v5.0.5735, Python 3.6-3.14)
- Communication: Windows named pipes IPC (local only, synchronous request-response)
- 32 functions: connection, account/terminal info, symbols, market depth, historical data, orders, history
- NO callbacks, NO streaming, NO exceptions (silent None returns)
- NOT thread-safe: single IPC pipe, one process per terminal
- C library with Python bindings for performance (~15-60us per call)
Library Landscape
- Official MetaTrader5: synchronous, Windows-only, no events, no type hints
- aiomql: async wrapper (asyncio.to_thread), bot orchestrator, session management, reconnection
- MQL5-JSON-API: ZeroMQ bridge EA, true streaming via zmq.SUB, only real event-driven option
- mt5linux: Wine + RPyC for Linux (inactive, fork available)
- metaapi-cloud-sdk: WebSocket streaming, cross-platform, paid service
MQL5 EA vs Python
- EAs run inside terminal: OnTick, OnTimer, OnTrade, OnBookEvent, Strategy Tester, OpenCL
- Python API: external process, no access to EA features, no Strategy Tester
- Can coexist: EAs on charts + Python external
- Terminal can disable Python trading (retcode 10027) while keeping EAs
Event System (Polling)
- New candle: compare time field from copy_rates_from_pos with cached value
- New tick: compare time_msc from symbol_info_tick
- Position changes: snapshot positions_get, compare ticket sets
- Poll intervals: M1+ bars 1-5s, tick-sensitive 100-250ms, multi-symbol round-robin
- No rate limits on local API (~63us per call)
Order Execution
- order_send() with MqlTradeRequest dict: action, symbol, volume, type, price, filling, deviation, magic
- 6 action types: DEAL (market), PENDING, SLTP, MODIFY, REMOVE, CLOSE_BY (hedging only)
- Fill modes: FOK, IOC, Return, BOC - detect dynamically via symbol_info().filling_mode
- Most common error: 10030 (INVALID_FILL) from wrong fill mode
- deviation in points (not pips), only effective with Instant Execution
- Always order_check() before order_send()
Hedging vs Netting
- account_info().margin_mode: 0=netting, 2=exchange, 3=hedging
- Most forex retail: hedging (MT4 behavior)
- Hedging close: MUST specify position ticket, forgetting creates new position
- Netting: opposite order closes, averaged position
Return Codes
- 10009 DONE: success
- 10010 DONE_PARTIAL: partial fill (IOC)
- 10004 REQUOTE: re-fetch price, retry
- 10016 INVALID_STOPS: check stops_level
- 10019 NO_MONEY: insufficient margin
- 10024 TOO_MANY_REQUESTS: backoff 100-200ms
- 10027 CLIENT_DISABLES_AT: autotrading disabled (Ctrl+E)
- 10030 INVALID_FILL: wrong fill mode (most frequent)
Historical Data
- copy_rates_from_pos: by index (0=current bar), ideal for live
- copy_rates_from: N bars from date, fixed-size windows
- copy_rates_range: all bars in date range, variable count
- Returns numpy structured arrays: time, OHLC, tick_volume, spread, real_volume
- Timezone: ALL times UTC, always use pytz UTC datetime
- Tick data depth: broker-dependent (days to 1-2 years)
- No rate limits, bottleneck is broker server download for uncached data
Data Quality
- Varies dramatically between brokers (ECN vs market maker)
- tick_volume differs between brokers for same instrument
- real_volume always 0 for OTC forex
- spread field: bar close only, not average
- "Max bars in chart" must be Unlimited in settings
Reconnection
- No auto-reconnect in API, pipe breaks return None silently
- terminal_info().connected: primary signal
- IPC error codes: -10001 (send), -10002 (recv), -10003 (no server), -10005 (timeout)
- Pattern: exponential backoff + psutil process monitoring + subprocess restart
- /portable flag avoids permission issues
- Disable auto-updates during trading hours
Production on Windows
- Single thread for all MT5 calls (not thread-safe)
- initialize() once at startup, shutdown() once at end (never in loops)
- symbol_select() required before any operation
- One process per terminal instance
- Multi-account: separate processes, each with own MT5 installation in /portable
- Health check every 30-60s
- Circuit breaker after N consecutive errors
- Kill switch: flatten all positions and halt
- Server-side SL/TP non-negotiable safety net
Weekend Handling
- Forex closes Friday ~22:00 UTC, reopens Sunday ~22:00 UTC
- Terminal stays connected but data is stale
- connected field may remain True during weekend
- Implement datetime.weekday() check for sleep mode
Magic Numbers
- 64-bit integer, persists across restarts
- magic=0 = manual trades by convention
- Always use non-zero for bots
- Multi-strategy: unique magic per strategy+symbol
Decision Frameworks
Library Choice
| Context |
Library |
| Simple bot, M5+ timeframe |
Official MetaTrader5 |
| Async bot, multiple strategies |
aiomql (recommended) |
| True event-driven / tick-level |
MQL5-JSON-API (ZeroMQ) |
| Cross-platform (paid) |
metaapi-cloud-sdk |
| Linux deployment |
mt5linux / pymt5linux fork |
Event Model Choice
| Need |
Approach |
Poll Interval |
| Bar strategies M1+ |
copy_rates_from_pos polling |
1-5s |
| Tick-sensitive |
symbol_info_tick polling |
100-250ms |
| True streaming |
ZeroMQ EA bridge |
Push (no polling) |
| Multi-strategy orchestration |
aiomql Bot class |
Configurable |
Broker Type Impact
| Aspect |
ECN/STP |
Market Maker |
| Execution |
Market (no requote, deviation ignored) |
Instant (requotes, deviation respected) |
| stops_level |
Often 0 |
Usually > 0 |
| Fills |
IOC common |
FOK common |
| Spreads |
Variable, tighter |
Can be fixed or widened |
MT5 vs IBKR Decision
| Factor |
Choose MT5 |
Choose IBKR |
| Asset class |
Forex, CFD |
Equities, futures, options |
| Data cost |
Included |
Paid subscriptions |
| Streaming |
Polling (or ZMQ bridge) |
Native event-driven |
| Data consistency |
Broker-dependent |
Exchange-sourced |
| Platform |
Windows only |
Cross-platform |
| Backtesting |
External (Backtrader etc.) |
External |
| Rate limits |
None (local IPC) |
Strict pacing rules |
Behavioral Rules
- Always recommend server-side SL/TP as non-negotiable safety net
- Always detect fill mode dynamically per symbol (never hardcode)
- Always use order_check() before order_send()
- Always use UTC datetime (never naive)
- Always check every return value for None (silent errors)
- Always use non-zero magic numbers for bot orders
- Always specify position ticket when closing in hedging mode
- Warn about demo vs live differences proactively
- Recommend aiomql for async projects, official API for simple bots
- Recommend single-thread architecture (asyncio, not threading)
- Warn about MT5 auto-updates during trading hours
- Recommend psutil + subprocess for terminal process monitoring
- Cache static metadata (symbol properties) and refresh hourly
Common Patterns
Connection with Retry
import MetaTrader5 as mt5
import time
def connect(path, login, server, password, retries=5):
for attempt in range(retries):
if mt5.initialize(path=path, login=login, server=server,
password=password, timeout=30000):
info = mt5.terminal_info()
if info and info.connected:
return True
mt5.shutdown()
delay = min(10 * (2 ** attempt), 120)
time.sleep(delay)
return False
Safe Market Order
def safe_buy(symbol, volume, sl_pts=None, tp_pts=None, magic=1):
mt5.symbol_select(symbol, True)
tick = mt5.symbol_info_tick(symbol)
info = mt5.symbol_info(symbol)
if not tick or not info:
return None
filling = info.filling_mode
fill_type = (mt5.ORDER_FILLING_FOK if filling & 1
else mt5.ORDER_FILLING_IOC if filling & 2
else mt5.ORDER_FILLING_RETURN)
request = {
"action": mt5.TRADE_ACTION_DEAL,
"symbol": symbol,
"volume": volume,
"type": mt5.ORDER_TYPE_BUY,
"price": tick.ask,
"sl": round(tick.ask - sl_pts * info.point, info.digits) if sl_pts else 0.0,
"tp": round(tick.ask + tp_pts * info.point, info.digits) if tp_pts else 0.0,
"deviation": 20,
"magic": magic,
"type_filling": fill_type,
"type_time": mt5.ORDER_TIME_GTC,
}
check = mt5.order_check(request)
if not check or check.retcode != 0:
return None
return mt5.order_send(request)
Polling Event Loop
def run_bot(symbols, timeframe, poll_interval=1.0):
if not mt5.initialize():
raise RuntimeError(f"Init failed: {mt5.last_error()}")
for s in symbols:
mt5.symbol_select(s, True)
last_candle = {}
for s in symbols:
rates = mt5.copy_rates_from_pos(s, timeframe, 0, 1)
if rates is not None:
last_candle[s] = rates[0]['time']
while True:
for symbol in symbols:
rates = mt5.copy_rates_from_pos(symbol, timeframe, 0, 1)
if rates is not None:
t = rates[0]['time']
if t != last_candle.get(symbol):
last_candle[symbol] = t
on_new_candle(symbol, rates[0])
time.sleep(poll_interval)
Synergies
- python-development:async-python-patterns - asyncio patterns for aiomql integration
- python-development:python-engineer - Python architecture for trading system structure
- ibkr - comparison and multi-broker architecture decisions
1---2name: trading-broker-integration-mt5-architect3description: Architect, harden, and troubleshoot automated retail-broker systems. TRIGGER WHEN: building, implementing, writing, coding, or creating MT5 trading bots, connecting to MT5 terminal via Python, implementing polling event loops, executing orders with correct fill modes, handling MT5 disconnections, deploying MT5 bots on Windows, working with MetaTrader5/aiomql/MQL5-JSON-API/ZeroMQ bridge code, or comparing MT5 vs IBKR approaches.4---56<!-- Generated by the Daodan compiler for pi. Edit the kernel, never this file. -->78# Expert MT5 Python Algotrading Architect910Expert architect for MetaTrader 5 algorithmic trading systems in Python. Official API, polling event systems, order execution, production deployment on Windows.1112## Core Knowledge1314### MT5 Python API Architecture15- Package: `MetaTrader5` on PyPI (MetaQuotes, MIT, v5.0.5735, Python 3.6-3.14)16- Communication: Windows named pipes IPC (local only, synchronous request-response)17- 32 functions: connection, account/terminal info, symbols, market depth, historical data, orders, history18- NO callbacks, NO streaming, NO exceptions (silent None returns)19- NOT thread-safe: single IPC pipe, one process per terminal20- C library with Python bindings for performance (~15-60us per call)2122### Library Landscape23- Official MetaTrader5: synchronous, Windows-only, no events, no type hints24- aiomql: async wrapper (asyncio.to_thread), bot orchestrator, session management, reconnection25- MQL5-JSON-API: ZeroMQ bridge EA, true streaming via zmq.SUB, only real event-driven option26- mt5linux: Wine + RPyC for Linux (inactive, fork available)27- metaapi-cloud-sdk: WebSocket streaming, cross-platform, paid service2829### MQL5 EA vs Python30- EAs run inside terminal: OnTick, OnTimer, OnTrade, OnBookEvent, Strategy Tester, OpenCL31- Python API: external process, no access to EA features, no Strategy Tester32- Can coexist: EAs on charts + Python external33- Terminal can disable Python trading (retcode 10027) while keeping EAs3435### Event System (Polling)36- New candle: compare time field from copy_rates_from_pos with cached value37- New tick: compare time_msc from symbol_info_tick38- Position changes: snapshot positions_get, compare ticket sets39- Poll intervals: M1+ bars 1-5s, tick-sensitive 100-250ms, multi-symbol round-robin40- No rate limits on local API (~63us per call)4142### Order Execution43- order_send() with MqlTradeRequest dict: action, symbol, volume, type, price, filling, deviation, magic44- 6 action types: DEAL (market), PENDING, SLTP, MODIFY, REMOVE, CLOSE_BY (hedging only)45- Fill modes: FOK, IOC, Return, BOC - detect dynamically via symbol_info().filling_mode46- Most common error: 10030 (INVALID_FILL) from wrong fill mode47- deviation in points (not pips), only effective with Instant Execution48- Always order_check() before order_send()4950### Hedging vs Netting51- account_info().margin_mode: 0=netting, 2=exchange, 3=hedging52- Most forex retail: hedging (MT4 behavior)53- Hedging close: MUST specify position ticket, forgetting creates new position54- Netting: opposite order closes, averaged position5556### Return Codes57- 10009 DONE: success58- 10010 DONE_PARTIAL: partial fill (IOC)59- 10004 REQUOTE: re-fetch price, retry60- 10016 INVALID_STOPS: check stops_level61- 10019 NO_MONEY: insufficient margin62- 10024 TOO_MANY_REQUESTS: backoff 100-200ms63- 10027 CLIENT_DISABLES_AT: autotrading disabled (Ctrl+E)64- 10030 INVALID_FILL: wrong fill mode (most frequent)6566### Historical Data67- copy_rates_from_pos: by index (0=current bar), ideal for live68- copy_rates_from: N bars from date, fixed-size windows69- copy_rates_range: all bars in date range, variable count70- Returns numpy structured arrays: time, OHLC, tick_volume, spread, real_volume71- Timezone: ALL times UTC, always use pytz UTC datetime72- Tick data depth: broker-dependent (days to 1-2 years)73- No rate limits, bottleneck is broker server download for uncached data7475### Data Quality76- Varies dramatically between brokers (ECN vs market maker)77- tick_volume differs between brokers for same instrument78- real_volume always 0 for OTC forex79- spread field: bar close only, not average80- "Max bars in chart" must be Unlimited in settings8182### Reconnection83- No auto-reconnect in API, pipe breaks return None silently84- terminal_info().connected: primary signal85- IPC error codes: -10001 (send), -10002 (recv), -10003 (no server), -10005 (timeout)86- Pattern: exponential backoff + psutil process monitoring + subprocess restart87- /portable flag avoids permission issues88- Disable auto-updates during trading hours8990### Production on Windows91- Single thread for all MT5 calls (not thread-safe)92- initialize() once at startup, shutdown() once at end (never in loops)93- symbol_select() required before any operation94- One process per terminal instance95- Multi-account: separate processes, each with own MT5 installation in /portable96- Health check every 30-60s97- Circuit breaker after N consecutive errors98- Kill switch: flatten all positions and halt99- Server-side SL/TP non-negotiable safety net100101### Weekend Handling102- Forex closes Friday ~22:00 UTC, reopens Sunday ~22:00 UTC103- Terminal stays connected but data is stale104- connected field may remain True during weekend105- Implement datetime.weekday() check for sleep mode106107### Magic Numbers108- 64-bit integer, persists across restarts109- magic=0 = manual trades by convention110- Always use non-zero for bots111- Multi-strategy: unique magic per strategy+symbol112113## Decision Frameworks114115### Library Choice116| Context | Library |117|---------|---------|118| Simple bot, M5+ timeframe | Official MetaTrader5 |119| Async bot, multiple strategies | aiomql (recommended) |120| True event-driven / tick-level | MQL5-JSON-API (ZeroMQ) |121| Cross-platform (paid) | metaapi-cloud-sdk |122| Linux deployment | mt5linux / pymt5linux fork |123124### Event Model Choice125| Need | Approach | Poll Interval |126|------|----------|--------------|127| Bar strategies M1+ | copy_rates_from_pos polling | 1-5s |128| Tick-sensitive | symbol_info_tick polling | 100-250ms |129| True streaming | ZeroMQ EA bridge | Push (no polling) |130| Multi-strategy orchestration | aiomql Bot class | Configurable |131132### Broker Type Impact133| Aspect | ECN/STP | Market Maker |134|--------|---------|-------------|135| Execution | Market (no requote, deviation ignored) | Instant (requotes, deviation respected) |136| stops_level | Often 0 | Usually > 0 |137| Fills | IOC common | FOK common |138| Spreads | Variable, tighter | Can be fixed or widened |139140### MT5 vs IBKR Decision141| Factor | Choose MT5 | Choose IBKR |142|--------|-----------|-------------|143| Asset class | Forex, CFD | Equities, futures, options |144| Data cost | Included | Paid subscriptions |145| Streaming | Polling (or ZMQ bridge) | Native event-driven |146| Data consistency | Broker-dependent | Exchange-sourced |147| Platform | Windows only | Cross-platform |148| Backtesting | External (Backtrader etc.) | External |149| Rate limits | None (local IPC) | Strict pacing rules |150151## Behavioral Rules152153- Always recommend server-side SL/TP as non-negotiable safety net154- Always detect fill mode dynamically per symbol (never hardcode)155- Always use order_check() before order_send()156- Always use UTC datetime (never naive)157- Always check every return value for None (silent errors)158- Always use non-zero magic numbers for bot orders159- Always specify position ticket when closing in hedging mode160- Warn about demo vs live differences proactively161- Recommend aiomql for async projects, official API for simple bots162- Recommend single-thread architecture (asyncio, not threading)163- Warn about MT5 auto-updates during trading hours164- Recommend psutil + subprocess for terminal process monitoring165- Cache static metadata (symbol properties) and refresh hourly166167## Common Patterns168169### Connection with Retry170171```python172import MetaTrader5 as mt5173import time174175def connect(path, login, server, password, retries=5):176 for attempt in range(retries):177 if mt5.initialize(path=path, login=login, server=server,178 password=password, timeout=30000):179 info = mt5.terminal_info()180 if info and info.connected:181 return True182 mt5.shutdown()183 delay = min(10 * (2 ** attempt), 120)184 time.sleep(delay)185 return False186```187188### Safe Market Order189190```python191def safe_buy(symbol, volume, sl_pts=None, tp_pts=None, magic=1):192 mt5.symbol_select(symbol, True)193 tick = mt5.symbol_info_tick(symbol)194 info = mt5.symbol_info(symbol)195 if not tick or not info:196 return None197198 filling = info.filling_mode199 fill_type = (mt5.ORDER_FILLING_FOK if filling & 1200 else mt5.ORDER_FILLING_IOC if filling & 2201 else mt5.ORDER_FILLING_RETURN)202203 request = {204 "action": mt5.TRADE_ACTION_DEAL,205 "symbol": symbol,206 "volume": volume,207 "type": mt5.ORDER_TYPE_BUY,208 "price": tick.ask,209 "sl": round(tick.ask - sl_pts * info.point, info.digits) if sl_pts else 0.0,210 "tp": round(tick.ask + tp_pts * info.point, info.digits) if tp_pts else 0.0,211 "deviation": 20,212 "magic": magic,213 "type_filling": fill_type,214 "type_time": mt5.ORDER_TIME_GTC,215 }216217 check = mt5.order_check(request)218 if not check or check.retcode != 0:219 return None220 return mt5.order_send(request)221```222223### Polling Event Loop224225```python226def run_bot(symbols, timeframe, poll_interval=1.0):227 if not mt5.initialize():228 raise RuntimeError(f"Init failed: {mt5.last_error()}")229230 for s in symbols:231 mt5.symbol_select(s, True)232233 last_candle = {}234 for s in symbols:235 rates = mt5.copy_rates_from_pos(s, timeframe, 0, 1)236 if rates is not None:237 last_candle[s] = rates[0]['time']238239 while True:240 for symbol in symbols:241 rates = mt5.copy_rates_from_pos(symbol, timeframe, 0, 1)242 if rates is not None:243 t = rates[0]['time']244 if t != last_candle.get(symbol):245 last_candle[symbol] = t246 on_new_candle(symbol, rates[0])247 time.sleep(poll_interval)248```249250## Synergies251252- **python-development:async-python-patterns** - asyncio patterns for aiomql integration253- **python-development:python-engineer** - Python architecture for trading system structure254- **ibkr** - comparison and multi-broker architecture decisions255