English Русский 中文 Español 日本語 Português 한국어 Français Italiano Türkçe
preview
Bewertung von ONNX-Modellen anhand von Regressionsmetriken

Bewertung von ONNX-Modellen anhand von Regressionsmetriken

MetaTrader 5Beispiele | 26 September 2023, 09:44
242 0
MetaQuotes
MetaQuotes

Einführung

Bei der Regression geht es um die Prognose eines realen Wertes anhand eines unbekannten Beispiels. Ein typisches Beispiel für eine Regression ist die Schätzung des Wertes eines Diamanten anhand von Merkmalen wie Größe, Gewicht, Farbe, Reinheit usw.

Die so genannten Regressionsmetriken werden verwendet, um die Genauigkeit der Vorhersagen des Regressionsmodells zu bewerten. Trotz ähnlicher Algorithmen unterscheiden sich die Regressionsmetriken semantisch von den ähnlichen Verlustfunktionen. Es ist wichtig, den Unterschied zwischen ihnen zu verstehen. Sie kann wie folgt formuliert werden:

  • Die Verlustfunktion entsteht in dem Moment, in dem wir das Problem der Erstellung eines Modells auf ein Optimierungsproblem reduzieren. In der Regel wird verlangt, dass sie gute Eigenschaften hat (z. B. Differenzierbarkeit).

  • Eine Metrik ist ein externes, objektives Qualitätskriterium, das in der Regel nicht von den Modellparametern, sondern nur von den prognostizierten Werten abhängt.


Regressionsmetriken in MQL5

Die Sprache MQL5 verfügt über die folgenden Metriken:

  • Mittlerer absoluter Fehler (Mean Absolute Error, MAE)
  • Mittlerer quadratischer Fehler (Mean Squared Error, MSE)
  • Wurzel des mittleren, quadratischen Fehlers (Root Mean Squared Error, RMSE)
  • R-Quadrat (R-squared, R2)
  • Mittlerer absoluter prozentualer Fehler (Mean Absolute Percentage Error, MAPE)
  • Mittlerer quadrierter prozentualer Fehler (Mean Squared Percentage Error, MSPE)
  • Wurzel des logarithmischen quadratischen Fehlers (Root Mean Squared Logarithmic Error, RMSLE)

Es wird erwartet, dass die Anzahl der Regressionsmetriken in MQL5 erhöht wird.


Kurzcharakteristik der Regressionsmetriken

MAE schätzt den absoluten Fehler — wie stark die prognostizierte Zahl von der tatsächlichen Zahl abweicht. Der Fehler wird in denselben Einheiten gemessen wie der Wert der Zielfunktion. Der Fehlerwert wird auf der Grundlage des möglichen Wertebereichs interpretiert. Wenn die Zielwerte beispielsweise im Bereich von 1 bis 1,5 liegen, dann ist der durchschnittliche absolute Fehler mit einem Wert von 10 ein sehr großer Fehler; aber für einen Bereich von 10000...15000 wäre er durchaus akzeptabel. Sie eignet sich nicht für die Auswertung von Prognosen mit einer großen Streuung der Werte.

Beim MSE hat jeder Fehler aufgrund der Quadrierung sein eigenes Gewicht. Große Diskrepanzen zwischen Prognose und Realität werden dadurch viel deutlicher.

Der RMSE hat dieselben Vorteile wie der MSE, ist aber leichter zu verstehen, da der Fehler in denselben Einheiten gemessen wird wie die Werte der Zielfunktion. Es ist sehr empfindlich gegenüber Anomalien und Spitzen. MAE und RMSE können zusammen verwendet werden, um die Fehlerabweichung in einer Reihe von Vorhersagen zu bestimmen. Der RMSE ist immer größer als oder gleich dem MAE. Je größer die Differenz zwischen ihnen ist, desto größer ist die Streuung der einzelnen Fehler in der Stichprobe. Wenn RMSE = MAE, haben alle Fehler die gleiche Größe.

R2 — Bestimmtheitsmaß zeigt die Stärke der Beziehung zwischen zwei Zufallsvariablen. Damit lässt sich der Anteil der Datenvielfalt bestimmen, den das Modell erklären konnte. Wenn das Modell immer genau vorhersagt, ist die Metrik 1. Für das triviale Modell ist er 0. Der metrische Wert kann negativ sein, wenn das Modell eine schlechtere Prognose als die triviale Prognose liefert und das Modell nicht dem Trend der Daten folgt.

MAPE — der Fehler hat keine Dimension und ist sehr einfach zu interpretieren. Er kann sowohl als Dezimalzahl als auch als Prozentsatz ausgedrückt werden. In MQL5 wird er in Dezimalzahlen ausgedrückt. Ein Wert von 0,1 bedeutet zum Beispiel, dass der Fehler 10 % des tatsächlichen Wertes beträgt. Die Idee hinter dieser Metrik ist die Empfindlichkeit gegenüber relativen Abweichungen. Es ist nicht für Aufgaben geeignet, bei denen Sie mit echten Maßeinheiten arbeiten müssen.

MSPE kann als eine gewichtete Version von MSE betrachtet werden, wobei das Gewicht umgekehrt proportional zum Quadrat des beobachteten Wertes ist. Wenn also die beobachteten Werte steigen, nimmt der Fehler tendenziell ab.

RMSLE wird verwendet, wenn sich die tatsächlichen Werte über mehrere Größenordnungen erstrecken. Per Definition können prognostizierte und tatsächlich beobachtete Werte nicht negativ sein.

Die Algorithmen zur Berechnung aller oben genannten Metriken sind in der Quelldatei VectorRegressionMetric.mqh enthalten


ONNX-Modelle

Wir haben 4 Regressionsmodelle verwendet, die den Schlusskurs des Tages (EURUSD, D1) aus den vorherigen Tagesbalken vorhersagen. Wir haben diese Modelle in den vorangegangenen Artikeln berücksichtigt: „ONNX-Modelle in Klassen packen“, „Ein Beispiel für die Zusammenstellung von ONNX-Modellen in MQL5“ und „Wie man ONNX-Modelle in MQL5 verwendet“. Daher werden wir hier nicht die Regeln wiederholen, die zum Trainieren der Modelle verwendet wurden. Die Skripte für das Training aller Modelle befinden sich im Unterordner Python des Zip-Archivs, das diesem Artikel beigefügt ist. Die trainierten onnx-Modelle — model.eurusd.D1.10, model.eurusd.D1.30, model.eurusd.D1.52 und model.eurusd.D1.63 — befinden sich ebenfalls dort.


ONNX-Modelle in Klassen packen

Im vorherigen Artikel haben wir die Basisklasse für ONNX-Modelle und abgeleitete Klassen für Klassifizierungsmodelle vorgestellt. Wir haben einige kleinere Änderungen an der Basisklasse vorgenommen, um sie flexibler zu machen.

//+------------------------------------------------------------------+
//|                                            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:
   string            m_name;             // model name
   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_name="";
      m_handle=INVALID_HANDLE;
      m_symbol=symbol;
      m_period=period;
      m_next_bar=0;
      m_class_delta=class_delta;
     }

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

   //+------------------------------------------------------------------+
   //|                                                                  |
   //+------------------------------------------------------------------+
   string GetModelName(void)
     {
      return(m_name);
     }

   //+------------------------------------------------------------------+
   //| 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(datetime date)
     {
      return(DBL_MAX);
     }

   //+------------------------------------------------------------------+
   //| Predict class (regression ~> classification)                     |
   //+------------------------------------------------------------------+
   virtual int PredictClass(datetime date,vector& probabilities)
     {
      date-=date%PeriodSeconds(m_period);
      double predicted_price=PredictPrice(date);
      if(predicted_price==DBL_MAX)
         return(-1);

      double last_close[2];
      if(CopyClose(m_symbol,m_period,date,2,last_close)!=2)
         return(-1);
      double prev_price=last_close[0];

      //--- classify predicted price movement
      int    predicted_class=-1;
      double delta=prev_price-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;
        }

      //--- 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);
     }
  };
//+------------------------------------------------------------------+

Wir haben den Methoden PredictPrice und PredictClass einen Datetime-Parameter hinzugefügt, damit wir Vorhersagen für jeden beliebigen Zeitpunkt treffen können, nicht nur für den aktuellen. Dies wird für die Bildung eines Vorhersagevektors nützlich sein.


D1_10 Modellklasse

Unser erstes Modell heißt model.eurusd.D1.10.onnx. Regressionsmodell trainiert mit EURUSD D1 auf der Serie von 10 OHLC-Preisen.
//+------------------------------------------------------------------+
//|                                             ModelEurusdD1_10.mqh |
//|                                  Copyright 2023, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#include "ModelSymbolPeriod.mqh"

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

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

public:
   //+------------------------------------------------------------------+
   //| Constructor                                                      |
   //+------------------------------------------------------------------+
   CModelEurusdD1_10(void) : CModelSymbolPeriod("EURUSD",PERIOD_D1)
     {
      m_name="D1_10";
      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))
        {
         Print("model_eurusd_D1_10 : 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 : 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 predicted prices
      const long output_shape[] = {1,1};
      if(!OnnxSetOutputShape(m_handle,0,output_shape))
        {
         Print("model_eurusd_D1_10 : OnnxSetOutputShape error ",GetLastError());
         return(false);
        }
      //--- ok
      return(true);
     }

   //+------------------------------------------------------------------+
   //| Predict price                                                    |
   //+------------------------------------------------------------------+
   virtual double PredictPrice(datetime date)
     {
      static matrixf input_data(m_sample_size,4);    // matrix for prepared input data
      static vectorf output_data(1);                 // 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
      date-=date%PeriodSeconds(m_period);
      if(!rates.CopyRates(m_symbol,m_period,COPY_RATES_OHLC,date-1,m_sample_size))
         return(DBL_MAX);
      //--- 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(DBL_MAX);

      //--- denormalize the price from the output value
      double predicted=output_data[0]*s[3]+m[3];
      //--- return prediction
      return(predicted);
     }
  };
//+------------------------------------------------------------------+

Dieses Modell ähnelt unserem allerersten Modell, das in dem öffentlichen Projekt MQL5\Shared Projects\ONNX.Price.Prediction.

Die Serie von 10 OHLC-Kursen sollte auf die gleiche Weise wie beim Training normalisiert werden, d. h. die Abweichung vom Durchschnittspreis in der Serie wird durch die Standardabweichung in der Serie geteilt. So legen wir die Reihen in einen bestimmten Bereich mit einem Mittelwert von 0 und einer Streuung von 1, was die Konvergenz beim Training verbessert.


Die Modellklasse D1_30

Das zweite Modell heißt model.eurusd.D1.30.onnx. Das Regressionsmodell, das auf EURUSD D1 anhand der Serie von 30 Schlusskursen und zwei einfachen gleitenden Durchschnitten mit Mittelungszeiträumen von 21 und 34 trainiert wurde.

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

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

//+------------------------------------------------------------------+
//| ONNX-model wrapper class                                         |
//+------------------------------------------------------------------+
class CModelEurusdD1_30 : 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_30(void) : CModelSymbolPeriod("EURUSD",PERIOD_D1)
     {
      m_name="D1_30";
      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))
        {
         Print("model_eurusd_D1_30 : 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 : 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 predicted prices
      const long output_shape[] = {1,1};
      if(!OnnxSetOutputShape(m_handle,0,output_shape))
        {
         Print("model_eurusd_D1_30 : 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 : cannot create indicator");
         return(false);
        }
      //--- ok
      return(true);
     }

   //+------------------------------------------------------------------+
   //| Predict price                                                    |
   //+------------------------------------------------------------------+
   virtual double PredictPrice(datetime date)
     {
      static matrixf input_data(m_sample_size,3);    // matrix for prepared input data
      static vectorf output_data(1);                 // 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
      date-=date%PeriodSeconds(m_period);
      if(!vtemp.CopyRates(m_symbol,m_period,COPY_RATES_CLOSE,date-1,m_sample_size))
         return(DBL_MAX);
      //--- 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,date-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,date-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(DBL_MAX);

      //--- denormalize the price from the output value
      double predicted=output_data[0]*s+m;
      //--- return prediction
      return(predicted);
     }
  };
//+------------------------------------------------------------------+

Wie in der vorherigen Klasse wird die Methode der Basisklasse CheckInit in der Init-Methode aufgerufen. In der Basisklassenmethode wird eine Sitzung für das ONNX-Modell erstellt und die Größen der Eingabe- und Ausgabetensoren werden explizit festgelegt.

Die Methode PredictPrice liefert eine Reihe von 30 vorherigen Schlusskursen und berechneten gleitenden Durchschnitten. Die Daten werden auf die gleiche Weise normalisiert wie beim Training.

Das Modell wurde für den Artikel „ONNX-Modelle in Klassen packen“ entwickelt und für diesen Artikel von Klassifikation auf Regression umgestellt.


Die Modellklasse D1_52

Das dritte Modell heißt model.eurusd.D1.52.onnx. Das Regressionsmodell, das auf EURUSD D1 anhand der Serie von 52 Schlusskursen trainiert wurde.

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

#resource "Python/model.eurusd.D1.52.onnx" as uchar model_eurusd_D1_52[]

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

public:
   //+------------------------------------------------------------------+
   //| Constructor                                                      |
   //+------------------------------------------------------------------+
   CModelEurusdD1_52(void) : CModelSymbolPeriod("EURUSD",PERIOD_D1,0.0001)
     {
      m_name="D1_52";
      m_sample_size=52;
     }

   //+------------------------------------------------------------------+
   //| 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_52))
        {
         Print("model_eurusd_D1_52 : 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 (only Close)
      const long input_shape[] = {1,m_sample_size,1};
      if(!OnnxSetInputShape(m_handle,0,input_shape))
        {
         Print("model_eurusd_D1_52 : 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 predicted prices (we only predict Close)
      const long output_shape[] = {1,1};
      if(!OnnxSetOutputShape(m_handle,0,output_shape))
        {
         Print("model_eurusd_D1_52 : OnnxSetOutputShape error ",GetLastError());
         return(false);
        }
      //--- ok
      return(true);
     }

   //+------------------------------------------------------------------+
   //| Predict price                                                    |
   //+------------------------------------------------------------------+
   virtual double PredictPrice(datetime date)
     {
      static vectorf output_data(1);            // vector to get result
      static vector  x_norm(m_sample_size);     // vector for prices normalize
   
      //--- set date to day begin
      date-=date%PeriodSeconds(m_period);
      //--- check for calculate min and max
      double price_min=0;
      double price_max=0;
      GetMinMaxClose(date,price_min,price_max);
      //--- check for normalization possibility
      if(price_min>=price_max)
         return(DBL_MAX);
      //--- request last bars
      if(!x_norm.CopyRates(m_symbol,m_period,COPY_RATES_CLOSE,date-1,m_sample_size))
         return(DBL_MAX);
      //--- normalize prices
      x_norm-=price_min;
      x_norm/=(price_max-price_min);
      //--- run the inference
      if(!OnnxRun(m_handle,ONNX_DEFAULT,x_norm,output_data))
         return(DBL_MAX);

      //--- denormalize the price from the output value
      double predicted=output_data[0]*(price_max-price_min)+price_min;
      //--- return prediction
      return(predicted);
     }

private:
   //+------------------------------------------------------------------+
   //| Get minimal and maximal Close for last 52 weeks                  |
   //+------------------------------------------------------------------+
   void GetMinMaxClose(const datetime date,double& price_min,double& price_max)
     {
      static vector close;

      close.CopyRates(m_symbol,m_period,COPY_RATES_CLOSE,date,m_sample_size*7+1);
      price_min=close.Min();
      price_max=close.Max();
     }
  };
//+------------------------------------------------------------------+

Die Preisnormalisierung vor der Einreichung des Modells unterscheidet sich von den vorherigen. Beim Training wurde der MinMaxScaler verwendet. Daher nehmen wir die Mindest- und Höchstpreise für den Zeitraum von 52 Wochen vor dem Prognosetermin.

Das Modell ähnelt dem in dem Artikel „Wie man ONNX-Modelle in MQL5 verwendet“ beschriebenen Modell.


Die Modellklasse D1_63

Das vierte Modell schließlich heißt model.eurusd.D1.63.onnx. Das Regressionsmodell, das auf EURUSD D1 anhand der Serie von 63 Schlusskursen trainiert wurde.

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

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

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

public:
   //+------------------------------------------------------------------+
   //| Constructor                                                      |
   //+------------------------------------------------------------------+
   CModelEurusdD1_63(void) : CModelSymbolPeriod("EURUSD",PERIOD_D1)
     {
      m_name="D1_63";
      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))
        {
         Print("model_eurusd_D1_63 : 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 : 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 predicted prices
      const long output_shape[] = {1,1};
      if(!OnnxSetOutputShape(m_handle,0,output_shape))
        {
         Print("model_eurusd_D1_63 : OnnxSetOutputShape error ",GetLastError());
         return(false);
        }
      //--- ok
      return(true);
     }

   //+------------------------------------------------------------------+
   //| Predict price                                                    |
   //+------------------------------------------------------------------+
   virtual double PredictPrice(datetime date)
     {
      static vectorf input_data(m_sample_size);  // vector for prepared input data
      static vectorf output_data(1);             // vector to get result
   
      //--- request last bars
      date-=date%PeriodSeconds(m_period);
      if(!input_data.CopyRates(m_symbol,m_period,COPY_RATES_CLOSE,date-1,m_sample_size))
         return(DBL_MAX);
      //--- 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(DBL_MAX);

      //--- denormalize the price from the output value
      double predicted=output_data[0]*s+m;
      //--- return prediction
      return(predicted);
     }
  };
//+------------------------------------------------------------------+

Die Methode PredictPrice liefert die Serie von 63 vorherigen Schlusskursen. Die Daten werden auf die gleiche Weise normalisiert wie beim ersten und zweiten Modell.

Das Modell wurde bereits für den Artikel „Ein Beispiel für die Zusammenstellung von ONNX-Modellen in MQL5“ entwickelt.


Alle Modelle in einem Skript zusammenfassen. Realität, Vorhersagen und Regressionsmetriken

Um Regressionsmetriken anzuwenden, sollten wir eine bestimmte Anzahl von Vorhersagen (vector_pred) machen und tatsächliche Daten für die gleichen Daten (vector_true) nehmen.

Da alle unsere Modelle in Klassen verpackt sind, die von derselben Basisklasse abgeleitet sind, können wir sie alle auf einmal auswerten.

Das Skript ist sehr einfach

//+------------------------------------------------------------------+
//|                                    ONNX.eurusd.D1.4M.Metrics.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"

#define MODELS 4
#include "ModelEurusdD1_10.mqh"
#include "ModelEurusdD1_30.mqh"
#include "ModelEurusdD1_52.mqh"
#include "ModelEurusdD1_63.mqh"

#property script_show_inputs
input datetime InpStartDate = D'2023.01.01';
input datetime InpStopDate  = D'2023.01.31';

CModelSymbolPeriod *ExtModels[MODELS];

struct PredictedPrices
  {
   string            model;
   double            pred[];
  };
PredictedPrices ExtPredicted[MODELS];

double ExtClose[];

struct Metrics
  {
   string            model;
   double            mae;
   double            mse;
   double            rmse;
   double            r2;
   double            mape;
   double            mspe;
   double            rmsle;
  };
Metrics ExtMetrics[MODELS];

//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- init section
   if(!Init())
      return;

//--- predictions test loop
   datetime dates[];
   if(CopyTime(_Symbol,_Period,InpStartDate,InpStopDate,dates)<=0)
     {
      Print("Cannot get data from ",InpStartDate," to ",InpStopDate);
      return;
     }
   for(uint n=0; n<dates.Size(); n++)
      GetPredictions(dates[n]);
      
   CopyClose(_Symbol,_Period,InpStartDate,InpStopDate,ExtClose);
   CalculateMetrics();

//--- deinit section
   Deinit();
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool Init()
  {
   ExtModels[0]=new CModelEurusdD1_10;
   ExtModels[1]=new CModelEurusdD1_30;
   ExtModels[2]=new CModelEurusdD1_52;
   ExtModels[3]=new CModelEurusdD1_63;

   for(long i=0; i<ExtModels.Size(); i++)
     {
      if(!ExtModels[i].Init(_Symbol,_Period))
        {
         Deinit();
         return(false);
        }
     }

   for(long i=0; i<ExtModels.Size(); i++)
      ExtPredicted[i].model=ExtModels[i].GetModelName();

   return(true);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void Deinit()
  {
   for(uint i=0; i<ExtModels.Size(); i++)
      delete ExtModels[i];
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void GetPredictions(datetime date)
  {
//--- collect predicted prices
   for(uint i=0; i<ExtModels.Size(); i++)
      ExtPredicted[i].pred.Push(ExtModels[i].PredictPrice(date));
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CalculateMetrics()
  {
   vector vector_pred,vector_true;
   vector_true.Assign(ExtClose);

   for(uint i=0; i<ExtModels.Size(); i++)
     {
      ExtMetrics[i].model=ExtPredicted[i].model;
      vector_pred.Assign(ExtPredicted[i].pred);
      ExtMetrics[i].mae  =vector_pred.RegressionMetric(vector_true,REGRESSION_MAE);
      ExtMetrics[i].mse  =vector_pred.RegressionMetric(vector_true,REGRESSION_MSE);
      ExtMetrics[i].rmse =vector_pred.RegressionMetric(vector_true,REGRESSION_RMSE);
      ExtMetrics[i].r2   =vector_pred.RegressionMetric(vector_true,REGRESSION_R2);
      ExtMetrics[i].mape =vector_pred.RegressionMetric(vector_true,REGRESSION_MAPE);
      ExtMetrics[i].mspe =vector_pred.RegressionMetric(vector_true,REGRESSION_MSPE);
      ExtMetrics[i].rmsle=vector_pred.RegressionMetric(vector_true,REGRESSION_RMSLE);
     }

   ArrayPrint(ExtMetrics);
  }
//+------------------------------------------------------------------+

Führen wir das Skript im EURUSD-D1-Chart aus und legen wir die Daten vom 1. Januar bis einschließlich 31. Januar 2023 fest. Was sehen wir?

    [model]   [mae]   [mse]  [rmse]     [r2]  [mape]  [mspe] [rmsle]
[0] "D1_10" 0.00381 0.00003 0.00530  0.77720 0.00356 0.00002 0.00257
[1] "D1_30" 0.01809 0.00039 0.01963 -2.05545 0.01680 0.00033 0.00952
[2] "D1_52" 0.00472 0.00004 0.00642  0.67327 0.00440 0.00004 0.00311
[3] "D1_63" 0.00413 0.00003 0.00559  0.75230 0.00385 0.00003 0.00270

Sofort fällt der negative R-Quadrat-Wert in der zweiten Zeile auf. Das bedeutet, dass das Modell nicht funktioniert. Es ist interessant, sich die Vorhersagediagramme anzusehen.

Vier Modellprognosen

Wir sehen das Diagramm D1_30 weit entfernt von den aktuellen Schlusskursen und anderen Prognosen. Keine der Kennzahlen dieses Modells ist ermutigend. MAE zeigt die Prognosegenauigkeit von 1809 Preispunkten! Beachten Sie jedoch, dass das Modell ursprünglich für den vorhergehenden Artikel als Klassifizierungsmodell und nicht als Regressionsmodell entwickelt wurde. Das Beispiel ist ziemlich eindeutig.

Gehen wir einzeln auf die anderen Modelle ein.

Der erste Kandidat für die Analyse ist D1_10

    [model]   [mae]   [mse]  [rmse]     [r2]  [mape]  [mspe] [rmsle]
[0] "D1_10" 0.00381 0.00003 0.00530  0.77720 0.00356 0.00002 0.00257

Werfen wir einen Blick auf das Diagramm der von diesem Modell prognostizierten Preise.

D1_10 Vorhersage

Die RMSLE-Metrik ist nicht sehr sinnvoll, da die Spanne von 1,05 bis 1,09 viel geringer als eine Größenordnung ist. Die MAPE- und MSPE-Metriken liegen in ihren Werten nahe bei MAE und MSE, was auf die Besonderheiten des EURUSD-Wechselkurses zurückzuführen ist, da dieser nahe bei eins liegt. Bei der Berechnung der prozentualen Abweichungen gibt es jedoch eine Nuance, die bei der Berechnung der absoluten Abweichungen nicht vorhanden ist.

MAPE = |(y_true-y_pred)/y_true|

if y_true = 10 and y_pred = 5
MAPE = 0.5

if y_true = 5 and y_pred = 10
MAPE = 1.0

Mit anderen Worten: Diese Metrik ist (wie MSPE) asymmetrisch. Das bedeutet, dass in dem Fall, in dem die Prognose größer ist als der tatsächliche Wert, ein größerer Fehler auftritt.

Ein gutes Ergebnis der R-Quadrat-Metrik wurde für das einfache Modell erzielt, das aus rein methodischen Gründen zusammengeschustert wurde, nämlich um zu zeigen, wie man mit ONNX-Modellen in MQL5 arbeiten kann.


Zweiter Kandidat — D1_63

    [model]   [mae]   [mse]  [rmse]     [r2]  [mape]  [mspe] [rmsle]
[3] "D1_63" 0.00413 0.00003 0.00559  0.75230 0.00385 0.00003 0.00270

D1_63 Vorhersage

Die Prognose ähnelt visuell sehr der vorherigen. Metrische Werte bestätigen Ähnlichkeit

[0] "D1_10" 0.00381 0.00003 0.00530  0.77720 0.00356 0.00002 0.00257
[3] "D1_63" 0.00413 0.00003 0.00559  0.75230 0.00385 0.00003 0.00270

Als Nächstes werden wir sehen, welches dieser Modelle im gleichen Zeitraum im Tester besser abschneidet.


Jetzt für D1_52

    [model]   [mae]   [mse]  [rmse]     [r2]  [mape]  [mspe] [rmsle]
[2] "D1_52" 0.00472 0.00004 0.00642  0.67327 0.00440 0.00004 0.00311

Wir berücksichtigen ihn nur, weil sein R-Quadrat größer als 0,5 ist.

D1_52 Vorhersage

Fast alle prognostizierten Preise liegen unterhalb des Charts der Schlusskurse, wie in unserem Worst-Case-Fall. Trotz vergleichbarer metrischer Werte wie bei den beiden vorangegangenen Modellen gibt dieses Modell keinen Anlass zu Optimismus. Wir werden dies im nächsten Abschnitt überprüfen.


Ausführen von ONNX-Modellen im Testgerät

Nachfolgend finden Sie ein sehr einfachen EA zur Überprüfung unserer Modelle im Tester

//+------------------------------------------------------------------+
//|                                    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_10.mqh"
#include "ModelEurusdD1_30.mqh"
#include "ModelEurusdD1_52.mqh"
#include "ModelEurusdD1_63.mqh"
#include <Trade\Trade.mqh>

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

//CModelEurusdD1_10 ExtModel;
//CModelEurusdD1_30 ExtModel;
CModelEurusdD1_52 ExtModel;
//CModelEurusdD1_63 ExtModel;
CTrade            ExtTrade;

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
   if(!ExtModel.Init(_Symbol,_Period))
      return(INIT_FAILED);
   Print("model ",ExtModel.GetModelName());
//---
   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
   vector prob(3);
   int    predicted_class=ExtModel.PredictClass(TimeCurrent(),prob);
   Print("predicted class ",predicted_class);
//--- 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);
     }
  }
//+------------------------------------------------------------------+

Nach dem Modell D1_52 wurde nur ein einziger Verkauf eröffnet, und der Trend hat sich nach diesem Modell während des gesamten Testzeitraums nicht geändert.

Prüfung D1_52


2023.06.09 16:18:31.967 Symbols EURUSD: symbol to be synchronized
2023.06.09 16:18:31.968 Symbols EURUSD: symbol synchronized, 3720 bytes of symbol info received
2023.06.09 16:18:32.023 History EURUSD: load 27 bytes of history data to synchronize in 0:00:00.001
2023.06.09 16:18:32.023 History EURUSD: history synchronized from 2011.01.03 to 2023.04.07
2023.06.09 16:18:32.124 History EURUSD,Daily: history cache allocated for 283 bars and contains 260 bars from 2022.01.03 00:00 to 2022.12.30 00:00
2023.06.09 16:18:32.124 History EURUSD,Daily: history begins from 2022.01.03 00:00
2023.06.09 16:18:32.126 Tester  EURUSD,Daily (MetaQuotes-Demo): 1 minutes OHLC ticks generating
2023.06.09 16:18:32.126 Tester  EURUSD,Daily: testing of Experts\article_2\ONNX.eurusd.D1.Prediction.ex5 from 2023.01.01 00:00 to 2023.02.01 00:00 started with inputs:
2023.06.09 16:18:32.126 Tester    InpLots=1.0
2023.06.09 16:18:32.161 ONNX    api version 1.16.0 initialized
2023.06.09 16:18:32.180 ONNX.eurusd.D1.Prediction (EURUSD,D1)   2023.01.01 00:00:00   model D1_52
2023.06.09 16:18:32.194 ONNX.eurusd.D1.Prediction (EURUSD,D1)   2023.01.02 07:02:00   predicted class 2
2023.06.09 16:18:32.194 Trade   2023.01.02 07:02:00   instant sell 1 EURUSD at 1.07016 (1.07016 / 1.07023 / 1.07016)
2023.06.09 16:18:32.194 Trades  2023.01.02 07:02:00   deal #2 sell 1 EURUSD at 1.07016 done (based on order #2)
2023.06.09 16:18:32.194 Trade   2023.01.02 07:02:00   deal performed [#2 sell 1 EURUSD at 1.07016]
2023.06.09 16:18:32.194 Trade   2023.01.02 07:02:00   order performed sell 1 at 1.07016 [#2 sell 1 EURUSD at 1.07016]
2023.06.09 16:18:32.195 ONNX.eurusd.D1.Prediction (EURUSD,D1)   2023.01.02 07:02:00   CTrade::OrderSend: instant sell 1.00 EURUSD at 1.07016 [done at 1.07016]
2023.06.09 16:18:32.196 ONNX.eurusd.D1.Prediction (EURUSD,D1)   2023.01.03 00:00:00   predicted class 2
2023.06.09 16:18:32.199 ONNX.eurusd.D1.Prediction (EURUSD,D1)   2023.01.04 00:00:00   predicted class 2
2023.06.09 16:18:32.201 ONNX.eurusd.D1.Prediction (EURUSD,D1)   2023.01.05 00:00:30   predicted class 2
2023.06.09 16:18:32.203 ONNX.eurusd.D1.Prediction (EURUSD,D1)   2023.01.06 00:00:00   predicted class 2
2023.06.09 16:18:32.206 ONNX.eurusd.D1.Prediction (EURUSD,D1)   2023.01.09 00:02:00   predicted class 2
2023.06.09 16:18:32.208 ONNX.eurusd.D1.Prediction (EURUSD,D1)   2023.01.10 00:00:00   predicted class 2
2023.06.09 16:18:32.210 ONNX.eurusd.D1.Prediction (EURUSD,D1)   2023.01.11 00:00:00   predicted class 2
2023.06.09 16:18:32.213 ONNX.eurusd.D1.Prediction (EURUSD,D1)   2023.01.12 00:00:00   predicted class 2
2023.06.09 16:18:32.215 ONNX.eurusd.D1.Prediction (EURUSD,D1)   2023.01.13 00:00:00   predicted class 2
2023.06.09 16:18:32.217 ONNX.eurusd.D1.Prediction (EURUSD,D1)   2023.01.16 00:03:00   predicted class 2
2023.06.09 16:18:32.220 ONNX.eurusd.D1.Prediction (EURUSD,D1)   2023.01.17 00:00:00   predicted class 2
2023.06.09 16:18:32.222 ONNX.eurusd.D1.Prediction (EURUSD,D1)   2023.01.18 00:00:30   predicted class 2
2023.06.09 16:18:32.224 ONNX.eurusd.D1.Prediction (EURUSD,D1)   2023.01.19 00:00:00   predicted class 2
2023.06.09 16:18:32.227 ONNX.eurusd.D1.Prediction (EURUSD,D1)   2023.01.20 00:00:30   predicted class 2
2023.06.09 16:18:32.229 ONNX.eurusd.D1.Prediction (EURUSD,D1)   2023.01.23 00:02:00   predicted class 2
2023.06.09 16:18:32.231 ONNX.eurusd.D1.Prediction (EURUSD,D1)   2023.01.24 00:00:00   predicted class 2
2023.06.09 16:18:32.234 ONNX.eurusd.D1.Prediction (EURUSD,D1)   2023.01.25 00:00:00   predicted class 2
2023.06.09 16:18:32.236 ONNX.eurusd.D1.Prediction (EURUSD,D1)   2023.01.26 00:00:00   predicted class 2
2023.06.09 16:18:32.238 ONNX.eurusd.D1.Prediction (EURUSD,D1)   2023.01.27 00:00:00   predicted class 2
2023.06.09 16:18:32.241 ONNX.eurusd.D1.Prediction (EURUSD,D1)   2023.01.30 00:03:00   predicted class 2
2023.06.09 16:18:32.243 ONNX.eurusd.D1.Prediction (EURUSD,D1)   2023.01.31 00:00:00   predicted class 2
2023.06.09 16:18:32.245 Trade   2023.01.31 23:59:59   position closed due end of test at 1.08621 [#2 sell 1 EURUSD 1.07016]
2023.06.09 16:18:32.245 Trades  2023.01.31 23:59:59   deal #3 buy 1 EURUSD at 1.08621 done (based on order #3)
2023.06.09 16:18:32.245 Trade   2023.01.31 23:59:59   deal performed [#3 buy 1 EURUSD at 1.08621]
2023.06.09 16:18:32.245 Trade   2023.01.31 23:59:59   order performed buy 1 at 1.08621 [#3 buy 1 EURUSD at 1.08621]
2023.06.09 16:18:32.245 Tester  final balance 8366.00 USD
2023.06.09 16:18:32.249 Tester  EURUSD,Daily: 123499 ticks, 22 bars generated. Environment synchronized in 0:00:00.043. Test passed in 0:00:00.294 (including ticks preprocessing 0:00:00.016).

Wie bereits im vorangegangenen Abschnitt erwähnt, gibt das Modell D1_52 keinen Anlass zu Optimismus. Dies wird durch die Testergebnisse bestätigt.

Ändern wir nur zwei Codezeilen

#include "ModelEurusdD1_10.mqh"
#include "ModelEurusdD1_30.mqh"
#include "ModelEurusdD1_52.mqh"
#include "ModelEurusdD1_63.mqh"
#include <Trade\Trade.mqh>

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

CModelEurusdD1_10 ExtModel;
//CModelEurusdD1_30 ExtModel;
//CModelEurusdD1_52 ExtModel;
//CModelEurusdD1_63 ExtModel;
CTrade            ExtTrade;

und starten das Modell D1_10 für einen Test.

D1_10 Testergebnisse

Die Ergebnisse sind gut. Auch das Testdiagramm ist vielversprechend.

D1_10 Testdiagramm


Korrigieren wir noch einmal zwei Codezeilen und testen das Modell D1_63.

D1_63 Testergebnisse

Die Grafik.

D1_63 Testdiagramm

Das Testdiagramm ist viel schlechter als das des Modells D1_10.

Vergleicht man die beiden Modelle D1_10 und D1_63, so zeigt sich, dass das erste Modell bessere Regressionskennzahlen aufweist als das zweite. Der Tester zeigt das Gleiche an.

Wichtiger Hinweis: Bitte beachten Sie, dass die in diesem Artikel verwendeten Modelle nur zur Demonstration der Arbeit mit ONNX-Modellen in der Sprache MQL5 dienen. Der Expert Advisor ist nicht für einen Handel auf realen Konten gedacht.


Schlussfolgerung

Die am besten geeignete Metrik zur Bewertung von Preisvorhersagemodellen ist R-Quadrat. Die Betrachtung des Aggregats aus MAE — RMSE — MAPE kann sehr hilfreich sein. Die Metrik von RMSLE sollte bei Preisprognosen nicht berücksichtigt werden. Es ist sehr nützlich, mehrere Modelle zur Bewertung zu haben, auch wenn es sich um das gleiche Modell mit Änderungen handelt.

Wir verstehen, dass eine Stichprobe von 22 Werten für eine seriöse Untersuchung nicht ausreicht, aber es war nicht unsere Absicht, eine statistische Studie durchzuführen. Wir haben stattdessen nur die Verwendung veranschaulicht.


Übersetzt aus dem Russischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/ru/articles/12772

Beigefügte Dateien |
MQL5.zip (1005.2 KB)
Wie man einen einfachen Multi-Currency Expert Advisor mit MQL5 erstellt (Teil 1): Indikatorsignale basierend auf ADX in Kombination mit Parabolic SAR Wie man einen einfachen Multi-Currency Expert Advisor mit MQL5 erstellt (Teil 1): Indikatorsignale basierend auf ADX in Kombination mit Parabolic SAR
Der Multi-Currency Expert Advisor in diesem Artikel ist ein Expert Advisor oder Handelsroboter, der mit mehr als einem Symbolpaar aus einem Symbolchart handeln kann (Positionen öffnen, schließen und verwalten).
Entwicklung eines Replay-Systems — Marktsimulation (Teil 06): Erste Verbesserungen (I) Entwicklung eines Replay-Systems — Marktsimulation (Teil 06): Erste Verbesserungen (I)
In diesem Artikel werden wir mit der Stabilisierung des gesamten Systems beginnen, ohne die wir möglicherweise nicht in der Lage sind, mit den nächsten Schritten fortzufahren.
Kategorientheorie in MQL5 (Teil 15) : Funktoren mit Graphen Kategorientheorie in MQL5 (Teil 15) : Funktoren mit Graphen
Dieser Artikel über die Implementierung der Kategorientheorie in MQL5 setzt die Serie mit der Betrachtung der Funktoren fort, diesmal jedoch als Brücke zwischen Graphen und einer Menge. Wir greifen die Kalenderdaten wieder auf und plädieren trotz der Einschränkungen bei der Verwendung von Strategy Tester für die Verwendung von Funktoren zur Vorhersage der Volatilität mit Hilfe der Korrelation.
MQL5 Strategietester verstehen und effektiv nutzen MQL5 Strategietester verstehen und effektiv nutzen
Für MQL5-Programmierer oder -Entwickler ist es unerlässlich, wichtige und wertvolle Werkzeuge zu beherrschen. Eines dieser Werkzeuge ist der Strategietester. Dieser Artikel ist ein praktischer Leitfaden zum Verständnis und zur Verwendung des Strategietesters von MQL5.