English Español Deutsch 日本語
preview
Метод группового учета аргументов: реализация комбинаторного алгоритма на MQL5

Метод группового учета аргументов: реализация комбинаторного алгоритма на MQL5

MetaTrader 5Примеры | 11 сентября 2024, 08:25
138 0
Francis Dube
Francis Dube

Введение

Комбинаторный алгоритм (COMBI) является базовой формой метода группового учета аргументов (МГУА) и служит основой для более сложных алгоритмов в рамках семейства. Подобно многослойному итеративному алгоритму, он работает с выборкой исходных данных, представленной в виде матрицы, содержащей наблюдения по набору переменных. Выборка делится на две части: обучающую и тестовую. Обучающая подвыборка используется для оценки коэффициентов полинома, а тестовая — для выбора структуры оптимальной модели на основе минимального значения выбранного критерия. В этой статье мы поговорим о расчетах комбинаторного алгоритма. Также представим его реализацию средствами MQL5. Для этого мы расширим класс GmdhModel, описанный в предыдущей статье. Также мы рассмотрим тесно связанный с ним комбинаторный селективный алгоритм и его реализацию на языке MQL5. И в заключение представим практическое применение алгоритмов МГУА. Для этого построим прогностические модели дневной цены биткоина.


Алгоритм COMBI

Принципиальное различие между многослойным итеративным (MIA) и комбинаторным (COMBI) алгоритмами заключается в их сетевой структуре. В отличие от многослойной структуры MIA, COMBI имеет один слой.

Сетевая структура комбинаторного метода

Количество узлов в слое определяется количеством входов. Каждый узел представляет собой модель-кандидат, определяемую одним или несколькими входными параметрами из выборки. Для иллюстрации рассмотрим пример системы, которая определяется двумя входными переменными. В алгоритме COMBI все возможные комбинации этих переменных используются для построения модели-кандидата. Количество возможных комбинаций определяется по формуле:

Формула максимального количества комбинаций



где:

  • m — количество входных переменных.


Для каждой из этих комбинаций генерируется модель-кандидат. Модели-кандидаты, оцениваемые в узлах первого и единственного слоя, определяются следующим образом:



Кандидатные полиномы



где:

  •  a — коэффициенты; a1 — первый коэффициент, a2 — второй.
  •  x — входные переменные, x1 — первая входная переменная или предиктор, а x2 — вторая.


Процесс включает в себя решение системы линейных уравнений для получения оценок коэффициентов модели-кандидата. Критерии эффективности каждой модели используются для выбора окончательной модели, которая наилучшим образом описывает данные.


Реализация алгоритма COMBI средствами MQL5

Код, реализующий алгоритм COMBI, основан на базовом классе GmdhModel, который был описан в предыдущей статье. Он также использует промежуточный класс LinearModel, объявленный в linearmodel.mqh. Это и отражает фундаментальную отличительную характеристику метода COMBI. То есть, тот факт, что модели COMBI являются исключительно линейными.

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

     }

  };

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

Файл combi.mqh содержит определение класса COMBI. Он унаследован от LinearModel и определяет методы fit() для обучения модели.

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


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



Использование класса COMBI

Обучение модели на выборке данных с использованием класса COMBI не отличается от обучения, которое мы проводили для класса MIA. Создаем экземпляр и вызываем один из методов fit(). Как показано в скриптах COMBI_test.mq5 и 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());

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

Оба скрипта запускают алгоритм COMBI на одном и том же наборе данных. Этот же набор мы использовали ранее в примерах алгоритма MIA в предыдущей статье.

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

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

По результатам COMBI_test.mq5 сложность обученного полинома можно сравнить со сложностью алгоритма MIA. Ниже приведены полиномы для временного ряда и многомерного набора данных соответственно.

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

А это полиномы, выявленные методом 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


Модель MIA получилась более сложной. Модель COMBI проще, собственно как и сама линейная последовательность. Это отражает линейную природу метода. Это также подтверждается результатом скрипта COMBI_Multivariable_test.mq5, который представляет собой попытку построить модель для суммирования трех чисел по указанным входным переменным. В данном случае алгоритм COMBI смог успешно определить базовые характеристики системы.

Полный поиск в алгоритме COMBI является одновременно и преимуществом, и недостатком. К положительным сторонам можно отнести оценку всех комбинаций входных данных, что гарантирует нахождение оптимального полинома, описывающего данные. Однако при наличии большого количества входных переменных такой обширный поиск может оказаться весьма затратным с точки зрения вычислений. Это вероятно приведет к увеличению времени обучения. Напомню, что количество моделей-кандидатов определяется как 2 в степени m, и чем больше m, тем большее количество моделей-кандидатов необходимо оценить. Собственно для решения такой проблемы и был разработан комбинаторный селективный алгоритм.


Комбинаторный селективный алгоритм

Комбинаторный селективный алгоритм, который мы будем называть MULTI, позиционируется как усовершенствованный метод COMBI с точки зрения эффективности. Здесь не приходится делать полный поиск за счет использования процессов, аналогичных тем, которые используются в многоуровневых алгоритмах МГУА. Таким образом, MULTI можно рассматривать как многоуровневый подход к одноуровневой природе алгоритма COMBI.

Сетевая структура комбинаторного селективного метода


 
На первом уровне оцениваются все модели, содержащие одну из входных переменных, при этом лучшая из них выбирается в соответствии с внешними критериями и передается на следующий уровень. На последующих уровнях выбираются и добавляются к этим потенциальным моделям различные входные переменные в попытке улучшить их. Добавление новых слоев зависит от того, повысилась ли точность оценки выходных данных, и/или от доступности входных переменных, которые еще не являются частью модели-кандидата. Таким образом, максимальное количество возможных слоев совпадает с общим количеством входных переменных.

Такая оценка моделей-кандидатов часто позволяет избежать необходимости проводить полный поиск, но, с другой стороны, это также создает проблему. Существует вероятность, что алгоритм не сможет найти оптимальный полином, наилучшим образом описывающий набор данных. Это говорит о том, что на этапе обучения нужно было рассмотреть больше моделей-кандидатов. Для вывода оптимального полинома необходима тщательная настройка гиперпараметров. В частности, количество моделей-кандидатов, оцениваемых на каждом уровне.


Реализация комбинаторного селективного алгоритма

Реализация комбинаторного селективного алгоритма представлена в файле multi.mqh. Он содержит определение класса MULTI, который наследуется от 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);
     }

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

Класс MULTI работает аналогично классу COMBI, использует метод fit(). Обратите внимание на большее количество гиперпараметров в методах fit() по сравнению с классом COMBI. kBest и pAverage — два параметра, которые необходимо тщательно настраивать при применении класса MULTI. Обучение модели на наборе данных происходит в скриптах MULTI_test.mq5 и MULTI_Multivariable_test.mq5. Код скриптов представлен ниже.

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

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

Как видите, при запуске скриптов полиномы, полученные из наших простых выборок, в точности совпадают с полиномами, полученными при применении алгоритма 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


Модель МГУА для прогнозирования цен на биткоины

В этом разделе попробуем применить метод МГУА, чтобы построить прогностическую модель дневных цен закрытия на биткоин. Реализуем это в скрипте GMDH_Price_Model.mq5, он целиком приложен в конце статьи. Хотя данный пример касается конкретно символа Bitcoin, скрипт можно применить к любому символу и таймфрейму. В скрипте есть несколько параметров, задаваемых пользователем, которые управляют различными аспектами программы.

//--- 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;

Их значение объясняется в следующей таблице.

Имя входного параметра
 Описание
SetSymbol
Задает имя символа, цены закрытия которого будут использоваться в качестве обучающих данных. Если оставить поле пустым, будет использоваться символ текущего графика, на котором запускается скрипт.
modeltype Перечисление для выбора алгоритма МГУА
TrainingSampleStartDate
Начальная дата для периода в выборке цены закрытия
TrainingSampleStopDate
Конечная дата для периода в выборке цены закрытия
TestSampleStartDate
Начальная дата периода цен вне выборки
TestSampleStopDate
Конечная дата периода цен вне выборки
tf
Используемый таймфрейм
Numlags
Определяет количество запаздывающих значений, которые будут использоваться для прогнозирования следующей цены закрытия
critType
Определяет внешние критерии для процесса построения модели
polyType
Тип полинома, который будет использоваться для построения новых переменных из существующих во время обучения, применим только при выборе алгоритма MIA
Average
Число лучших частичных моделей, которые следует учитывать при расчете критериев остановки
Numbest
Для моделей MIA параметр определяет количество лучших частичных моделей, на основе которых будут построены новые входы последующего слоя
Для моделей COMBI это количество моделей-кандидатов, которые будут выбраны на каждом уровне для рассмотрения в последующих итерациях.
DataSplitSize
Доля входных данных, которая должна использоваться для оценки моделей
critLimit
Минимальное значение, на которое внешний критерий должен улучшаться для продолжения обучения
NumTestSamplePlot
Значение определяет количество выборок из набора данных вне выборки, которые будут визуализированы вместе с соответствующими прогнозами.

Скрипт начинается с выбора подходящей выборки наблюдений для использования в качестве обучающих данных.

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

Загружается еще одна выборка цен закрытия, которая будет использоваться для визуализации эффективности модели с построением прогнозируемых и фактических цен закрытия.

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

В зависимости от выбора пользователя, скрипт будет использовать одну из трех моделей МГУА, которые мы обсудили и реализовали.

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

В завершение программы отображается графика прогнозируемых цен закрытия вместе с фактическими значениями из данных вне выборки.

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

Ниже показаны прогнозов, сделанные с использованием комбинаторного и многослойного итеративного алгоритмов соответственно.

Комбинаторная модель предсказаний цен на биткоин

Модель MIA предсказаний цен на биткоин


Заключение

В заключение следует отметить, что комбинаторный алгоритм МГУА предлагает основу для моделирования сложных систем. Он особенно хорошо работает в областях, где большую пользу приносят индуктивные подходы, основанные на данных. Его практическое применение ограничено из-за недостаточной эффективности алгоритма при обработке больших наборов данных. Комбинаторный селективный алгоритм в некоторой степени смягчает этот недостаток. Однако рост скорости нивелируется введением большего количества гиперпараметров, которые необходимо тщательно настраивать, чтобы получить максимальные результаты от алгоритма. В целом же применение методов группового учета аргументов (МГУА) в финансовом анализе временных рядов демонстрирует его потенциал в предоставлении ценной информации.

Файл
Описание
Mql5\include\VectorMatrixTools.mqh
Заголовочный файл определений функций, используемых для работы с векторами и матрицами
Mql5\include\JAson.mqh
Содержит определение пользовательских типов, используемых для анализа и генерации объектов JSON
Mql5\include\GMDH\gmdh_internal.mqh
Заголовочный файл, содержащий определения пользовательских типов, используемых в библиотеке gmdh
Mql5\include\GMDH\gmdh.mqh
Файл include с определением базового класса GmdhModel
Mql5\include\GMDH\linearmodel.mqh
Включаемый файл, содержащий определение промежуточного класса LinearModel, который является основой для классов COMBI и MULTI
Mql5\include\GMDH\combi.mqh
Включаемый файл с определением класса COMBI
Mql5\include\GMDH\multi.mqh
Включаемый файл с определением класса MULTI
Mql5\include\GMDH\mia.mqh
Содержит класс MIA, реализующий многослойный итерационный алгоритм
Mql5\script\COMBI_test.mq5
Cкрипт, демонстрирующий использование класса COMBI через построение модели простого временного ряда
Mql5\script\COMBI_Multivariable_test.mq5
Скрипт, демонстрирующий применение класса COMBI для построения модели многомерного набора данных
Mql5\script\MULTI_test.mq5
Скрипт, демонстрирующий использование класса MULTI через построение модели простого временного ряда
Mql5\script\MULTI_Multivariable_test.mq5
Скрипт, демонстрирующий применение класса MULTI для построения модели многомерного набора данных
Mql5\script\GMDH_Price_Model.mqh
Скрипт, демонстрирующий модель МГУА на ценовом ряде

Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/14804

Прикрепленные файлы |
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)
Возможности Мастера MQL5, которые вам нужно знать (Часть 17): Мультивалютная торговля Возможности Мастера MQL5, которые вам нужно знать (Часть 17): Мультивалютная торговля
По умолчанию торговля несколькими валютами недоступна при сборке советника с помощью Мастера. Мы рассмотрим два возможных приема, к которым могут прибегнуть трейдеры, желающие проверить свои идеи на нескольких символах одновременно.
Нейросети в трейдинге: Transformer для облака точек (Pointformer) Нейросети в трейдинге: Transformer для облака точек (Pointformer)
В данной статье мы поговорим об алгоритмах использования методов внимания при решении задач обнаружения объектов в облаке точек. Обнаружение объектов в облаках точек имеет важное значение для многих реальных приложений.
Алгоритм стрельбы из лука — Archery Algorithm (AA) Алгоритм стрельбы из лука — Archery Algorithm (AA)
В данной статье подробно рассматривается алгоритм оптимизации, вдохновленный стрельбой из лука, с акцентом на использование метода рулетки в качестве механизма выбора перспективных областей для "стрел". Этот метод позволяет оценивать качество решений и отбирать наиболее многообещающие позиции для дальнейшего изучения.
Создаем и оптимизируем торговую систему на основе волатильности с индикатором Чайкина Создаем и оптимизируем торговую систему на основе волатильности с индикатором Чайкина
В этой статье мы поговорим об индикаторе волатильности Чайкина (Chaikin Volatility, CHV). Разберемся, что делает этот индикатор, как и в каких условиях его можно использовать и как создать пользовательский индикатор волатильности. Проанализируем несколько простых стратегий и протестируем их, чтобы понять, какая стратегия лучше.