English Русский 中文 Español Deutsch 日本語 Português 한국어 Italiano Türkçe
preview
Envelopper les modèles ONNX dans des classes

Envelopper les modèles ONNX dans des classes

MetaTrader 5Exemples | 24 janvier 2024, 17:00
459 0
MetaQuotes
MetaQuotes

Introduction

Dans l'article précédent, nous avons utilisé 2 modèles ONNX pour organiser le classificateur de vote. L'ensemble du code source a été organisé en un seul fichier MQ5. L'ensemble du code a été divisé en fonctions. Mais que se passe-t-il si nous essayons d'échanger nos modèles ? Ou si nous essayons d’ajouter un autre modèle ? Le code original sera encore plus grand. Alors essayons l'approche orientée objet.


1. Quels modèles allons-nous utiliser ?

Dans le classificateur de vote précédent, nous avons utilisé un modèle de classification et un modèle de régression. Dans le modèle de régression, au lieu du mouvement de prix prédit (à la baisse, à la hausse ou sans changement), nous obtenons le prix prédit utilisé pour calculer la classe. Toutefois, dans ce cas, nous ne disposons pas d'une distribution de probabilités par classe, ce qui ne permet pas de procéder à ce que l'on appelle un "vote doux".

Nous avons préparé 3 modèles de classification. Deux modèles ont déjà été utilisés dans l'article "An example of how to ensemble ONNX models in MQL5". Le premier modèle (régression) a été converti en modèle de classification. L’entraînement a été effectué sur une série de 10 prix OHLC. Le deuxième modèle est celui de la classification. L’entraînement a porté sur une série de 63 prix de Clôture.

Il existe finalement un autre modèle. Le modèle de classification a été entraîné sur une série de 30 prix de clôture et une série de moyennes mobiles simples avec des périodes moyennes de 21 et 34. Nous n'avons fait aucune hypothèse sur l'intersection des moyennes mobiles avec le graphique des prix et entre elles : tous les modèles seront calculés et mémorisés par le réseau sous la forme de matrices de coefficients entre les couches.

Tous les modèles ont été entraînés sur les données du serveur MetaQuotes-Demo, sur l’EURUSD en D1 du 01/01/2010 au 01/01/2023. Les scripts d'entraînement pour les 3 modèles sont écrits en Python et sont attachés à cet article. Nous ne fournirons pas les codes sources ici afin de ne pas détourner l'attention du lecteur du sujet principal de notre article.


2. Une classe de base pour tous les modèles est nécessaire

Il existe 3 modèles. Chacun diffère des autres par la taille et par la préparation des données d'entrée. Tous les modèles sont dotés de la même interface. Les classes de tous les modèles doivent être héritées de la même classe de base.

Essayons de représenter la classe de base.

//+------------------------------------------------------------------+
//|                                            ModelSymbolPeriod.mqh |
//|                                  Copyright 2023, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+

//--- price movement prediction
#define PRICE_UP   0
#define PRICE_SAME 1
#define PRICE_DOWN 2

//+------------------------------------------------------------------+
//| Base class for models based on trained symbol and period         |
//+------------------------------------------------------------------+
class CModelSymbolPeriod
  {
protected:
   long              m_handle;           // created model session handle
   string            m_symbol;           // symbol of trained data
   ENUM_TIMEFRAMES   m_period;           // timeframe of trained data
   datetime          m_next_bar;         // time of next bar (we work at bar begin only)
   double            m_class_delta;      // delta to recognize "price the same" in regression models

public:
   //+------------------------------------------------------------------+
   //| Constructor                                                      |
   //+------------------------------------------------------------------+
   CModelSymbolPeriod(const string symbol,const ENUM_TIMEFRAMES period,const double class_delta=0.0001)
     {
      m_handle=INVALID_HANDLE;
      m_symbol=symbol;
      m_period=period;
      m_next_bar=0;
      m_class_delta=class_delta;
     }

   //+------------------------------------------------------------------+
   //| Destructor                                                       |
   //+------------------------------------------------------------------+
   ~CModelSymbolPeriod(void)
     {
      Shutdown();
     }

   //+------------------------------------------------------------------+
   //| virtual stub for Init                                            |
   //+------------------------------------------------------------------+
   virtual bool Init(const string symbol,const ENUM_TIMEFRAMES period)
     {
      return(false);
     }

   //+------------------------------------------------------------------+
   //| Check for initialization, create model                           |
   //+------------------------------------------------------------------+
   bool CheckInit(const string symbol,const ENUM_TIMEFRAMES period,const uchar& model[])
     {
      //--- check symbol, period
      if(symbol!=m_symbol || period!=m_period)
        {
         PrintFormat("Model must work with %s,%s",m_symbol,EnumToString(m_period));
         return(false);
        }

      //--- create a model from static buffer
      m_handle=OnnxCreateFromBuffer(model,ONNX_DEFAULT);
      if(m_handle==INVALID_HANDLE)
        {
         Print("OnnxCreateFromBuffer error ",GetLastError());
         return(false);
        }

      //--- ok
      return(true);
     }

   //+------------------------------------------------------------------+
   //| Release ONNX session                                             |
   //+------------------------------------------------------------------+
   void Shutdown(void)
     {
      if(m_handle!=INVALID_HANDLE)
        {
         OnnxRelease(m_handle);
         m_handle=INVALID_HANDLE;
        }
     }

   //+------------------------------------------------------------------+
   //| Check for continue OnTick                                        |
   //+------------------------------------------------------------------+
   virtual bool CheckOnTick(void)
     {
      //--- check new bar
      if(TimeCurrent()<m_next_bar)
         return(false);
      //--- set next bar time
      m_next_bar=TimeCurrent();
      m_next_bar-=m_next_bar%PeriodSeconds(m_period);
      m_next_bar+=PeriodSeconds(m_period);

      //--- work on new day bar
      return(true);
     }

   //+------------------------------------------------------------------+
   //| virtual stub for PredictPrice (regression model)                 |
   //+------------------------------------------------------------------+
   virtual double PredictPrice(void)
     {
      return(DBL_MAX);
     }

   //+------------------------------------------------------------------+
   //| Predict class (regression -> classification)                     |
   //+------------------------------------------------------------------+
   virtual int PredictClass(void)
     {
      double predicted_price=PredictPrice();
      if(predicted_price==DBL_MAX)
         return(-1);

      int    predicted_class=-1;
      double last_close=iClose(m_symbol,m_period,1);
      //--- classify predicted price movement
      double delta=last_close-predicted_price;
      if(fabs(delta)<=m_class_delta)
         predicted_class=PRICE_SAME;
      else
        {
         if(delta<0)
            predicted_class=PRICE_UP;
         else
            predicted_class=PRICE_DOWN;
        }

      //--- return predicted class
      return(predicted_class);
     }
  };
//+------------------------------------------------------------------+

La classe de base peut être utilisée pour les modèles de régression et de classification. Il suffit d'implémenter la bonne méthode dans la classe fille : PredictPrice ou PredictClass.

La classe de base définit la période et le symbole avec laquelle le modèle doit travailler (les données sur lesquelles le modèle a été formé). La classe de base vérifie également que l'EA utilisant le modèle fonctionne sur la période et le symbole requis et crée une session ONNX pour exécuter le modèle. La classe de base ne fournit du travail qu'au début d'une nouvelle mesure.


3. Première classe de modèles

Notre premier modèle s'appelle model.eurusd.D1.10.class.onnx, qui est un modèle de classification entraîné sur l’EURUSD en D1 sur une série de 10 prix OHLC.

//+------------------------------------------------------------------+
//|                                        ModelEurusdD1_10Class.mqh |
//|                                  Copyright 2023, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#include "ModelSymbolPeriod.mqh"

#resource "Python/model.eurusd.D1.10.class.onnx" as uchar model_eurusd_D1_10_class[]

//+------------------------------------------------------------------+
//| ONNX-model wrapper class                                         |
//+------------------------------------------------------------------+
class CModelEurusdD1_10Class : public CModelSymbolPeriod
  {
private:
   int               m_sample_size;

public:
   //+------------------------------------------------------------------+
   //| Constructor                                                      |
   //+------------------------------------------------------------------+
   CModelEurusdD1_10Class(void) : CModelSymbolPeriod("EURUSD",PERIOD_D1)
     {
      m_sample_size=10;
     }

   //+------------------------------------------------------------------+
   //| ONNX-model initialization                                        |
   //+------------------------------------------------------------------+
   virtual bool Init(const string symbol, const ENUM_TIMEFRAMES period)
     {
      //--- check symbol, period, create model
      if(!CModelSymbolPeriod::CheckInit(symbol,period,model_eurusd_D1_10_class))
        {
         Print("model_eurusd_D1_10_class : initialization error");
         return(false);
        }

      //--- since not all sizes defined in the input tensor we must set them explicitly
      //--- first index - batch size, second index - series size, third index - number of series (OHLC)
      const long input_shape[] = {1,m_sample_size,4};
      if(!OnnxSetInputShape(m_handle,0,input_shape))
        {
         Print("model_eurusd_D1_10_class : OnnxSetInputShape error ",GetLastError());
         return(false);
        }
   
      //--- since not all sizes defined in the output tensor we must set them explicitly
      //--- first index - batch size, must match the batch size of the input tensor
      //--- second index - number of classes (up, same or down)
      const long output_shape[] = {1,3};
      if(!OnnxSetOutputShape(m_handle,0,output_shape))
        {
         Print("model_eurusd_D1_10_class : OnnxSetOutputShape error ",GetLastError());
         return(false);
        }
      //--- ok
      return(true);
     }

   //+------------------------------------------------------------------+
   //| Predict class                                                    |
   //+------------------------------------------------------------------+
   virtual int PredictClass(void)
     {
      static matrixf input_data(m_sample_size,4);    // matrix for prepared input data
      static vectorf output_data(3);                 // vector to get result
      static matrix  mm(m_sample_size,4);            // matrix of horizontal vectors Mean
      static matrix  ms(m_sample_size,4);            // matrix of horizontal vectors Std
      static matrix  x_norm(m_sample_size,4);        // matrix for prices normalize
   
      //--- prepare input data
      matrix rates;
      //--- request last bars
      if(!rates.CopyRates(m_symbol,m_period,COPY_RATES_OHLC,1,m_sample_size))
         return(-1);
      //--- get series Mean
      vector m=rates.Mean(1);
      //--- get series Std
      vector s=rates.Std(1);
      //--- prepare matrices for prices normalization
      for(int i=0; i<m_sample_size; i++)
        {
         mm.Row(m,i);
         ms.Row(s,i);
        }
      //--- the input of the model must be a set of vertical OHLC vectors
      x_norm=rates.Transpose();
      //--- normalize prices
      x_norm-=mm;
      x_norm/=ms;
   
      //--- run the inference
      input_data.Assign(x_norm);
      if(!OnnxRun(m_handle,ONNX_NO_CONVERSION,input_data,output_data))
         return(-1);
      //--- evaluate prediction
      return(int(output_data.ArgMax()));
     }
  };
//+------------------------------------------------------------------+

Comme nous l'avons déjà mentionné plus haut : "Il existe 3 modèles. Chacun diffère des autres par la taille et par la préparation des données d'entrée". Nous n'avons redéfini que 2 méthodes : Init et PredictClass. Les mêmes méthodes seront redéfinies dans les deux autres classes pour les deux autres modèles.

La méthode Init appelle la méthode CheckInit de la classe de base où une session pour notre modèle ONNX est créée et les tailles des tenseurs d'entrée et de sortie sont explicitement définies. Il y a plus de commentaires que de code ici.

La méthode PredictClass fournit exactement la même préparation des données d'entrée que lors de l'entraînement du modèle. L'entrée est une matrice de prix OHLC normalisés.


4. Voyons comment cela fonctionne

Un Expert Advisor très compact a été créé pour tester les performances de notre classe.

//+------------------------------------------------------------------+
//|                                    ONNX.eurusd.D1.Prediction.mq5 |
//|                                  Copyright 2023, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright   "Copyright 2023, MetaQuotes Ltd."
#property link        "https://www.mql5.com"
#property version     "1.00"

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

input double InpLots = 1.0;    // Lots amount to open position

CModelEurusdD1_10Class ExtModel;
CTrade                 ExtTrade;

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
   if(!ExtModel.Init(_Symbol,_Period))
      return(INIT_FAILED);
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   ExtModel.Shutdown();
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
   if(!ExtModel.CheckOnTick())
      return;

//--- predict next price movement
   int predicted_class=ExtModel.PredictClass();
//--- check trading according to prediction
   if(predicted_class>=0)
      if(PositionSelect(_Symbol))
         CheckForClose(predicted_class);
      else
         CheckForOpen(predicted_class);
  }
//+------------------------------------------------------------------+
//| Check for open position conditions                               |
//+------------------------------------------------------------------+
void CheckForOpen(const int predicted_class)
  {
   ENUM_ORDER_TYPE signal=WRONG_VALUE;
//--- check signals

   if(predicted_class==PRICE_DOWN)
      signal=ORDER_TYPE_SELL;    // sell condition
   else
     {
      if(predicted_class==PRICE_UP)
         signal=ORDER_TYPE_BUY;  // buy condition
     }

//--- open position if possible according to signal
   if(signal!=WRONG_VALUE && TerminalInfoInteger(TERMINAL_TRADE_ALLOWED))
     {
      double price=SymbolInfoDouble(_Symbol,(signal==ORDER_TYPE_SELL) ? SYMBOL_BID : SYMBOL_ASK);
      ExtTrade.PositionOpen(_Symbol,signal,InpLots,price,0,0);
     }
  }
//+------------------------------------------------------------------+
//| Check for close position conditions                              |
//+------------------------------------------------------------------+
void CheckForClose(const int predicted_class)
  {
   bool bsignal=false;
//--- position already selected before
   long type=PositionGetInteger(POSITION_TYPE);
//--- check signals
   if(type==POSITION_TYPE_BUY && predicted_class==PRICE_DOWN)
      bsignal=true;
   if(type==POSITION_TYPE_SELL && predicted_class==PRICE_UP)
      bsignal=true;

//--- close position if possible
   if(bsignal && TerminalInfoInteger(TERMINAL_TRADE_ALLOWED))
     {
      ExtTrade.PositionClose(_Symbol,3);
      //--- open opposite
      CheckForOpen(predicted_class);
     }
  }
//+------------------------------------------------------------------+

Puisque le modèle a été entraîné sur des données de prix jusqu'en 2023, lançons le test à partir du 1er janvier 2023.

Paramètres du test

Voici le résultat :

Résultats des tests

Comme nous pouvons le constater, le modèle est entièrement fonctionnel.


5. Deuxième classe de modèles

Le deuxième modèle est appelé model.eurusd.D1.30.class.onnx. Le modèle de classification entraîné sur l’EURUSD en D1 sur une série de 30 prix de Clôture et sur 2 moyennes mobiles simples avec des périodes de moyenne de 21 et 34.

//+------------------------------------------------------------------+
//|                                        ModelEurusdD1_30Class.mqh |
//|                                  Copyright 2023, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#include "ModelSymbolPeriod.mqh"

#resource "Python/model.eurusd.D1.30.class.onnx" as uchar model_eurusd_D1_30_class[]

//+------------------------------------------------------------------+
//| ONNX-model wrapper class                                         |
//+------------------------------------------------------------------+
class CModelEurusdD1_30Class : public CModelSymbolPeriod
  {
private:
   int               m_sample_size;
   int               m_fast_period;
   int               m_slow_period;
   int               m_sma_fast;
   int               m_sma_slow;

public:
   //+------------------------------------------------------------------+
   //| Constructor                                                      |
   //+------------------------------------------------------------------+
   CModelEurusdD1_30Class(void) : CModelSymbolPeriod("EURUSD",PERIOD_D1)
     {
      m_sample_size=30;
      m_fast_period=21;
      m_slow_period=34;
      m_sma_fast=INVALID_HANDLE;
      m_sma_slow=INVALID_HANDLE;
     }

   //+------------------------------------------------------------------+
   //| ONNX-model initialization                                        |
   //+------------------------------------------------------------------+
   virtual bool Init(const string symbol, const ENUM_TIMEFRAMES period)
     {
      //--- check symbol, period, create model
      if(!CModelSymbolPeriod::CheckInit(symbol,period,model_eurusd_D1_30_class))
        {
         Print("model_eurusd_D1_30_class : initialization error");
         return(false);
        }

      //--- since not all sizes defined in the input tensor we must set them explicitly
      //--- first index - batch size, second index - series size, third index - number of series (Close, MA fast, MA slow)
      const long input_shape[] = {1,m_sample_size,3};
      if(!OnnxSetInputShape(m_handle,0,input_shape))
        {
         Print("model_eurusd_D1_30_class : OnnxSetInputShape error ",GetLastError());
         return(false);
        }
   
      //--- since not all sizes defined in the output tensor we must set them explicitly
      //--- first index - batch size, must match the batch size of the input tensor
      //--- second index - number of classes (up, same or down)
      const long output_shape[] = {1,3};
      if(!OnnxSetOutputShape(m_handle,0,output_shape))
        {
         Print("model_eurusd_D1_30_class : OnnxSetOutputShape error ",GetLastError());
         return(false);
        }
      //--- indicators
      m_sma_fast=iMA(m_symbol,m_period,m_fast_period,0,MODE_SMA,PRICE_CLOSE);
      m_sma_slow=iMA(m_symbol,m_period,m_slow_period,0,MODE_SMA,PRICE_CLOSE);
      if(m_sma_fast==INVALID_HANDLE || m_sma_slow==INVALID_HANDLE)
        {
         Print("model_eurusd_D1_30_class : cannot create indicator");
         return(false);
        }
      //--- ok
      return(true);
     }

   //+------------------------------------------------------------------+
   //| Predict class                                                    |
   //+------------------------------------------------------------------+
   virtual int PredictClass(void)
     {
      static matrixf input_data(m_sample_size,3);    // matrix for prepared input data
      static vectorf output_data(3);                 // vector to get result
      static matrix  x_norm(m_sample_size,3);        // matrix for prices normalize
      static vector  vtemp(m_sample_size);
      static double  ma_buffer[];
   
      //--- request last bars
      if(!vtemp.CopyRates(m_symbol,m_period,COPY_RATES_CLOSE,1,m_sample_size))
         return(-1);
      //--- get series Mean
      double m=vtemp.Mean();
      //--- get series Std
      double s=vtemp.Std();
      //--- normalize
      vtemp-=m;
      vtemp/=s;
      x_norm.Col(vtemp,0);
      //--- fast sma
      if(CopyBuffer(m_sma_fast,0,1,m_sample_size,ma_buffer)!=m_sample_size)
         return(-1);
      vtemp.Assign(ma_buffer);
      m=vtemp.Mean();
      s=vtemp.Std();
      vtemp-=m;
      vtemp/=s;
      x_norm.Col(vtemp,1);
      //--- slow sma
      if(CopyBuffer(m_sma_slow,0,1,m_sample_size,ma_buffer)!=m_sample_size)
         return(-1);
      vtemp.Assign(ma_buffer);
      m=vtemp.Mean();
      s=vtemp.Std();
      vtemp-=m;
      vtemp/=s;
      x_norm.Col(vtemp,2);
   
      //--- run the inference
      input_data.Assign(x_norm);
      if(!OnnxRun(m_handle,ONNX_NO_CONVERSION,input_data,output_data))
         return(-1);
      //--- evaluate prediction
      return(int(output_data.ArgMax()));
     }
  };
//+------------------------------------------------------------------+

Comme dans la classe précédente, la méthode CheckInit de la classe de base est appelée dans la méthode Init. Dans la méthode de la classe de base, une session est créée pour le modèle ONNX et les tailles des tenseurs d'entrée et de sortie sont explicitement définies.

La méthode PredictClass fournit une série de 30 clôtures précédentes et des moyennes mobiles calculées. Les données sont normalisées de la même manière que pour l’entraînement.

Voyons comment fonctionne ce modèle. Pour cela, modifions seulement deux chaînes de l’EA de test :

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

input double InpLots = 1.0;    // Lots amount to open position

CModelEurusdD1_30Class ExtModel;
CTrade                 ExtTrade;

Les paramètres du test sont les mêmes.

Résultats des tests du deuxième modèle

Nous constatons que le modèle fonctionne.


6. Troisième classe de modèles

Le dernier modèle est appelé model.eurusd.D1.63.class.onnx. Le modèle de classification entraîné sur l’EURUSD en D1 sur une série de 63 prix de clôture.

//+------------------------------------------------------------------+
//|                                             ModelEurusdD1_63.mqh |
//|                                  Copyright 2023, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#include "ModelSymbolPeriod.mqh"

#resource "Python/model.eurusd.D1.63.class.onnx" as uchar model_eurusd_D1_63_class[]

//+------------------------------------------------------------------+
//| ONNX-model wrapper class                                         |
//+------------------------------------------------------------------+
class CModelEurusdD1_63Class : public CModelSymbolPeriod
  {
private:
   int               m_sample_size;

public:
   //+------------------------------------------------------------------+
   //| Constructor                                                      |
   //+------------------------------------------------------------------+
   CModelEurusdD1_63Class(void) : CModelSymbolPeriod("EURUSD",PERIOD_D1,0.0001)
     {
      m_sample_size=63;
     }

   //+------------------------------------------------------------------+
   //| ONNX-model initialization                                        |
   //+------------------------------------------------------------------+
   virtual bool Init(const string symbol, const ENUM_TIMEFRAMES period)
     {
      //--- check symbol, period, create model
      if(!CModelSymbolPeriod::CheckInit(symbol,period,model_eurusd_D1_63_class))
        {
         Print("model_eurusd_D1_63_class : initialization error");
         return(false);
        }

      //--- since not all sizes defined in the input tensor we must set them explicitly
      //--- first index - batch size, second index - series size
      const long input_shape[] = {1,m_sample_size};
      if(!OnnxSetInputShape(m_handle,0,input_shape))
        {
         Print("model_eurusd_D1_63_class : OnnxSetInputShape error ",GetLastError());
         return(false);
        }
   
      //--- since not all sizes defined in the output tensor we must set them explicitly
      //--- first index - batch size, must match the batch size of the input tensor
      //--- second index - number of classes (up, same or down)
      const long output_shape[] = {1,3};
      if(!OnnxSetOutputShape(m_handle,0,output_shape))
        {
         Print("model_eurusd_D1_63_class : OnnxSetOutputShape error ",GetLastError());
         return(false);
        }
      //--- ok
      return(true);
     }

   //+------------------------------------------------------------------+
   //| Predict class                                                    |
   //+------------------------------------------------------------------+
   virtual int PredictClass(void)
     {
      static vectorf input_data(m_sample_size);  // vector for prepared input data
      static vectorf output_data(3);             // vector to get result
   
      //--- request last bars
      if(!input_data.CopyRates(m_symbol,m_period,COPY_RATES_CLOSE,1,m_sample_size))
         return(-1);
      //--- get series Mean
      float m=input_data.Mean();
      //--- get series Std
      float s=input_data.Std();
      //--- normalize prices
      input_data-=m;
      input_data/=s;
   
      //--- run the inference
      if(!OnnxRun(m_handle,ONNX_NO_CONVERSION,input_data,output_data))
         return(-1);
      //--- evaluate prediction
      return(int(output_data.ArgMax()));
     }
  };
//+------------------------------------------------------------------+

Il s'agit du modèle le plus simple des 3. C'est pourquoi le code de la méthode PredictClass est si compact.

Modifions à nouveau 2 chaînes dans l'EA :

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

input double InpLots = 1.0;    // Lots amount to open position

CModelEurusdD1_63Class ExtModel;
CTrade                 ExtTrade;

Et lançons le test avec les mêmes paramètres :

Résultats des tests du troisième modèle

Le modèle fonctionne.



7. Rassemblement de tous les modèles dans un seul EA. Vote à l’unanimité

Les 3 modèles ont montré leur capacité de travail. Essayons maintenant de combiner leurs efforts. Organisons un vote de modèles.

Déclarations et définitions

#include "ModelEurusdD1_10Class.mqh"
#include "ModelEurusdD1_30Class.mqh"
#include "ModelEurusdD1_63Class.mqh"
#include <Trade\Trade.mqh>

input double  InpLots  = 1.0;    // Lots amount to open position

CModelSymbolPeriod *ExtModels[3];
CTrade              ExtTrade;

Fonction OnInit

int OnInit()
  {
   ExtModels[0]=new CModelEurusdD1_10Class;
   ExtModels[1]=new CModelEurusdD1_30Class;
   ExtModels[2]=new CModelEurusdD1_63Class;

   for(long i=0; i<ExtModels.Size(); i++)
      if(!ExtModels[i].Init(_Symbol,_Period))
         return(INIT_FAILED);
//---
   return(INIT_SUCCEEDED);
  }

Fonction OnTick

void OnTick()
  {
   for(long i=0; i<ExtModels.Size(); i++)
      if(!ExtModels[i].CheckOnTick())
         return;

//--- predict next price movement
   int returned[3]={0,0,0};
//--- collect returned classes
   for(long i=0; i<ExtModels.Size(); i++)
     {
      int pred=ExtModels[i].PredictClass();
      if(pred>=0)
         returned[pred]++;
     }
//--- get one prediction for all models
   int predicted_class=-1;
//--- count votes for predictions
   for(int n=0; n<3; n++)
     {
      if(returned[n]>=2)
        {
         predicted_class=n;
         break;
        }
     }

//--- check trading according to prediction
   if(predicted_class>=0)
      if(PositionSelect(_Symbol))
         CheckForClose(predicted_class);
      else
         CheckForOpen(predicted_class);
  }

La majorité des voix est calculée selon l'équation <nombre total de voix>/2 + 1. Pour un total de 3 votes, la majorité est de 2 votes. C'est ce que l'on appelle un "vote à l’unanimité".

Le résultat du test est toujours obtenu avec les mêmes paramètres :

Résultats des tests du vote à l’unanimité

Rappelons le travail des 3 modèles séparément, à savoir le nombre de transactions rentables et non rentables. Premier modèle - 11 : 3, deuxième - 6 : 1, troisième - 16 : 10.

Il semble que nous ayons amélioré le résultat avec l'aide du vote à l’unanimité - 16 : 4. Mais, bien entendu, nous devons examiner des rapports complets et des tableaux de bord.


8. Vote en douceur

Le vote doux diffère du vote à l’unanimité en ce sens que ce n'est pas le nombre de votes qui est pris en compte, mais la somme des probabilités des trois classes issues des trois modèles. La classe est choisie selon la probabilité la plus élevée.

Pour garantir un vote en douceur, certains changements doivent être apportés.

Dans la classe de base :

   //+------------------------------------------------------------------+
   //| Predict class (regression -> classification)                     |
   //+------------------------------------------------------------------+
   virtual int PredictClass(vector& probabilities)
     {
...
      //--- set predicted probability as 1.0
      probabilities.Fill(0);
      if(predicted_class<(int)probabilities.Size())
         probabilities[predicted_class]=1;
      //--- and return predicted class
      return(predicted_class);
     }

Dans les classes filles :

   //+------------------------------------------------------------------+
   //| Predict class                                                    |
   //+------------------------------------------------------------------+
   virtual int PredictClass(vector& probabilities)
     {
...
      //--- evaluate prediction
      probabilities.Assign(output_data);
      return(int(output_data.ArgMax()));
     }

Dans l'EA :

#include "ModelEurusdD1_10Class.mqh"
#include "ModelEurusdD1_30Class.mqh"
#include "ModelEurusdD1_63Class.mqh"
#include <Trade\Trade.mqh>

enum EnVotes
  {
   Two=2,    // Two votes
   Three=3,  // Three votes
   Soft=4    // Soft voting
  };

input double  InpLots  = 1.0;    // Lots amount to open position
input EnVotes InpVotes = Two;    // Votes to make trade decision

CModelSymbolPeriod *ExtModels[3];
CTrade              ExtTrade;
void OnTick()
  {
   for(long i=0; i<ExtModels.Size(); i++)
      if(!ExtModels[i].CheckOnTick())
         return;

//--- predict next price movement
   int    returned[3]={0,0,0};
   vector soft=vector::Zeros(3);
//--- collect returned classes
   for(long i=0; i<ExtModels.Size(); i++)
     {
      vector prob(3);
      int    pred=ExtModels[i].PredictClass(prob);
      if(pred>=0)
        {
         returned[pred]++;
         soft+=prob;
        }
     }
//--- get one prediction for all models
   int predicted_class=-1;
//--- soft or hard voting
   if(InpVotes==Soft)
      predicted_class=(int)soft.ArgMax();
   else
     {
      //--- count votes for predictions
      for(int n=0; n<3; n++)
        {
         if(returned[n]>=InpVotes)
           {
            predicted_class=n;
            break;
           }
        }
     }

//--- check trading according to prediction
   if(predicted_class>=0)
      if(PositionSelect(_Symbol))
         CheckForClose(predicted_class);
      else
         CheckForOpen(predicted_class);
  }

Les paramètres de test sont les mêmes. Dans les entrées, sélectionnez Soft :

Paramètres d'entrée

Le résultat est le suivant :

Résultats des tests du vote doux

Transactions rentables - 15, transactions non rentables - 3. En termes monétaires, le vote dur (à l’unanimité) s'est également avéré meilleur que le vote doux.


Examinons le résultat d'un vote à l'unanimité, c'est-à-dire avec un nombre de voix de 3.

Résultats du test du vote à l'unanimité

Trading très prudent. La seule transaction non rentable a été clôturée à la fin du test (peut-être n'est-elle pas non rentable).

Graphique du test du vote à l'unanimité


Remarque importante : Veuillez noter que les modèles utilisés dans l'article sont présentés uniquement pour démontrer comment travailler avec les modèles ONNX en utilisant le langage MQL5. L'Expert Advisor n'est pas destiné à être utilisé sur des comptes réels.


Conclusion

Dans cet article, nous avons montré comment la programmation orientée objet facilite l'écriture des programmes. Toutes les complexités des modèles sont cachées dans leurs classes (les modèles peuvent être beaucoup plus complexes que ceux que nous avons présentés à titre d'exemple). Le reste de la "complexité" tient dans les 45 chaînes de la fonction OnTick.


Traduit du russe par MetaQuotes Ltd.
Article original : https://www.mql5.com/ru/articles/12484

Fichiers joints |
MQL5.zip (190.3 KB)
Apprenez à concevoir un système de trading basé sur l’Alligator Apprenez à concevoir un système de trading basé sur l’Alligator
Dans cet article, nous compléterons notre série sur la façon de concevoir un système de trading basé sur les indicateurs techniques les plus populaires. Nous apprendrons comment créer un système de trading basé sur l'indicateur Alligator.
Matrices et vecteurs en MQL5 : Fonctions d'activation Matrices et vecteurs en MQL5 : Fonctions d'activation
Nous ne décrirons ici qu'un seul des aspects de l'apprentissage automatique, à savoir les fonctions d'activation. Dans les réseaux neuronaux artificiels, la fonction d'activation d'un neurone calcule la valeur d'un signal de sortie en fonction des valeurs d'un signal d'entrée ou d'un ensemble de signaux d'entrée. Nous nous pencherons sur les rouages du processus.
Développer un Expert Advisor de trading à partir de zéro (Partie 24) : Assurer la robustesse du système (I) Développer un Expert Advisor de trading à partir de zéro (Partie 24) : Assurer la robustesse du système (I)
Dans cet article, nous allons rendre le système plus fiable afin d’en garantir une utilisation robuste et sûre. L'un des moyens d'obtenir la robustesse souhaitée est d'essayer de réutiliser le code autant que possible afin qu'il soit constamment testé dans différents cas. Mais ce n'est qu'un moyen parmi d'autres. Une autre solution consiste la POO.
Un exemple d'assemblage de modèles ONNX dans MQL5 Un exemple d'assemblage de modèles ONNX dans MQL5
ONNX (Open Neural Network eXchange) est un format ouvert conçu pour représenter les réseaux neuronaux. Dans cet article, nous allons montrer comment utiliser simultanément 2 modèles ONNX dans un Expert Advisor.