Запуск модели

Для запуска ONNX модели в MQL5 необходимо выполнить 3 шага:

  1. Загрузить её из *.onnx файла с помощью функции OnnxCreate или из массива с помощью OnnxCreateFromBuffer.
  2. Указать форму входных и выходных данных функциями OnnxSetInputShape и OnnxSetOutputShape.
  3. Запустить модель с помощью OnnxRun, передав ей  входные и выходные параметры.
  4. Завершить при необходимости работу модели с помощью OnnxRelease.

 

При создании ONNX модели нужно учитывать существующие лимиты и ограничения, которые описаны в https://github.com/microsoft/onnxruntime/blob/rel-1.14.0/docs/OperatorKernels.md

Например, вот некоторые из них:

Операция

Поддерживаемые типы данных

ReduceSum

tensor(double), tensor(float), tensor(int32), tensor(int64)

Mul

tensor(bfloat16), tensor(double), tensor(float), tensor(float16), tensor(int32), tensor(int64), tensor(uint32), tensor(uint64)

 

Ниже представлен пример MQL5 кода из публичного проекта ONNX.Price.Prediction.

const long   ExtOutputShape[] = {1,1};    // форма выходных данных модели
const long   ExtInputShape [] = {1,10,4}; // форма входных данных модели
#resource "Python/model.onnx" as uchar ExtModel[]// модель в виде ресурса
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
int OnStart(void)
  {
   matrix rates;
//--- получаем 10 баров
   if(!rates.CopyRates("EURUSD",PERIOD_H1,COPY_RATES_OHLC,2,10))
      return(-1);
//--- подаем на вход набор векторов OHLC
   matrix x_norm=rates.Transpose();
   vector m=x_norm.Mean(0);               
   vector s=x_norm.Std(0);
   matrix mm(10,4);
   matrix ms(10,4);
//--- заполним матрицы нормировки
   for(int i=0i<10i++)
     {
      mm.Row(m,i);
      ms.Row(s,i);
     }
//--- нормируем входные данные
   x_norm-=mm;
   x_norm/=ms;
//--- создаём модель
   long handle=OnnxCreateFromBuffer(ExtModel,ONNX_DEBUG_LOGS);
//--- укажем форму входных данных
   if(!OnnxSetInputShape(handle,0,ExtInputShape))
     {
      Print("OnnxSetInputShape failed, error ",GetLastError());
      OnnxRelease(handle);
      return(-1);
     }
//--- укажем форму выходных данных
   if(!OnnxSetOutputShape(handle,0,ExtOutputShape))
     {
      Print("OnnxSetOutputShape failed, error ",GetLastError());
      OnnxRelease(handle);
      return(-1);
     }
//--- конвертируем нормированные входные данные в тип float
   matrixf x_normf;
   x_normf.Assign(x_norm);
//--- сюда получим выходные данные модели - предсказание цены
   vectorf y_norm(1);
//--- запускаем модель
   if(!OnnxRun(handle,ONNX_DEBUG_LOGS | ONNX_NO_CONVERSION,x_normf,y_norm))
     {
      Print("OnnxRun failed, error ",GetLastError());
      OnnxRelease(handle);
      return(-1);
     }
//--- выведем в лог выходное значение модели
   Print(y_norm);
//--- сделаем обратное преобразование для  получения предсказанной цены
   double y_pred=y_norm[0]*s[3]+m[3];
   Print("price predicted:",y_pred);
//--- завершили работу
   OnnxRelease(handle);
   return(0);
  }

Пример выполнения скрипта:

ONNXCreating and using per session threadpools since use_per_session_threads_ is true
ONNXDynamic block base set to 0
ONNXInitializing session.
ONNXAdding default CPU execution provider.
ONNXTotal shared scalar initializer count0
ONNXTotal fused reshape node count0
ONNXTotal shared scalar initializer count0
ONNXTotal fused reshape node count0
ONNXUse DeviceBasedPartition as default
ONNXSaving initialized tensors.
ONNXDone saving initialized tensors
ONNXSession successfully initialized.
[0.28188983]
predicted 1.0559258806393044

Терминал MetaTrader 5 самостоятельно выбрал оптимального исполнителя для проведения вычислений — ONNX Runtime Execution Provider. В данном случае модель отработала с использованием CPU.

Изменим скрипт, чтобы посчитать процент успешных предсказаний цены Close на основании значений предыдущих 10 баров.

#resource "Python/model.onnx" as uchar ExtModel[]// модель в виде ресурса
 
#define TESTS 10000  // количество тестовых выборок
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
int OnStart()
  {
//--- создаём модель
   long session_handle=OnnxCreateFromBuffer(ExtModel,ONNX_DEBUG_LOGS);
   if(session_handle==INVALID_HANDLE)
     {
      Print("Cannot create model. Error ",GetLastError());
      return(-1);
     }
 
//--- так как у модели не определены размеры входного тензора, задаём их явно
//--- первый индекс - размер пачки, второй индекс - размер серии, третий индекс количество серий (OHLC)
   const long input_shape[]={1,10,4};
   if(!OnnxSetInputShape(session_handle,0,input_shape))
     {
      Print("OnnxSetInputShape error ",GetLastError());
      return(-2);
     }
 
//--- так как у модели не определены размеры выходного тензора, задаём их явно
//--- первый индекс - размер пачки, должен соответствовать размеру пачки входного тензора
//--- второй индекс - количество предсказанных цен (мы предсказываем только Close)
   const long output_shape[]={1,1};
   if(!OnnxSetOutputShape(session_handle,0,output_shape))
     {
      Print("OnnxSetOutputShape error ",GetLastError());
      return(-3);
     }
//--- запускаем тесты
   vector closes(TESTS);      // вектор для хранения проверочных цен
   vector predicts(TESTS);    // вектор для хранения полученных предсказаний
   vector prev_closes(TESTS); // вектор для хранения предпоследних цен 
 
   matrix rates;              // матрица для получения серии OHLC
   matrix splitted[2];        // две подматрицы для разделения серии на тестовую и проверочную
   ulong  parts[]={10,1};     // размеры разделяемых подматриц
 
//--- начинаем с предыдущего бара
   for(int i=1i<=TESTSi++)
     {
      //--- получим 11 баров
      rates.CopyRates("EURUSD",PERIOD_H1,COPY_RATES_OHLC,i,11);
      //--- делим матрицу на тестовую и проверочную
      rates.Vsplit(parts,splitted);
      //--- из проверочной матрицы берём цену Close
      closes[i-1]=splitted[1][3][0];
      //--- последний Close в тестируемой серии
      prev_closes[i-1]=splitted[0][3][9];
 
      //--- тестовую матрицу из 10 баров отправляем в тест
      predicts[i-1]=PricePredictionTest(session_handle,splitted[0]);
      //--- ошибка выполнения
      if(predicts[i-1]<=0)
        {
         OnnxRelease(session_handle);
         return(-4);
        }
     }
//--- завершили работу
   OnnxRelease(session_handle);
//--- оценим правильность движения предсказанной цены
   int    right_directions=0;
   vector delta_predicts=prev_closes-predicts;
   vector delta_actuals=prev_closes-closes;
 
   for(int i=0i<TESTSi++)
      if((delta_predicts[i]>0 && delta_actuals[i]>0) || (delta_predicts[i]<0 && delta_actuals[i]<0))
         right_directions++;
   PrintFormat("right direction predictions = %.2f%%",(right_directions*100.0)/double(TESTS));
//--- 
   return(0);
  }
//+------------------------------------------------------------------+
//|  Подготовка данных и запуск модели                               |
//+------------------------------------------------------------------+
double PricePredictionTest(const long session_handle,matrixrates)
  {
   static matrixf input_data(10,4); // матрица для преобразованных входных данных
   static vectorf output_data(1);   // вектор для получения результата
   static matrix mm(10,4);          // матрица горизонтальных векторов Mean
   static matrix ms(10,4);          // матрица горизонтальных векторов Std
 
//--- на вход модели должен подаваться набор вертикальных векторов OHLC
   matrix x_norm=rates.Transpose();
//--- нормируем цены
   vector m=x_norm.Mean(0);
   vector s=x_norm.Std(0);
   for(int i=0i<10i++)
     {
      mm.Row(m,i);
      ms.Row(s,i);
     }
   x_norm-=mm;
   x_norm/=ms;
 
//--- запускаем модель
   input_data.Assign(x_norm);
   if(!OnnxRun(session_handle,ONNX_DEBUG_LOGS,input_data,output_data))
     {
      Print("OnnxRun error ",GetLastError());
      return(0);
     }
//--- обратно разнормируем цену из выходного значения
   double y_pred=output_data[0]*s[3]+m[3];
 
   return(y_pred);
  }

Запускаем скрипт и получаем точность предсказаний примерно 51%

ONNX: Creating and using per session threadpools since use_per_session_threads_ is true
ONNX: Dynamic block base set to 0
ONNX: Initializing session.
ONNX: Adding default CPU execution provider.
ONNX: Total shared scalar initializer count: 0
ONNX: Total fused reshape node count: 0
ONNX: Total shared scalar initializer count: 0
ONNX: Total fused reshape node count: 0
ONNX: Use DeviceBasedPartition as default
ONNX: Saving initialized tensors.
ONNX: Done saving initialized tensors
ONNX: Session successfully initialized.
right direction predictions = 51.34 %