English Русский 中文 Español Deutsch Português
preview
MQL5におけるARIMAモデルによる予測

MQL5におけるARIMAモデルによる予測

MetaTrader 5トレーディングシステム | 7 8月 2023, 11:25
340 0
Francis Dube
Francis Dube

はじめに

MQL5におけるARIMA訓練アルゴリズムの実装では、ARIMAモデルを構築するためのCArimaクラスについて説明しています。このクラスをそのまま使ってモデルを適用して予測をおこなうことは技術的には可能ですが、直感的ではありません。この記事では、この欠点に対処し、予測にモデルを適用するためのメソッドを使いやすくするためにクラスを拡張します。予測の実装にまつわる複雑な問題や、このクラスに追加された新機能についても説明します。最後に、完全なクラスを使ってモデルを構築し、それをエキスパートアドバイザー(EA)と指標に適用してFX価格を予測します。


一連の入力

ARIMAモデルがデータセットの時間依存性に依存していることはよく知られています。したがって、1つ以上の予測をおこなうには、一連の入力データをモデルに与える必要があります。モデルの仕様は、入力系列の最小サイズを決定します。このことを知れば、入力系列が不適切であれば、予測をおこなうことができないか、少なくとも予測は適用されたモデルを反映していないことは明らかです。異なるタイプのARIMAモデルは、モデルの次数だけでなく、入力系列のサイズに対する要求も様々です。

純粋な自己回帰モデルに対する予測の実装は、モデルの最大ラグに等しい入力が必要なだけなので、些細なことです。移動平均項を使用する混合モデルは、予測をおこなう際に問題を引き起こします。実際のエラーやイノベーションのシリーズはまだありません。これを克服するためには、まず誤差の初期値をどのように計算するかを決めなければなりません。

このプロセスでは、まず利用可能なモデルパラメータを使用して、移動平均項を除いたモデルの初期状態を得ます。この段階では0であると想定されているためです。次に、既知の系列値を使用して、多数の冗長な予測を循環させて初期誤差値を計算します。これらの初期予測は、最終的に関心のある最終予測とは何の関係もないため、冗長です。このため、予測に必要な入力の数がより多く要求されるのは明らかです。ここで重要なことは、有効な予測をおこなうために適切な誤差系列値を導き出すのに、どれだけの冗長な予測サイクルをおこなうべきかということです。

モデルの入力数という点では、まだ考慮すべきことがあります。CArimaクラスには、連続しないラグでモデルを指定する機能があります。これにより、必要な入力の数がさらに要求されることになります。この場合、どちらのタイプ(AR/MA)でも最大のラグが入力サイズに追加されます。関数                                                                                             

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

によって定義されたモデルを考えてみましょう。この関数は、4ラグで1つのAR項を持つモデルを指定します。つまり、現在の価格は4つ前のタイムスロットの値によって部分的に決定されるということです。そのような値が1つだけ必要だとしても、入力の時間的関係を維持することを意識しなければなりません。したがって、入力要件は1つだけでなく、他のモデル要件に加えて、実際には4つ必要です。入力系列の大きさに関する最終的な決定要因は、差分が必要かどうかに依存します。


差分の会計処理

差分の要求が必要な入力の数を増やすのは、予測値の計算に関係するものではなく、差分が情報の損失につながるからです。差分系列を作成する場合、元の系列に比べて長さが常に1つ短くなります。この短い系列が最終的にモデルへの入力として渡されます。そのため一般的には、モデルで指定された差分の次数に対応する補正のために、余分な入力が必要となります。

差分が入力サイズに与える影響に加え、差分領域での予測になるため、最終的な予測値にも影響します。供給された入力系列とともに予測値を結合し、積分して、元の領域に結合値を返さなければなりません。


はるか未来を予測する

ユーザーは、1つの入力セットに基づいて、将来のいくつかのタイムスロットを予測することに興味があるかもしれません。そうすることは推奨されませんが、探求し、解き明かす価値のある道です。はるか先の未来を予測するとき、私たちはある真実を理解しなければなりません。まず、先に進めば進むほど、最終的には自己回帰項の入力として以前のタイムスロットからの予測を使わなければならなくなります。  初期入力として使用されるsriesの既知の値を超えると、誤差値を計算する手段がなくなります。将来の真の価値は未知数です。したがって、そのような時間帯の移動平均項はゼロであると仮定されます。これは、予測された系列が、指定された場合には純粋な自己回帰過程か、定数項のみによって定義された過程のどちらかに退化することにつながります。はるか先の未来に複数の予測を立てることは、固有の限界を認識しながらおこなうべきです。

CArimaクラスの追加

クラスに加えられた最初の変更は、親であるCPowellsMethodに関するものです。Optimize()メソッドにアクセス修飾子protectedが付き、クラス外からアクセスできなくなりました。当然、この変更は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メソッドはモデルの保存を可能にし、モデルの新しい名前となる文字列入力を必要とします。このメソッド自体は、共通ファイルフォルダ(TerminalCommonFilesmodels)のmodelsディレクトリに保存されたバイナリ.modelファイルに書き込みます。保存されたファイルには、モデルの次数と、もし学習済みであればそのパラメータ(平方誤差の和(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関数と同様に動作します。ベイズ情報量規準(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()という2つのオーバーロードされたメソッドを通して実装されます。どちらも同じような2つの入力を入力とします。

  • 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メソッドは、関数パラメータの数が異なります。2つのパラメータを必要とする最初のメソッドは、モデルの最適係数を導出するために使用される訓練データに基づいて予測をおこなうために使用されます。

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

  }


2番目のPredictメソッドは、予測値を計算するために使用される一連の入力を含む3番目の入力パラメータ配列を必要とします。どちらのメソッドもブール値の値を返し、evaluate private関数も利用します。

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
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()メソッドに似ていますが、若干の違いがあります。これは唯一の引数として希望する予測数を取り、モデルの仕様に応じて最大5つの配列にわたってスイープします。新しい予測値を計算し、必要に応じて誤差(イノベーション)系列に新しい値を追加します。一旦完了すると、予測値は抽出され、Predict()メソッドに供給された宛先配列にコピーされます。Predictメソッドは、成功すればtrueを返し、エラーが発生すればfalseを返します。


クラスの使用

修正されたCArimaクラスの使い方を示すために、いくつかのモデルを訓練し、総当たり検索を実行することによって最良のものを保存するスクリプトを作成します。そして、この保存されたモデルを、一歩先の予測に使うことで、指標にどのように使えるかを示します。最後に、同じモデルを使って簡単なEAを作成します。

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


EAは、保存されたモデルを再び適用してシンプルな戦略を実行します。次のバーの予測に基づき、予測終値が直近の終値より大きければ買います。そして、予想がより小さければ売ります。 

バックテスト結果

                                         

 これは単なるデモンストレーションです。ライブ口座での取引には使用しないでください。

完全なCArimaクラスとその依存関係のコードは、記事で説明したスクリプト、指標、EAとともに、記事の最後に添付したzipファイルに含まれています。


結論

自己回帰モデルは、MT5ターミナルを使用して簡単に訓練して、あらゆる種類のプログラムに適用することができます。難しいのはモデルの仕様と選択です。これらの課題を克服し、FX分析において効果的な自己回帰モデルを構築するために、トレーダーはいくつかのベストプラクティスに従うべきです。これらには次のものが含まれます。
  • 明確な研究上の質問から始める:自己回帰モデルを構築する前に、トレーダーは明確な研究上の質問と検証したい仮説を定義すべきです。これにより、モデリングプロセスの焦点を合わせ、トレーダーの目標に関連性を保たせることができます。
  • 質の高いデータを集める:モデルの精度は、使用するデータの質に大きく依存します。トレーダーは信頼できるデータソースを使用し、データがクリーンで完全であり、研究上の質問に関連していることを確認すべきです。
  • 複数のモデルをテストする:トレーダーは、ラグ長やパラメータを変えて複数のモデルをテストし、自分のデータに最も正確で効果的なモデルを決定すべきです。
  • モデルを検証する:モデルが構築されたら、トレーダーは統計的手法を使ってその精度を検証しなければなりません。
  • モデルを監視し、調整する:市場環境が時間とともに変化すれば、モデルの有効性も変化する可能性があります。トレーダーは、時間の経過とともにモデルのパフォーマンスをモニターし、将来の値動きについて正確な洞察を提供し続けられるよう、必要に応じて調整をおこなう必要があります。
これらのベストプラクティスに従うことで、トレーダーはFX分析において効果的な自己回帰モデルを構築し、将来の市場動向に関する貴重な洞察を得ることができます。このコードが他のユーザーの役に立ち、ライブラリーをさらに拡張するきっかけになることを願っています。ご成功をお祈りしています。


ファイル
詳細
Mql5/include/Powells.mqh
CPowellsMethodクラスの宣言と定義を含むインクルードファイル
Mql5/include/Arima.mqh
CArimaクラスのインクルードファイル
Mql5/indicator/ArimaOneStepForecast.mql5
指標にモデルを読み込んで適用する方法を示す指標ソースコード
Mql5/scripts/TrainARModel.mql5
モデルを訓練し、後で使用するために保存する方法を示すスクリプト
Mql5/experts/ArForecasterEA.mql5
保存されたモデルをEAで使用することを示すEA


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)
シンプルな平均回帰取引戦略 シンプルな平均回帰取引戦略
平均回帰とは、トレーダーが価格が何らかの形の均衡に戻ることを期待する逆張り取引の一種で、通常は平均値または別の中心的傾向の統計によって測定されます。
MQL5のインタラクティブGUIで取引チャートを改善する(前編):移動可能なGUI (I) MQL5のインタラクティブGUIで取引チャートを改善する(前編):移動可能なGUI (I)
MQL5で動かせるGUIを作成するための包括的なガイドで、取引戦略やユーティリティでのダイナミックなデータ表現の力を解き放ちましょう。チャートイベントのコアコンセプトに触れ、同じチャート上にシンプルで複数の移動可能なGUIをデザインし、実装する方法を学びます。この記事では、GUIに要素を追加し、機能性と美しさを向上させるプロセスについても説明します。
MQL5オブジェクト指向プログラミング(OOP)について MQL5オブジェクト指向プログラミング(OOP)について
開発者として、私たちは、特に異なる動作をするオブジェクトがある場合に、コードを重複せずに再利用可能で柔軟なソフトウェアを作成し開発する方法を学ぶ必要があります。これは、オブジェクト指向プログラミングのテクニックと原則を使うことでスムーズにおこなうことができます。この記事では、MQL5オブジェクト指向プログラミングの基本を紹介し、この重要なトピックの原則とプラクティスをソフトウェアでどのように使用できるかを説明します。
MQL5の圏論(第10回):モノイド群 MQL5の圏論(第10回):モノイド群
MQL5における圏論の実装についての連載を続けます。ここでは、モノイド集合を正規化して、より幅広いモノイド集合とデータ型にわたって比較しやすくする手段としてモノイド群を見ていきます。