English Deutsch 日本語
preview
Интеграция MQL5 с пакетами обработки данных (Часть 7): Построение многоагентных сред для межсимвольного взаимодействия

Интеграция MQL5 с пакетами обработки данных (Часть 7): Построение многоагентных сред для межсимвольного взаимодействия

MetaTrader 5Примеры |
79 1
Hlomohang John Borotho
Hlomohang John Borotho

Содержание:

  1. Введение
  2. Обзор и понимание системы
  3. Приступим
  4. Объединяем все в MQL5
  5. Демонстрация в реальном времени
  6. Заключение


Введение

В части 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.

Signals

MT5 Experts Journal


Заключение

В итоге разработана многоагентная торговая среда, где независимые агенты взаимодействуют через общий сервис на 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

Прикрепленные файлы |
Multi_Agents.mq5 (13.07 KB)
Json.mqh (4.69 KB)
Последние комментарии | Перейти к обсуждению на форуме трейдеров (1)
saeedsoleimani1371
saeedsoleimani1371 | 31 мая 2026 в 16:58
Мне очень понравилась ваша статья. С нетерпением жду продолжения… Полагаю, вы ещё не закончили её…Термин «многоагентная система» в данном контексте носит скорее концептуальный характер, чем обозначает реальную систему искусственного интеллекта. Эти агенты не обучаются самостоятельно; в основном они представляют собой просто логику «if/else». Таким образом, это скорее совокупность правил, чем сложная система искусственного интеллекта на основе агентов. Кроме того, хотя термин «ЛИКВИДНОСТЬ» и даётся определение, он не используется в процессе принятия решений.
Изучение стандартной библиотеки MQL5 (Часть 13): Применение библиотеки математических решателей в торговле Изучение стандартной библиотеки MQL5 (Часть 13): Применение библиотеки математических решателей в торговле
Мы представляем полный рабочий процесс для адаптивной фильтрации в MQL5 с использованием решателя CNlEq, подобного алгоритму Левенберга — Марквардта. Советник подбирает параметры модели VAMAC — две экспоненциальные скользящие средние (EWMA) с масштабированием на основе ATR — путем передачи остатков и Якобиана через цикл механизма обратного обмена с решателем CNlEq, с возможностью использования численных или аналитических производных. Код, инструкции по настройке и тесты по паре GBPUSD на таймфрейме H1 показывают, как заменить статические пороги динамической переоценкой на закрытии каждого бара.
Нейросети в трейдинге: Двухуровневая адаптация торговой политики (Объект верхнего уровня) Нейросети в трейдинге: Двухуровневая адаптация торговой политики (Объект верхнего уровня)
Продолжаем адаптацию фреймворка D2Skill и переходим к построению объекта верхнего уровня CD2Skill для двухуровневой коррекции торговой политики. Механизм интегрируется в архитектуру ORION и дополняет существующий Актёр банками Task Skills и Step Skills. Показаны принципы их инициализации, включения в прямой проход и обучения по псевдоостаткам. Особое внимание уделяется сохранению исходного вычислительного конвейера, причинного порядка и независимости Критика.
Автоматизация индикатора Market Memory Zones: Зоны, куда цена с высокой вероятностью вернется Автоматизация индикатора Market Memory Zones: Зоны, куда цена с высокой вероятностью вернется
В этой статье Market Memory Zones превращается из сугубо визуальной концепции на графике, в полноценный советник MQL5. Советник автоматизирует зоны смещения, изменения структуры (CHoCH) и снятия ликвидности с помощью фильтров ATR и свечной структуры, применяет подтверждение на младшем таймфрейме и рассчитывает размер позиции с учетом риска, используя динамический стоп-лосс и тейк-профит на основе структуры. Рассматривается архитектура кода для обнаружения зон, входов в сделки, управления сделками и визуализации, а также краткий обзор тестирования на исторических данных.
Мастер-класс по созданию пользовательских индикаторов (Часть 1): Разработка индикатора Supertrend на MQL5 Мастер-класс по созданию пользовательских индикаторов (Часть 1): Разработка индикатора Supertrend на MQL5
Создайте индикатор Supertrend без перерисовки на языке MQL5 для MetaTrader 5 с нуля. Используются хэндл iATR и CopyBuffer для получения волатильности, буферы привязываются через SetIndexBuffer, а графические построения (DRAWCOLORCANDLES и две полосы, отображаемые линиями) настраиваются через PlotIndexSetInteger. Логика обновляется только на закрытых барах и использует EMPTY_VALUE для скрытия неактивных полос, при этом используются входные параметры atrPeriod и atrMultiplier. Получается аккуратный индикатор, накладываемый поверх графика, готовый для использования в советниках, с документированными буферами для стратегий и сигналов.