任意のインジケータの計算部分をEAのコードに転送する方法

Dmitriy Gizlyk | 3 7月, 2018


目次

概論

プログラマーがインジケーターからシグナルを受信するEAを作成するとき、インジケータへのアクセスや、EAへのインジケータコードの転送を使用するか?という問題にいつも直面するものだと思います。この理由になり得るものとしては、インジケータやストラテジーを秘密にしておきたい場合や、EAを単一のファイルで配布する必要がある場合、すべてのシグナル/インジケータ・バッファが使用されていない場合に実行操作数を減らしたい場合など様々です。きっとこの質問をするのは、私が最初でも最後でもないと思います。Nikolay Kositsin氏がMetaTrader 4について同様の話題を取り扱っていました。これをMetaTrader 5プラットフォームでどのように行うことができるか見てみましょう。

1. コード転送の原則

作業に移る前に、インジケータとEAの動作の違いを確認しましょう。空のインジケータテンプレートを見てみましょう。

//+------------------------------------------------------------------+
//|                                                        Blanc.mq5 |
//|                                             Copyright 2018, DNG® |
//|                                 http://www.mql5.com/ja/users/dng |
//+------------------------------------------------------------------+
#property copyright "Copyright 2018, DNG®"
#property link      "http://www.mql5.com/ja/users/dng"
#property version   "1.00"
#property indicator_chart_window
#property indicator_buffers 1
#property indicator_plots   1
//--- plot Buffer
#property indicator_label1  "Buffer"
#property indicator_type1   DRAW_LINE
#property indicator_color1  clrRed
#property indicator_style1  STYLE_SOLID
#property indicator_width1  1
//--- indicator buffers
double         BufferBuffer[];
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- indicator buffers mapping
   SetIndexBuffer(0,BufferBuffer,INDICATOR_DATA);
   
//---
   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[])
  {
//---
   
//--- return value of prev_calculated for next call
   return(rates_total);
  }
//+------------------------------------------------------------------+

インジケータコードの始めに、バッファ配列が宣言されて他のプログラムとデータを交換します。これらの配列はタイムシリーズであり、その要素には価格バーへのリンクがあります。このリンクは、ターミナルによって直接サポートされます。インジケータは、計算結果をこれらの配列に保存します。新しいローソクが出現したときにサイズの変更やデータの転送を心配する必要はありません。EAにはこのような配列がないため、インジケータコードをEAに転送する場合は、作成する必要があります。計算部分の他に、ツールチャート上のバーと配列要素間のリンクを構成する必要があります。他方では、EAには履歴全体(インジケータ内で起こった事)で計算を行わないようにする機能もあります。使用されるデータの深さの再計算があれば十分なため、

EAにインジケータバッファを作成する必要があります。インジケータは、グラフ上に情報を表示するためのバッファだけでなく、中間的な計算のための補助バッファを持つことができることを覚えておく必要があります。これらも作成する必要があります。EAのストラテジーがインジケータラインの色の変更を想定していない場合は、描画のカラーバッファを無視しても大丈夫です。

インジケータとEAとのアーキテクチャ上の相違点は、ティックを処理する機能にあります。MetaTrader 4とは異なり、MetaTrader 5ではインジケータとEAのエントリーティックの処理が分かれています。新しいティックが入ってくると、インジケータでOnCalculate関数が呼び出されます。パラメータには、チャート上のバーの総数、前回呼び出し時のバー数、およびインジケータを計算するのに必要な時系列データが入力されます。EAでは、新しいティックはパラメータを持たないOnTick関数で処理されます。したがって、私たちは独自に時系列データへのアクセスを作成し、チャート上での変更の追跡を確実にする必要があります。

2. インジケータ計算クラスの作成

異なるパラメータを持つ1つのインジケータがしばしばEAの戦略に使用されるため、私の意見としては、OOPの機能を活用し、私達のインジケータをCIndicatorクラスに入れることは意味があると思います。

まとめてみましょう。インジケータの計算部分をアドバイザーに転送するためにこれを行う必要があります。

  1. インジケータバッファの動作を作成します。これを行うために、CArrayBufferクラスを作成します。このクラスには、データを格納し、それらに簡単にアクセスできるメソッドがあります。あとで、インジケータのバッファ数でこのようなクラスの配列の作成をします。
  2. OnCalculate関数からのインジケータの計算部分は、私達のクラスのCalculate関数に転送されます。
  3. インジケータはタイムシリーズへのアクセスを、EAの関数には存在しないOnCalculate関数のパラメータから取得します。したがって、LoadHistory関数で必要なタイムシリーズのロードを作成します。
  4. 再計算されたインジケータのデータへのアクセスを統一するために、CIndicatorクラスに必要なパラメータを持つCopyBuffer関数を作成します。 

すべての作業は、以下のスキームに要約することができます。


次にインジケータについて話しますが、ここではEAのコードで作成されたインジケータのコピーのことを言っています。

2.1. インジケータバッファを作成する

インジケータバッファを作成するには、CArrayDoubleクラスを使用します。このベースに、新しいクラスCArrayBufferを作成します。

class CArrayBuffer   :  public CArrayDouble
  {
public:
                     CArrayBuffer(void);
                    ~CArrayBuffer(void);
//---
   int               CopyBuffer(const int start, const int count, double &double_array[]);
   int               Initilize(void);
   virtual bool      Shift(const int shift);
  };

インジケータへの標準参照に似た形式でデータを取得するためにCopyBufferメソッドを作成しましょう。また、次の2つ(Initilize:バッファのデータを消去する、Shift:新しいローソクが出現したときにバッファ内のデータをシフトする)のユーティリティメソッドを追加します。関数コードは添付ファイルから直接見ることができます。

2.2. 将来のインジケータの親クラス

次のステップは、基本クラスCIndicatorにインジケータの「スケルトン」を作成することです。

class CIndicator
  {
private:
//---
   datetime             m_last_load;
public:
                        CIndicator(void);
                       ~CIndicator(void);
   virtual bool         Create(const string symbol=NULL, const ENUM_TIMEFRAMES timeframe=PERIOD_CURRENT, const ENUM_APPLIED_PRICE price=PRICE_CLOSE);
//--- Set indicator's main settings
   virtual bool         SetBufferSize(const int bars);
//--- Get indicator's data
   virtual int          CopyBuffer(const uint buffer_num,const uint start, const uint count, double &double_array[]);
   virtual double       GetData(const uint buffer_num,const uint shift);

protected:
   double               m_source_data[];
   CArrayBuffer         ar_IndBuffers[];
   int                  m_buffers;
   int                  m_history_len;
   int                  m_data_len;
//---
   string               m_Symbol;
   ENUM_TIMEFRAMES      m_Timeframe;
   ENUM_APPLIED_PRICE   m_Price;      
//--- Set indicator's main settings
   virtual bool         SetHistoryLen(const int bars=-1);
//---
   virtual bool         LoadHistory(void);
   virtual bool         Calculate()                         {  return true;   }
  };

このクラスには6つのパブリックメソッドがあります:

  • コンストラクタ
  • デストラクタ
  • クラス初期化メソッド
  • インジケータバッファのサイズを表示する方法
  • インジケータデータにアクセスするための2つの方法(①データをバッチアップロードのためのもの、②特定のアイテムへのアドレスアクセス用)

クラスのメンバの主要部分は、protectedゾーンで宣言されます。こちらで宣言します。

  • 計算のためのソースデータの配列(m_source_data)
  • インジケータバッファの配列(ar_IndBuffers)
  • インジケータバッファの数(m_buffers)、ソースデータの必要な履歴の深度(m_history_len)、インジケータ値の必要な履歴の深さ(m_data_len)を格納する変数
  • 使用シンボル(m_Symbol)と時間枠(m_Timeframe)
  • インジケータを計算するための価格タイプ(m_Price)
  • 設定の為のメソッド:ソースデータの深さ(SetHistoryLen)、タイムシリーズの履歴データのロード(LoadHistory)、インジケータの再計算(Calculate) 

すべてのメソッドは、後で特定のインジケータのニーズに合わせて調整できるように、仮想メソッドによって作成されます。クラスコンストラクタで、変数を初期化して配列を解放します。

CIndicator::CIndicator()   :  m_buffers(0),
                              m_Symbol(_Symbol),
                              m_Timeframe(PERIOD_CURRENT),
                              m_Price(PRICE_CLOSE),
                              m_last_load(0)
  {
   m_data_len=m_history_len  =  Bars(m_Symbol,m_Timeframe)-1;
   ArrayFree(ar_IndBuffers);
   ArrayFree(m_source_data);
  }

クラス初期化関数では、最初に指定された文字を使用できるかどうかをチェックします。これを行うために、市場調査でそれが有効かどうかをチェックし、そうでなければそれの選択を試みます。シンボルを使用できない場合、関数はfalseを返します。チェックが成功すると、シンボル、時間枠、および関連する変数の計算に使用された価格が保存されます。

bool CIndicator::Create(const string symbol=NULL,const ENUM_TIMEFRAMES timeframe=0,const ENUM_APPLIED_PRICE price=1)
  {
   m_Symbol=(symbol==NULL ? _Symbol : symbol);
   if(!SymbolInfoInteger(m_Symbol,SYMBOL_SELECT))
      if(!SymbolSelect(m_Symbol,true))
         return false;
//---
   m_Timeframe=timeframe;
   m_Price=price;
//---
   return true;
  }

インジケータバッファのサイズを設定するメソッドには、サイズという1つのパラメータのみがあります。この際、使用可能な履歴全体を使用する場合には、関数に「0」以下の数値を渡すだけで十分です。関数本体で、まず転送されたパラメータの値を対応する変数に保存します。次に、時系列データの履歴データが十分なものであるかチェックして、インジケータの指定した履歴を取得します。初期値が不十分な場合は、ダウンロードするデータのサイズが大きくなります。関数の最後でクリアを行い、すべてのインジケータバッファのサイズを変更します。

bool CIndicator::SetBufferSize(const int bars)
  {
   if(bars>0)
      m_data_len  =  bars;
   else
      m_data_len  =  Bars(m_Symbol,m_Timeframe);
//---
   if(m_data_len<=0)
     {
      for(int i=0;i<m_buffers;i++)
         ar_IndBuffers[i].Shutdown();
      return false;
     }
//---
   if(m_history_len<m_data_len)
      if(!SetHistoryLen(m_data_len))
         return false;
//---
   for(int i=0;i<m_buffers;i++)
     {
      ar_IndBuffers[i].Shutdown();
      if(!ar_IndBuffers[i].Resize(m_data_len))
         return false;
     }
//---
   return true;
  }

時系列の履歴データを取得する為に、LoadHistory関数を使用します。これはパラメータを持たず、前の関数に格納されたデータから初期値を導き出します。

しばしば、ローソクの形成中にインジケータの現在の表示も変化し、これが誤ったシグナルの出現を招くことがあります。このため、多くのインジケータ戦略では、形成が完了したローソクのデータを使用しています。この論理でいうと、EAのニーズに応じて、新しいローソクの形成時に履歴データを一度読み込むだけで十分ということになります。したがって、関数の始めに、新しいバーの開始の検査を行います。新しいバーが開いておらず、データが既にロードされている場合、関数を終了します。データをロードする場合は、次の関数ブロックに進みます。インジケータの計算が1つの時系列に対して十分であれば、必要なデータをソースデータの配列にロードします。インディケータが中央値、標準または加重平均価格を使用する場合は、最初に履歴データをMqlRates構造の配列にロードし、そのサイクルで必要な価格計算を行います。計算結果は、後で使用するためにソースデータの配列に格納されます。

bool CIndicator::LoadHistory(void)
  {
   datetime cur_date=(datetime)SeriesInfoInteger(m_Symbol,m_Timeframe,SERIES_LASTBAR_DATE);
   if(m_last_load>=cur_date && ArraySize(m_source_data)>=m_history_len)
      return true;
//---
   MqlRates rates[];
   int total=0,i;
   switch(m_Price)
     {
      case PRICE_CLOSE:
        total=CopyClose(m_Symbol,m_Timeframe,1,m_history_len,m_source_data);
        break;
      case PRICE_OPEN:
        total=CopyOpen(m_Symbol,m_Timeframe,1,m_history_len,m_source_data);
      case PRICE_HIGH:
        total=CopyHigh(m_Symbol,m_Timeframe,1,m_history_len,m_source_data);
      case PRICE_LOW:
        total=CopyLow(m_Symbol,m_Timeframe,1,m_history_len,m_source_data);
      case PRICE_MEDIAN:
        total=CopyRates(m_Symbol,m_Timeframe,1,m_history_len,rates);
        if(total!=ArraySize(m_source_data))
           total=ArrayResize(m_source_data,total);
        for(i=0;i<total;i++)
           m_source_data[i]=(rates[i].high+rates[i].low)/2;
        break;
      case PRICE_TYPICAL:
        total=CopyRates(m_Symbol,m_Timeframe,1,m_history_len,rates);
        if(total!=ArraySize(m_source_data))
           total=ArrayResize(m_source_data,total);
        for(i=0;i<total;i++)
           m_source_data[i]=(rates[i].high+rates[i].low+rates[i].close)/3;
        break;
      case PRICE_WEIGHTED:
        total=CopyRates(m_Symbol,m_Timeframe,1,m_history_len,rates);
        if(total!=ArraySize(m_source_data))
           total=ArrayResize(m_source_data,total);
        for(i=0;i<total;i++)
           m_source_data[i]=(rates[i].high+rates[i].low+2*rates[i].close)/4;
        break;
     }
//---
   if(total<=0)
      return false;
//---
   m_last_load=cur_date;
   return (total>0);
  }

関数の実行中にデータがロードされなかった場合、関数はfalseを返します。

インジケータデータを取得する方法は、インジケータバッファへの標準アクセスと同じように行われます。これを行うには、バッファ番号、データコピーの開始位置、必要な要素の数、データを取得するための配列を転送するパラメータにCopyBuffer関数を作成します。実行後、関数はコピーされた要素の数を返します。

int CIndicator::CopyBuffer(const uint buffer_num,const uint start,const uint count,double &double_array[])
  {
   if(!Calculate())
      return -1;
//---
   if((int)buffer_num>=m_buffers)
     {
      ArrayFree(double_array);
      return -1;
     }
//---
   return ar_IndBuffers[buffer_num].CopyBuffer(start,count,double_array);
  }

ユーザが常に実際のデータを取得するためには、関数の始めにインジケータの再計算関数を呼び出します(このクラスでは仮想関数を宣言し、インジケータの最終クラスで直接計算を行います)。インジケータの値を再計算した後、指定されたバッファがあるかどうかを確認します。バッファ番号が正しくない場合は、出力先の配列をクリアし、"-1"の結果の関数を終了します。バッファ番号が正常にチェックされた場合は、対応するバッファ配列のCopyBufferメソッドを呼び出します。

データへのアドレスアクセスの関数も同様に構築されます。

クラスの全コードとそのすべての関数は、添付ファイルにあります。

2.3. 移動平均のインジケータクラス

この技術を実証するために、私は移動平均指標(MA)を選びました。私の選択は偶然ではありません。この技術的な分析インジケータは、トレーダーが古典的な形で使用するだけでなく、他の指標を構築するためにも広く使用されています。この他、MACD、Alligatorなど多くのものがあります。さらに、「パッケージ」供給にはMAの例があり、iCustom関数を介してデータを受け取り、EAのインジケータへのアクセス速度とデータ計算の速度を比較することができます。

私たちはCMAクラスでMAを計算します。私たちのクラスには、コンストラクタ、デストラクタ、初期化メソッド(Create)、およびインジケータの履歴データの深さを設定するメソッド(私達が再作成するもの)の4つのパブリックメソッドがあります。クラスのインジケータデータへのアクセス方法は、親クラスから継承されます。

class CMA : public CIndicator
  {
private:
   int               m_Period;
   int               m_Shift;
   ENUM_MA_METHOD    m_Method;
   datetime          m_last_calculate;
   
public:
                     CMA();
                    ~CMA();
   bool              Create(const string symbol, const ENUM_TIMEFRAMES timeframe, const int ma_period, const int ma_shift, const ENUM_MA_METHOD ma_method, const ENUM_APPLIED_PRICE price=PRICE_CLOSE);
   virtual bool      SetBufferSize(const int bars);
   
protected:
   virtual bool      Calculate();
   virtual double    CalculateSMA(const int shift);
   virtual double    CalculateEMA(const int shift);
   virtual double    CalculateLWMA(const int shift);
   virtual double    CalculateSMMA(const int shift);
  };

上記のクラスの見出しからわかるように、インジケータを直接計算するための要素がこの段階で表示されます。これらは、期間、シフト、およびインジケータの計算方法を格納するための専用変数です。protectedブロックでは、Calculateインジケータの仮想計算機能を書き換えます。指定したインジケータの計算方法に応じて、サブ関数CalculateSMA、CalculateEMA、CalculateLWMAまたはCalculateSMMAを呼び出します。

クラスのコンストラクタでは、変数を初期化し、インジケータバッファの数を指定し、インジケータバッファを作成します。

CMA::CMA()  :  m_Period(25),
               m_Shift(0),
               m_Method(MODE_SMA)
  {
   m_buffers=1;
   ArrayResize(ar_IndBuffers,1);
  }

クラス初期化関数のパラメータでは、必要なシンボル、時間枠、およびインジケータを計算するためのパラメータを指定します。関数自体では、まず親クラスの初期化関数を呼び出します。次に、この平均期間の正当性をチェックします(正でなければなりません)。その後、インジケータパラメータを適切なクラス変数に保存し、インジケータバッファとロードされた時系列データの為の履歴の深さを設定します。エラーが発生した場合、関数はfalseを返します。初期化に成功すると、trueを返します。

bool CMA::Create(const string symbol,const ENUM_TIMEFRAMES timeframe,const int ma_period,const int ma_shift,const ENUM_MA_METHOD ma_method,const ENUM_APPLIED_PRICE price=1)
  {
   if(!CIndicator::Create(symbol,timeframe,price))
      return false;
//---
   if(ma_period<=0)
      return false;
//---
   m_Period=ma_period;
   m_Shift=ma_shift;
   m_Method=ma_method;
//---
   if(!SetBufferSize(ma_period))
      return false;
   if(!SetHistoryLen(2*ma_period+(m_Shift>0 ? m_Shift : 0)))
      return false;
//---
   return true;
  }

Calculate関数はインジケータを直接計算します。前に親クラスの作成する際に、新しいローソクが始まった時に時系列データの履歴データを読み込むことにしました。したがって、インジケータデータを同じ頻度で再計算します。これを行うために、関数の始めで、新しいローソクの始まりをチェックします。現在のバーがすでに計算されている場合は、結果がtrueの関数を終了します。

次に、新しいバーが開かれると、時系列のデータを読み込む関数が呼び出されます。履歴データが正常に読み込まれた場合は、インジケータの最終計算後に生成されたローソクの数をチェックします。新しいローソクの数がインジケータバッファのサイズよりも大きい場合は、再度初期化します。新しいローソクの数が少なければ、バッファ内のデータを現れたバーの数にシフトします。次に、新しい要素のみを再計算します。

ここではインジケータバッファの新しい要素を再計算するためのサイクルを構成します。再計算された要素が、現在のインジケータバッファのサイズを超えている場合(これは、計算が最初に開始されたとき、または接続が切断された後、新しいローソクの数がバッファサイズを超えたときに起こりえます)、データはAddメソッドによってバッファに追加されます。再計算された要素が既存のバッファのサイズに収まる場合、要素の値はUpdateメソッドによって更新されます。インジケータ値の直接計算は、平均化方法に対応するサブ関数で実行されます。計算ロジックは、MetaTrader 5の標準出荷のインジケータCustom Moving Average.mq5から得ています。

インジケータバッファの再計算が成功した後、最後の再計算の時間を保存し、結果をtrueにして関数を終了します。

bool CMA::Calculate(void)
  {
   datetime cur_date=(datetime)SeriesInfoInteger(m_Symbol,m_Timeframe,SERIES_LASTBAR_DATE);
   if(m_last_calculate==cur_date && ArraySize(m_source_data)==m_history_len)
      return true;
//---
   if(!LoadHistory())
      return false;
//---
   int shift=Bars(m_Symbol,m_Timeframe,m_last_calculate,cur_date)-1;
   if(shift>m_data_len)
     {
      ar_IndBuffers[0].Initilize();
      shift=m_data_len;
     }
   else
      ar_IndBuffers[0].Shift(shift);
//---
   for(int i=(m_data_len-shift);i<m_data_len;i++)
     {
      int data_total=ar_IndBuffers[0].Total();
      switch(m_Method)
        {
         case MODE_SMA:
           if(i>=data_total)
              ar_IndBuffers[0].Add(CalculateSMA(i+m_Shift));
           else
              ar_IndBuffers[0].Update(i,CalculateSMA(i+m_Shift));
           break;
         case MODE_EMA:
           if(i>=data_total)
              ar_IndBuffers[0].Add(CalculateEMA(i+m_Shift));
           else
              ar_IndBuffers[0].Update(i,CalculateEMA(i+m_Shift));
           break;
         case MODE_SMMA:
           if(i>=data_total)
              ar_IndBuffers[0].Add(CalculateSMMA(i+m_Shift));
           else
              ar_IndBuffers[0].Update(i,CalculateSMMA(i+m_Shift));
           break;
         case MODE_LWMA:
           if(i>=data_total)
              ar_IndBuffers[0].Add(CalculateLWMA(i+m_Shift));
           else
              ar_IndBuffers[0].Update(i,CalculateLWMA(i+m_Shift));
           break;
        }
     }
//---
   m_last_calculate=cur_date;
   m_data_len=ar_IndBuffers[0].Total();
//---
   return true;
  }

このクラスでも、インジケータバッファの目的のサイズを設定する親クラスの仮想関数を書き換えます。これは、インジケータバッファの深さと時系列データの履歴データの深さとの間の一致を検証するために必要です。親クラスでは、時系列内の項目の数がインジケータバッファ内の要素の数より少なくなるように指定しました。そして、MAを計算するためには、少なくとも平均期間の間、時系列の要素の数は指標バッファのサイズよりも大きくなければなりません。

3.EAへのインジケータクラスの追加例

私がこの記事を書くことを計画した時の私の目標の1つは、EA内部のデータ処理のスピードを比較し、インジケータからデータを取得することでした。したがって、クラスの動作を実証するために、私は本格的なトレーディングロボットを作成しないことにしました。インジケータのシグナルを処理する自分のロジックを完成させることができる、EAの準備に注意をすることを勧めたいと思います。

EAの新ファイルのTest_Class.mq5を作成します。その入力パラメータは、使用されるインジケータのパラメータに似ています。

input int                  MA_Period   =  25;
input int                  MA_Shift    =  0;
input ENUM_MA_METHOD       MA_Method   =  MODE_SMA;
input ENUM_APPLIED_PRICE   MA_Price    =  PRICE_CLOSE;

グローバル的には、インジケータクラスのインスタンスとインジケータデータを取得する配列を宣言します。

CMA   *MA;
double c_data[];

OnInit関数では、インジケータクラスのインスタンスを初期化し、ソースデータをそのインスタンスに渡す必要があります。

int OnInit()
  {
//---
   MA=new CMA;
   if(CheckPointer(MA)==POINTER_INVALID)
      return INIT_FAILED;
//---
   if(!MA.Create(_Symbol,PERIOD_CURRENT,MA_Period,MA_Shift,MA_Method,MA_Price))
      return INIT_FAILED;
   MA.SetBufferSize(3);
//---
   return(INIT_SUCCEEDED);
  }

EAの動作の終わりに、メモリをクリアし、OnDeinit関数でクラスのインスタンスを削除する必要があります。

void OnDeinit(const int reason)
  {
//---
   if(CheckPointer(MA)!=POINTER_INVALID)
      delete MA;
  }

これでクラスの準備が完了しました。残るはOnTick関数でインジケータデータの受信を追加するだけです。関数の冒頭で、新しいバーの開始をチェックし、クラスのCopyBufferメソッドを呼び出します。次に、独自のシグナル処理コードと取引操作の完了の追跡が行われます。

void OnTick()
  {
//---
   static datetime last_bar=0;
   datetime cur_date=(datetime)SeriesInfoInteger(_Symbol,PERIOD_CURRENT,SERIES_LASTBAR_DATE);
   if(last_bar==cur_date)
      return;
   last_bar=cur_date;
//---
   if(!MA.CopyBuffer(MAIN_LINE,0,3,c_data))
      return;

//---
//    ここにシグナルと取引操作の処理コードを追加します
//---
   return;
  }

添付ファイルには、すべてのプログラムとクラスの完全なコードがあります。

4.転送されたインジケータを使用する "コスト"

インジケータコードのシフトはEAの仕事にどのような影響を与えるのかというもう一つの重要な疑問があります。それに答えるために、いくつかの実験を行います。

4.1. 実験1

私はすでに偶然ではなくMAの指標を選んだと述べています。ここでは同じデータを取得する速度を3つの以下の方法で確認できます。

  • インジケータターミナル(iMA)に内蔵された機能を介する。
  • アナログのカスタムインジケータ(iCustom)の呼び出しを介する。
  • EAの中で直接計算する。

最初に頭に浮かぶのは、MetaEditorエディタのプロファイリング機能を使用することです。これを行うには、トレードしないEAを作成します。これは、3つのソースすべてから同時にデータを受信します。私は、このEAの動作の完全な説明をここに書いておく必要はないと思います。このコードは添付ファイルからみることができます。実験の純度のために、新しいローソクが開けられたときにのみ、3つのデータソースすべてにアクセスしたということのみ述べたいと思います。

プロファイリングはM15時間枠で15ヶ月間のストラテジーテスターで実施されました。実験の結果、私は以下のデータを入手しました。

機能 平均実行時間、マイクロ秒 合計時間の割合
OnTick
99.14%
新しいバーの開始を確認 0.528 67,23%
内部計算
21.524 2.36%
      chCopyCloseを含む 1.729  0.19%
 iMA  2.231  0.24%
 iCustom  0.748  0.08%
 OnInit  241439  0.86%
 iCustomハンドルの取得  235676  0.84%

まず「気になる」ことは、iCustom関数を使ってインジケータハンドルを取得する時間が長いことです。これはインジケータクラスの初期化やiMA関数を介してのインジケータハンドルの取得時間が数十倍になります。同時に、iCustom関数で初期化されたインジケータからのデータ受信は、iMAインジケータからのデータ受信よりも3倍速く、クラス内のインジケータ値の計算より30倍速いです。


私達のインジケータクラスのさまざまな関数の実行時間をより詳細に見てみましょう。CopyClose関数による履歴データの受信時間は、インジケータデータの取得時間と同等です。インジケータは計算にほとんど時間を使わないということでしょうか?実際は少し違います。インジケータの値への非同期アクセスは、MetaTrader 5アーキテクチャで構成されています。言い換えれば、インジケータハンドルを受け取ると、それがチャートに添付されます。次に、このインジケータは、EAのフローの外側で計算を行います。それらは、時系列データを受信するのと同様に、データ転送の段階でのみ相互作用します。したがって、これらの操作を実行する時間は同等になります。

上記の実験を要約してみます。MetaEditorプロファイリング関数を使用して、EAで使用されているインジケータの計算時間を見積りのエラーを証明しました。

4.2. 実験2

私たちは4つの別々のEAを作成します。

  1. 標準で、関数を実行しない空のEA。これは相場の履歴選別する為に、ターミナル自体が費やす時間の評価に役立ちます。
  2. インジケータクラスで値を計算することでデータを受け取るEA。
  3. iMAインディケーターからデータを受け取るEA。
  4. カスタムインジケータからデータを受け取るEA。

その後、ストラテジーテスターの11回のパスで最適化を開始し、1回のパスの平均時間を比較します。

実験2実験2

EAの計算を使用すると、時間を節約できるという結果になりました。最も時間のかかる作業は、カスタムインジケータからデータを取得することでした。


注意:実験ではMAは終値で計算されています。このインジケータの計算部分は非常に単純なものです。そのため、計算が複雑になった場合どうなるのかという疑問が生じます。これを別の実験をすることで明らかにしましょう。

4.3. 実験3

この実験では前の手順を繰り返しますが、計算部分の負荷を増やすために、インジケータは加重平均価格の線形加重平均のために計算されました。

実験3実験3

データを取得するすべての方法で消費時間が増えたことがわかります。この場合、1パスの時間の比例した増加が観察され、全体的にこれまでの実験の結果を裏付けることになりました。


まとめ

この記事では、インジケータの計算部分をEAに移す技術を紹介しました。OOPの使用により、インジケータの最終データへのアクセスを、インジケータバッファからの標準的な情報取得に検索にかなり近づけることができます。これにより、EAが修正されたときに、EAのソースコードへの介入が最小限に抑えられます。

実施された実験の結果に基づけば、このようなアプローチは、テストやEAの最適化の時間を節約することもできます。しかし、EAがリアルタイムで動作する場合、この利点はMetaTrader 5のマルチスレッドアーキテクチャによって平均化することががあるかもしれません。

記事で使用されているプログラム:

#
 名前
タイプ 
説明 
1 Indicarot.mqh  クラスライブラリ  インジケーターを移動するための基本クラス
2 MA.mqh  クラスライブラリ  EAの中のMAインジケータを計算するためのクラス
3 Test.mq5  EA  実験の1用のEA
4 Test_Class.mq5  EA  EAの中でインジケータ計算をするEA(実験2と3)
5 Test_iMA.mq5  EA  iMAを介してインジケータデータを取得するEA
6 Test_iCustom.mq5  EA  iCustomを介してでインジケータデータを取得するEA