Интеграция MQL5 с пакетами обработки данных (Часть 6): Объединение рыночной обратной связи и адаптации модели
Содержание:
- Введение
- Обзор и понимание системы
- Приступим
- Объединяем все в MQL5
- Демонстрация в реальном времени
- Заключение
Введение
В предыдущей статье рассматривались адаптивное обучение и гибкость. Основное внимание уделялось созданию системы, способной корректировать процессы принятия решений в ответ на изменение рыночных условий. На этом этапе подчеркивалась важность адаптивности в алгоритмической торговле: модель должна динамически изменять параметры на основе меняющихся закономерностей в данных, а не опираться на статические исторические паттерны. Благодаря обучению с подкреплением и адаптивной параметризации система приобрела способность к самооптимизации, заложив основу для непрерывного совершенствования советника.
В этой части концепция развивается введением цикла обучения с обратной связью между результатами работы на рынке в реальном времени и обученной моделью. Цель состоит в том, чтобы связать среду выполнения 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
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.
Индикатор Sigma Score для MetaTrader 5: Простой детектор статистических аномалий
Кодирование свечных моделей (Часть 3): Частотный анализ односвечных структур
Мастер-класс по созданию пользовательских индикаторов (Часть 1): Разработка индикатора Supertrend на MQL5
Изучение стандартной библиотеки MQL5 (Часть 12): Мультитаймфреймный дашборд на основе составного показателя
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования