Inicialização do modelo

Para utilizar o modelo ONNX em MQL5, é necessário seguir três etapas:

  1. Carregá-lo a partir do arquivo *.onnx usando a função OnnxCreate ou a partir de um array usando OnnxCreateFromBuffer.
  2. Especificar a forma dos dados de entrada e saída por meio das funções OnnxSetInputShape e OnnxSetOutputShape.
  3. Iniciar o modelo usando OnnxRun, após lhe dar parâmetros de entrada e saída.
  4. Encerrar o modelo, se necessário, com OnnxRelease.

 

Ao criar o modelo ONNX, é necessário considerar os limites e restrições existentes descritos em https://github.com/microsoft/onnxruntime/blob/rel-1.14.0/docs/OperatorKernels.md

Por exemplo, aqui estão algumas delas:

Operação

Tipos de dados suportados

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)

 

Veja abaixo um exemplo de código MQL5 extraído do projeto público ONNX.Price.Prediction.

const long   ExtOutputShape[] = {1,1};    // forma dos dados de saída do modelo
const long   ExtInputShape [] = {1,10,4}; // forma dos dados de entrada do modelo
#resource "Python/model.onnx" as uchar ExtModel[]// modelo em forma de recurso
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
int OnStart(void)
  {
   matrix rates;
//--- obtemos 10 barras
   if(!rates.CopyRates("EURUSD",PERIOD_H1,COPY_RATES_OHLC,2,10))
      return(-1);
//--- alimentamos com um conjunto de vetores 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);
//--- preenchemos matrizes de normalização
   for(int i=0i<10i++)
     {
      mm.Row(m,i);
      ms.Row(s,i);
     }
//--- normalizamos os dados de entrada
   x_norm-=mm;
   x_norm/=ms;
//--- criamos o modelo
   long handle=OnnxCreateFromBuffer(ExtModel,ONNX_DEBUG_LOGS);
//--- especificamos a forma dos dados de entrada
   if(!OnnxSetInputShape(handle,0,ExtInputShape))
     {
      Print("OnnxSetInputShape failed, error ",GetLastError());
      OnnxRelease(handle);
      return(-1);
     }
//--- especificamos a forma dos dados de saída
   if(!OnnxSetOutputShape(handle,0,ExtOutputShape))
     {
      Print("OnnxSetOutputShape failed, error ",GetLastError());
      OnnxRelease(handle);
      return(-1);
     }
//--- convertemos dados de entrada normalizados em dados do tipo float
   matrixf x_normf;
   x_normf.Assign(x_norm);
//--- obtemos o resultado do modelo - a previsão de preço
   vectorf y_norm(1);
//--- iniciamos o modelo
   if(!OnnxRun(handle,ONNX_DEBUG_LOGS | ONNX_NO_CONVERSION,x_normf,y_norm))
     {
      Print("OnnxRun failed, error ",GetLastError());
      OnnxRelease(handle);
      return(-1);
     }
//--- registramos no log o valor de saída do modelo
   Print(y_norm);
//--- realizamos uma transformação inversa para obter o preço previsto
   double y_pred=y_norm[0]*s[3]+m[3];
   Print("price predicted:",y_pred);
//--- concluímos o trabalho
   OnnxRelease(handle);
   return(0);
  }

Veja um exemplo de execução do script:

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

O terminal MetaTrader 5 seleciona de forma independente o melhor executor para os cálculos – ONNX Runtime Execution Provider. Neste caso, o modelo realiza seu trabalho usando a CPU.

Vamos modificar o script para calcular a porcentagem de previsões bem-sucedidas do preço de fechamento com base nos valores das 10 barras anteriores.

#resource "Python/model.onnx" as uchar ExtModel[]// modelo em forma de recurso
 
#define TESTS 10000  // número de amostras de teste
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
int OnStart()
  {
//--- criamos o modelo
   long session_handle=OnnxCreateFromBuffer(ExtModel,ONNX_DEBUG_LOGS);
   if(session_handle==INVALID_HANDLE)
     {
      Print("Cannot create model. Error ",GetLastError());
      return(-1);
     }
 
//--- como o modelo não tem dimensões de tensor de entrada definidas, devemos defini-las explicitamente
//--- o primeiro índice é o tamanho do pacote; o segundo índice, tamanho da série; o terceiro índice, número de séries (OHLC)
   const long input_shape[]={1,10,4};
   if(!OnnxSetInputShape(session_handle,0,input_shape))
     {
      Print("OnnxSetInputShape error ",GetLastError());
      return(-2);
     }
 
//--- como o modelo não tem dimensões de tensor de saída definidas, devemos defini-las explicitamente
//--- o primeiro índice é o tamanho do lote, que deve corresponder ao tamanho do lote do tensor de entrada
//--- o segundo índice é o número de preços previstos (prevemos apenas o Close)
   const long output_shape[]={1,1};
   if(!OnnxSetOutputShape(session_handle,0,output_shape))
     {
      Print("OnnxSetOutputShape error ",GetLastError());
      return(-3);
     }
//--- iniciamos os testes
   vector closes(TESTS);      // vetor para armazenar preços de verificação
   vector predicts(TESTS);    // vetor para armazenar as previsões resultantes
   vector prev_closes(TESTS); // vetor para armazenar os penúltimos preços
 
   matrix rates;              // matriz para obter uma série OHLC
   matrix splitted[2];        // duas submatrizes para dividir a série em uma de teste e outra de verificação
   ulong  parts[]={10,1};     // dimensões dos subarrays separáveis
 
//--- começamos a partir da barra anterior
   for(int i=1i<=TESTSi++)
     {
      //--- obtemos 11 barras
      rates.CopyRates("EURUSD",PERIOD_H1,COPY_RATES_OHLC,i,11);
      //--- dividimos a matriz em uma de teste e outra de verificação
      rates.Vsplit(parts,splitted);
      //--- pegamos o preço Close a partir da matriz de verificação
      closes[i-1]=splitted[1][3][0];
      //--- último Close na série de teste
      prev_closes[i-1]=splitted[0][3][9];
 
      //--- enviamos ao teste uma matriz de teste de 10 barras
      predicts[i-1]=PricePredictionTest(session_handle,splitted[0]);
      //--- erro de execução
      if(predicts[i-1]<=0)
        {
         OnnxRelease(session_handle);
         return(-4);
        }
     }
//--- concluímos o trabalho
   OnnxRelease(session_handle);
//--- avaliamos se o movimento de preço previsto está correto
   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);
  }
//+------------------------------------------------------------------+
//|  Preparando os dados e utilizando o modelo                       |
//+------------------------------------------------------------------+
double PricePredictionTest(const long session_handle,matrixrates)
  {
   static matrixf input_data(10,4); // matriz para dados de entrada transformados
   static vectorf output_data(1);   // vetor para obter o resultado
   static matrix mm(10,4);          // matriz de vetores horizontais Mean
   static matrix ms(10,4);          // matriz de vetores horizontais Std
 
//--- o modelo deve ser alimentado com um conjunto de vetores OHLC verticais
   matrix x_norm=rates.Transpose();
//--- normalizamos os preços
   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;
 
//--- iniciamos o modelo
   input_data.Assign(x_norm);
   if(!OnnxRun(session_handle,ONNX_DEBUG_LOGS,input_data,output_data))
     {
      Print("OnnxRun error ",GetLastError());
      return(0);
     }
//--- revertemos a normalização do preço a partir do valor de saída
   double y_pred=output_data[0]*s[3]+m[3];
 
   return(y_pred);
  }

Executamos o script e obtemos uma precisão de previsão de cerca de 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 %