English Русский Deutsch 日本語
preview
El método de manejo de datos en grupo: implementación del algoritmo combinatorio en MQL5

El método de manejo de datos en grupo: implementación del algoritmo combinatorio en MQL5

MetaTrader 5Ejemplos | 11 septiembre 2024, 09:23
69 0
Francis Dube
Francis Dube

Introducción

El algoritmo combinatorio de GMDH, a menudo denominado COMBI, es la forma básica de GMDH y sirve de base para algoritmos más complejos dentro de la familia. Al igual que el algoritmo iterativo multicapa (MIA, Multilayered Iterative Algorithm), opera sobre una muestra de datos de entrada representada como una matriz que contiene observaciones sobre un conjunto de variables. La muestra de datos se divide en dos partes: una muestra de entrenamiento y una muestra de prueba. La submuestra de entrenamiento se utiliza para estimar los coeficientes del polinomio, mientras que la submuestra de prueba se utiliza para seleccionar la estructura del modelo óptimo en función del valor mínimo del criterio seleccionado. En este artículo describiremos el cálculo del algoritmo COMBI. Así como presentar su implementación en MQL5 extendiendo la clase «GmdhModel» descrita en el artículo anterior. Más adelante también discutiremos el algoritmo combinatorio selectivo, estrechamente relacionado, y su implementación en MQL5. Y, por último, concluimos proporcionando una aplicación práctica de los algoritmos GMDH, mediante la construcción de modelos predictivos del precio diario del Bitcoin.


El algoritmo COMBI

La diferencia fundamental entre MIA y COMBI radica en su estructura de red. En contraste con la naturaleza multicapa de MIA, el COMBI se caracteriza por una sola capa.

Estructura de red del método combinatorio

El número de nodos de esta capa viene determinado por el número de entradas. Donde cada nodo representa un modelo candidato definido por una o más de todas las entradas. Para ilustrarlo, consideremos un ejemplo de un sistema que queremos modelar, definido por 2 variables de entrada. Aplicando el algoritmo COMBI, se utilizan todas las combinaciones posibles de estas variables para construir un modelo candidato. El número de combinaciones posibles viene dado por:

Fórmula del número máximo de combinaciones



Donde:

  • 'm' representa el número de variables de entrada.


Para cada una de estas combinaciones se genera un modelo candidato. Los modelos candidatos evaluados en los nodos de la primera y única capa vienen dados por:



Polinomios candidatos



Donde:

  •  'a' son los coeficientes , 'a1' es el primer coeficiente y 'a2' es el segundo.
  •  'x' son las variables de entrada, 'x1' representa la primera variable de entrada, o predictor, y 'x2' es la segunda.


El proceso consiste en resolver un sistema de ecuaciones lineales para obtener estimaciones de los coeficientes de un modelo candidato. Los criterios de rendimiento de cada modelo se utilizan para seleccionar el modelo final que mejor describe los datos.


Implementación de COMBI en MQL5

El código que implementa el algoritmo COMBI se basa en la clase base GmdhModel descrita en el artículo anterior. También depende de una clase intermedia LinearModel, declarada en linearmodel.mqh. Que resume la característica distintiva más fundamental del método COMBI. El hecho de que los modelos COMBI sean exclusivamente lineales.

//+------------------------------------------------------------------+
//|                                                  linearmodel.mqh |
//|                                  Copyright 2024, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2024, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#include "gmdh.mqh"
//+------------------------------------------------------------------+
//| Class implementing the general logic of GMDH linear algorithms   |
//+------------------------------------------------------------------+
class LinearModel:public GmdhModel
  {

protected:
   vector            calculatePrediction(vector& x)
     {
      if(x.Size()<ulong(inputColsNumber))
         return vector::Zeros(ulong(inputColsNumber));

      matrix modifiedX(1,x.Size()+ 1);

      modifiedX.Row(x,0);

      modifiedX[0][x.Size()] = 1.0;

      vector comb = bestCombinations[0][0].combination();
      matrix xx(1,comb.Size());
      for(ulong i = 0; i<xx.Cols(); ++i)
         xx[0][i] = modifiedX[0][ulong(comb[i])];
      vector c,b;
      c = bestCombinations[0][0].bestCoeffs();
      b = xx.MatMul(c);

      return b;
     }



   virtual matrix    xDataForCombination(matrix& x,  vector& comb) override
     {
      matrix out(x.Rows(), comb.Size());

      for(ulong i = 0; i<out.Cols(); i++)
         out.Col(x.Col(int(comb[i])),i);

      return out;
     }

   virtual string    getPolynomialPrefix(int levelIndex, int combIndex) override
     {
      return "y=";
     }

   virtual string    getPolynomialVariable(int levelIndex, int coeffIndex, int coeffsNumber, vector& bestColsIndexes) override
     {
      return ((coeffIndex != coeffsNumber - 1) ? "x" + string(int(bestColsIndexes[coeffIndex]) + 1) : "");
     }



public:
                     LinearModel(void)
     {
     }

   vector            predict(vector& x, int lags) override
     {
      if(lags <= 0)
        {
         Print(__FUNCTION__," lags value must be a positive integer");
         return vector::Zeros(1);
        }

      if(!training_complete)
        {
         Print(__FUNCTION__," model was not successfully trained");
         return vector::Zeros(1);
        }

      vector expandedX = vector::Zeros(x.Size() + ulong(lags));
      for(ulong i = 0; i<x.Size(); i++)
         expandedX[i]=x[i];

      for(int i = 0; i < lags; ++i)
        {
         vector vect(x.Size(),slice,expandedX,ulong(i),x.Size()+ulong(i)-1);
         vector res = calculatePrediction(vect);
         expandedX[x.Size() + i] = res[0];
        }

      vector vect(ulong(lags),slice,expandedX,x.Size());
      return vect;

     }

  };

//+------------------------------------------------------------------+

El archivo combi.mqh contiene la definición de la clase COMBI. Hereda de LinearModel y define los métodos «fit()» para ajustar un modelo.

//+------------------------------------------------------------------+
//|                                                        combi.mqh |
//|                                  Copyright 2024, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2024, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#include "linearmodel.mqh"
//+------------------------------------------------------------------+
//| Class implementing combinatorial COMBI algorithm                 |
//+------------------------------------------------------------------+
class COMBI:public LinearModel
  {
private:
   Combination       getBest(CVector &combinations)
     {
      double proxys[];
      int best[];

      ArrayResize(best,combinations.size());
      ArrayResize(proxys,combinations.size());

      for(int k = 0; k<combinations.size(); k++)
        {
         best[k]=k;
         proxys[k]=combinations[k].evaluation();
        }

      MathQuickSortAscending(proxys,best,0,int(proxys.Size()-1));

      return combinations[best[0]];

     }
protected:

   virtual void      removeExtraCombinations(void) override
     {
      CVector2d realBestCombinations;
      CVector n;
      Combination top;
      for(int i = 0 ; i<bestCombinations.size(); i++)
        {
         top = getBest(bestCombinations[i]);
         n.push_back(top);
        }

      top = getBest(n);

      CVector sorted;

      sorted.push_back(top);

      realBestCombinations.push_back(sorted);

      bestCombinations = realBestCombinations;
     }

   virtual bool      preparations(SplittedData &data, CVector &_bestCombinations) override
     {
      lastLevelEvaluation = DBL_MAX;
      return (bestCombinations.push_back(_bestCombinations) && ulong(level+1) < data.xTrain.Cols());
     }

   void              generateCombinations(int n_cols,vector &out[])  override
     {
      GmdhModel::nChooseK(n_cols,level,out);
      return;
     }

public:
                     COMBI(void):LinearModel()
     {
      modelName = "COMBI";
     }

   bool              fit(vector &time_series,int lags,double testsize=0.5,CriterionType criterion=stab)
     {

      if(lags < 1)
        {
         Print(__FUNCTION__," lags must be >= 1");
         return false;
        }

      PairMVXd transformed = timeSeriesTransformation(time_series,lags);

      SplittedData splited = splitData(transformed.first,transformed.second,testsize);

      Criterion criter(criterion);

      int pAverage = 1;
      double limit  = 0;
      int kBest = pAverage;

      if(validateInputData(testsize, pAverage, limit, kBest))
         return false;

      return GmdhModel::gmdhFit(splited.xTrain, splited.yTrain, criter, kBest, testsize, pAverage, limit);
     }

   bool              fit(matrix &vars,vector &targets,double testsize=0.5,CriterionType criterion=stab)
     {

      if(vars.Cols() < 1)
        {
         Print(__FUNCTION__," columns in vars must be >= 1");
         return false;
        }

      if(vars.Rows() != targets.Size())
        {
         Print(__FUNCTION__, " vars dimensions donot correspond with targets");
         return false;
        }

      SplittedData splited = splitData(vars,targets,testsize);

      Criterion criter(criterion);

      int pAverage = 1;
      double limit  = 0;
      int kBest = pAverage;

      if(validateInputData(testsize, pAverage, limit, kBest))
         return false;

      return GmdhModel::gmdhFit(splited.xTrain, splited.yTrain, criter, kBest, testsize, pAverage, limit);
     }


  };
//+------------------------------------------------------------------+



Usando la clase COMBI

Ajustar un modelo a un conjunto de datos utilizando la clase COMBI es exactamente lo mismo que aplicar la clase MIA. Creamos una instancia y llamamos a uno de los métodos «fit()». Esto se demuestra en los scripts COMBI_test.mq5 y COMBI_Multivariable_test.mq5.

//+------------------------------------------------------------------+
//|                                                   COMBI_test.mq5 |
//|                                  Copyright 2024, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2024, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"
#property script_show_inputs
#include <GMDH\combi.mqh>

input int NumLags = 2;
input int NumPredictions = 6;
input CriterionType critType = stab;
input double DataSplitSize = 0.33;
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//---
   vector tms = {1,2,3,4,5,6,7,8,9,10,11,12};

   if(NumPredictions<1)
     {
      Alert("Invalid setting for NumPredictions, has to be larger than 0");
      return;
     }

   COMBI combi;

   if(!combi.fit(tms,NumLags,DataSplitSize,critType))
      return;

   string modelname = combi.getModelName()+"_"+EnumToString(critType)+"_"+string(DataSplitSize);

   combi.save(modelname+".json");

   vector in(ulong(NumLags),slice,tms,tms.Size()-ulong(NumLags));

   vector out = combi.predict(in,NumPredictions);

   Print(modelname, " predictions ", out);

   Print(combi.getBestPolynomial());

  }
//+------------------------------------------------------------------+

Ambos aplican el algoritmo COMBI a los mismos conjuntos de datos utilizados en los scripts de ejemplo que demuestran cómo aplicar el algoritmo MIA de un artículo anterior.

//+------------------------------------------------------------------+
//|                                     COMBI_Multivariable_test.mq5 |
//|                                  Copyright 2024, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2024, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"
#property script_show_inputs
#include <GMDH\combi.mqh>

input CriterionType critType = stab;
input double DataSplitSize = 0.33;
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//---
   matrix independent = {{1,2,3},{3,2,1},{1,4,2},{1,1,3},{5,3,1},{3,1,9}};
   vector dependent = {6,6,7,5,9,13};

   COMBI combi;

   if(!combi.fit(independent,dependent,DataSplitSize,critType))
      return;

   string modelname = combi.getModelName()+"_"+EnumToString(critType)+"_"+string(DataSplitSize)+"_multivars";

   combi.save(modelname+".json");

   matrix unseen = {{8,6,4},{1,5,3},{9,-5,3}};

   for(ulong row = 0; row<unseen.Rows(); row++)
     {
      vector in = unseen.Row(row);
      Print("inputs ", in, " prediction ", combi.predict(in,1));
     }

   Print(combi.getBestPolynomial());

  }
//+------------------------------------------------------------------+

Observando la salida de COMBI_test.mq5 podemos comparar la complejidad del polinomio ajustado con el producido por el algoritmo MIA. A continuación se muestran los polinomios para las series temporales y el conjunto de datos multivariable, respectivamente.

LR      0       14:54:15.354    COMBI_test (BTCUSD,D1)  COMBI_stab_0.33 predictions [13,14,15,16,17,18.00000000000001]
PN      0       14:54:15.355    COMBI_test (BTCUSD,D1)  y= 1.000000e+00*x1 + 2.000000e+00
CI      0       14:54:29.864    COMBI_Multivariable_test (BTCUSD,D1)    inputs [8,6,4] prediction [18.00000000000001]
QD      0       14:54:29.864    COMBI_Multivariable_test (BTCUSD,D1)    inputs [1,5,3] prediction [9]
QQ      0       14:54:29.864    COMBI_Multivariable_test (BTCUSD,D1)    inputs [9,-5,3] prediction [7.00000000000001]
MM      0       14:54:29.864    COMBI_Multivariable_test (BTCUSD,D1)    y= 1.000000e+00*x1 + 1.000000e+00*x2 + 1.000000e+00*x3 - 7.330836e-15

A continuación, los polinomios revelados por el método MIA.

JQ      0       14:43:07.969    MIA_Test (Step Index 500,M1)    MIA_stab_0.33_1_0.0 predictions [13.00000000000001,14.00000000000002,15.00000000000004,16.00000000000005,17.0000000000001,18.0000000000001]
IP      0       14:43:07.969    MIA_Test (Step Index 500,M1)    y = - 9.340179e-01*x1 + 1.934018e+00*x2 + 3.865363e-16*x1*x2 + 1.065982e+00
II      0       14:52:59.698    MIA_Multivariable_test (BTCUSD,D1)      inputs [1,2,4] prediction [6.999999999999998]
CF      0       14:52:59.698    MIA_Multivariable_test (BTCUSD,D1)      inputs [1,5,3] prediction [8.999999999999998]
JR      0       14:52:59.698    MIA_Multivariable_test (BTCUSD,D1)      inputs [9,1,3] prediction [13.00000000000001]
NP      0       14:52:59.698    MIA_Multivariable_test (BTCUSD,D1)      f1_1 = 1.071429e-01*x1 + 6.428571e-01*x2 + 4.392857e+00
LR      0       14:52:59.698    MIA_Multivariable_test (BTCUSD,D1)      f1_2 = 6.086957e-01*x2 - 8.695652e-02*x3 + 4.826087e+00
ME      0       14:52:59.698    MIA_Multivariable_test (BTCUSD,D1)      f1_3 = - 1.250000e+00*x1 - 1.500000e+00*x3 + 1.125000e+01
DJ      0       14:52:59.698    MIA_Multivariable_test (BTCUSD,D1)      
IP      0       14:52:59.698    MIA_Multivariable_test (BTCUSD,D1)      f2_1 = 1.555556e+00*f1_1 - 6.666667e-01*f1_3 + 6.666667e-01
ER      0       14:52:59.698    MIA_Multivariable_test (BTCUSD,D1)      f2_2 = 1.620805e+00*f1_2 - 7.382550e-01*f1_3 + 7.046980e-01
ES      0       14:52:59.698    MIA_Multivariable_test (BTCUSD,D1)      f2_3 = 3.019608e+00*f1_1 - 2.029412e+00*f1_2 + 5.882353e-02
NH      0       14:52:59.698    MIA_Multivariable_test (BTCUSD,D1)      
CN      0       14:52:59.698    MIA_Multivariable_test (BTCUSD,D1)      f3_1 = 1.000000e+00*f2_1 - 3.731079e-15*f2_3 + 1.155175e-14
GP      0       14:52:59.698    MIA_Multivariable_test (BTCUSD,D1)      f3_2 = 8.342665e-01*f2_2 + 1.713326e-01*f2_3 - 3.359462e-02
DO      0       14:52:59.698    MIA_Multivariable_test (BTCUSD,D1)      
OG      0       14:52:59.698    MIA_Multivariable_test (BTCUSD,D1)      y = 1.000000e+00*f3_1 + 3.122149e-16*f3_2 - 1.899249e-15


El modelo MIA es enrevesado, mientras que el modelo COMBI es menos complicado, como la propia serie. Un reflejo de la naturaleza lineal del método. Esto también se confirma a partir de la salida del script COMBI_Multivariable_test.mq5, que representa un intento de construir un modelo para sumar 3 números proporcionando variables de entrada y ejemplos de salida correspondientes. En este caso, el algoritmo COMBI fue capaz de deducir con éxito las características subyacentes del sistema.

La búsqueda exhaustiva del algoritmo COMBI es a la vez una ventaja y un inconveniente. En el lado positivo, la evaluación de todas las combinaciones de las entradas garantiza que se encuentra el polinomio óptimo que describe los datos. Sin embargo, si existen numerosas variables de entrada, esta búsqueda exhaustiva puede resultar costosa desde el punto de vista informático. Posiblemente se traduzca en largos tiempos de entrenamiento. Recordemos, que el número de modelos candidatos viene dado por 2 elevado a la potencia 'm', todo menos 1. Cuanto mayor sea 'm' mayor será el número de modelos candidatos que haya que evaluar. El algoritmo combinatorio selectivo se desarrolló para resolver este problema.


El algoritmo combinatorio selectivo

El algoritmo combinatorio selectivo, que denominaremos MULTI, se presenta como una mejora del método COMBI en términos de eficacia. Se evita la búsqueda exhaustiva empleando procedimientos similares a los utilizados en los algoritmos multicapa del GMDH. Por lo tanto, puede considerarse un enfoque multicapa frente a la naturaleza monocapa del algoritmo COMBI.

Estructura de red del método combinatorio selectivo


 
En la primera capa se estiman todos los modelos que contienen una de las variables de entrada, seleccionándose el mejor según los criterios externos y pasando a la capa siguiente. En capas posteriores, se seleccionan diferentes variables de entrada y se añaden a estos modelos candidatos, con la esperanza de mejorarlos. De este modo, se añaden nuevas capas en función de si aumenta la precisión en la estimación de los resultados y/o de la disponibilidad de variables de entrada que no formen ya parte de un modelo candidato. Esto significa que el número máximo de capas posibles coincide con el número total de variables de entrada.

Evaluar los modelos candidatos de esta manera suele evitar una búsqueda exhaustiva, pero también introduce un problema. Existe la posibilidad de que el algoritmo no encuentre el polinomio óptimo que mejor describa el conjunto de datos. Una indicación de que deberían haberse considerado más modelos candidatos en la fase de formación. Deducir el polinomio óptimo se convierte en una cuestión de un ajuste cuidadoso de hiperparámetros. En particular, el número de modelos candidatos que se evalúan en cada capa.


Implementación del algoritmo combinatorio selectivo

La implementación del algoritmo combinatorio selectivo se encuentra en multi.mqh. Contiene la definición de la clase MULTI, que hereda de LinearModel.

//+------------------------------------------------------------------+
//|                                                        multi.mqh |
//|                                  Copyright 2024, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2024, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#include "linearmodel.mqh"

//+------------------------------------------------------------------+
//|  Class implementing combinatorial selection MULTI algorithm      |
//+------------------------------------------------------------------+
class MULTI : public LinearModel
  {

protected:

   virtual void      removeExtraCombinations(void) override
     {
      CVector2d realBestCombinations;
      CVector n;
      n.push_back(bestCombinations[0][0]);
      realBestCombinations.push_back(n);
      bestCombinations = realBestCombinations;
     }

   virtual bool      preparations(SplittedData &data, CVector &_bestCombinations) override
     {
      return (bestCombinations.setAt(0,_bestCombinations) && ulong(level+1) < data.xTrain.Cols());
     }

   void              generateCombinations(int n_cols,vector &out[])  override
     {
      if(level == 1)
        {
         nChooseK(n_cols,level,out);
         return;
        }

      for(int i = 0; i<bestCombinations[0].size(); i++)
        {

         for(int z = 0; z<n_cols; z++)
           {
            vector comb = bestCombinations[0][i].combination();
            double array[];
            vecToArray(comb,array);
            int found = ArrayBsearch(array,double(z));
            if(int(array[found])!=z)
              {
               array.Push(double(z));
               ArraySort(array);
               comb.Assign(array);
               ulong dif = 1;
               for(uint row = 0; row<out.Size(); row++)
                 {
                  dif = comb.Compare(out[row],1e0);
                  if(!dif)
                     break;
                 }

               if(dif)
                 {
                  ArrayResize(out,out.Size()+1,100);
                  out[out.Size()-1] = comb;
                 }
              }

           }
        }
     }



public:
                     MULTI(void):LinearModel()
     {
      CVector members;
      bestCombinations.push_back(members);
      modelName = "MULTI";
     }

   bool              fit(vector &time_series,int lags,double testsize=0.5,CriterionType criterion=stab,int kBest = 3,int pAverage = 1,double limit = 0.0)
     {

      if(lags < 1)
        {
         Print(__FUNCTION__," lags must be >= 1");
         return false;
        }

      PairMVXd transformed = timeSeriesTransformation(time_series,lags);

      SplittedData splited = splitData(transformed.first,transformed.second,testsize);

      Criterion criter(criterion);

      if(validateInputData(testsize, pAverage, limit, kBest))
         return false;

      return GmdhModel::gmdhFit(splited.xTrain, splited.yTrain, criter, kBest, testsize, pAverage, limit);
     }

   bool              fit(matrix &vars,vector &targets,double testsize=0.5,CriterionType criterion=stab,int kBest = 3,int pAverage = 1,double limit = 0.0)
     {

      if(vars.Cols() < 1)
        {
         Print(__FUNCTION__," columns in vars must be >= 1");
         return false;
        }

      if(vars.Rows() != targets.Size())
        {
         Print(__FUNCTION__, " vars dimensions donot correspond with targets");
         return false;
        }

      SplittedData splited = splitData(vars,targets,testsize);

      Criterion criter(criterion);

      if(validateInputData(testsize, pAverage, limit, kBest))
         return false;

      return GmdhModel::gmdhFit(splited.xTrain, splited.yTrain, criter, kBest, testsize, pAverage, limit);
     }

  };
//+------------------------------------------------------------------+

La clase MULTI funciona de forma similar a la clase COMBI con los conocidos métodos «fit()». En relación con la clase COMBI, los lectores deben tener en cuenta el mayor número de hiperparámetros en los métodos «fit()». «kBest» y “pAverage” son dos parámetros que pueden necesitar un ajuste cuidadoso al aplicar la clase MULTI. El ajuste de un modelo a un conjunto de datos se muestra en los scripts MULTI_test.mq5 y MULTI_Multivariable_test.mq5. El código se presenta a continuación.

//+----------------------------------------------------------------------+
//|                                                   MULTI_test.mq5           |
//|                                  Copyright 2024, MetaQuotes Ltd. |
//|                                             https://www.mql5.com     |
//+----------------------------------------------------------------------+
#property copyright "Copyright 2024, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"
#property script_show_inputs
#include <GMDH\multi.mqh>

input int NumLags = 2;
input int NumPredictions = 6;
input CriterionType critType = stab;
input int Average  = 1;
input int NumBest  = 3;
input double DataSplitSize = 0.33;
input double critLimit = 0;
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//---
   vector tms = {1,2,3,4,5,6,7,8,9,10,11,12};

   if(NumPredictions<1)
     {
      Alert("Invalid setting for NumPredictions, has to be larger than 0");
      return;
     }

   MULTI multi;

   if(!multi.fit(tms,NumLags,DataSplitSize,critType,NumBest,Average,critLimit))
      return;

   vector in(ulong(NumLags),slice,tms,tms.Size()-ulong(NumLags));

   vector out = multi.predict(in,NumPredictions);

   Print(" predictions ", out);

   Print(multi.getBestPolynomial());

  }
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
//|                                    MULTI_Mulitivariable_test.mq5 |
//|                                  Copyright 2024, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2024, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"
#property script_show_inputs
#include <GMDH\multi.mqh>

input CriterionType critType = stab;
input double DataSplitSize = 0.33;
input int Average  = 1;
input int NumBest  = 3;
input double critLimit = 0;
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//---
   matrix independent = {{1,2,3},{3,2,1},{1,4,2},{1,1,3},{5,3,1},{3,1,9}};
   vector dependent = {6,6,7,5,9,13};

   MULTI multi;

   if(!multi.fit(independent,dependent,DataSplitSize,critType,NumBest,Average,critLimit))
      return;

   matrix unseen = {{1,2,4},{1,5,3},{9,1,3}};

   for(ulong row = 0; row<unseen.Rows(); row++)
     {
      vector in = unseen.Row(row);
      Print("inputs ", in, " prediction ", multi.predict(in,1));
     }

   Print(multi.getBestPolynomial());

  }
//+------------------------------------------------------------------+

Se puede observar, que al ejecutar los scripts, los polinomios inducidos a partir de nuestros conjuntos de datos simples son exactamente los mismos que los obtenidos aplicando el algoritmo COMBI.

IG      0       18:24:28.811    MULTI_Mulitivariable_test (BTCUSD,D1)   inputs [1,2,4] prediction [7.000000000000002]
HI      0       18:24:28.812    MULTI_Mulitivariable_test (BTCUSD,D1)   inputs [1,5,3] prediction [9]
NO      0       18:24:28.812    MULTI_Mulitivariable_test (BTCUSD,D1)   inputs [9,1,3] prediction [13.00000000000001]
PP      0       18:24:28.812    MULTI_Mulitivariable_test (BTCUSD,D1)   y= 1.000000e+00*x1 + 1.000000e+00*x2 + 1.000000e+00*x3 - 7.330836e-15
DP      0       18:25:04.454    MULTI_test (BTCUSD,D1)   predictions [13,14,15,16,17,18.00000000000001]
MH      0       18:25:04.454    MULTI_test (BTCUSD,D1)  y= 1.000000e+00*x1 + 2.000000e+00


Un modelo GMDH para los precios de Bitcoin

En esta sección del texto aplicaremos el método GMDH para construir un modelo predictivo de los precios de cierre diarios de Bitcoin. Esto se realiza en el script GMDH_Price_Model.mq5 adjunto al final del artículo. Aunque nuestra demostración trata específicamente del símbolo Bitcoin, el script puede aplicarse a cualquier símbolo y marco temporal. El script tiene varios parámetros mutables por el usuario que controlan varios aspectos del programa.

//--- input parameters
input string   SetSymbol="";
input ENUM_GMDH_MODEL modelType = Combi;
input datetime TrainingSampleStartDate=D'2019.12.31';
input datetime TrainingSampleStopDate=D'2022.12.31';
input datetime TestSampleStartDate = D'2023.01.01';
input datetime TestSampleStopDate = D'2023.12.31';
input ENUM_TIMEFRAMES tf=PERIOD_D1;    //time frame
input int Numlags = 3;
input CriterionType critType = stab;
input PolynomialType polyType = linear_cov;
input int Average  = 10;
input int NumBest  = 10;
input double DataSplitSize = 0.2;
input double critLimit = 0;
input ulong NumTestSamplesPlot = 20;

Se enumeran y describen en el siguiente cuadro.

Nombre del parámetro de entrada
 Descripción
SetSymbol
Establece el nombre de los símbolos de los precios de cierre que se utilizarán como datos de entrenamiento, si se deja en blanco, se asume el símbolo del gráfico al que se aplica el script.
modeltype Se trata de una enumeración que representa la elección del algoritmo GMDH.
TrainingSampleStartDate
Fecha de inicio del periodo de precios de cierre de la muestra.
TrainingSampleStopDate
Fecha final del periodo de precios de cierre de la muestra.
TestSampleStartDate
Fecha de inicio del periodo de precios fuera de muestra.
TestSampleStopDate
Fecha final del periodo de precios fuera de muestra.
tf
El marco temporal aplicado.
Numlags
Define el número de valores retardados que se utilizarán para pronosticar el próximo precio de cierre.
critType
Especifica los criterios externos para el proceso de construcción del modelo.
polyType
Tipo de polinomio que se utilizará para construir nuevas variables a partir de las existentes durante el entrenamiento, aplicable sólo cuando se selecciona el algoritmo MIA.
Average
El número de los mejores modelos parciales basados a considerar en el cálculo de los criterios de parada
Numbest
Para modelos MIA define el número de los mejores modelos parciales en base a los cuales se construirán las nuevas entradas de la capa siguiente,
mientras que para los modelos COMBI es el número de modelos candidatos que se seleccionarán en cada capa para su consideración en iteraciones posteriores.
DataSplitSize
Fracción de los datos de entrada que deben utilizarse para evaluar los modelos.
critLimit
El valor mínimo por el cual el criterio externo debe mejorar para continuar el entrenamiento.
NumTestSamplePlot
El valor define el número de muestras del conjunto de datos fuera de la muestra que serán visualizadas junto con las predicciones correspondientes.

El script comienza con la selección de una muestra adecuada de observaciones que se utilizarán como datos de entrenamiento.

//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//get relative shift of IS and OOS sets
   int trainstart,trainstop, teststart, teststop;
   trainstart=iBarShift(SetSymbol!=""?SetSymbol:NULL,tf,TrainingSampleStartDate);
   trainstop=iBarShift(SetSymbol!=""?SetSymbol:NULL,tf,TrainingSampleStopDate);
   teststart=iBarShift(SetSymbol!=""?SetSymbol:NULL,tf,TestSampleStartDate);
   teststop=iBarShift(SetSymbol!=""?SetSymbol:NULL,tf,TestSampleStopDate);
//check for errors from ibarshift calls
   if(trainstart<0 || trainstop<0 || teststart<0 || teststop<0)
     {
      Print(ErrorDescription(GetLastError()));
      return;
     }
//---set the size of the sample sets
   size_observations=(trainstart - trainstop) + 1 ;
   size_outsample = (teststart - teststop) + 1;
//---check for input errors
   if(size_observations <= 0  || size_outsample<=0)
     {
      Print("Invalid inputs ");
      return;
     }
//---download insample prices for training
   int try
         = 10;
   while(!prices.CopyRates(SetSymbol,tf,COPY_RATES_CLOSE,TrainingSampleStartDate,TrainingSampleStopDate) && try)
     {
      try
         --;
      if(!try)
        {
         Print("error copying to prices  ",GetLastError());
         return;
        }
      Sleep(5000);

Se descarga otra muestra de precios de cierre, que se utilizará para visualizar el rendimiento del modelo con un gráfico de precios de cierre previstos y reales.

//---download out of sample prices testing
   try
         = 10;
   while(!testprices.CopyRates(SetSymbol,tf,COPY_RATES_CLOSE|COPY_RATES_TIME|COPY_RATES_VERTICAL,TestSampleStartDate,TestSampleStopDate) && try)
     {
      try
         --;
      if(!try)
        {
         Print("error copying to testprices  ",GetLastError());
         return;
        }
      Sleep(5000);
     }

Los parámetros del script definidos por el usuario permiten la aplicación de uno de los tres modelos GMDH que hemos discutido e implementado.

//--- train and make predictions
   switch(modelType)
     {
      case Combi:
        {
         COMBI combi;
         if(!combi.fit(prices,Numlags,DataSplitSize,critType))
            return;
         Print("Model ", combi.getBestPolynomial());
         MakePredictions(combi,testprices.Col(0),predictions);
        }
      break;

      case Mia:
        {
         MIA mia;
         if(!mia.fit(prices,Numlags,DataSplitSize,polyType,critType,NumBest,Average,critLimit))
            return;
         Print("Model ", mia.getBestPolynomial());
         MakePredictions(mia,testprices.Col(0),predictions);
        }
      break;

      case Multi:
        {
         MULTI multi;
         if(!multi.fit(prices,Numlags,DataSplitSize,critType,NumBest,Average,critLimit))
            return;
         Print("Model ", multi.getBestPolynomial());
         MakePredictions(multi,testprices.Col(0),predictions);
        }
      break;

      default:
         Print("Invalid GMDH model type ");
         return;
     }
//---

El programa termina mostrando un gráfico de los precios de cierre previstos junto con los valores reales del conjunto de datos fuera de muestra.

//---
   ulong TestSamplesPlot = (NumTestSamplesPlot>0)?NumTestSamplesPlot:20;
//---
   if(NumTestSamplesPlot>=testprices.Rows())
      TestSamplesPlot = testprices.Rows()-Numlags;
//---
   vector testsample(100,slice,testprices.Col(0),Numlags,Numlags+TestSamplesPlot-1);
   vector testpredictions(100,slice,predictions,0,TestSamplesPlot-1);
   vector dates(100,slice,testprices.Col(1),Numlags,Numlags+TestSamplesPlot-1);
//---
//Print(testpredictions.Size(), ":", testsample.Size());
//---
   double y[], y_hat[];
//---
   if(vecToArray(testpredictions,y_hat) && vecToArray(testsample,y) && vecToArray(dates,xaxis))
     {
      PlotPrices(y_hat,y);
     }
//---
   ChartRedraw();
  }

A continuación se muestran los gráficos de las predicciones realizadas con los algoritmos iterativo combinatorio y multicapa, respectivamente.

Predicciones de Bitcoin mediante un modelo combinatorio

Predicciones de Bitcoin mediante un modelo MIA


Conclusión

En conclusión, el algoritmo combinatorio del GMDH ofrece un marco para modelar sistemas complejos, con puntos fuertes particulares en áreas donde los enfoques inductivos basados en datos son ventajosos. Su aplicación práctica es limitada debido a la ineficacia del algoritmo a la hora de manejar grandes conjuntos de datos. El algoritmo combinatorio selectivo mitiga esta deficiencia sólo hasta cierto punto. Las ganancias de velocidad se ven lastradas por la introducción de más hiperparámetros que hay que ajustar para sacar el máximo partido del algoritmo. La aplicación del método GMDH al análisis de series temporales financieras demuestra su potencial para proporcionar información valiosa.

Archivo
Descripción
Mql5\include\VectorMatrixTools.mqh
Archivo de cabecera de definiciones de funciones utilizado para manipular vectores y matrices.
Mql5\include\JAson.mqh
Contiene la definición de los tipos personalizados utilizados para analizar y generar objetos JSON.
Mql5\include\GMDH\gmdh_internal.mqh
Archivo de cabecera que contiene las definiciones de los tipos personalizados utilizados en la biblioteca GMDH.
Mql5\include\GMDH\gmdh.mqh
Archivo de inclusión con la definición de la clase base GmdhModel.
Mql5\include\GMDH\linearmodel.mqh
Archivo de inclusión con la definición de la clase intermediaria LinearModel, que es la base para las clases COMBI y MULTI.
Mql5\include\GMDH\combi.mqh
Archivo de inclusión con la definición de la clase COMBI.
Mql5\include\GMDH\multi.mqh
Archivo de inclusión con la definición de la clase MULTI.
Mql5\include\GMDH\mia.mqh
Contiene la clase MIA, que implementa el algoritmo iterativo multicapa.
Mql5\script\COMBI_test.mq5
Un script que demuestra el uso de la clase COMBI construyendo un modelo de una serie temporal simple.
Mql5\script\COMBI_Multivariable_test.mq5
Un script que muestra la aplicación de la clase COMBI para construir un modelo de un conjunto de datos multivariable.
Mql5\script\MULTI_test.mq5
Un script que demuestra el uso de la clase MULTI construyendo un modelo de una serie temporal simple.
Mql5\script\MULTI_Multivariable_test.mq5
Un script que muestra la aplicación de la clase MULTI para construir un modelo de un conjunto de datos multivariable.
Mql5\script\GMDH_Price_Model.mqh
Un script que demuestra un modelo GMDH de una serie de precios.

Traducción del inglés realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/en/articles/14804

Archivos adjuntos |
JAson.mqh (33.43 KB)
combi.mqh (4.02 KB)
gmdh.mqh (23.86 KB)
gmdh_internal.mqh (82.09 KB)
linearmodel.mqh (2.84 KB)
mia.mqh (12.1 KB)
multi.mqh (4.05 KB)
COMBI_test.mq5 (1.59 KB)
MULTI_test.mq5 (1.55 KB)
Mql5.zip (31.81 KB)
Desarrollando un cliente MQTT para MetaTrader 5: metodología TDD (Parte 6) Desarrollando un cliente MQTT para MetaTrader 5: metodología TDD (Parte 6)
Este artículo supone la sexta parte de la serie que describe las etapas de desarrollo de un cliente MQL5 nativo para el protocolo MQTT 5.0. En esta parte, describiremos los principales cambios en nuestra primera refactorización, obteniendo un borrador de trabajo de nuestras clases de construcción de paquetes, creando los paquetes PUBLISH y PUBACK y la semántica de los códigos de motivo PUBACK.
Introducción a MQL5 (Parte 5): Funciones de trabajo con arrays para principiantes Introducción a MQL5 (Parte 5): Funciones de trabajo con arrays para principiantes
En el quinto artículo de nuestra serie, nos familiarizaremos con el mundo de los arrays en MQL5. Este artículo ha sido pensado para principiantes. En este artículo intentaremos repasar conceptos complejos de programación de manera simplificada para que el material resulte comprensible para todos. Asimismo, exploraremos conceptos básicos, discutiremos diferentes cuestiones y compartiremos conocimientos.
Características del Wizard MQL5 que debe conocer (Parte 17): Negociación con multidivisas Características del Wizard MQL5 que debe conocer (Parte 17): Negociación con multidivisas
La negociación con varias divisas no está disponible por defecto cuando se crea un asesor experto mediante el asistente. Examinamos dos posibles trucos que los operadores pueden utilizar para poner a prueba sus ideas con más de un símbolo a la vez.
Cómo construir y optimizar un sistema de trading basado en la volatilidad (Chaikin Volatility - CHV) Cómo construir y optimizar un sistema de trading basado en la volatilidad (Chaikin Volatility - CHV)
En este artículo, proporcionaremos otro indicador basado en la volatilidad llamado Chaikin Volatility. Entenderemos cómo construir un indicador personalizado después de identificar cómo se puede utilizar y construir. Compartiremos algunas estrategias sencillas que se pueden utilizar y luego las pondremos a prueba para entender cuál puede ser mejor.