English
preview
Создание объектно-ориентированного движка инференса ONNX на языке MQL5

Создание объектно-ориентированного движка инференса ONNX на языке MQL5

MetaTrader 5 — Интеграция |
55 0
Amanda Vitoria De Paula Pereira
Amanda Vitoria De Paula Pereira

Вы обучили модель на Python, экспортировали её в формат ONNX и теперь хотите выполнять инференс непосредственно в MQL5 без использования внешних процессов Python, веб-сокетов или нестабильных DLL-библиотек.

На практике такие интеграции, как правило, дают сбой в четырёх местах: терминал выдает ошибку из-за несоответствия форм входных/выходных тензоров скомпилированному графу; прогнозы в режиме реального времени становятся некорректными, поскольку предобработка данных в реальном времени отличается от предобработки данных при обучении; дескрипторы сеансов ONNX приводят к утечкам памяти, если жизненный цикл сеанса не контролируется достаточно строго; а выполнение инференса при каждом входящем тике перегружает ЦП и снижает производительность бэктестинга.

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

Печать



Архитектура ONNX и вычислительные графы

Чтобы понять механику реализации, необходимо проанализировать, как модели машинного обучения сохраняются и экспортируются с точки зрения их структуры. Open Neural Network Exchange — это универсальный открытый формат, разработанный для единообразного представления моделей машинного обучения в различных программных средах. Когда нейронная сеть экспортируется из Python в формат ONNX с помощью таких библиотек, как TensorFlow или Scikit-Learn, она сериализуется в статический математический граф, состоящий из узлов и ребер. Каждый узел представляет собой конкретную математическую операцию, такую как умножение матриц или нелинейная функция активации, а ребра — многомерные массивы, называемые тензорами, которые передаются между этими операциями.

Когда терминал MetaTrader 5 загружает файл ONNX, он использует свой собственный внутренний движок для воссоздания именно этого математического графа в системной памяти. Терминалу требуются точные указания относительно размерности данных, которые он будет получать, и размерности данных, которые он должен выводить. Если нейронная сеть была обучена на языке Python для приема пяти технических индикаторов в качестве входных данных, то форма входного тензора строго определяется как массив из пяти элементов. Попытка передать необработанные рыночные цены закрытия непосредственно в движок ONNX без соответствия этой заранее заданной форме тензора приведет к немедленной ошибке терминала и сбою сеанса инференса. Программное управление этими формами входных и выходных тензоров является абсолютной основой стабильной интеграции.


Аппаратное ускорение и наборы инструкций AVX

Распространенным заблуждением среди частных разработчиков является мнение, что запуск моделей машинного обучения через внешние веб-сокеты Python с точки зрения вычислительных затрат эквивалентен их нативному запуску. Это серьезный архитектурный просчет. Когда советник полагается на порты локальной сети для передачи тиковых данных в реальном времени в скрипт на Python, он сталкивается с серьезной задержкой сериализации и десериализации, часто теряя критически важные миллисекунды во время публикации важных новостей. При прямой загрузке графа ONNX в среду MetaTrader 5 терминал автоматически задействует технологию Advanced Vector Extensions, аппаратно поддерживаемую современными процессорами. Такое аппаратное ускорение обосновывает целесообразность прямой привязки тензоров в MQL5 вместо использования внешних интеграций, которые увеличивают задержку и снижают производительность советника, обеспечивая среду выполнения, способную беспрепятственно обрабатывать сложные количественные модели институционального уровня.


Объектно-ориентированная инкапсуляция сеанса инференса

Управление моделями машинного обучения требует тщательного выделения ресурсов. Открытие сеанса ONNX потребляет значительный объем локальной памяти, и если эти ресурсы не освобождать, это приведет к исчерпанию ресурсов. Для обеспечения стабильности в промышленной эксплуатации мы инкапсулируем весь конвейер инференса в отдельный класс MQL5. Такой подход позволяет избежать использования глобальных переменных и строго регулирует жизненный цикл графа нейронной сети. Мы объявляем закрытые переменные для хранения уникального дескриптора ONNX, присвоенного терминалом, а также конкретных структур массивов, необходимых для передачи данных в обоих направлениях.

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

//+------------------------------------------------------------------+
//|                                                  ONNX_Engine.mqh |
//|                                  Copyright 2026, MetaQuotes Ltd. |
//+------------------------------------------------------------------+
#property copyright "Open Source"
#property version   "1.00"

//+------------------------------------------------------------------+
//| Class: CONNXPredictor                                            |
//| Purpose: Manages ONNX session, tensor binding, and inference     |
//+------------------------------------------------------------------+
class CONNXPredictor
  {
private:
   long              m_onnx_handle;       // Unique session identifier
   string            m_model_path;        // Directory path to the .onnx file
   bool              m_is_initialized;    // State validation flag

   bool              SetTensorShapes(void);

public:
                     CONNXPredictor(string model_filename);
                    ~CONNXPredictor(void);
                    
   bool              Initialize(void);
   double            PredictDirection(const double &features[]);
   void              NormalizeData(double &features[], double min_val, double max_val);
   bool              IsReady(void) const { return m_is_initialized; }
  };

//+------------------------------------------------------------------+
//| Constructor: Assigns the file path and sets default states       |
//+------------------------------------------------------------------+
CONNXPredictor::CONNXPredictor(string model_filename)
  {
   m_model_path = model_filename;
   m_onnx_handle = INVALID_HANDLE;
   m_is_initialized = false;
  }

//+------------------------------------------------------------------+
//| Destructor: Safely releases the ONNX graph from system memory    |
//+------------------------------------------------------------------+
CONNXPredictor::~CONNXPredictor(void)
  {
   if(m_onnx_handle != INVALID_HANDLE)
     {
      OnnxRelease(m_onnx_handle);
     }
  }


Определение формы тензора и конвейер выполнения

После создания дескриптора сеанса движок должен зафиксировать размерности тензоров. Функции OnnxSetInputShape и OnnxSetOutputShape предоставляют терминалу MQL5 точную архитектурную спецификацию импортированной нейронной сети. Для демонстрации в рамках данного фреймворка мы предполагаем стандартную классификационную модель, обученную обрабатывать пять различных рыночных признаков и выдавать одно значение прогнозной вероятности. Таким образом, форма входного тензора определяется как массив, содержащий размерности, а именно: одну строку и пять столбцов. Форма выходного тензора определяется как одна строка и один столбец. Если формы, определенные в коде MQL5, отличаются от форм, скомпилированных на этапе обучения в Python, блок выполнения немедленно прерывается.

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

//+------------------------------------------------------------------+
//| Initializes the ONNX session and binds the required tensor shapes|
//+------------------------------------------------------------------+
bool CONNXPredictor::Initialize(void)
  {
   m_onnx_handle = OnnxCreate(m_model_path, FILE_COMMON);
   
   if(m_onnx_handle == INVALID_HANDLE)
     {
      PrintFormat("Error: Failed to load ONNX model. Code: %d", GetLastError());
      return false;
     }
     
   if(!SetTensorShapes()) return false;
   
   m_is_initialized = true;
   return true;
  }

//+------------------------------------------------------------------+
//| Defines the strict mathematical boundaries for the data exchange |
//+------------------------------------------------------------------+
bool CONNXPredictor::SetTensorShapes(void)
  {
   long input_shape[] = {1, 5}; 
   if(!OnnxSetInputShape(m_onnx_handle, 0, input_shape)) return false;
   
   long output_shape[] = {1, 1}; 
   if(!OnnxSetOutputShape(m_onnx_handle, 0, output_shape)) return false;
   
   return true;
  }

//+------------------------------------------------------------------+
//| Injects live data into the graph and extracts the prediction     |
//+------------------------------------------------------------------+
double CONNXPredictor::PredictDirection(const double &features[])
  {
   if(!m_is_initialized || ArraySize(features) != 5) return 0.0;
   
   double vector_input[];
   double vector_output[1];
   
   ArrayCopy(vector_input, features);
   
   if(!OnnxRun(m_onnx_handle, ONNX_NO_CONVERSION, vector_input, vector_output))
     {
      Print("Error: ONNX inference execution failed.");
      return 0.0;
     }
     
   return vector_output[0];
  }

//+------------------------------------------------------------------+
//| Applies Min-Max Normalization to the extracted feature array     |
//+------------------------------------------------------------------+
void CONNXPredictor::NormalizeData(double &features[], double min_val, double max_val)
  {
   int total = ArraySize(features);
   double range = max_val - min_val;
   
   if(range <= 0.0) return; // Prevent division by zero
   
   for(int i = 0; i < total; i++)
     {
      features[i] = (features[i] - min_val) / range;
      
      // Clamp values to ensure strict boundaries
      if(features[i] > 1.0) features[i] = 1.0;
      if(features[i] < 0.0) features[i] = 0.0;
     }
  }


Извлечение признаков и математические основы нормализации в реальном времени

Модель машинного обучения полностью зависит от качества и формата входных данных. Нейронные сети чрезвычайно чувствительны к абсолютному масштабу чисел. Если модель была обучена в Python с использованием данных, нормализованных в диапазоне от нуля до единицы, то подача в нее необработанных валютных цен, например 1,0950, серьезно исказит математические веса внутри скрытых слоев и выдаст бессмысленный прогноз. Разработчики должны тщательно воспроизвести точную логику предобработки, использовавшуюся на этапе обучения, непосредственно в среде MQL5, прежде чем передавать массив в движок ONNX.

Для этого требуется динамическая подготовка данных. В нашей архитектуре советник отвечает за извлечение определенных технических показателей из графика в реальном времени. Затем эти исходные значения математически стандартизируются или подвергаются min-max-нормализации с помощью скрипта MQL5. В нашем классе мы реализуем специальный метод NormalizeData. Этот метод проходит по извлеченному массиву, вычитает теоретическое минимальное значение и делит результат на теоретический диапазон. Важно отметить, что минимальные и максимальные параметры, передаваемые в этот метод, должны быть зашиты в коде или динамически импортированы непосредственно из объекта-масштабировщика Python, использовавшегося при первоначальном обучении модели. Это гарантирует, что итоговый массив, передаваемый в сеанс ONNX, точно воспроизводит статистическую среду, ожидаемую нейронной сетью, обеспечивая детерминированные и надежные прогнозные результаты.


Деградация на вневыборочных данных и статистическая деградация

Даже безупречно интегрированный математический движок не способен защитить алгоритмический портфель от статистической деградации. Финансовые рынки постоянно переживают смену режимов, резко переходя от фаз низкой волатильности с возвратом к среднему значению к импульсным фазам высокой волатильности. Нейронная сеть, обученная на конкретном историческом наборе данных, неизбежно будет страдать от деградации на вневыборочных данных по мере появления новых, ранее не встречавшихся рыночных структур, которые делают недействительными исходные допущения, лежащие в основе обучения. Хотя наш объектно-ориентированный класс обеспечивает бесперебойную работу модели ONNX, разработчикам необходимо активно отслеживать точность прогнозирования с течением времени. Для развертывания советника, основанного на машинном обучении, необходимо установить четкий график переобучения, автоматически обновлять веса модели в Python и заменять файл ONNX, чтобы избежать торговли с использованием устаревших параметров модели.


Динамика умножения матриц и защита ЦП

Чтобы развернуть этот движок, мы создаём советник, который инициализирует экземпляр класса CONNXPredictor. Основное правило высокопроизводительной количественной разработки остается неизменным: защищать центральный процессор от избыточных вычислений. Инференс в машинном обучении — это вычислительно затратная операция. Даже относительно небольшая нейронная сеть требует от процессора выполнения ресурсоемких матричных операций, чтобы вычислить вычислительный граф. Запуск функции OnnxRun на каждом входящем рыночном тике является серьезной архитектурной ошибкой, которая ухудшит производительность в реальном времени и значительно замедлит тестирование на истории.

Мы избегаем избыточных сканирований за счет реализации строгого алгоритмического фильтра в обработчике события OnTick. Отслеживая время открытия текущего бара, советник устанавливает четкое прикладное правило: извлечение признаков и инференс выполняются строго один раз на каждый новый бар. Когда закрывается новый бар, извлеченные признаки нормализуются и обрабатываются. Если нейронная сеть выдает прогнозную вероятность, превышающую заданный порог, алгоритм использует эти данные для принятия решений об исполнении сделок. Такое структурное согласование обеспечивает максимальную эффективность работы терминала.

//+------------------------------------------------------------------+
//|                                          EA_ONNX_Predictor.mq5   |
//|                                  Copyright 2026, MetaQuotes Ltd. |
//+------------------------------------------------------------------+
#property copyright "Open Source"
#property version   "1.00"

#include <Trade\Trade.mqh>
#include "ONNX_Engine.mqh"

//--- Input Parameters
input string InpModelName = "predictive_model.onnx"; // Model filename
input double InpThreshold = 0.75;                    // Confidence threshold

//--- Global Objects
CONNXPredictor *g_ai_engine;
CTrade          g_trade;

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
   g_ai_engine = new CONNXPredictor(InpModelName);
   
   if(!g_ai_engine.Initialize())
     {
      return INIT_FAILED;
     }
     
   return(INIT_SUCCEEDED);
  }

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   if(CheckPointer(g_ai_engine) == POINTER_DYNAMIC)
     {
      delete g_ai_engine;
     }
  }

//+------------------------------------------------------------------+
//| Expert tick function with strict CPU optimization                |
//+------------------------------------------------------------------+
void OnTick()
  {
   static datetime last_bar_time = 0;
   datetime current_bar_time = iTime(_Symbol, PERIOD_CURRENT, 0);
   
   if(current_bar_time != last_bar_time)
     {
      last_bar_time = current_bar_time;
      
      if(CheckPointer(g_ai_engine) != POINTER_INVALID && g_ai_engine.IsReady())
        {
         double live_features[5];
         
         // Feature Extraction Logic (Example variables)
         for(int i = 0; i < 5; i++) live_features[i] = iClose(_Symbol, PERIOD_CURRENT, i+1); 
         
         // Normalize the data between theoretical min and max bounds
         g_ai_engine.NormalizeData(live_features, 1.0000, 1.2000);
         
         // Request prediction from the computational graph
         double prediction = g_ai_engine.PredictDirection(live_features);
         
         PrintFormat("AI Model Confidence: %.2f", prediction);
         
         // Execute trade routing based on model probability
         if(prediction >= InpThreshold && !PositionSelect(_Symbol))
           {
            g_trade.Buy(0.10, _Symbol, 0, 0, 0, "AI Output");
           }
        }
     }
  }
//+------------------------------------------------------------------+


Заключение

Мы создали компактную и работоспособную интеграцию, которая устраняет четыре описанных выше практических типовых точек отказа. Основным компонентом является класс CONNXPredictor, который безопасно создает и освобождает дескриптор сеанса ONNX, явно привязывает формы входных/выходных тензоров, проверяет размерности входных векторов, выполняет детерминированную min-max-нормализацию в MQL5 и запускает OnnxRun с проверкой ошибок. В результате вы можете добавить файл ONNX в проект, задать вектор признаков фиксированного размера, просматривать выходные прогнозы в журнале терминала и запускать торговые решения на основе настраиваемого порогового значения.

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


Таблица структуры файлов

Имя файла
Описание
ONNX_Engine.mqh
Полный исходный код объектно-ориентированного менеджера сеанса ONNX.
EA_ONNX_Predictor.mq5
Полный исходный код исполняющего советника.

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

Прикрепленные файлы |
ONNX_Engine.mqh (4.49 KB)
Нейронная сеть на практике: Начинаем строить цепочку Нейронная сеть на практике: Начинаем строить цепочку
Пришло время начать разбираться в том, как соединять перцептроны в цепочку. Это позволит нам реализовать то, что известно как сеть перцептронов. Эта сеть составляет основу всего того, что вы знаете и используете как ту или иную форму «искусственного интеллекта». Однако построение такой сети связано с рядом трудностей. Здесь мы начнём разбираться, в чём они заключаются.
Альтернатива временным барам (Часть 2): Построение баров серий в MQL5 Альтернатива временным барам (Часть 2): Построение баров серий в MQL5
Мы реализуем тиковые, объемные и долларовые бары серий в Python и MQL5 и встраиваем их в существующий фреймворк построения баров. В статье подробно описаны обновление двух накопителей, офлайн-калибровка с начальными значениями для каждого направления, сохранение состояния в советниках и проверка совпадения результатов Python и MQL5. Бары серий (runs bar) выявляют односторонние всплески активности, которые может скрывать метрика чистого дисбаланса, улучшая тем самым охват данных в спокойные сессии и для моделей возврата к среднему.
Особенности написания экспертов Особенности написания экспертов
Написание и тестирование экспертов в торговой системе MetaTrader 4.
От начального к среднему уровню: Файл шаблона графика (I) От начального к среднему уровню: Файл шаблона графика (I)
В статье объясняется, как перенести настройки основного графика на встроенный в OBJ_CHART график, учитывая, что наследуется только часть шаблона. Мы покажем, как синхронизировать цвета и свойства, подстраиваться под изменения размера и применять настройки к внутреннему графику, используя идентификатор графика и явные вызовы обновления. Читатель научится предсказуемо воспроизводить внешний вид и поведение основного графика в OBJ_CHART.