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

Для запуска 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=0; i<10; i++)
     {
      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);
  }

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

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.
[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=1; i<=TESTS; i++)
     {
      //--- получим 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=0; i<TESTS; i++)
      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,matrix& rates)
  {
   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=0; i<10; i++)
     {
      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 %