Интеграция MQL5 с пакетами обработки данных (Часть 7): Построение многоагентных сред для межсимвольного взаимодействия
Содержание:
- Введение
- Обзор и понимание системы
- Приступим
- Объединяем все в MQL5
- Демонстрация в реальном времени
- Заключение
Введение
В части 6 "Объединение рыночной обратной связи с адаптацией модели" мы сосредоточились на организации замкнутого цикла между поведением рынка и логикой принятия решений. Вместо статических сигналов внедрены механизмы, позволяющие торговой системе отслеживать собственные результаты, реагировать на изменение рыночных условий и соответствующим образом адаптировать внутренние параметры. Для этого использовалась обратная связь в реальном времени: результаты сделок, изменения волатильности и структурные изменения рынка. Она позволяет постоянно совершенствовать интерпретацию сигналов и их исполнение в гибридной архитектуре MQL5 и Python.
В этой части интеграция расширяется для разработки многоагентных сред, способных к межсимвольному взаимодействию. Цель состоит в разработке фреймворка, в котором независимые агенты анализируют разные рынки или символы, обмениваются аналитическими выводами и совместно влияют на торговые решения. Этот подход задействует межсимвольные взаимосвязи, например валютные корреляции или аппетит к риску, чтобы повысить качество сигналов, сократить число ложных срабатываний и создать более устойчивую торговую систему, учитывающую более широкий рыночный контекст, а не изолированную динамику цены.
Обзор и понимание системы
При построении многоагентных сред для межсимвольного взаимодействия основная парадигма смещается от изолированных стратегий для отдельных символов к архитектуре распределенного интеллекта, в которой агенты выступают специализированными узлами общего графа принятия решений. Каждый агент специализируется на конкретном символе или сегменте рынка, будь то EURUSD, золото или индексы, и поддерживает динамичные каналы взаимодействия с другими агентами. Эта структура позволяет системе воспринимать межрыночные связи не как статические корреляции, а как динамические каналы, пригодные для практического использования в принятии решений.

Операционная логика среды основана на скоординированной автономии: агенты обладают локальными полномочиями на исполнение, но работают в рамках глобального уровня координации, управляющего системными ограничениями и синергетическими эффектами. Этот уровень обеспечивает достижение целей на уровне портфеля, например соблюдение лимита коррелированной экспозиции и целевых показателей межсимвольной волатильности. Для этого действия отдельных агентов корректируются через общие контекстные буферы, модификацию функции вознаграждения и легковесные механизмы консенсуса. Например, когда несколько агентов обнаруживают противоречивые сигналы, один агент дает бычий прогноз по GBPUSD, а другой – медвежий по индексу доллара, уровень координации может инициировать структурированную процедуру инференса. Перед определением торгового направления он взвешивает иерархии подтверждающих факторов и исторические паттерны взаимодействия.

Приступим
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)
Для начала настраивается основная среда, необходимая для подключения Python к MetaTrader 5 и управления данными, расписанием и внешними взаимодействиями. Импортируются MetaTrader5 для доступа к рынку, pandas и NumPy для обработки данных, а также Flask для предоставления сигналов и статуса системы через легковесный API. Дополнительные модули отвечают за планирование задач, многопоточность, работу с временными метками и сериализацию JSON. Это позволяет торговой системе работать непрерывно и безопасно взаимодействовать с MQL5 в реальном времени. Наконец, переменные аккаунта, пароля и сервера задают учетные данные, необходимые для подключения к реальному или демонстрационному счету MetaTrader 5.
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
В этом классе определяется основная структура, отвечающая за управление многоагентной торговой средой в реальном времени. Конструктор задает учетные данные аккаунта, предопределенный список символов и подготавливает контейнеры для агентов, открытых позиций и истории решений. Затем устанавливается соединение с MetaTrader 5: инициализируется терминал, выполняется вход в указанный торговый счет, проверяется подключение и получается базовая информация о счете. Такая настройка обеспечивает работу всех агентов в общей аутентифицированной сессии MetaTrader 5, создавая основу для скоординированного принятия решений по разным символам в реальном времени.
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() }
В этом разделе инициализируются агенты для торговли в реальном времени, формирующие многоагентную систему. Каждый агент получает конкретную роль и область ответственности: Gold Agent – для анализа конкретного торгового символа, Context Agent – для более широкого рыночного контекста, а Liquidity Agent – для оценки волатильности и объема. Эти агенты настраиваются с учетом взвешенных факторов принятия решений и параметров индикаторов. Вместо того чтобы полагаться на одну монолитную стратегию, система получает специализированную аналитику от каждого из агентов.
Затем методы обработки данных формируют пайплайн рыночных данных в реальном времени из MetaTrader 5. Ценовые данные извлекаются непосредственно из MetaTrader 5, преобразуются в структурированный pandas DataFrame и дополняются техническими признаками: доходностью, экспоненциальными скользящими средними, RSI и ATR. Специализированные вспомогательные функции вручную вычисляют RSI и ATR, обеспечивая полную прозрачность и гибкость настройки индикаторов. Вся логика предварительной обработки централизована и может повторно использоваться различными агентами.
Метод analyze_symbol представляет основной механизм принятия решений для каждого агента, специализирующегося на конкретном символе. Он оценивает структуру тренда, моментум и волатильность, независимо присваивая оценки бычьим и медвежьим условиям. Так исходные рыночные данные преобразуются в сигналы, пригодные для исполнения. На основе этих оценок агент формирует торговое направление, уровень уверенности и пояснение. Затем эти данные могут быть переданы другим агентам или уровню исполнения для координированных межсимвольных торговых решений.
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()
Эта часть кода вводит слой анализа контекста, работающий как агент более высокого уровня, а не как трейдер, специализирующийся на конкретном символе. Метод analyze_context оценивает общую силу доллара США, анализируя коррелирующие основные пары, например EURUSD, GBPUSD и USDJPY. Измеряя недавние процентные изменения на этих рынках и объединяя их в единый показатель силы доллара США, система формирует оценку направленности на макроуровне, влияющую на решения по золоту – XAUUSD. Это позволяет стратегии учитывать межрыночные взаимосвязи, а не полагаться исключительно на изолированное ценовое движение.
Метод run_decision_pipeline затем организует полный многоагентный рабочий процесс. Он собирает решения агента, специализирующегося на XAUUSD, и контекстного агента, записывает результаты в лог и агрегирует их с помощью модели взвешенного консенсуса. Каждый агент вносит вклад в окончательное решение с учетом уровня уверенности и предопределенных весов важности. Благодаря этому более сильные или надежные сигналы имеют большее влияние. Этот механизм консенсуса преобразует несколько независимых мнений в единое согласованное торговое действие с соответствующим уровнем уверенности.
После формирования окончательного решения система записывает его для обеспечения прозрачности и сохранения данных. Решение добавляется во внутренний лог и записывается в JSON-файл, который может использовать внешний советник MQL5. Если уверенность превышает предопределенный порог, система переходит к исполнению сделки, связывая аналитическую логику с действиями на реальном рынке. Эта архитектура четко разделяет формирование решений, их хранение и исполнение, поддерживая тесную синхронизацию этих процессов.
Оставшиеся методы обеспечивают исполнение сделок в реальном времени и управление позициями непосредственно через MetaTrader 5. Размер позиции определяется динамически на основе баланса счета, риска с учетом уверенности и расстояния до стопа на основе ATR. Это обеспечивает последовательный контроль риска. Система также поддерживает закрытие противоположных позиций, мониторинг открытых сделок и непрерывную работу по расписанию. Вместе эти компоненты образуют полный адаптивный торговый цикл, анализирующий рыночный контекст, формирующий многоагентный консенсус, исполняющий дисциплинированные сделки и непрерывно контролирующий результаты в реальном времени.
# ================================================ # 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) })
Этот раздел кода создает безопасную и контролируемую среду выполнения для торговой системы: инициализирует приложение Flask, общие переменные состояния и механизмы синхронизации. Вводятся глобальные флаги и блокировки для отслеживания работы системы и предотвращения состояний гонки (race conditions) при запуске или остановке торгового движка. Корневой эндпоинт / выполняет легковесную проверку состояния, возвращая текущую версию API и информацию об активности многоагентной системы. Это полезно для отладки и внешнего мониторинга.
Остальные маршруты Flask обеспечивают внешний контроль над жизненным циклом торговой системы. Эндпоинт /start безопасно инициализирует многоагентный движок с использованием учетных данных из запроса и запускает его в фоновом потоке для непрерывной работы. Эндпоинт /stop корректно останавливает выполнение, не завершая работу самого API. Эндпоинт /status предоставляет состояние системы в реальном времени, включая последнее решение и общее число принятых решений. Это позволяет MQL5 и другим клиентским приложениям контролируемо синхронизироваться с многоагентной логикой на Python без блокировок.
Объединяем все в MQL5
//+------------------------------------------------------------------+ //| Multi-Agents.mq5 | //| GIT under Copyright 2025, MetaQuotes Ltd. | //| https://www.mql5.com/ru/users/johnhlomohang/ | //+------------------------------------------------------------------+ #property copyright "GIT under Copyright 2025, MetaQuotes Ltd." #property link "https://www.mql5.com/ru/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); } }
Теперь в MQL5 определяется советник MetaTrader 5, выступающий мостом между торговым терминалом и внешней многоагентной системой на Python. Инициализируются торговые параметры: риск, частота опроса сигналов и магическое число для отслеживания ордеров. Затем настраивается объект CTrade для асинхронного исполнения. На каждом рыночном тике советник периодически отправляет HTTP-запрос на сервер Python для получения торгового сигнала. Возвращаемый JSON-ответ разбирается для извлечения полей symbol, action, confidence и price. Базовая логика фильтрации допускает открытие сделок только при достаточно высокой уверенности и включенной торговле.
Оставшаяся логика сосредоточена на поведении во время выполнения и надежности системы. Советник непрерывно управляет существующими позициями в ожидании новых сигналов, ведет лог ключевых событий и предусматривает защиту от неудачных веб-запросов и неправильно настроенных разрешений. Перенос генерации сигналов на сервер Python и сохранение легковесной логики исполнения внутри MetaTrader 5 четко разделяют принятие решений и исполнение сделок. Это позволяет многоагентной системе развиваться независимо от торгового терминала.
//+------------------------------------------------------------------+ //| 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)); }
Здесь реализуется полный пайплайн исполнения сделок, который принимает торговое направление и преобразует его в контролируемый рыночный ордер с учетом рисков. Функция ExecuteSignal начинается с определения корректной цены исполнения по текущим Bid или Ask. При необходимости она заменяется действительной внешней ценой сигнала. Затем отфильтровываются избыточные сделки, если цена не изменилась существенным образом с момента последнего сигнала. Она обеспечивает согласованность по направлению позиции, сначала закрывая противоположные позиции, динамически рассчитывает размер позиции на основе уверенности сигнала и определяет адаптивные уровни стоп-лосса и тейк-профита с использованием ATR для учета текущей рыночной волатильности.
Перед размещением ордера все ценовые уровни нормируются к размеру тика и точности символа, обеспечивая соответствие требованиям брокера. Затем ордера buy или sell отправляются и для прозрачности полностью фиксируются в логе. Вспомогательные функции измеряют волатильность с помощью ATR с безопасным резервным механизмом и выполняют точное нормирование цены. Это делает логику исполнения надежной, адаптивной и устойчивой к распространенным торговым ошибкам и сбоям на стороне брокера.
//+------------------------------------------------------------------+ //| Json.mqh | //| GIT under Copyright 2025, MetaQuotes Ltd. | //| https://www.mql5.com/ru/users/johnhlomohang/ | //+------------------------------------------------------------------+ #property copyright "GIT under Copyright 2025, MetaQuotes Ltd." #property link "https://www.mql5.com/ru/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
В JSON-файле определяется легковесная самодостаточная утилита разбора JSON для MQL5, позволяющая безопасно извлекать простые пары "ключ:значение" без использования внешних библиотек. Класс CJson хранит необработанную JSON-строку и использует контролируемое сканирование строки для поиска ключей, пропуска пробельных символов и корректной интерпретации строковых, числовых и булевых значений. Он предоставляет типизированные методы доступа (GetString, GetNumber, GetInteger и GetBool), которые корректно возвращают значения по умолчанию при отсутствии ключей или их неправильном формате. Это обеспечивает надежность в условиях реальной торговли. Чтобы еще больше упростить использование, статические вспомогательные методы позволяют выполнять однострочный разбор непосредственно из JSON-данных. Это делает класс подходящим для обработки ответов API, сообщений конфигурации и межпроцессного взаимодействия, где важны производительность, безопасность и минимальные зависимости.
Демонстрация в реальном времени
Ниже сервер Flask успешно инициализирован и работает на localhost на порту 5000, передавая сигналы из Jupyter Lab в MetaTrader 5.


Заключение
В итоге разработана многоагентная торговая среда, где независимые агенты взаимодействуют через общий сервис на Python и работают не изолированно, а по нескольким символам одновременно. Каждый агент генерирует структурированные сигналы, передаваемые через JSON, которые советник MQL5 безопасно разбирает и оценивает с использованием порогов уверенности, контроля рисков и проверки символов. Эта архитектура обеспечивает учет межсимвольных взаимосвязей, координированное принятие решений и адаптацию на основе обратной связи, объединяя рыночную информацию с нескольких инструментов в единый согласованный слой исполнения в MetaTrader 5.
Наконец, этот подход может значительно улучшить торговлю, снижая эффект узкого фокуса на одном инструменте и позволяя стратегиям учитывать более широкий рыночный контекст и коллективное поведение коррелирующих активов. Передавая аналитические функции взаимодействующим агентам и сохраняя исполнение, управление рисками и проверку под строгим контролем в советнике, трейдеры получают более гибкую, масштабируемую и легко развиваемую систему. В результате достигаются более четкое разделение логики и исполнения, более быстрое тестирование новых идей и более интеллектуальный торговый процесс, естественно адаптирующийся к изменению рыночных условий.
| Имя файла | Описание файла |
|---|---|
| Multi Agents.mq5 | Основной советник, подключающийся к серверу Python, получающий многоагентные торговые сигналы через JSON, управляющий рисками и исполняющий сделки в MetaTrader 5. |
| Json.mqh | Легковесный пользовательский парсер JSON, используемый советником для безопасного извлечения строк, чисел и булевых значений из ответов Python, содержащих сигналы. |
| MultiAgentsLab.ipynb | Блокнот Python, создающий и запускающий многоагентную среду, генерирующий межсимвольные торговые сигналы и передающий их в MetaTrader через локальный API. |
Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/21115
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.
Изучение стандартной библиотеки MQL5 (Часть 13): Применение библиотеки математических решателей в торговле
Нейросети в трейдинге: Двухуровневая адаптация торговой политики (Объект верхнего уровня)
Автоматизация индикатора Market Memory Zones: Зоны, куда цена с высокой вероятностью вернется
Мастер-класс по созданию пользовательских индикаторов (Часть 1): Разработка индикатора Supertrend на MQL5
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования