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

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

MetaTrader 5Трейдинг |
84 1
Hlomohang John Borotho
Hlomohang John Borotho

Содержание:

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


Введение

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

В этой части концепция развивается введением цикла обучения с обратной связью между результатами работы на рынке в реальном времени и обученной моделью. Цель состоит в том, чтобы связать среду выполнения MQL5 со средой обработки данных в Jupyter Lab, чтобы результаты сделок, изменения волатильности и поведенческие аномалии рынка стали активными компонентами переобучения модели. Такая интеграция позволяет системе не только совершать сделки, но и учиться на результатах собственной работы, постоянно повышая точность прогнозов и качество решений в ответ на текущий ритм рынка.


Обзор и понимание системы

Основная идея этой реализации – создать экосистему непрерывного обучения, связывающую торговую среду MetaTrader 5 с аналитической средой Jupyter Lab. Вместо фиксированной предварительно обученной модели советник должен активно учиться на результатах торговли и со временем адаптировать свое поведение. Это означает, что каждая сделка, независимо от результата, становится сигналом для обучения. Собирая результаты сделок, данные признаков, например RSI, ATR, волатильность и ценовую структуру, а также обратную связь по исполнению, советник передает ценные сведения в Python. Там модель анализирует новую информацию и использует ее для тонкой настройки параметров.


В основе этой системы лежит цикл обратной связи и адаптации. Советник непрерывно извлекает из рынка признаки в реальном времени, отправляет их на сервер Python REST для прогноза и в ответ получает оценку вероятности торгового решения или предпочтительное направление сделки. После завершения каждой сделки советник передает на сервер ее результаты: прибыль или убыток, длительность, просадку и т.д. Данные отправляются через эндпоинт /feedback. Сервер объединяет эти образцы обратной связи в растущий набор данных, отражающий опыт модели на рынке в реальном времени. Периодически модель использует эти данные для переобучения или корректировки весов, создавая более точную и отзывчивую версию. Затем модель снова экспортируется в формат ONNX для выполнения инференса в MQL5.

В итоге такая реализация превращает торговую систему в саморазвивающуюся архитектуру. Благодаря интеграции данные о поведении рынка, обратной связи по исполнению и ошибках прогнозирования собираются и вновь используются в процессе обучения. Со временем советник все лучше распознает прибыльные структуры, адаптируется к циклам волатильности и уменьшает дрейф прогнозов. Этот подход повышает стабильность результатов и имитирует естественный процесс обучения опытных трейдеров: наблюдение, адаптацию и совершенствование решений при каждом взаимодействии с рынком.


Приступим

from flask import Flask, request, jsonify
import threading, time, os, json
import torch
import torch.nn as nn
import torch.optim as optim
import numpy as np
import pandas as pd
import onnx
import onnxruntime as ort
from sklearn.preprocessing import StandardScaler
from datetime import datetime

Начнем с настройки серверной среды Python, которая станет связующим звеном между торговой логикой на языке MQL5 и адаптивной моделью обучения. Используя Flask как легковесный фреймворк REST API, создаем эндпоинты для обмена данными в реальном времени между MetaTrader 5 и моделью на Python. Основные библиотеки – torch, onnx и onnxruntime – выполняют операции с моделями глубокого обучения, а pandas и numpy отвечают за обработку данных и формирование признаков. Масштабировщик из sklearn обеспечивает единообразную нормализацию входных рыночных признаков и поддерживает стабильность модели при изменении рыночных условий. Кроме того, threading и datetime используются для планирования фоновых задач, например обработки обратной связи и переобучения, без прерывания прогнозирования в реальном времени. Такая конфигурация закладывает основу для объединения рыночной обратной связи в реальном времени с адаптацией модели, обеспечивая непрерывное развитие торговой модели непосредственно в торговой среде.

app = Flask(__name__)

# ---------- Config ----------
FEATURE_DIM = 12            # Must match MQL5 feature dimension
RETRAIN_THRESHOLD = 100     # number of feedback samples before retrain
ONNX_PATH = "live_model.onnx"
MODEL_PATH = "live_model.pt"
FEEDBACK_CSV = "feedback_log.csv"

# ---------- Simple model ----------
class MLP(nn.Module):
    def __init__(self, n_in):
        super().__init__()
        self.net = nn.Sequential(
            nn.Linear(n_in, 64),
            nn.ReLU(),
            nn.Linear(64, 32),
            nn.ReLU(),
            nn.Linear(32, 1)      # regression output (predicted expected reward)
        )
    def forward(self, x):
        return self.net(x)

device = torch.device("cpu")
model = MLP(FEATURE_DIM).to(device)
optimizer = optim.Adam(model.parameters(), lr=1e-3)
loss_fn = nn.MSELoss()
scaler = StandardScaler()

# Initialize scaler with some default values to avoid errors
try:
    default_features = np.zeros((100, FEATURE_DIM))
    scaler.fit(default_features)
    print("Initialized scaler with default values")
except Exception as e:
    print(f"Scaler initialization error: {e}")

# Keep feedback in-memory buffer for quick retrain
feedback_buffer = []  # list of dicts

# ---------- Helpers ----------
def features_to_tensor(f):
    try:
        arr = np.array(f, dtype=float).reshape(1, -1)
        # Check if scaler is fitted and has the right dimension
        if hasattr(scaler, 'mean_') and scaler.mean_ is not None and len(scaler.mean_) == FEATURE_DIM:
            arr = scaler.transform(arr)
        else:
            # If scaler not properly fitted, use raw features (will be fixed during retraining)
            print("Scaler not properly fitted, using raw features")
        t = torch.tensor(arr, dtype=torch.float32)
        return t
    except Exception as e:
        print(f"Error in features_to_tensor: {e}")
        # Return zeros if there's an error
        return torch.zeros(1, FEATURE_DIM, dtype=torch.float32)

def predict_raw(features):
    try:
        model.eval()
        with torch.no_grad():
            t = features_to_tensor(features).to(device)
            out = model(t).cpu().numpy().ravel()[0]
        return float(out)
    except Exception as e:
        print(f"Prediction error: {e}")
        return 0.0

В этом разделе инициализируется приложение Flask и настраиваются основные параметры работы адаптивной модели. Определяются константы: размерность признаков FEATURE_DIM, порог переобучения RETRAIN_THRESHOLD и пути к файлам для хранения модели и данных обратной связи. В основе этой конфигурации лежит простой многослойный перцептрон (MLP), реализованный в PyTorch: легковесная нейронная сеть с двумя скрытыми слоями (64 и 32 нейрона) для регрессионного прогнозирования ожидаемого вознаграждения или силы движения цены. Для эффективного обучения и минимизации ошибок прогнозирования используются оптимизатор Adam и функция потерь MSELoss, а StandardScaler стандартизирует все входные признаки, поддерживая стабильную работу модели при различных состояниях рынка. Предварительная подгонка масштабировщика на фиктивных данных предотвращает ошибки инициализации до поступления реальных рыночных данных.

Вспомогательные функции преобразуют входные признаки из MetaTrader 5 в тензоры, совместимые с моделью, и обеспечивают надежное выполнение прогнозирования. Функция features_to_tensor() проверяет форму признаков, применяет масштабирование при его доступности и безопасно возвращает тензор PyTorch даже при возникновении ошибок, предотвращая прерывание выполнения. Функция predict_raw() выполняет прямой проход по сети в режиме оценки и получает результат в виде числового прогноза с плавающей точкой. Эта модульная структура позволяет модели сохранять отзывчивость и устойчивость к ошибкам, а также гибко включать новые данные обратной связи в последующие циклы переобучения.

def save_feedback_to_csv(entry):
    try:
        # entry is a dict; features saved as JSON
        row = entry.copy()
        row["features"] = json.dumps(row["features"])
        df = pd.DataFrame([row])
        if not os.path.exists(FEEDBACK_CSV):
            df.to_csv(FEEDBACK_CSV, index=False)
        else:
            df.to_csv(FEEDBACK_CSV, mode='a', header=False, index=False)
    except Exception as e:
        print(f"Error saving feedback to CSV: {e}")

def retrain_model():
    global model, optimizer, scaler, feedback_buffer
    if len(feedback_buffer) < 10:  # Minimum samples to retrain
        print(f"Not enough samples for retraining: {len(feedback_buffer)}")
        return
        
    print(f"[{datetime.utcnow().isoformat()}] Retraining model on {len(feedback_buffer)} samples...")
    try:
        # load buffer into DataFrame
        df = pd.DataFrame(feedback_buffer)
        X = np.vstack(df["features"].apply(lambda x: np.array(x)).values)
        y = df["reward"].astype(float).values.reshape(-1,1)
        
        # Fit scaler on current data
        scaler.fit(X)
        Xs = scaler.transform(X)
        Xs = torch.tensor(Xs, dtype=torch.float32)
        ys = torch.tensor(y, dtype=torch.float32)
        
        # small training loop
        model.train()
        epochs = 40
        batch_size = min(32, len(Xs))
        
        for ep in range(epochs):
            perm = torch.randperm(Xs.size(0))
            for i in range(0, Xs.size(0), batch_size):
                idx = perm[i:i+batch_size]
                xb = Xs[idx]
                yb = ys[idx]
                pred = model(xb)
                loss = loss_fn(pred, yb)
                optimizer.zero_grad()
                loss.backward()
                optimizer.step()
                
        # save model to disk (torch)
        torch.save(model.state_dict(), MODEL_PATH)
        print(f"Model saved to {MODEL_PATH}")
        
        # export to ONNX
        dummy = torch.randn(1, FEATURE_DIM, dtype=torch.float32)
        model.eval()
        try:
            torch.onnx.export(model, dummy, ONNX_PATH, input_names=['input'], output_names=['output'], opset_version=11)
            print(f"ONNX exported to {ONNX_PATH}")
        except Exception as e:
            print("ONNX export failed:", e)
            
        # append buffer to CSV
        for row in feedback_buffer:
            save_feedback_to_csv(row)
            
        # clear buffer
        feedback_buffer = []
        print("Retrain complete.")
        
    except Exception as e:
        print(f"Error during retraining: {e}")

# background trainer thread that monitors buffer size
def trainer_loop():
    while True:
        try:
            if len(feedback_buffer) >= RETRAIN_THRESHOLD:
                retrain_model()
        except Exception as e:
            print("trainer error:", e)
        time.sleep(10)  # Check every 10 seconds

trainer_thread = threading.Thread(target=trainer_loop, daemon=True)
trainer_thread.start()

Здесь реализуется система управления обратной связью и переобучения модели, служащая адаптивным ядром процесса обучения. Функция save_feedback_to_csv() записывает в постоянный CSV-лог данные обратной связи: извлеченные признаки, полученное вознаграждение и дополнительные метаданные. Это позволяет проводить долгосрочный анализ. Для сохранения гибкости массивы признаков сериализуются в JSON-строки перед добавлением в файл, сохраняя их структуру для последующей загрузки. Такой подход позволяет поддерживать полный и постоянно растущий набор данных о результатах сделок, который модель позднее использует для переобучения. Если CSV-файл отсутствует, он создается автоматически; в противном случае новые строки добавляются без нарушения текущих процессов.

Функция retrain_model() преобразует накопленную рыночную обратную связь в обновления модели в процессе обучения. Когда в буфере накапливается достаточное число образцов, функция загружает собранные данные, масштабирует их с помощью StandardScaler и выполняет несколько эпох обучения для тонкой настройки весов нейронной сети. После обучения модель сохраняется в форматах PyTorch (.pt) и ONNX, что позволяет MetaTrader 5 напрямую использовать обновленную версию для прогнозирования в реальном времени. Чтобы процесс оставался автономным, функция trainer_loop() непрерывно выполняется в фоновом потоке, отслеживает размер буфера и запускает переобучение при достижении порога. Такая конфигурация обеспечивает развитие торговой системы: она получает обратную связь в реальном времени, переобучается и обновляет модель инференса без ручного вмешательства, достигая подлинной адаптивности при исполнении сделок.

# ---------- Flask endpoints ----------
@app.route('/predict', methods=['POST'])
def predict_endpoint():
    try:
        payload = request.get_json(force=True)
        if not payload:
            return jsonify({"error": "No JSON data received"}), 400
            
        features = payload.get("features")
        if features is None or len(features) != FEATURE_DIM:
            return jsonify({
                "error": "bad features", 
                "expected_dim": FEATURE_DIM,
                "received_dim": len(features) if features else 0
            }), 400
            
        pred = predict_raw(features)
        return jsonify({"prediction": pred})
        
    except Exception as e:
        print(f"Prediction endpoint error: {e}")
        return jsonify({"error": str(e)}), 500

@app.route('/feedback', methods=['POST'])
def feedback_endpoint():
    try:
        payload = request.get_json(force=True)
        if not payload:
            return jsonify({"error": "No JSON data received"}), 400
            
        # minimal validation; ensure reward exists
        if "features" not in payload or "reward" not in payload:
            return jsonify({"error": "need features and reward"}), 400
            
        # Calculate reward if not provided (fallback logic)
        reward = payload.get("reward")
        if reward is None:
            # Try to calculate from pips_profit
            pips_profit = payload.get("pips_profit")
            if pips_profit is not None:
                reward = float(pips_profit) / 100.0  # Normalize
            else:
                reward = 0.0
                
        # store in buffer
        entry = {
            "timestamp": payload.get("timestamp", datetime.utcnow().isoformat()),
            "symbol": payload.get("symbol", ""),
            "tf": payload.get("tf", ""),
            "features": payload["features"],
            "action": payload.get("action_taken", 0),
            "entry_price": payload.get("entry_price", 0.0),
            "exit_price": payload.get("exit_price", 0.0),
            "pips_profit": payload.get("pips_profit", 0.0),
            "reward": float(reward)
        }
        feedback_buffer.append(entry)
        
        # also save immediately to CSV for persistence
        save_feedback_to_csv(entry)
        
        return jsonify({
            "status": "ok", 
            "buffer_size": len(feedback_buffer),
            "reward_received": float(reward)
        })
        
    except Exception as e:
        print(f"Feedback endpoint error: {e}")
        return jsonify({"error": str(e)}), 500

В этом разделе создаются два основных эндпоинта Flask – /predict и /feedback – для взаимодействия в реальном времени между торговой системой и адаптивной моделью обучения. Эндпоинт /predict обрабатывает входящие JSON-данные с рыночными признаками, проверяет размерность входных данных и возвращает прогнозы, сформированные моделью. Благодаря этому каждый запрос на прогнозирование имеет корректную структуру и соответствует ожидаемому формату входных данных модели. Эндпоинт /feedback, напротив, получает данные по завершенной сделке: совершенные действия, цены входа, прибыль или убыток и полученное вознаграждение. Затем эта информация добавляется в буфер обратной связи и сохраняется в CSV-файле, формируя основу для непрерывного обучения. Вместе эти эндпоинты создают экосистему на основе обратной связи, в которой модель формирует прогнозы и итеративно учится на рыночных результатах, замыкая цикл между инференсом модели и адаптацией ее работы.

@app.route('/health', methods=['GET'])
def health_check():
    """Health check endpoint for monitoring"""
    return jsonify({
        "status": "healthy",
        "timestamp": datetime.utcnow().isoformat(),
        "buffer_size": len(feedback_buffer),
        "feature_dim": FEATURE_DIM
    })

# optional endpoint to force retrain (admin)
@app.route('/retrain', methods=['POST'])
def retrain_now():
    threading.Thread(target=retrain_model).start()
    return jsonify({"status": "retrain_started", "buffer_size": len(feedback_buffer)})

@app.route('/info', methods=['GET'])
def info():
    """Get information about the current model state"""
    return jsonify({
        "feature_dim": FEATURE_DIM,
        "feedback_buffer_size": len(feedback_buffer),
        "retrain_threshold": RETRAIN_THRESHOLD,
        "model_path": MODEL_PATH,
        "scaler_fitted": hasattr(scaler, 'mean_') and scaler.mean_ is not None
    })

if __name__ == "__main__":
    print(f"Starting ML Server for MQL5 EA")
    print(f"Feature dimension: {FEATURE_DIM}")
    print(f"Retrain threshold: {RETRAIN_THRESHOLD}")
    print(f"Server will run on http://127.0.0.1:5000")
    print(f"Endpoints available:")
    print(f"  POST /predict - Get prediction for features")
    print(f"  POST /feedback - Send trade feedback")
    print(f"  GET  /health - Health check")
    print(f"  GET  /info - Model information")
    
    # Start the server
    app.run(host="127.0.0.1", port=5000, debug=False, threaded=True)
Реализация завершается добавлением трех служебных эндпоинтов – /health, /retrain и /info, – обеспечивающих мониторинг, управление и сведения о рабочем состоянии модели. Эндпоинт /health служит быстрым диагностическим инструментом: он сообщает текущий статус системы, временную метку и размер буфера, что помогает контролировать стабильность работы системы. Эндпоинт /retrain позволяет администраторам при необходимости вручную запускать переобучение модели. Оно выполняется асинхронно, чтобы не прерывать основной процесс сервера. Наконец, эндпоинт /info предоставляет подробные метаданные о модели: размерность признаков, порог переобучения и состояние масштабировщика. Это обеспечивает прозрачность состояния обучения системы.


Объединяем все в MQL5

Для интеграции в MQL5 за основу взята одна из существующих и хорошо зарекомендовавших себя стратегий – Dynamic Swing Architecture. Эта стратегия уже обеспечивает надежную основу для распознавания рыночной структуры и адаптивного исполнения сделок, поэтому она хорошо подходит для следующего этапа развития. В этой части рассматриваются обновления и улучшения, необходимые для объединения рыночной обратной связи с адаптивным обучением модели. Вместо повторного рассмотрения базовой логики свинговых структур подробнее разберем, как торговая обратная связь в реальном времени из MQL5 собирается, передается и используется для постоянного совершенствования процесса принятия решений модели. Это позволяет ей динамически развиваться при изменении рыночных условий.

input group "ML Model Parameters"
input string  PythonHost = "127.0.0.1";         // Python server host
input int     PythonPort = 5000;                // Python server port

//--- input parameters for indicators
input int   InpATRPeriod       = 14;
input int   InpRSIPeriod       = 14;
input int   InpMomPeriod       = 10;
input int   InpTrendLookback   = 20;
input int   InpVolLookback     = 20;

//--- global handles
int   hATR       = INVALID_HANDLE;
int   hRSI       = INVALID_HANDLE;

//--- global swing variables
double g_lastSwingHigh = 0.0;
double g_lastSwingLow = 0.0;
bool   g_lastSwingWasBullish = false;
Для извлечения признаков модели непосредственно в MQL5 добавлена новая группа входных параметров ML Model Parameters. Она позволяет настроить подключение к серверу машинного обучения и используемые моделью признаки на основе индикаторов. В этом разделе представлены входные параметры для подключения к хосту и порту Python, а также параметры, задающие периоды расчета ключевых технических показателей: ATR, RSI, моментума, тренда и волатильности. Инициализированы соответствующие глобальные хэндлы ATR и RSI, обеспечивающие эффективные вычисления и повторное использование данных индикаторов при работе советника.
//+------------------------------------------------------------------+
//| Extracts feature vector (double array)                           |
//+------------------------------------------------------------------+
bool ExtractFeatures(double &features[], int dim)
{
   if(dim != 12)
   {
      Print("ExtractFeatures: expected dim=12, got ", dim);
      return(false);
   }
   ArrayResize(features, dim);
   ArrayInitialize(features, 0.0);

   //--- 1. ATR (most recent)
   double atr_buffer[];
   if(CopyBuffer(hATR, 0, 0, 1, atr_buffer) != 1)
   {
      Print("CopyBuffer ATR failed");
      return(false);
   }
   double atr_value = atr_buffer[0];

   //--- 2. RSI (latest)
   double rsi_buffer[];
   if(CopyBuffer(hRSI, 0, 0, 1, rsi_buffer) != 1)
   {
      Print("CopyBuffer RSI failed");
      return(false);
   }
   double rsi_value = rsi_buffer[0];

   //--- 3. Distance to last swing high / low
   double lastSwingHigh = g_lastSwingHigh;
   double lastSwingLow = g_lastSwingLow;
   double priceNow = iClose(_Symbol, _Period, 0);

   double distHigh = (lastSwingHigh > 0) ? (priceNow - lastSwingHigh) : 0.0;
   double distLow = (lastSwingLow > 0) ? (lastSwingLow - priceNow) : 0.0;

   // normalize by ATR to scale
   double normHigh = (atr_value > 0 && lastSwingHigh > 0) ? distHigh/atr_value : 0.0;
   double normLow = (atr_value > 0 && lastSwingLow > 0) ? distLow/atr_value : 0.0;

   //--- 4. Swing strength = (high-low)/ATR
   double swingStrength = 0.0;
   if(lastSwingHigh > 0 && lastSwingLow > 0 && atr_value > 0)
      swingStrength = (lastSwingHigh - lastSwingLow) / atr_value;

   //--- 5. Swing direction: 1 for bullish, -1 for bearish
   int lastSwingDir = g_lastSwingWasBullish ? 1 : -1;
   double swingDirNorm = (double)lastSwingDir;

   //--- 6. Momentum: price difference over InpMomPeriod
   double pastClose = iClose(_Symbol, _Period, InpMomPeriod);
   double momentum = (priceNow - pastClose) / (atr_value > 0 ? atr_value : 1.0);

   //--- 7. TrendSlope: linear regression slope of last InpTrendLookback bars (close prices)
   double slope = 0.0;
   double arr[];
   ArrayResize(arr, InpTrendLookback);
   for(int i = 0; i < InpTrendLookback; i++)
      arr[i] = iClose(_Symbol, _Period, i);
   
   // Calculate linear regression slope manually
   double sum_x = 0, sum_y = 0, sum_xy = 0, sum_xx = 0;
   for(int i = 0; i < InpTrendLookback; i++)
   {
      sum_x += i;
      sum_y += arr[i];
      sum_xy += i * arr[i];
      sum_xx += i * i;
   }
   double n = (double)InpTrendLookback;
   slope = (n * sum_xy - sum_x * sum_y) / (n * sum_xx - sum_x * sum_x);
   slope = slope / (atr_value > 0 ? atr_value : 1.0);

   //--- 8. Volume ratio: current volume / average of last InpVolLookback
   double volNow = iVolume(_Symbol, _Period, 0);
   double sumVol = 0.0;
   for(int i = 1; i <= InpVolLookback; i++)
      sumVol += iVolume(_Symbol, _Period, i);
   double avgVol = sumVol / InpVolLookback;
   double volRatio = (avgVol > 0) ? volNow / avgVol : 0.0;

   //--- 9 & 10. BreakAbove / BreakBelow flags
   double breakAbove = (lastSwingHigh > 0 && priceNow > lastSwingHigh) ? 1.0 : 0.0;
   double breakBelow = (lastSwingLow > 0 && priceNow < lastSwingLow) ? 1.0 : 0.0;

   //--- 11. TimeOfDay normalized
   MqlDateTime time_struct;
   TimeCurrent(time_struct);
   double hr = (double)time_struct.hour;
   double tod = hr / 24.0;

   //--- Assign features in array
   features[0]  = normHigh;
   features[1]  = normLow;
   features[2]  = swingStrength;
   features[3]  = swingDirNorm;
   features[4]  = atr_value;
   features[5]  = rsi_value / 100.0;   // scale RSI 0-1
   features[6]  = momentum;
   features[7]  = slope;
   features[8]  = volRatio;
   features[9]  = breakAbove;
   features[10] = breakBelow;
   features[11] = tod;

   return(true);
}

Функция ExtractFeatures создает полный 12-мерный срез состояния рынка, преобразуя исходные ценовые данные в нормализованные признаки, масштабированные по ATR. Они отражают волатильность (ATR), импульс (RSI и ценовой моментум), динамику свингов (расстояние до последних максимумов и минимумов, а также силу свинга), направление тренда (наклон линейной регрессии), активность объема (отношение текущего объема к среднему), сигналы пробоя (выход выше или ниже уровней свинга) и временные закономерности (время суток). Этот процесс формирования признаков преобразует сложные рыночные движения в стандартизированный числовой вектор для анализа моделью машинного обучения. Каждый признак тщательно нормализуется для единообразного масштабирования: например, ценовые расстояния делятся на ATR для сопоставления в разных рыночных условиях, а RSI приводится к диапазону от 0 до 1. Благодаря этому алгоритм машинного обучения может эффективно выявлять закономерности и взаимосвязи для торгового прогнозирования независимо от абсолютных уровней цен и режимов волатильности.

//--- Converts feature array to JSON
string FeaturesToJson(double &features[], int dim)
{
   string json = "[";
   for(int i = 0; i < dim; i++)
   {
      json += DoubleToString(features[i], 8);
      if(i < dim - 1) json += ",";
   }
   json += "]";
   return json;
}

//--- Generic HTTP POST
string HttpPostJson(string url, string json_body, int &status_code)
{
   string headers = "Content-Type: application/json\r\n";
   char data[], result[];
   ArrayResize(data, StringLen(json_body));
   StringToCharArray(json_body, data, 0, StringLen(json_body));
   int timeout = 5000; // 5 seconds
   string result_headers;
   
   ResetLastError();
   int res = WebRequest("POST", url, headers, timeout, data, result, result_headers);
   status_code = res;
   
   if(res == -1)
   {
      int error_code = GetLastError();
      Print("WebRequest failed. Error: ", error_code, " - ", GetLastError());
      return "";
   }
   return CharArrayToString(result);
}

Функция FeaturesToJson сериализует числовой массив признаков в JSON-строку, которую можно передать по HTTP на Python-сервер машинного обучения. Функция создает JSON-массив, перебирая элементы вектора признаков, преобразуя значения двойной точности в строковое представление с восемью знаками после запятой и размещая между элементами запятые. Массив корректно заключается в квадратные скобки. Это преобразование важно, поскольку переводит внутреннюю числовую структуру данных советника в стандартизированный платформонезависимый формат, который Python-сервер Flask легко разбирает и обрабатывает. При этом сохраняются точные числовые значения, необходимые для точных прогнозов машинного обучения, и соблюдаются стандарты веб-обмена.

Функция HttpPostJson обеспечивает HTTP-взаимодействие между платформой MetaTrader 5 и Python-сервером машинного обучения: она формирует и отправляет JSON-данные. Функция задает необходимые HTTP-заголовки с типом содержимого JSON, преобразует JSON-строку в массив символов для передачи и использует WebRequest платформы MetaTrader 5 для выполнения POST-запроса с таймаутом пять секунд, предотвращающим зависание. Функция содержит комплексную обработку ошибок: при сбое запроса она фиксирует и сообщает конкретные коды ошибок, а при успешном соединении возвращает ответ сервера в виде строки. Эта надежная реализация HTTP-клиента обеспечивает обмен данными в реальном времени между торговой платформой и серверной частью машинного обучения. Она образует важный канал связи, позволяющий советнику получать прогнозы и отправлять обратную связь для непрерывного обучения.

//--- Get model prediction from Python
double GetPrediction(string host, int port, string symbol, string tf, double &features[], int dim)
{
   string url = StringFormat("http://%s:%d/predict", host, port);
   string json = "{";
   json += "\"symbol\":\"" + symbol + "\",";
   json += "\"tf\":\"" + tf + "\",";
   json += "\"features\":" + FeaturesToJson(features, dim);
   json += "}";
   
   int code = 0;
   string resp = HttpPostJson(url, json, code);
   
   Print("Prediction Request - Code: ", code, ", Response: ", resp);
   
   if(code == 200 && StringFind(resp, "prediction") >= 0)
   {
      int p = StringFind(resp, "\"prediction\":");
      if(p >= 0)
      {
         int start = p + StringLen("\"prediction\":");
         int end = StringFind(resp, "}", start);
         if(end == -1) end = StringLen(resp);
         string val = StringSubstr(resp, start, end - start);
         // Remove any trailing commas or spaces
         StringReplace(val, ",", "");
         StringReplace(val, " ", "");
         StringReplace(val, "}", "");
         double prediction = StringToDouble(val);
         Print("Parsed prediction value: ", prediction);
         return prediction;
      }
   }
   Print("Prediction failed. Code=", code, ", Response=", resp);
   return 0.0;
}

//--- Send feedback to Python
bool SendFeedback(string host, int port, string symbol, string tf, double &features[], int dim,
                  int action, double entry_price, double exit_price, double pips_profit, double reward)
{
   string url = StringFormat("http://%s:%d/feedback", host, port);
   string json = "{";
   json += "\"symbol\":\"" + symbol + "\",";
   json += "\"tf\":\"" + tf + "\",";
   json += "\"features\":" + FeaturesToJson(features, dim) + ",";
   json += "\"action_taken\":" + IntegerToString(action) + ",";
   json += "\"entry_price\":" + DoubleToString(entry_price, _Digits) + ",";
   json += "\"exit_price\":" + DoubleToString(exit_price, _Digits) + ",";
   json += "\"pips_profit\":" + DoubleToString(pips_profit, 4) + ",";
   json += "\"reward\":" + DoubleToString(reward, 6);
   json += "}";
   
   int code = 0;
   string resp = HttpPostJson(url, json, code);
   if(code == 200)
   {
      Print("Feedback sent successfully. Position profit: ", pips_profit, " pips, Reward: ", reward);
      return true;
   }
   Print("Feedback failed. Code=", code, " Resp=", resp);
   return false;
}

Функция GetPrediction служит основным интерфейсом для получения торговых сигналов от модели машинного обучения в реальном времени. Она формирует HTTP-запрос со всем необходимым рыночным контекстом (символом и таймфреймом), а также 12-мерный вектор признаков. Затем разбирается JSON-ответ сервера для извлечения числового значения прогноза. Функция реализует надежную обработку ошибок и подробное логирование на всех этапах: сначала формирует корректный JSON-пакет с признаками, затем выполняет POST-запрос к эндпоинту /predict и, наконец, извлекает значение прогноза из строки ответа, обрабатывая символы и удаляя лишнее для учета различных вариантов форматирования JSON. Функция предоставляет полную отладочную информацию, записывая в лог HTTP-код статуса и необработанный ответ сервера. Это делает процесс прогнозирования прозрачным; при сбое связи или неожиданном формате ответа функция безопасно возвращает резервное значение 0.0.

Функция SendFeedback завершает цикл машинного обучения, передавая данные о результатах сделок на Python-сервер. Это позволяет модели учиться на фактических рыночных результатах и постоянно улучшать прогнозы. Функция создает подробный JSON-объект с полным контекстом сделки: исходными признаками, на основе которых было принято торговое решение, выполненным действием (buy или sell), ценами входа и выхода, рассчитанной прибылью в пунктах и нормализованным значением вознаграждения. Эти данные необходимы для обучения с учителем. Этот механизм обратной связи важен для адаптивной системы обучения, поскольку позволяет модели машинного обучения сопоставлять предыдущие прогнозы с фактическими рыночными результатами. Модель постепенно уточняет, какие сочетания признаков приводят к успешным, а какие – к неудачным сделкам, формируя самообучающуюся торговую систему, точность которой со временем растет благодаря накопленному опыту.

//+------------------------------------------------------------------+
//| Calculate reward for ML feedback                                 |
//+------------------------------------------------------------------+
double CalculateReward(double profit, double pipsProfit, double volume)
{
    // Customize this function based on your reward strategy
    // Simple implementation: normalize profit by volume and scale
    if(volume > 0)
    {
        double normalizedProfit = profit / (volume * 1000); // Adjust scaling factor as needed
        return normalizedProfit;
    }
    
    // Alternative: use pips profit directly
    return pipsProfit / 100.0; // Scale down pips to reasonable range
}

//+------------------------------------------------------------------+
//| Get historical features for a specific time                     |
//+------------------------------------------------------------------+
bool GetHistoricalFeatures(datetime targetTime, double &features[])
{
    // Simplified implementation - uses current features
    // In production, you might want to store features when trades are opened
    ArrayResize(features, 12);
    return ExtractFeatures(features, 12);
}

//+------------------------------------------------------------------+
//| Track processed positions to avoid duplicates                   |
//+------------------------------------------------------------------+
bool IsPositionProcessed(ulong positionTicket)
{
    // Simple implementation using global variable
    static ulong lastProcessedTicket = 0;
    return (positionTicket == lastProcessedTicket);
}

//+------------------------------------------------------------------+
//| Mark position as processed                                      |
//+------------------------------------------------------------------+
void MarkPositionAsProcessed(ulong positionTicket)
{
    static ulong lastProcessedTicket = 0;
    lastProcessedTicket = positionTicket;
}
В этом разделе кода реализуются основные вспомогательные функции, поддерживающие цикл обратной связи машинного обучения: CalculateReward преобразует исходные результаты сделок в нормализованные значения вознаграждения для обучения модели: функция масштабирует денежную прибыль по объему сделки либо использует прибыль в пунктах напрямую. GetHistoricalFeatures предоставляет упрощенный механизм получения рыночных признаков: сейчас в качестве практического приближения используются текущие признаки, поскольку хранение точных исторических признаков потребовало бы существенной дополнительной инфраструктуры. Функции IsPositionProcessed и MarkPositionAsProcessed образуют базовую систему предотвращения дублирования: статическая переменная отслеживает тикет последней обработанной позиции. Поэтому обратная связь по каждой сделке отправляется в модель машинного обучения только один раз, несмотря на многократные проверки. Это сохраняет целостность данных и делает реализацию простой и эффективной в рамках среды MQL5.


Демонстрация в реальном времени

Сервер Flask успешно инициализирован и работает на порту 5000. Он готов принимать запросы на прогнозирование от вашего советника MT5 и предоставлять торговые сигналы на основе машинного обучения.

Теперь, как показано ниже, MetaTrader 5 получает прогнозы от модели, работающей в Jupyter Lab.




Заключение

В итоге нам удалось объединить рыночную обратную связь с адаптацией модели через комплексный конвейер машинного обучения, который превращает торговый советник из статичной системы на основе правил в динамический самообучающийся алгоритм. Этого удалось достичь благодаря нескольким ключевым компонентам: надежной системе извлечения признаков, преобразующей рыночные данные в 12 нормализованных признаков; HTTP-обмену в реальном времени с Python-сервером машинного обучения для запросов прогнозов; циклу обратной связи, автоматически передающему результаты сделок обратно в модель; а также адаптивной системе обучения, в которой нейронная сеть непрерывно переобучается на новом рыночном опыте. Интеграция включает корректную обработку ошибок, подробное логирование для отладки и механизмы предотвращения дублирования. В результате создается система с замкнутым контуром обратной связи, в которой успех или неудача каждой сделки непосредственно способствует улучшению будущих торговых решений посредством обучения с учителем.

В заключение эта интеграция машинного обучения существенно расширит возможности трейдеров, создав адаптивную систему, которая учится как на успешных, так и на неудачных сделках и постепенно формирует более точные прогнозы рынка с учетом текущих условий. В отличие от традиционных статичных торговых алгоритмов эта система постоянно развивается и улучшает процесс принятия решений на основе фактических данных о результатах работы. Это может привести к повышению прибыльности, улучшению управления рисками и большей стабильности результатов со временем. Автоматизированный цикл обратной связи устраняет эмоциональные искажения в торговле, а способность модели распознавать сложные нелинейные закономерности в рыночных данных помогает выявлять возможности, которые может упустить традиционный технический анализ. В итоге трейдер получает продвинутый самооптимизирующийся инструмент, ценность которого возрастает с каждым торговым решением, на результатах которого модель обучается.


Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/20235

Прикрепленные файлы |
Последние комментарии | Перейти к обсуждению на форуме трейдеров (1)
YDILLC
YDILLC | 21 нояб. 2025 в 06:23
Вопрос по теме «Интеграция MQL5 с пакетами обработки данных (Часть 6): объединение рыночной обратной связи с адаптацией модели». ... Файл, приложенный к этой статье. Это настоящий советник? Как его использовать?
Индикатор Sigma Score для MetaTrader 5: Простой детектор статистических аномалий Индикатор Sigma Score для MetaTrader 5: Простой детектор статистических аномалий
Создайте практический индикатор Sigma Score для MetaTrader 5 с нуля и изучите, что он на самом деле измеряет – z-оценку логарифмических доходностей: на сколько стандартных отклонений последнее движение отличается от недавнего среднего. В статье рассматривается каждый блок кода в OnInit(), OnCalculate() и OnDeinit(), а также интерпретация пороговых значений, например ±2. Индикатор Sigma Score применяется как простой индикатор рыночного стресса для торговли на основе возврата к среднему и моментума.
Кодирование свечных моделей (Часть 3): Частотный анализ односвечных структур Кодирование свечных моделей (Часть 3): Частотный анализ односвечных структур
В данной статье представлена схема частотного анализа закодированных свечных паттернов в MQL5. Преобразуя свечи в буквенные символы, можно анализировать историческое движение цен как статистическую последовательность, а не как визуальный график. Используя пару GBPUSD и золото на нескольких таймфреймах, в этом исследовании мы изучаем частоту появления отдельных типов свечей, выявляем доминирующие рыночные структуры и показываем симметрию между бычьими и медвежьими движениями цен. Результаты закладывают количественную основу для обнаружения паттернов и подготавливают почву для анализа многосвечных последовательностей и их прогностического потенциала в алгоритмических торговых системах.
Мастер-класс по созданию пользовательских индикаторов (Часть 1): Разработка индикатора Supertrend на MQL5 Мастер-класс по созданию пользовательских индикаторов (Часть 1): Разработка индикатора Supertrend на MQL5
Создайте индикатор Supertrend без перерисовки на языке MQL5 для MetaTrader 5 с нуля. Используются хэндл iATR и CopyBuffer для получения волатильности, буферы привязываются через SetIndexBuffer, а графические построения (DRAWCOLORCANDLES и две полосы, отображаемые линиями) настраиваются через PlotIndexSetInteger. Логика обновляется только на закрытых барах и использует EMPTY_VALUE для скрытия неактивных полос, при этом используются входные параметры atrPeriod и atrMultiplier. Получается аккуратный индикатор, накладываемый поверх графика, готовый для использования в советниках, с документированными буферами для стратегий и сигналов.
Изучение стандартной библиотеки MQL5 (Часть 12): Мультитаймфреймный дашборд на основе составного показателя Изучение стандартной библиотеки MQL5 (Часть 12): Мультитаймфреймный дашборд на основе составного показателя
В статье реализован CMultiTimeframeMatrix, переиспользуемый дашборд, который сопоставляет символы с таймфреймами и отображает числовой показатель с цветовой кодировкой. Показатель объединяет тренд и импульс с вычетом волатильности, обновляется по таймеру и учитывает ограничения производительности. Вы узнаете, как создавать пользовательский интерфейс с помощью CAppDialog/CLabel, вычислять метрики с помощью CMatrixDouble и встраивать компонент в простой советник для получения целостной картины рынка в реальном времени.