English 中文 Español Deutsch 日本語 Português
preview
Прогнозирование с помощью моделей ARIMA в MQL5

Прогнозирование с помощью моделей ARIMA в MQL5

MetaTrader 5Торговые системы | 1 августа 2023, 12:55
1 065 3
Francis Dube
Francis Dube

Введение

Статья "Реализация алгоритма обучения ARIMA на MQL5" описывает класс CArima для построения моделей ARIMA. Хотя технически можно использовать класс для применения модели и прогнозирования, его нельзя назвать интуитивно понятным. В этой статье мы устраним этот недостаток и расширим класс, чтобы упростить использование методов применения моделей для прогнозирования. Мы обсудим некоторые сложности, связанные с реализацией прогнозов, а также некоторые новые функции, добавленные в класс. В заключении мы будем использовать полный класс для построения модели для прогнозирования цен на рынке Форекс, применяя его к советнику и индикатору.


Серия входных параметров

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

Реализация прогнозов для чистых авторегрессионных моделей тривиальна, поскольку все, что требуется, — это входные данные, равные наибольшему отставанию модели. Смешанные модели, использующие термины скользящего среднего, создают проблемы при составлении прогнозов. У нас пока нет фактических серий ошибок или инноваций. Чтобы преодолеть это ограничение, мы должны сначала решить, как будут рассчитываться начальные значения ошибок.

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

Тем не менее, есть еще что рассмотреть с точки зрения количества входных данных модели. Класс CArima имеет возможность указывать модели с несмежными лагами. Это предъявляет еще более высокие требования к количеству необходимых входных данных. В этом случае наибольшая задержка любого типа (AR/MA) добавит требования к размеру входных данных. Рассмотрим модель, определяемую функцией                                                                                             

price(t) = constant_term + AR*price(t-4)

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


Учет дифференцирования

Требования дифференцирования увеличивают количество требуемых входных данных не из-за факторов, связанных с вычислением прогнозируемого значения (значений), а из-за того, что дифференцирование приводит к потере информации. При создании дифференцированного ряда он всегда будет на единицу меньше по длине относительно исходного ряда. Этот более короткий ряд в конечном итоге будет передан в качестве входных данных для модели. Таким образом, в целом для компенсации необходимы дополнительные входные данные, которые соответствуют порядку дифференцирования, указанному моделью.

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


Прогнозирование далеко в будущее

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

Дополнения к классу CArima

Первое изменение, внесенное в класс, относится к его родительскому методу CPowellsMethod. Метод Optimize() теперь защищен модификатором доступа и не может быть доступен извне класса. Естественно, это изменение распространяется и на CArima. В связи с этой модификацией имя включаемого файла, содержащего класс CPowellsMethod, изменено на Powells.mqh.

//-----------------------------------------------------------------------------------
// Minimization of Functions.
// Unconstrained Powell’s Method.
// References:
// 1. Numerical Recipes in C. The Art of Scientific Computing.
//-----------------------------------------------------------------------------------
class PowellsMethod:public CObject
  {
protected:
   double            P[],Xi[];
   double            Pcom[],Xicom[],Xt[];
   double            Pt[],Ptt[],Xit[];
   int               N;
   double            Fret;
   int               Iter;
   int               ItMaxPowell;
   double            FtolPowell;
   int               ItMaxBrent;
   double            FtolBrent;
   int               MaxIterFlag;
   int               Optimize(double &p[],int n=0);
public:
   void              PowellsMethod(void);
   void              SetItMaxPowell(int n)           { ItMaxPowell=n; }
   void              SetFtolPowell(double er)        { FtolPowell=er; }
   void              SetItMaxBrent(int n)            { ItMaxBrent=n;  }
   void              SetFtolBrent(double er)         { FtolBrent=er;  }
   double            GetFret(void)                   { return(Fret);  }
   int               GetIter(void)                   { return(Iter);  }
private:
   void              powell(void);
   void              linmin(void);
   void              mnbrak(double &ax,double &bx,double &cx,double &fa,double &fb,double &fc);
   double            brent(double ax,double bx,double cx,double &xmin);
   double            f1dim(double x);
   virtual double    func(const double &p[]) { return(0); }
  };


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

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CArima::SaveModel(const string model_name)
  {
   uint model_order[]= {m_const,m_ar_order,m_diff_order,m_ma_order};

   CFileBin file;
   ResetLastError();
   if(!file.Open("models\\"+model_name+".model",FILE_WRITE|FILE_COMMON))
     {
      Print("Failed to save model.Error: ",GetLastError());
      return false;
     }

   m_modelname=(m_modelname=="")?model_name:m_modelname;

   long written=0;

   written = file.WriteIntegerArray(model_order);

   if(!written)
     {
      Print("Failed write operation, ",__LINE__,".Error: ",GetLastError());
      return false;
     }

   if(m_ar_order)
     {
      written = file.WriteIntegerArray(m_arlags);

      if(!written)
        {
         Print("Failed write operation, ",__LINE__,".Error: ",GetLastError());
         return false;
        }
     }

   if(m_ma_order)
     {
      written = file.WriteIntegerArray(m_malags);

      if(!written)
        {
         Print("Failed write operation, ",__LINE__,".Error: ",GetLastError());
         return false;
        }
     }

   written = file.WriteDoubleArray(m_model);

   if(!written)
     {
      Print("Failed write operation, ",__LINE__,".Error: ",GetLastError());
      return false;
     }

   written = file.WriteDouble(m_sse);

   if(!written)
     {
      Print("Failed write operation, ",__LINE__,".Error: ",GetLastError());
      return false;
     }


   file.Close();

   return true;

  }


Метод SaveModel позволяет сохранять модели. Для этого требуется ввод строки, которая будет новым именем модели. Сам метод записывается в двоичный файл .model, хранящийся в каталоге моделей папки общих файлов (Terminal\Common\Files\models). Сохраненный файл содержит порядок модели, а также ее параметры, если она была обучена, включая значение суммы квадратов ошибок (sse).

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CArima::LoadModel(const string model_name)
  {
   int found=StringFind(model_name,".model");

   if(found>=0)
      m_modelname=StringSubstr(model_name,0,found);
   else
      m_modelname=model_name;

   if(StringFind(m_modelname,"\\")>=0)
      return false;

   string filename="models\\"+m_modelname+".model";

   if(!FileIsExist(filename,FILE_COMMON))
     {
      Print("Failed to find model, ",__LINE__,".Error: ",GetLastError());
      return false;
     }

   CFileBin file;
   ResetLastError();
   if(file.Open(filename,FILE_READ|FILE_COMMON)<0)
     {
      Print("Failed open operation, ",__LINE__,".Error: ",GetLastError());
      return false;
     }

   uint model_order[];


   file.Seek(0,SEEK_SET);

   if(!file.ReadIntegerArray(model_order,0,4))
     {
      Print("Failed read operation, ",__LINE__,".Error: ",GetLastError());
      return false;
     }

   m_const=bool(model_order[0]);
   m_ar_order=model_order[1];
   m_diff_order=model_order[2];
   m_ma_order=model_order[3];

   file.Seek(sizeof(uint)*4,SEEK_SET);

   if(m_ar_order && !file.ReadIntegerArray(m_arlags,0,m_ar_order))
     {
      Print("Failed read operation, ",__LINE__,".Error: ",GetLastError());
      return false;
     }

   if(!m_ar_order)
      ArrayFree(m_arlags);



   if(m_ar_order)
      file.Seek(sizeof(uint)*(4+m_ar_order),SEEK_SET);


   if(m_ma_order && !file.ReadIntegerArray(m_malags,0,m_ma_order))
     {
      Print("Failed read operation, ",__LINE__,".Error: ",GetLastError());
      return false;
     }

   ArrayPrint(m_malags);

   if(!m_ma_order)
      ArrayFree(m_malags);


   if(m_ar_order || m_ma_order)
      file.Seek(sizeof(uint)*(4+m_ar_order+m_ma_order),SEEK_SET);



   if(!file.ReadDoubleArray(m_model,0,m_ma_order+m_ar_order+1))
     {
      Print("Failed read operation, ",__LINE__,".Error: ",GetLastError());
      return false;
     }

   file.Seek(sizeof(uint)*(4+m_ar_order+m_ma_order) + sizeof(double)*ArraySize(m_model),SEEK_SET);

   if(!file.ReadDouble(m_sse))
     {
      Print("Failed read operation, ",__LINE__,".Error: ",GetLastError());
      return false;
     }

   if(m_model[1])
      m_istrained=true;
   else
      m_istrained=false;

   ZeroMemory(m_differenced);
   ZeroMemory(m_leads);
   ZeroMemory(m_innovation);

   m_insize=0;

   return true;
  }


Методу LoadModel требуется имя модели, и он считывает все атрибуты ранее сохраненной модели. Оба метода возвращают либо true, либо false, а полезные сообщения об ошибках записываются в журнал терминала.

string            GetModelName(void)                      { return m_modelname;}

Метод GetModelName() возвращает имя модели. Он вернет пустую строку, если модель никогда не сохранялась, в противном случае он вернет имя, заданное при сохранении модели.

//+------------------------------------------------------------------+
//| calculate the bayesian information criterion                     |
//+------------------------------------------------------------------+
double CArima::BIC(void)
  {
   if(!m_istrained||!m_sse)
     {
      Print(m_modelname," Model not trained. Train the model first to calculate the BIC.");
      return 0;
     }

   if(!m_differenced.Size())
     {
      Print("To calculate the BIC, supply a training data set");
      return 0;
     }
   uint n = m_differenced.Size();
   uint k = m_ar_order+m_ma_order+m_diff_order+uint(m_const);

   return((n*MathLog(m_sse/n)) + (k*MathLog(n)));

  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
double CArima::AIC(void)
  {
   if(!m_istrained||!m_sse)
     {
      Print(m_modelname," Model not trained. Train the model first to calculate the AIC.");
      return 0;
     }

   if(!m_differenced.Size())
     {
      Print("To calculate the AIC, supply a training data set");
      return 0;
     }

   uint n = m_differenced.Size();
   uint k = m_ar_order+m_ma_order+m_diff_order+uint(m_const);

   return((2.0*k)+(double(n)*MathLog(m_sse/double(n))));
  }
//+------------------------------------------------------------------+


Также добавлены методы BIC и AIC. Метод BIC возвращает байесовский информационный критерий на основе значения модели sse. Метод AIC вычисляет информационный критерий акаике и работает аналогично функции BIC. Байесовский информационный критерий (БИК) и Информационный критерий Акаике (AIC) — это статистические показатели, используемые для выбора модели. Оба критерия направлены на то, чтобы сбалансировать качество подгонки модели с ее сложностью, поэтому предпочтение отдается более простым моделям, если они соответствуют данным почти так же хорошо, как и более сложные модели.

BIC и AIC отличаются тем, как они уравновешивают качество подгонки и сложность. BIC уделяет больше внимания простоте модели, чем AIC, что означает, что он отдает предпочтение даже более простым моделям, чем AIC. С другой стороны, AIC с большей вероятностью выберет более сложные модели, чем BIC. Проще говоря, BIC и AIC позволяют нам сравнивать разные модели и выбирать ту, которая лучше всего соответствует нашим данным, принимая во внимание сложность модели. Выбирая более простую модель, мы можем избежать избыточной подгонки, которая происходит, когда модель слишком сложна и слишком точно соответствует данным, что делает ее менее полезной для прогнозирования новых наблюдений.

Обе они возвращают 0 при ошибке и должны вызываться сразу после обучения модели, пока данные обучения еще загружены. Когда обученная модель инициализируется, данные, используемые для обучения, будут недоступны, поэтому ни BIC, ни AIC не могут быть рассчитаны.

uint              GetMinModelInputs(void)                 { return(m_diff_order + GetMaxArLag() + (GetMaxMaLag()*m_infactor));}

Теперь также можно запросить минимальное количество входных данных, требуемых моделью. Это делается с помощью метода GetMinModelInputs().

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CArima::Summary(void)
  {

   string print = m_modelname+" Arima("+IntegerToString(m_ar_order)+","+IntegerToString(m_diff_order)+","+IntegerToString(m_ma_order)+")\n";
   print+= "SSE : "+string(m_sse);

   int k=0;
   if(m_const)
      print+="\nConstant: "+string(m_model[k++]);
   else
      k++;
   for(uint i=0; i<m_ar_order; i++)
      print+="\nAR coefficient at lag "+IntegerToString(m_arlags[i])+": "+string(m_model[k++]);
   for(uint j=0; j<m_ma_order; j++)
      print+="\nMA coefficient at lag "+IntegerToString(m_malags[j])+": "+string(m_model[k++]);

   Print(print);

   return;

  }


Наконец, вызов Summary() записывает атрибуты модели в терминал. Строка больше не возвращается.

Реализация прогнозов

   bool              Predict(const uint num_pred,double &predictions[]);
   bool              Predict(const uint num_pred,double &in_raw[], double &predictions[]);
   bool              SaveModel(const string model_name);
   bool              LoadModel(const string model_name);
   double            BIC(void);
   double            AIC(void);

Применение модели для прогнозирования реализуется с помощью двух перегруженных методов Predict(). Оба принимают в качестве входных данных два одинаковых входных параметра:

  • num_pred - это целочисленное значение определяет количество желаемых прогнозов.
  • predictions — это массив типа double, в котором выводятся прогнозируемые значения.
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CArima::Predict(const uint num_pred,double &predictions[])
  {
   if(!num_pred)
     {
      Print("Invalid number of predictions");
      return false;
     }

   if(!m_istrained || !m_insize)
     {
      ZeroMemory(predictions);
      if(m_istrained)
         Print("Model not trained");
      else
         Print("No input data available to make predictions");
      return false;
     }

   ArrayResize(m_differenced,ArraySize(m_differenced)+num_pred,num_pred);

   ArrayResize(m_innovation,ArraySize(m_differenced));

   evaluate(num_pred);

   if(m_diff_order)
     {
      double raw[];
      integrate(m_differenced,m_leads,raw);
      ArrayPrint(raw,_Digits,NULL,m_insize-5);
      ArrayCopy(predictions,raw,0,m_insize+m_diff_order);
      ArrayFree(raw);
     }
   else
      ArrayCopy(predictions,m_differenced,0,m_insize);

   return true;
  }

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

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CArima::Predict(const uint num_pred,double &in_raw[],double &predictions[])
  {
   if(!num_pred)
     {
      Print("Invalid number of predictions");
      return false;
     }

   if(!m_istrained)
     {
      ZeroMemory(predictions);
      Print("Model not trained");
      return false;
     }

   int numofinputs=0;

   if(m_ar_order)
      numofinputs+=(int)GetMaxArLag();
   if(m_ma_order)
      numofinputs+=int(GetMaxMaLag()*m_infactor);
   if(m_diff_order)
      numofinputs+=(int)m_diff_order;

   if(in_raw.Size()<(uint)numofinputs)
     {
      ZeroMemory(predictions);
      Print("Input dataset size inadequate. Size required: ",numofinputs);
      return false;
     }

   ZeroMemory(m_differenced);

   if(m_diff_order)
     {
      difference(m_diff_order,in_raw,m_differenced,m_leads);
      m_insize=m_differenced.Size();
     }
   else
     {
      m_insize=in_raw.Size();
      ArrayCopy(m_differenced,in_raw);
     }


   if(m_differenced.Size()!=(m_insize+num_pred))
      ArrayResize(m_differenced,m_insize+num_pred,num_pred);

   ArrayFill(m_differenced,m_insize,num_pred,0.0);

   if(m_innovation.Size()!=m_insize+num_pred)
      ArrayResize(m_innovation,ArraySize(m_differenced));

   ArrayInitialize(m_innovation,0.0);

   evaluate(num_pred);

   if(m_diff_order)
     {
      double raw[];
      integrate(m_differenced,m_leads,raw);
      ArrayCopy(predictions,raw,0,m_insize+m_diff_order);
      ArrayFree(raw);
     }
   else
      ArrayCopy(predictions,m_differenced,0,m_insize);

   return true;

  }


Для второго метода Predict требуется третий массив входных параметров, который должен содержать серию входных данных, которые будут использоваться для расчета прогнозов. Оба метода возвращают логическое значение, а также используют частную функцию evaluate.

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CArima::evaluate(const uint num_p)
  {

   double pred=0;
   uint start_shift=(m_ma_order)?((!m_innovation[m_insize-1])?m_insize-(GetMaxMaLag()*m_infactor):m_insize):m_insize;
   uint d_size=(uint)ArraySize(m_differenced);

   int p_shift;

   for(uint i=start_shift; i<d_size; i++)
     {
      p_shift=0;
      pred=0;
      if(i>=m_insize)
         m_innovation[i]=0.0;
      if(m_const)
         pred+=m_model[p_shift++];
      for(uint j=0; j<m_ar_order; j++)
         pred+=m_model[p_shift++]*m_differenced[i-m_arlags[j]];
      for(uint k=0; i>=GetMaxMaLag() && k<m_ma_order; k++)
         pred+=m_model[p_shift++]*m_innovation[i-m_malags[k]];
      if(i>=m_insize)
         m_differenced[i]=pred;
      if(i<m_insize)
         m_innovation[i]=pred-m_differenced[i];
     }

   return;
  }


Метод evaluate() похож на метод func() с некоторыми небольшими отличиями. Он принимает желаемое количество прогнозов в качестве единственного аргумента и просматривает до пяти массивов в зависимости от спецификации модели. Он вычисляет новые прогнозы и при необходимости добавляет новые значения в ряд ошибок (инноваций). После этого прогнозируемые значения извлекаются и копируются в целевой массив, предоставленный методу Predict(). Методы Predict возвращают true в случае успеха и false в случае ошибки.


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

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

//+------------------------------------------------------------------+
//|                                                 TrainARModel.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"
#property script_show_inputs
#include<Arima.mqh>

enum ENUM_QUOTES
  {
   closeprices,//Close price
   medianprices//Mid price
  };

input uint MaximumSearchLag=5;
input bool DifferenceQuotes=true;
input datetime TrainingDataStartDate=D'2020.01.01 00:01';
input datetime TrainingDataStopDate=D'2021.01.01 23:59';
input string Sy="AUDUSD";//Set The Symbol
input ENUM_TIMEFRAMES SetTimeFrame=PERIOD_M1;
input ENUM_QUOTES quotestypes = closeprices;
input string SetModelName = "ModelName";

//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//---
   CArima *arima[];
   uint max_it=MaximumSearchLag;
   double sse[],quotes[],mid_prices[];


   if(!max_it)
      ++max_it;


   MqlRates prices[];

   int a_size=CopyRates(Sy,SetTimeFrame,TrainingDataStartDate,TrainingDataStopDate,prices);

   if(a_size<=0)
     {
      Print("downloaded size is ", a_size," error ",GetLastError());
      return;
     }

   ArrayResize(arima,max_it);
   ArrayResize(sse,max_it);
   ArrayResize(quotes,a_size);

   for(uint i=0; i<prices.Size(); i++)
     {

      switch(quotestypes)
        {
         case medianprices:
            quotes[i]=(prices[i].high+prices[i].low)/2;
            break;
         case closeprices:
            quotes[i]=prices[i].close;
            break;
        }
     }

   uint u=0;
   for(uint i=0; i<max_it; i++)
     {
      u=uint(DifferenceQuotes);
      arima[i]=new CArima(i+1,u,0,true);
      if(arima[i].Fit(quotes))
        {
         sse[i]=arima[i].GetSSE()*1.e14;
         Print("Fitting model ",i+1," completed successfully.");
        }
      else
        {
         sse[i]=DBL_MAX;
         Print("Fitting model ",i+1, " failed.");
        }


     }

   int index = ArrayMinimum(sse);
   Print("**** Saved model *****");
   arima[index].Summary();
//save the best model for later use.
   arima[index].SaveModel(SetModelName);

   for(int i=0; i<(int)arima.Size(); i++)
      if(CheckPointer(arima[i])==POINTER_DYNAMIC)
         delete arima[i];

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


Скрипт упрощает поиск оптимальной чистой авторегрессионной модели, которая соответствует выборке цен закрытия. Следует подчеркнуть, что это всего лишь демонстрация. Можно реализовать более сложные модели с вариациями количества и типов терминов (AR/MA), не забывая при этом о возможности указывать несмежные лаги для этих терминов. Так что возможности обширны. Пока мы ограничимся подбором чистой авторегрессионной модели.


Обучение моделей


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

Отображаются сохраненные параметры модели


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

//+------------------------------------------------------------------+
//|                                         ArimaOneStepForecast.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"
#property indicator_chart_window
#property indicator_buffers 1
#property indicator_plots   1
#include<Arima.mqh>
//--- plot PredictedPrice
#property indicator_label1  "PredictedPrice"
#property indicator_type1   DRAW_LINE
#property indicator_color1  clrRed
#property indicator_style1  STYLE_SOLID
#property indicator_width1  1
//--- input parameters
input string   ModelName = "Model name";

//--- indicator buffers
uint     NumberOfPredictions=1;
double         PredictedPriceBuffer[];
double         forecast[];
double         pricebuffer[];
uint         modelinputs;

CArima arima;
double mj[];

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- indicator buffers mapping
   SetIndexBuffer(0,PredictedPriceBuffer,INDICATOR_DATA);


   ArraySetAsSeries(PredictedPriceBuffer,true);

   if(!arima.LoadModel(ModelName))
      return(INIT_FAILED);
//---
   modelinputs=arima.GetMinModelInputs();

   ArrayResize(pricebuffer,modelinputs);

   ArrayResize(forecast,NumberOfPredictions);

   if(modelinputs<=0)
      return(INIT_FAILED);

   arima.Summary();

   arima.GetModelParameters(mj);

   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[])
  {
//---
   ArraySetAsSeries(time,true);
   int limit = (prev_calculated<=0)?1000-(int)modelinputs-1:rates_total-prev_calculated+1;

   if(NewBar(time[0]))
     {
      for(int i = limit; i>=0; i--)
        {
         if(CopyClose(_Symbol,_Period,i+1,modelinputs,pricebuffer)==modelinputs)
            if(arima.Predict(NumberOfPredictions,pricebuffer,forecast))
               PredictedPriceBuffer[i]=forecast[NumberOfPredictions-1];
        }
     }

//--- return value of prev_calculated for next call
   return(rates_total);
  }
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool NewBar(datetime c_time)
  {
   static datetime prev_time;

   if(c_time>prev_time)
     {
      prev_time=c_time;
      return true;
     }

   return false;
  }
//+------------------------------------------------------------------+


Индикатор использует выбранную модель, чтобы делать прогнозы на шаг вперед.

Индикатор

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

//+------------------------------------------------------------------+
//|                                               ArForecasterEA.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 <Trade\Trade.mqh>
#include <Trade\SymbolInfo.mqh>
#include <Trade\PositionInfo.mqh>
#include <Trade\AccountInfo.mqh>
#include <Arima.mqh>
//---
input string ModelName   ="model name"; // Saved Arima model name
input long   InpMagicNumber = 87383;
input double InpLots          =0.1; // Lots
input int    InpTakeProfit    =40;  // Take Profit (in pips)
input int    InpTrailingStop  =30;  // Trailing Stop Level (in pips)

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
int    InpOpenThreshold =1; // Differential to trigger trade (in points)
int    InpStopLoss     = 0;//   Stoploss (in pips)

//---
//+------------------------------------------------------------------+
//| ARIMA Sample expert class                                        |
//+------------------------------------------------------------------+
class CArExpert
  {
protected:
   double            m_adjusted_point;             // point value adjusted for 3 or 5 points
   CTrade            m_trade;                      // trading object
   CSymbolInfo       m_symbol;                     // symbol info object
   CPositionInfo     m_position;                   // trade position object
   CAccountInfo      m_account;                    // account info wrapper
   CArima            *m_arima;                      // Arma object pointer
   uint              m_inputs;                     // Minimum number of inputs for Arma model
   //--- indicator buffers
   double            m_buffer[];                   // close prices buffer
   double            m_pbuffer[1];                 // predicted close prices go here


   //---
   double            m_open_level;


   double            m_traling_stop;
   double            m_take_profit;
   double            m_stop_loss;

public:
                     CArExpert(void);
                    ~CArExpert(void);
   bool              Init(void);
   void              Deinit(void);
   void              OnTick(void);

protected:
   bool              InitCheckParameters(const int digits_adjust);
   bool              InitModel(void);
   bool              CheckForOpen(void);
   bool              LongModified(void);
   bool              ShortModified(void);
   bool              LongOpened(void);
   bool              ShortOpened(void);
  };


Советник снова применяет сохраненную модель для реализации простой стратегии. Основываясь на предсказании следующего бара, мы покупаем, если прогнозируемое закрытие больше, чем последнее известное закрытие. И продаем, если прогнозируемые закрытие меньше. 

Результаты тестирования на истории

                                         

  Это просто демонстрация, которую нельзя использовать для торговли на реальном счете.

Код полного класса CArima и его зависимостей содержится в zip-файле, прикрепленном в конце статьи, вместе со скриптом, индикатором и советником, описанными в статье.


Заключение

Модели авторегрессии легко обучаются с помощью терминала MetaTrader 5 и применяются во всевозможных программах. Наиболее трудная часть - это спецификация модели и выбор. Чтобы преодолеть эти проблемы и построить эффективные авторегрессионные модели в анализе рынка форекс, трейдеры должны следовать некоторым передовым методам, а именно:
  • Начните с четкого исследовательского вопроса: прежде чем строить авторегрессионную модель, трейдеры должны определить четкий исследовательский вопрос и гипотезу, которую они хотят проверить. Это помогает гарантировать, что процесс моделирования остается сфокусированным и соответствующим целям трейдера.
  • Собирайте высококачественные данные: точность модели во многом зависит от качества используемых данных. Трейдеры должны использовать надежные источники данных и следить за тем, чтобы они были чистыми, полными и соответствовали их исследовательскому вопросу.
  • Тестируйте несколько моделей: трейдеры должны тестировать несколько моделей с разной задержкой и параметрами, чтобы определить наиболее точную и эффективную модель для своих данных.
  • Проверяйте модели: после построения модели трейдеры должны проверить ее точность с помощью статистических методов.
  • Контролируйте и корректируйте модель: поскольку рыночные условия со временем меняются, эффективность модели также может меняться. Трейдеры должны отслеживать производительность своей модели с течением времени и вносить необходимые коррективы, чтобы она продолжала давать точное представление о будущих движениях цен.
Следуя этим передовым методам, трейдеры могут создавать эффективные авторегрессионные модели для анализа рынка форекс и получать ценную информацию о будущих рыночных трендах. Я надеюсь, что код будет полезен другим пользователям и, возможно, вдохновит их на дальнейшее расширение библиотеки. Удачи!


Файл
Описание
Mql5/include/Powells.mqh
файл include, содержащий объявление и определение класса CPowellsMethod
Mql5/include/Arima.mqh
файл include для класса CArima
Mql5/indicator/ArimaOneStepForecast.mql5
исходный код индикатора, показывающий, как загрузить и применить модель в индикаторе
Mql5/scripts/TrainARModel.mql5
скрипт, демонстрирующий, как обучить модель и сохранить ее для последующего использования
Mql5/experts/ArForecasterEA.mql5
советник, показывающий использование сохраненной модели в советнике.


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

Прикрепленные файлы |
Arima.mqh (22.87 KB)
Powells.mqh (17.57 KB)
ArForecasterEA.mq5 (14.83 KB)
TrainARModel.mq5 (2.77 KB)
Mql5.zip (12.64 KB)
Последние комментарии | Перейти к обсуждению на форуме трейдеров (3)
Ivan Butko
Ivan Butko | 2 авг. 2023 в 01:06
Какая бестолковая штука. 

Как только не извратился, результата не получил. 

В статье прикреплен скриншот из тестера, у которого дикий профит-фактор, но низкий фактор восстановления. Что там такое может быть и как выглядит график - секрет.
Если у автора получилось что-то, то, видимо, это особая тайна. 

Кстати, настройки отличаются от того, что в статье. В общем, странный контент. 
Yevgeniy Koshtenko
Yevgeniy Koshtenko | 2 авг. 2023 в 14:27
Браво! Долго мучился над Аримой, ничего путнего не выходило. А комментатору выше - неужели вы всерьез думаете, что вам кто-то Грааль на блюдечке положит?)
Ivan Butko
Ivan Butko | 2 авг. 2023 в 16:45
Yevgeniy Koshtenko #:
Долго мучился над Аримой, ничего путнего не выходило.

А сейчас путное вышло?

Нейросети — это просто (Часть 52): Исследование с оптимизмом и коррекцией распределения Нейросети — это просто (Часть 52): Исследование с оптимизмом и коррекцией распределения
По мере обучения модели на базе буфера воспроизведения опыта текущая политика Актера все больше отдаляется от сохраненных примеров, что снижает эффективность обучения модели в целом. В данной статье мы рассмотрим алгоритм повышения эффективности использования образцов в алгоритмах обучения с подкреплением.
Как стать успешным поставщиком сигналов на MQL5.com Как стать успешным поставщиком сигналов на MQL5.com
Основная цель статьи — предоставить простой пошаговый путь, пройдя по которому вы сможете стать лучшим поставщиком сигналов на MQL5.com. Опираясь на свои знания и опыт, я объясню, что нужно, чтобы стать успешным поставщиком сигналов, в том числе, как найти, протестировать и оптимизировать хорошую стратегию. Кроме того, я дам советы по публикации вашего сигнала, написанию убедительного описания и эффективному продвижению и управлению.
Торговые транзакции. Структуры запросов и ответов, описание и вывод в журнал Торговые транзакции. Структуры запросов и ответов, описание и вывод в журнал
В статье рассмотрим работу со структурами торговых запросов — для создания запроса, его предварительной проверки перед отправкой на сервер, ответ сервера на торговый запрос и структуру торговых транзакций. Создадим простые удобные функции для отправки торговых приказов на сервер и, на основе всего рассмотренного, создадим советник-информер о торговых транзакциях.
Теория категорий в MQL5 (Часть 10): Моноидные группы Теория категорий в MQL5 (Часть 10): Моноидные группы
Статья продолжает серию о реализации теории категорий в MQL5. Здесь мы рассматриваем группы моноидов как средство, нормализующее множества моноидов и делающее их более сопоставимыми в более широком диапазоне множеств моноидов и типов данных.