Integrating MQL5 with Data Processing Packages (Part 7): Building Multi-Agent Environments for Cross-Symbol Collaboration
Table of Contents:
- Introduction
- System Overview and Understanding
- Getting Started
- Putting It All Together on MQL5
- Live Demo
- Conclusion
Introduction
In part 6, Merging Market Feedback with Model Adaptation, we focused on closing the loop between market behavior and decision-making logic. Rather than relying on static signals, we introduced mechanisms that allow the trading system to observe its own performance, react to changing market conditions, and adapt its internal parameters accordingly. This included using live feedback such as trade outcomes, volatility shifts, and structural market changes to continuously refine how signals are interpreted and executed within the MQL5–Python hybrid architecture.
In this part, we extend this integration to develop multi-agent environments capable of cross-symbol collaboration. The goal is to design a framework where independent agents analyze different markets or symbols, share insights, and collectively influence trading decisions in a coordinated way. This approach aims to leverage inter-symbol relationships (like currency correlations or risk sentiment) to improve signal quality, reduce false triggers, and create a more robust trading system that adapts to broader market context rather than isolated price action.
System Overview and Understanding
For building multi-agent environments enabling cross-symbol collaboration, the core paradigm shifts from isolated per-symbol strategies to a networked intelligence architecture where agents operate as specialized nodes within a collective decision-making graph. Each agent embodies expertise on a specific symbol or market segment—be it EURUSD, gold, or indices—but maintains dynamic communication channels with peer agents. This structure allows the system to perceive inter-market relationships not as static correlations but as live, actionable pathways.

The environment’s operational logic centers on orchestrated autonomy: agents possess localized execution authority but subscribe to a global coordination layer that manages systemic constraints and synergies. This layer enforces portfolio-level objectives—such as maximum correlated exposure or cross-symbol volatility targets—by modulating individual agent actions through shared context buffers, reward shaping, and lightweight consensus mechanisms. For instance, when multiple agents detect conflicting signals (e.g., one agent bullish on GBPUSD while another bearish on the dollar index), the coordination layer can initiate a structured inference session, weighing evidence hierarchies and historical interaction patterns before resolving directional bias.

Getting Started
import MetaTrader5 as mt5 import pandas as pd import numpy as np import json import time import schedule from datetime import datetime, timedelta from flask import Flask, jsonify, request import threading import warnings warnings.filterwarnings('ignore') account = '123456' password = 'YourPass@MT5' server = 'YourBrokersServer-Demo' print("=" * 80) print(" MULTI-AGENT TRADING SYSTEM - LIVE DEPLOYMENT") print("=" * 80)
Getting started, we set up the core environment required to connect Python with MetaTrader 5 and manage data, timing, and external communication. We import MetaTrader 5 for market access, pandas and NumPy for data processing, and Flask for exposing signals and system status through a lightweight API. Additional modules handle scheduling, threading, timestamps, and JSON serialization, allowing the trading system to run continuously and interact safely with MQL5 in real time. Finally, the account, password, and server variables define the credentials needed to establish a live or demo MetaTrader 5 connection.
class LiveMultiAgentSystem: def __init__(self, account_number, password, server): self.account_number = account_number self.password = password self.server = server self.symbols = ["XAUUSD", "EURUSD", "GBPUSD", "USDJPY"] self.agents = {} self.positions = {} self.decision_log = [] # Initialize MT5 self.init_mt5() # Initialize agents self.init_agents() print(":/ Live Multi-Agent System Initialized") def init_mt5(self): """Initialize connection to MT5""" if not mt5.initialize(): print("X MT5 initialization failed") print(f" Error: {mt5.last_error()}") return False # Login to account authorized = mt5.login( login=self.account_number, password=self.password, server=self.server ) if authorized: print(f":/ Connected to MT5 Account: {self.account_number}") account_info = mt5.account_info() print(f" Balance: ${account_info.balance:.2f}") print(f" Equity: ${account_info.equity:.2f}") return True else: print(f"X Login failed: {mt5.last_error()}") return False
In this class we define the core structure responsible for managing a live multi-agent trading environment. The constructor sets up account credentials, assigns a predefined list of symbols, and prepares containers for agents, open positions, and decision history. It then establishes a connection to MetaTrader 5 by initializing the terminal and logging into the specified trading account, validating connectivity, and retrieving basic account information. This setup ensures that all agents operate within a shared, authenticated MetaTrader 5 session, forming the foundation for coordinated, real-time, cross-symbol decision-making.
def init_agents(self): """Initialize trading agents""" print("\n Initializing Live Agents...") from collections import defaultdict self.agents = { "XAUUSD": { "name": "Gold Agent", "weights": { "trend": 1.5, "momentum": 1.2, "volatility": 1.0 }, "params": { "ema_period": 20, "rsi_period": 14, "atr_period": 14 } }, "CONTEXT": { "name": "Context Agent", "weights": { "usd_strength": 1.3, "risk_sentiment": 1.1 } }, "LIQUIDITY": { "name": "Liquidity Agent", "weights": { "volatility": 1.2, "volume": 1.0 } } } print(f":/ Created {len(self.agents)} agents") def get_live_data(self, symbol, timeframe=mt5.TIMEFRAME_M5, bars=100): """Get live data from MT5""" try: rates = mt5.copy_rates_from_pos(symbol, timeframe, 0, bars) if rates is None or len(rates) == 0: print(f"❌ No data for {symbol}") return None df = pd.DataFrame(rates) df['time'] = pd.to_datetime(df['time'], unit='s') df.set_index('time', inplace=True) # Calculate features df['returns'] = df['close'].pct_change() df['ema_20'] = df['close'].ewm(span=20).mean() df['ema_50'] = df['close'].ewm(span=50).mean() df['rsi'] = self.calculate_rsi(df['close']) df['atr'] = self.calculate_atr(df) return df except Exception as e: print(f"X Error getting data for {symbol}: {e}") return None def calculate_rsi(self, prices, period=14): """Calculate RSI""" delta = prices.diff() gain = (delta.where(delta > 0, 0)).rolling(window=period).mean() loss = (-delta.where(delta < 0, 0)).rolling(window=period).mean() rs = gain / loss rsi = 100 - (100 / (1 + rs)) return rsi def calculate_atr(self, df, period=14): """Calculate ATR""" high = df['high'] low = df['low'] close = df['close'] tr1 = high - low tr2 = abs(high - close.shift()) tr3 = abs(low - close.shift()) tr = pd.concat([tr1, tr2, tr3], axis=1).max(axis=1) atr = tr.rolling(period).mean() return atr def analyze_symbol(self, symbol): """Agent analysis for a symbol""" data = self.get_live_data(symbol) if data is None or len(data) < 50: return {"action": 0, "confidence": 0.0, "reason": "Insufficient data"} current_price = data['close'].iloc[-1] ema_20 = data['ema_20'].iloc[-1] ema_50 = data['ema_50'].iloc[-1] rsi = data['rsi'].iloc[-1] atr = data['atr'].iloc[-1] # Decision logic bullish = 0 bearish = 0 # Trend if current_price > ema_20 > ema_50: bullish += 2 elif current_price < ema_20 < ema_50: bearish += 2 # Momentum if 40 < rsi < 70: bullish += 1 elif 30 < rsi < 60: bearish += 1 # Volatility (using ATR) if atr / current_price < 0.002: # Low volatility bullish += 0.5 bearish += 0.5 # Make decision if bullish > bearish and bullish >= 2: action = 1 confidence = min(bullish / 4, 0.8) reason = f"Bullish: trend={bullish}, RSI={rsi:.1f}" elif bearish > bullish and bearish >= 2: action = -1 confidence = min(bearish / 4, 0.8) reason = f"Bearish: trend={bearish}, RSI={rsi:.1f}" else: action = 0 confidence = 0.1 reason = f"Neutral: Bull={bullish}, Bear={bearish}" return { "symbol": symbol, "action": action, "confidence": confidence, "reason": reason, "price": current_price, "timestamp": datetime.now().isoformat() }
In this section we initialize the live trading agents that form the multi-agent system. Each agent is defined with a specific role and responsibility, such as the Gold Agent for symbol-specific analysis, a Context Agent for broader market conditions, and a Liquidity Agent for volatility and volume awareness. These agents are configured with weighted decision factors and indicator parameters, allowing each one to contribute specialized intelligence to the overall system rather than relying on a single, monolithic strategy.
The data-handling methods then establish a real-time market data pipeline from MetaTrader 5. Price data is pulled directly from MetaTrader 5, converted into a structured pandas DataFrame, and enriched with technical features such as returns, exponential moving averages, RSI, and ATR. Dedicated helper functions calculate RSI and ATR manually, ensuring full transparency and flexibility over indicator behavior while keeping all preprocessing logic centralized and reusable across agents.
The analyze_symbol method represents the core decision engine for each symbol-specific agent. It evaluates trend structure, momentum, and volatility to score bullish and bearish conditions independently, transforming raw market data into actionable signals. Based on these scores, the agent outputs a directional action, confidence level, and explanatory reasoning, which can later be shared with other agents or passed to the execution layer for coordinated, cross-symbol trading decisions.
def analyze_context(self): """Context agent analysis""" # Get USD strength from major pairs eur_data = self.get_live_data("EURUSD", bars=50) gbp_data = self.get_live_data("GBPUSD", bars=50) jpy_data = self.get_live_data("USDJPY", bars=50) if not all([eur_data is not None, gbp_data is not None, jpy_data is not None]): return {"action": 0, "confidence": 0.0, "reason": "Missing data"} # Calculate USD strength eur_change = (eur_data['close'].iloc[-1] / eur_data['close'].iloc[-20] - 1) gbp_change = (gbp_data['close'].iloc[-1] / gbp_data['close'].iloc[-20] - 1) jpy_change = (jpy_data['close'].iloc[-1] / jpy_data['close'].iloc[-20] - 1) # EURUSD and GBPUSD down = USD strong, USDJPY up = USD strong usd_strength = (-eur_change - gbp_change + jpy_change) / 3 if usd_strength > 0.01: action = -1 # USD strong → bearish for gold confidence = min(abs(usd_strength) * 10, 0.7) reason = f"USD Strong: {usd_strength:.2%}" elif usd_strength < -0.01: action = 1 # USD weak → bullish for gold confidence = min(abs(usd_strength) * 10, 0.7) reason = f"USD Weak: {usd_strength:.2%}" else: action = 0 confidence = 0.1 reason = "USD Neutral" return { "agent": "CONTEXT", "action": action, "confidence": confidence, "reason": reason, "usd_strength": usd_strength } def run_decision_pipeline(self, target_symbol="XAUUSD"): """Run complete decision pipeline""" print(f"\nO- {datetime.now().strftime('%H:%M:%S')} - Analyzing market...") # Collect agent decisions decisions = [] # Symbol agents for symbol in self.symbols: if symbol == target_symbol: decision = self.analyze_symbol(symbol) decisions.append(decision) print(f" {symbol}: {decision['action']} ({decision['confidence']:.1%}) - {decision['reason']}") # Context agent context_decision = self.analyze_context() decisions.append(context_decision) print(f" CONTEXT: {context_decision['action']} ({context_decision['confidence']:.1%}) - {context_decision['reason']}") # Calculate consensus weighted_sum = 0 total_weight = 0 weights = { "XAUUSD": 1.5, "EURUSD": 1.0, "GBPUSD": 1.0, "USDJPY": 1.0, "CONTEXT": 1.3 } for decision in decisions: symbol = decision.get('symbol', decision.get('agent', 'UNKNOWN')) weight = weights.get(symbol, 1.0) weighted_sum += decision['action'] * decision['confidence'] * weight total_weight += weight * decision['confidence'] # Make final decision if total_weight == 0: final_action = 0 confidence = 0.0 reason = "No consensus" else: consensus = weighted_sum / total_weight if consensus > 0.3: final_action = 1 confidence = min(abs(consensus), 0.9) reason = f"Bullish consensus: {consensus:.2f}" elif consensus < -0.3: final_action = -1 confidence = min(abs(consensus), 0.9) reason = f"Bearish consensus: {consensus:.2f}" else: final_action = 0 confidence = 0.2 reason = f"Neutral: {consensus:.2f}" final_decision = { "timestamp": datetime.now().isoformat(), "symbol": target_symbol, "action": final_action, "confidence": confidence, "reason": reason, "consensus": consensus if total_weight > 0 else 0, "agent_decisions": decisions } self.decision_log.append(final_decision) # Save decision to file for MQL5 to read self.save_decision(final_decision) # Execute trade if confidence is high enough if confidence > 0.5: self.execute_trade(final_decision) return final_decision def write_signal(signal): with open("multi_agent_signal.json", "w") as f: json.dump(signal, f, indent=2) def save_decision(self, decision): """Save decision to JSON file for MQL5""" try: with open('multi_agent_signal.json', 'w') as f: json.dump(decision, f, indent=2) print(f"💾 Signal saved to file") except Exception as e: print(f"X Error saving signal: {e}") def execute_trade(self, decision): """Execute trade via MT5""" symbol = decision['symbol'] action = decision['action'] confidence = decision['confidence'] # Check existing positions positions = mt5.positions_get(symbol=symbol) if positions: print(f"!! Existing position found for {symbol}") # Check if we should close it current_position = positions[0] if (action == 1 and current_position.type == 1) or (action == -1 and current_position.type == 0): print(f" Same direction, considering adding to position") return else: print(f" Opposite direction, closing position first") self.close_position(symbol) # Prepare trade request symbol_info = mt5.symbol_info(symbol) if symbol_info is None: print(f"X Symbol {symbol} not found") return point = symbol_info.point price = mt5.symbol_info_tick(symbol).ask if action == 1 else mt5.symbol_info_tick(symbol).bid # Calculate position size based on risk account_info = mt5.account_info() balance = account_info.balance risk_amount = balance * 0.01 * confidence # 1% risk adjusted by confidence # Calculate stop loss based on ATR data = self.get_live_data(symbol, bars=50) if data is not None and 'atr' in data.columns: atr = data['atr'].iloc[-1] stop_distance = atr * 1.5 else: stop_distance = price * 0.01 # 1% stop # Calculate volume volume = risk_amount / stop_distance volume = round(volume, 2) # Round to 2 decimal places # Validate volume min_volume = symbol_info.volume_min max_volume = symbol_info.volume_max volume = max(min_volume, min(volume, max_volume)) # Prepare order request = { "action": mt5.TRADE_ACTION_DEAL, "symbol": symbol, "volume": volume, "type": mt5.ORDER_TYPE_BUY if action == 1 else mt5.ORDER_TYPE_SELL, "price": price, "sl": price - stop_distance if action == 1 else price + stop_distance, "tp": price + stop_distance * 2 if action == 1 else price - stop_distance * 2, "deviation": 10, "magic": 234000, "comment": f"Multi-Agent: {decision['reason'][:30]}", "type_time": mt5.ORDER_TIME_GTC, "type_filling": mt5.ORDER_FILLING_IOC, } # Send order result = mt5.order_send(request) if result.retcode == mt5.TRADE_RETCODE_DONE: print(f":/ Trade executed: {'BUY' if action == 1 else 'SELL'} {volume} {symbol} at {price}") print(f" SL: {result.request.sl:.5f}, TP: {result.request.tp:.5f}") print(f" Order ID: {result.order}") else: print(f"X Trade failed: {result.comment}") def close_position(self, symbol): """Close all positions for a symbol""" positions = mt5.positions_get(symbol=symbol) if not positions: return for position in positions: tick = mt5.symbol_info_tick(symbol) request = { "action": mt5.TRADE_ACTION_DEAL, "symbol": symbol, "volume": position.volume, "type": mt5.ORDER_TYPE_BUY if position.type == 1 else mt5.ORDER_TYPE_SELL, "position": position.ticket, "price": tick.ask if position.type == 1 else tick.bid, "deviation": 10, "magic": 234000, "comment": "Close by Multi-Agent", "type_time": mt5.ORDER_TIME_GTC, "type_filling": mt5.ORDER_FILLING_IOC, } result = mt5.order_send(request) if result.retcode == mt5.TRADE_RETCODE_DONE: print(f":/ Position {position.ticket} closed") else: print(f"X Failed to close position {position.ticket}: {result.comment}") def monitor_positions(self): """Monitor and manage open positions""" positions = mt5.positions_get() for position in positions: symbol = position.symbol current_price = mt5.symbol_info_tick(symbol).bid # Check stop loss and take profit # These are managed by MT5 automatically, but we can log them unrealized_pnl = position.profit print(f" {symbol}: {position.type} {position.volume} | P&L: ${unrealized_pnl:.2f}") def run_continuously(self, interval_minutes=5): """Run the system continuously""" print(f"\n<=> Starting continuous monitoring every {interval_minutes} minutes...") def job(): try: # Run analysis decision = self.run_decision_pipeline("XAUUSD") # Monitor positions self.monitor_positions() # Log status print(f" Decision: {decision['action']} ({decision['confidence']:.1%}) - {decision['reason']}") except Exception as e: print(f"X Error in scheduled job: {e}") # Schedule the job schedule.every(interval_minutes).minutes.do(job) # Run immediately first time job() # Keep running try: while True: schedule.run_pending() time.sleep(1) except KeyboardInterrupt: print("\n!!! Stopping system...") mt5.shutdown()
This part of the code introduces the context analysis layer, which acts as a higher-level intelligence agent rather than a symbol-specific trader. The analyze_context method evaluates overall USD strength by observing correlated major pairs such as EURUSD, GBPUSD, and USDJPY. By measuring recent percentage changes across these markets and combining them into a single USD-strength metric, the system derives macro-level bias that influences gold (XAUUSD) decisions. This allows the strategy to account for inter-market relationships instead of relying solely on isolated price action.
The run_decision_pipeline method then orchestrates the full multi-agent workflow. It collects decisions from the symbol-specific agent (XAUUSD) and the context agent, logs their outputs, and aggregates them using a weighted consensus model. Each agent contributes to the final decision based on both confidence and predefined importance weights, ensuring that stronger or more reliable signals have greater influence. This consensus mechanism transforms multiple independent opinions into a single, coherent trading action with an associated confidence score.
Once a final decision is formed, the system records it for transparency and persistence. The decision is appended to an internal log and written to a JSON file that can be consumed externally by an MQL5 Expert Advisor. If the confidence exceeds a predefined threshold, the system proceeds to trade execution, bridging analytical intelligence with real-market action. This design cleanly separates decision generation, storage, and execution while keeping them tightly synchronized.
The remaining methods handle live trade execution and position management directly through MetaTrader 5. Trades are sized dynamically based on account balance, confidence-adjusted risk, and ATR-based stop distances, ensuring consistent risk control. The system also supports closing opposing positions, monitoring open trades, and running continuously on a scheduled interval. Together, these components form a complete adaptive trading loop that analyzes market context, reaches multi-agent consensus, executes disciplined trades, and continuously monitors performance in real time.
# ================================================ # FLASK API FOR EXTERNAL CONTROL # ================================================ from flask import Flask, request, jsonify import threading import time import json import MetaTrader5 as mt5 app = Flask(__name__) trading_system = None system_thread = None system_running = False system_lock = threading.Lock() @app.route('/') def home(): return jsonify({ "status": "Multi-Agent Trading System API", "version": "1.1", "running": system_running }) @app.route('/start', methods=['POST']) def start_system(): global trading_system, system_thread, system_running global account, password, serve with system_lock: if system_running: return jsonify({"error": "System already running"}), 400 data = request.json or {} account = data.get('account') password = data.get('password') server = data.get('server', 'SpaceMarkets-Live') if not all([account, password]): return jsonify({"error": "Missing account or password"}), 400 # Create system trading_system = LiveMultiAgentSystem(account, password, server) system_running = True # Background execution loop def runner(): while system_running: try: trading_system.run_once() time.sleep(5) except Exception as e: print("X System error:", e) time.sleep(5) system_thread = threading.Thread(target=runner, daemon=True) system_thread.start() return jsonify({ "status": "System started", "account": account, "server": server }) @app.route('/stop', methods=['POST']) def stop_system(): global system_running with system_lock: if not system_running: return jsonify({"error": "System not running"}), 400 system_running = False return jsonify({"status": "System stopping"}) @app.route('/status', methods=['GET']) def get_status(): if not system_running or trading_system is None: return jsonify({"status": "stopped"}) last_decision = ( trading_system.decision_log[-1] if trading_system.decision_log else None ) return jsonify({ "status": "running", "last_decision": last_decision, "total_decisions": len(trading_system.decision_log) })
This section of the code sets up a safe and controlled execution environment for the trading system by initializing the Flask application, shared state variables, and synchronization mechanisms. Global flags and locks are introduced to track whether the system is running and to prevent race conditions when starting or stopping the trading engine. The root (/) endpoint acts as a lightweight health check, returning the current API version and whether the multi-agent system is actively running, which is useful for both debugging and external monitoring.
The remaining Flask routes provide external control over the trading system lifecycle. The /start endpoint safely initializes the multi-agent engine using credentials provided in the request and launches it in a background thread for continuous operation, while /stop cleanly halts execution without terminating the API itself. The /status endpoint exposes real-time system state, including the most recent decision and total decisions made, enabling MQL5 or other clients to query and synchronize with the Python-based multi-agent logic in a controlled, non-blocking manner.
Putting It All Together on MQL5
//+------------------------------------------------------------------+ //| Multi-Agents.mq5 | //| GIT under Copyright 2025, MetaQuotes Ltd. | //| https://www.mql5.com/en/users/johnhlomohang/ | //+------------------------------------------------------------------+ #property copyright "GIT under Copyright 2025, MetaQuotes Ltd." #property link "https://www.mql5.com/en/users/johnhlomohang/" #property version "1.00" #include <Trade/Trade.mqh> #include "Json.mqh" // Include our JSON parser //--- Input parameters input string PythonServer = "http://127.0.0.1:5000"; // Python server address input double RiskPercent = 1.0; // Risk percentage input int SignalCheckInterval = 30; // Check signal every N seconds input bool EnableTrading = true; // Enable trading input string CommentText = "Multi-Agent"; // Order comment //--- Global variables CTrade trade; datetime lastSignalCheck = 0; string currentSignal = ""; double lastSignalPrice = 0; int magicNumber = 234000; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { // Set magic number for order identification trade.SetExpertMagicNumber(magicNumber); // Set asynchronous mode trade.SetAsyncMode(true); // Print initialization message Print("Multi-Agent EA Initialized - Simple JSON Version"); Print("Python Server: ", PythonServer); Print("Risk: ", RiskPercent, "%"); Print("Magic Number: ", magicNumber); return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { Print("Multi-Agent EA Deinitialized"); } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { // Check if it's time to check for new signals if(TimeCurrent() - lastSignalCheck >= SignalCheckInterval) { CheckSignal(); lastSignalCheck = TimeCurrent(); } // Manage existing positions ManagePositions(); } //+------------------------------------------------------------------+ //| Check signal from Python server | //+------------------------------------------------------------------+ void CheckSignal() { string url = PythonServer + "/signal"; string payload = "{}"; // empty body is fine char data[]; StringToCharArray(payload, data); char result[]; string headers = "Content-Type: application/json\r\n"; string result_headers; int res = WebRequest( "POST", url, headers, 5000, data, result, result_headers ); if(res != 200) { Print("HTTP request failed: ", res); return; } Print("LastError=", GetLastError()); if(res == 200) // HTTP OK { string jsonStr = CharArrayToString(result); Print("Received JSON: ", jsonStr); // Parse using our simple JSON parser string symbol = CJson::ParseString(jsonStr, "symbol", ""); int action = CJson::ParseInteger(jsonStr, "action", 0); double confidence = CJson::ParseNumber(jsonStr, "confidence", 0); string reason = CJson::ParseString(jsonStr, "reason", ""); double price = CJson::ParseNumber(jsonStr, "price", 0); Print("Parsed Signal: Symbol=", symbol, " Action=", action, " Confidence=", confidence, " Reason=", reason); // Only trade if confidence is high enough if(confidence >= 0.5 && EnableTrading) { // Check if we should trade this symbol if(symbol == _Symbol || symbol == "") { ExecuteSignal(action, confidence, reason, price); } } // Update current signal display currentSignal = reason; } else if(res == -1) { Print("WebRequest failed. Error: ", GetLastError()); Print("Make sure to add URL to allowed list: ", url); // To add URL to allowed list in MT5: // 1. Go to Tools -> Options -> Expert Advisors // 2. Click "Add" under "Allowed URLs" // 3. Add: http://localhost:5000 } else { Print("HTTP request failed: ", res); } }
Now in MQL5 we define a MetaTrader 5 Expert Advisor that acts as a bridge between the trading terminal and an external Python-based multi-agent system. We initialize trading parameters such as risk, signal polling frequency, and a magic number for order tracking, then configure the CTrade object for asynchronous execution. On every market tick, the EA periodically sends an HTTP request to the Python server to fetch a trading signal, parses the returned JSON payload to extract fields like symbol, action, confidence, and price, and applies basic filtering logic to ensure trades are only considered when confidence is sufficiently high and trading is enabled.
The remaining logic focuses on runtime behavior and system robustness. The EA continuously manages existing positions while waiting for new signals, logs key events for transparency, and includes safeguards for failed web requests or misconfigured permissions. By offloading signal generation to the Python server and keeping execution logic lightweight inside MetaTrader 5, this design cleanly separates decision-making from trade execution, allowing the multi-agent intelligence to evolve independently of the trading terminal.
//+------------------------------------------------------------------+ //| Execute trading signal | //+------------------------------------------------------------------+ void ExecuteSignal(int action, double confidence, string reason, double signalPrice = 0) { // Get current price double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK); double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID); double price = (action == 1) ? ask : bid; // Use signal price if provided and valid if(signalPrice > 0 && MathAbs(signalPrice - price) / price < 0.05) // Within 5% { price = signalPrice; } // Check if price has changed significantly if(MathAbs(price - lastSignalPrice) / price < 0.001) // Less than 0.1% change { Print("Price hasn't changed enough, skipping trade"); return; } lastSignalPrice = price; // Close opposite positions first if(action == 1) // Buy signal { CloseSellPositions(); } else if(action == -1) // Sell signal { CloseBuyPositions(); } // Calculate position size double volume = CalculateVolume(confidence); if(volume <= 0) { Print("Volume too small, skipping trade"); return; } // Calculate stop loss and take profit based on ATR double atr = CalculateATR(14); double stopLoss = 0; double takeProfit = 0; if(action == 1) // Buy { stopLoss = price - (atr * 1.5); takeProfit = price + (atr * 3.0); } else // Sell { stopLoss = price + (atr * 1.5); takeProfit = price - (atr * 3.0); } // Normalize SL/TP to tick size stopLoss = NormalizePrice(stopLoss); takeProfit = NormalizePrice(takeProfit); // Place order if(action == 1) { if(trade.Buy(volume, _Symbol, price, stopLoss, takeProfit, reason)) { Print("BUY order placed: ", DoubleToString(volume, 2), " ", _Symbol, " at ", DoubleToString(price, 5)); Print("SL: ", DoubleToString(stopLoss, 5), " TP: ", DoubleToString(takeProfit, 5)); } else { Print("Failed to place BUY order: ", trade.ResultRetcodeDescription()); } } else if(action == -1) { if(trade.Sell(volume, _Symbol, price, stopLoss, takeProfit, reason)) { Print("SELL order placed: ", DoubleToString(volume, 2), " ", _Symbol, " at ", DoubleToString(price, 5)); Print("SL: ", DoubleToString(stopLoss, 5), " TP: ", DoubleToString(takeProfit, 5)); } else { Print("Failed to place SELL order: ", trade.ResultRetcodeDescription()); } } } //+------------------------------------------------------------------+ //| Calculate ATR | //+------------------------------------------------------------------+ double CalculateATR(int period) { double atr = 0; // Try to get ATR from indicator int atrHandle = iATR(_Symbol, PERIOD_M5, period); if(atrHandle != INVALID_HANDLE) { double atrArray[]; if(CopyBuffer(atrHandle, 0, 0, 1, atrArray) > 0) { atr = atrArray[0]; } IndicatorRelease(atrHandle); } if(atr <= 0) { // Fallback: use percentage of price atr = SymbolInfoDouble(_Symbol, SYMBOL_BID) * 0.002; // 0.2% } return atr; } //+------------------------------------------------------------------+ //| Normalize price to tick size | //+------------------------------------------------------------------+ double NormalizePrice(double price) { double tickSize = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE); if(tickSize > 0) { price = MathRound(price / tickSize) * tickSize; } return NormalizeDouble(price, (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS)); }
Here we implement a complete trade-execution pipeline that takes a directional signal and turns it into a controlled, risk-aware market order. The ExecuteSignal function begins by determining the correct execution price using the current bid or ask, optionally overriding it with a valid external signal price, and then filters out redundant trades by ensuring the price has moved meaningfully since the last signal. It enforces directional discipline by closing any opposing positions first, calculates position size dynamically from the signal confidence, and derives adaptive stop-loss and take-profit levels using ATR to reflect current market volatility.
Before placing the order, all price levels are normalized to the symbol’s tick size and precision, ensuring broker compliance, after which buy or sell orders are submitted and fully logged for transparency. The supporting functions handle volatility measurement via the ATR indicator with a safe fallback mechanism, and precise price normalization, making the execution logic robust, adaptive, and resistant to common trading and broker-side errors.
//+------------------------------------------------------------------+ //| Json.mqh | //| GIT under Copyright 2025, MetaQuotes Ltd. | //| https://www.mql5.com/en/users/johnhlomohang/ | //+------------------------------------------------------------------+ #property copyright "GIT under Copyright 2025, MetaQuotes Ltd." #property link "https://www.mql5.com/en/users/johnhlomohang/" #ifndef JSON_MQH #define JSON_MQH class CJson { private: string m_json; string ExtractValue(string key) { int keyPos = StringFind(m_json, "\"" + key + "\""); if(keyPos == -1) return ""; int colonPos = StringFind(m_json, ":", keyPos); if(colonPos == -1) return ""; // Find the start of the value int valueStart = colonPos + 1; while(valueStart < StringLen(m_json) && (m_json[valueStart] == ' ' || m_json[valueStart] == '\t' || m_json[valueStart] == '\n' || m_json[valueStart] == '\r')) { valueStart++; } if(valueStart >= StringLen(m_json)) return ""; char firstChar = m_json[valueStart]; // String value if(firstChar == '\"') { int endQuote = StringFind(m_json, "\"", valueStart + 1); if(endQuote == -1) return ""; return StringSubstr(m_json, valueStart + 1, endQuote - valueStart - 1); } // Number or boolean value int valueEnd = valueStart; while(valueEnd < StringLen(m_json) && ((m_json[valueEnd] >= '0' && m_json[valueEnd] <= '9') || m_json[valueEnd] == '-' || m_json[valueEnd] == '.' || m_json[valueEnd] == 'e' || m_json[valueEnd] == 'E' || m_json[valueEnd] == 't' || m_json[valueEnd] == 'r' || m_json[valueEnd] == 'u' || m_json[valueEnd] == 'e' || m_json[valueEnd] == 'f' || m_json[valueEnd] == 'a' || m_json[valueEnd] == 'l' || m_json[valueEnd] == 's' || m_json[valueEnd] == 'n' || m_json[valueEnd] == 'u' || m_json[valueEnd] == 'l')) { valueEnd++; } return StringSubstr(m_json, valueStart, valueEnd - valueStart); } public: void SetJson(string json) { m_json = json; } string GetString(string key, string defaultValue = "") { string value = ExtractValue(key); if(value == "") return defaultValue; // Check if it's actually a string (starts with quote) if(StringGetCharacter(value, 0) == '\"') { return StringSubstr(value, 1, StringLen(value) - 2); } return value; } double GetNumber(string key, double defaultValue = 0) { string value = ExtractValue(key); if(value == "") return defaultValue; // Check for true/false if(value == "true") return 1; if(value == "false") return 0; return StringToDouble(value); } int GetInteger(string key, int defaultValue = 0) { string value = ExtractValue(key); if(value == "") return defaultValue; // Check for true/false if(value == "true") return 1; if(value == "false") return 0; return (int)StringToInteger(value); } bool GetBool(string key, bool defaultValue = false) { string value = ExtractValue(key); if(value == "") return defaultValue; if(value == "true") return true; if(value == "false") return false; if(value == "1") return true; if(value == "0") return false; return defaultValue; } // Static helper methods for quick parsing static string ParseString(string json, string key, string defaultValue = "") { CJson parser; parser.SetJson(json); return parser.GetString(key, defaultValue); } static double ParseNumber(string json, string key, double defaultValue = 0) { CJson parser; parser.SetJson(json); return parser.GetNumber(key, defaultValue); } static int ParseInteger(string json, string key, int defaultValue = 0) { CJson parser; parser.SetJson(json); return parser.GetInteger(key, defaultValue); } static bool ParseBool(string json, string key, bool defaultValue = false) { CJson parser; parser.SetJson(json); return parser.GetBool(key, defaultValue); } }; #endif
In the JSON file, we define a lightweight, self-contained JSON parsing utility for MQL5 that enables safe extraction of simple key–value pairs without relying on external libraries. The CJson class stores a raw JSON string and uses a controlled string-scanning approach to locate keys, skip whitespace, and correctly interpret string, numeric, and boolean values. It exposes typed accessors (GetString, GetNumber, GetInteger, and GetBool) that gracefully fall back to defaults when keys are missing or malformed, ensuring robustness in live trading environments. To simplify usage even further, static helper methods allow one-line parsing directly from a JSON payload, making this class ideal for handling API responses, configuration messages, or inter-process communication where performance, safety, and minimal dependencies are critical.
Live Demo
Below, the Flask server has successfully initialized and is running on localhost on port 5000, and getting signals from Jupyter Lab to MetaTrader5.


Conclusion
In summary, we developed a multi-agent trading environment where independent agents communicate through a shared Python service and collaborate across multiple symbols instead of operating in isolation. Each agent produces structured signals that are transmitted via JSON, parsed safely inside the MQL5 Expert Advisor, and evaluated using confidence thresholds, risk controls, and symbol validation. This architecture allows cross-symbol awareness, coordinated decision-making, and feedback-driven adaptation, effectively merging market intelligence from several instruments into a single, coherent execution layer within MetaTrader 5.
In conclusion, this approach can significantly enhance trading by reducing tunnel vision and allowing strategies to benefit from broader market context and collective behavior across correlated assets. By offloading intelligence to cooperating agents while keeping execution, risk management, and validation tightly controlled in the EA, traders can gain a system that is more flexible, scalable, and easier to evolve. The result is a cleaner separation of logic and execution, faster experimentation, and a smarter trading workflow that adapts more naturally to changing market conditions.
| File Name | File Description |
|---|---|
| Multi Agents.mq5 | The main Expert Advisor that connects to the Python server, receives multi-agent trading signals via JSON, manages risk, and executes trades in MetaTrader 5. |
| Json.mq5 | A lightweight custom JSON parser used by the EA to safely extract strings, numbers, and booleans from Python-generated signal responses. |
| MultiAgentsLab.ipynb | The Python notebook that builds and runs the multi-agent environment, generates cross-symbol trading signals, and serves them to MetaTrader through a local API. |
Warning: All rights to these materials are reserved by MetaQuotes Ltd. Copying or reprinting of these materials in whole or in part is prohibited.
This article was written by a user of the site and reflects their personal views. MetaQuotes Ltd is not responsible for the accuracy of the information presented, nor for any consequences resulting from the use of the solutions, strategies or recommendations described.
MQL5 Trading Tools (Part 16): Improved Super-Sampling Anti-Aliasing (SSAA) and High-Resolution Rendering
From Basic to Intermediate: Indicator (IV)
Features of Experts Advisors
Introduction to MQL5 (Part 39): Beginner Guide to File Handling in MQL5 (I)
- Free trading apps
- Over 8,000 signals for copying
- Economic news for exploring financial markets
You agree to website policy and terms of use