インディケーターを別のインディケーターに適用

MetaQuotes | 13 8月, 2015

はじめに

他のインディケーター値に適用されるインディケーターの改善タスクを考えましょう。本記事では前の記事 "MQL5: 自分自身のインディケーターの作成"で考察したTrue Strength Index (TSI)を続けて使用します。


他のインディケーター値に基づいたカスタムインディケーター

OnCalculate()関数呼び出しのショートフォームを使ってインディケーターを書く場合、価格データだけでなく、他のインディケーターデータによっても計算できる事実を見逃すかもしれません。 (内蔵またはカスタムのインディケーターにかかわらず)

簡単な例を取りましょう。内蔵 RSI インディケーターを標準設定でチャートに添付し、True_Strength_Index_ver2.mq5カスタムインディケーターをRSI インディケーター ウインドウにドラッグ しましょう。表示ウインドウのパラメータ タブは、このインディケーターが Previous Indicator's Data (RSI(14))に適用されべきであることを指定します。

結果は予測とはだいぶ違います。TSIインディケーターの追加ラインはRSI インディケーター ウインドウに表示されなく、Data Windowではその値がまた不明瞭であることがわかります。

RSI値は全ヒストリーに渡って定義されるにもかかわらず、 TSI値 (RSIデータに適用される) は全く不在か(始めの部分で)または常に-100に等しいです。

こうような事はbegin パラメータの値が、我々のTrue_Strength_Index_ver2.mq5のOnCalculate() でどこにも使われていないために起こります。 beginパラメータは価格[] 入力 パラメータにある空の値の数を指定します。これらの空の値はインディケーター 値の計算に使えません。 OnCalculate() 関数呼び出しの最初のフォームの定義を思い出しましょう。
int OnCalculate (const int rates_total,      // price[] array length
                 const int prev_calculated,  // number of bars calculated after previous call
                 const int begin,            // start index of meaningful data
                 const double& price[]       // array for calculation
   );

価格定数の一つを指定している価格データにインディケーターを適用する時、各バーには指定された価格タイプがあるため begin パラメータは0に等しいです。そのため 価格[] 入力配列は常に最初のエレメント 価格[0]から始まる正しい情報を持っています。. かならずしもそうなるとは限りませんただし、他のインディケーターデータを計算のソースとして指定すると、かならずしもそうなるとは限りません。


OnCalculate()の begin パラメータ

他のインディケーターのデータを使って計算されているか価格[] 配列に含まれる値を確認しましょう。そのためには、OnCalculate() 関数に、確認したい値を出力するコードを追加します。 OnCalculate() 関数の始まりはこのように見えます。

//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate (const int rates_total,    // price[] array length;
                 const int prev_calculated,// number of available bars after previous call;
                 const int begin,          // start index of meaningful data in price[] array 
                 const double &price[])    // data array, that will be used for calculations;
  {
//--- flag for single output of price[] values
   static bool printed=false;
//--- if begin isn't zero, then there are some values that we shouldn't take into account

   if(begin>0 && !printed)
     {
      //--- let's output them
      Print("Data for calculation begin from index equal to ",begin,
            "   price[] array length =",rates_total);

      //--- let's show the values that we shouldn't take into account for calculation
      for(int i=0;i<=begin;i++)
        {
         Print("i =",i,"  value =",price[i]);
        }
      //--- set printed flag to confirm that we have already logged the values
      printed=true;
     }

もう一度、我々の修正版インディケーターをRSI(14) ウインドウにドラグ&ドロップし、計算のために前のインディケーター データを指定しましょう。 RSI(14) インディケーター値が使われている場合、プロットされず計算の考慮に入れるべきでない値が見えます。


インディケーターバッファーとDBL_MAXの空の値

0 から13のインデックスを持つ価格[] 配列の最初の14 エレメントは 1.797693134862316e+308と同じ値を持ちます。これはインディケーターバッファー内の空の値を指摘するために使用される内蔵 EMPTY_VALUE 定数のため、この数によく出くわします。

空の値をゼロで埋めることは、この値が他のインディケーターによる計算結果にもなりえるので万能の解決策ではありません。このため、 クライアントターミナルの全ての内蔵インディケーター は空の値に戻ります。1.797693134862316e+308の値はdoubleタイプの最大化可能値のため選ばれました。 またMQL5では利便性のためDBL_MAX 定数として表されています。

ダブルタイプ数 が空か否かを確認するには、EMPTY_VALUEまたはDBL_MAX 定数を比較できます。両方の変数は等しいですが、コードでEMPTY_VALUE 定数を使い明瞭にさせるといいです。

//+------------------------------------------------------------------+
//| returns true for "empty" values                               |
//+------------------------------------------------------------------+
bool isEmptyValue(double value_to_check)
  {
//--- if the value is equal DBL_MAX, it has an empty value
   if(value_to_check==EMPTY_VALUE) return(true);
//--- it isn't equal DBL_MAX
   return(false);
  }

DBL_MAX はとても巨大な数でRSI インディケーター は本質的にこのような値に戻ることができません!配列(インデックス14)の15個目のエレメントだけが50に等しい合理的な値を持ちます。計算されるべきソースデータとして、このインディケーター全く知らないとしても、begin パラメータを使ってこのようなケースのデータ処理を適切に組織化できます。 具体的には、 計算で これらの空の値を避けなければなりません。


begin パラメータとPLOT_DRAW_BEGIN プロパティの関係

OnCalculate() 関数に転送されるbegin パラメータと作画なしで初期バー数を定義するPLOT_DRAW_BEGINプロパティには密接な関係があることに注意するべきです。MetaTrader5標準パッケージからRSIソースコードを調べると、 OnInit() 関数に以下のコードが見えます。

//--- sets first bar from what index will be drawn
   PlotIndexSetInteger(0,PLOT_DRAW_BEGIN,ExtPeriodRSI);

これは、インデックス 0 のグラフィックプロットがExtPeriodRSI(RSIインディケーター期間を指定する 入力変数)に等しいインデックスを持つバーからしか始まらず、そのバー以前ではプロットがないことを意味します。

MQL5 言語では、インディケーターデータBに基づくインディケーターAの計算は常にインディケーターのゼロバッファー値で実行されます。インディケーター B のゼロバッファー 値は価格[] 入力パラメータ としてインディケーターAのOnCalculate() 関数に転送されます。本質的に、 ゼロバッファー はSetIndexBuffer() 関数と一緒にゼログラフィカルプロットに割り当てられます。そのため、

PLOT_DRAW_BEGIN プロパティにbegin パラメータを転送するルール: 他の(base) インディケーターデータ Bに基づくインディケーター A の計算では、OnCalculate() 関数のbegin入力パラメータ値がbase インディケーター B のゼログラフィカルプロッティングのPLOT_DRAW_BEGIN プロパティ 値に等しいです。

そのため、 期間14でRSI インディケーター (インディケーター B) を作成していたら、そのデータに基づき、カスタムインディケーター True Strength Index (インディケーター A) を作成したことになります。

最初のバーによってはインディケーター値が判定されないため、TSI インディケーターがチャートのはじめから作画されないことを覚えておいて下さい。以下の場合、TSI インディケーターにラインでプロットされる最初のバーインデックスはr+s-1に等しいです。

r+s-1 未満のインデックスを持つバーでは、TSI インディケーターをプロットする値がありません。そのため, TSI インディケーターの計算に使われる最終的なMA2_MTMBuffer[] と EMA2_AbsMTMBuffer[] 配列では、データに追加オフセットがあり、 r+s-1 インデックスから始まります。詳細は 「MQL5: 自分のインディケーターの作成」 の記事にあります。

OnInit() 関数に最初の r+s-1 バーの作画を無効にする記載があります。

//--- first bar to draw
   PlotIndexSetInteger(0,PLOT_DRAW_BEGIN,r+s-1);

入力データの始めが begin バーで前にシフトされたので、それを考慮に入れ 、OnCalculate() 関数のbegin バーによってデータ作画の始まりのポジションを増やさなければなりません。

if(prev_calculated==0)
     { 
      //--- let's increase beginning position of data by begin bars,
      //--- because we use other indicator's data for calculation
      if(begin>0)PlotIndexSetInteger(0,PLOT_DRAW_BEGIN,begin+r+s-1);
     }
ここでTSIインディケーター値を計算するbegin パラメータを考慮していきます。また、もし他のインディケーターが計算にTSI値を使う場合、begin パラメータは移転されたプロパティです。 : beginother_indicator=beginour_indicator+r+s-1. そのため, 1つのインディケーターを別のインディケーター値に負荷するルールを作れます。

インディケーター負荷のルー ル もしあるカスタムインディケーターA がNa ポジションから作画され (最初のNa 値はプロットされない) 、他のインディケーターBに基づく場合 、ポジションNbから作画されます。 結果としてのインディケーター A{B} は Nab=Na+Nb ポジションから作画されます。 そこではA{B} はインディケーター Aがインディケーター Bのゼロバッファー 値に計算されることを意味します。

そのため, TSI (25,13) {RSI (14)}TSI (25,13) インディケーター がRSI (14) インディケーター値でできていることを意味します。負荷の結果、今やデータの始めは (25+13-1)+14=51です。つまり、インディケーターの作画が52個めのバーから始まります。 (バーインデックスは0から始まる)


インディケーター計算の使用にbegin 値を追加

価格[] 配列の意味ある値が常にbegin パラメータで指定されたポジションから始まることはすでに知っています。コードをステップごとに修正していきましょう。まずはMTMBuffer[] とAbsMTMBuffer[] 配列値を計算するコードです。begin パラメータなしでは、配列埋め込みがインデックス 1で始まりました。

//--- calculate values for mtm and |mtm|
   int start;
   if(prev_calculated==0) start=1;  // start filling MTMBuffer[] and AbsMTMBuffer[] arrays from 1st index 
   else start=prev_calculated-1;    // set start equal to last array index
   for(int i=start;i<rates_total;i++)
     {
      MTMBuffer[i]=price[i]-price[i-1];
      AbsMTMBuffer[i]=fabs(MTMBuffer[i]);
     }

(begin+1) ポジションから始め、修正されたコードは以下のように見えます。 (コード変更は太字にしてあります。)

//--- calculate values for mtm and |mtm|
   int start;
   if(prev_calculated==0) start=begin+1;  // start filling MTMBuffer[] and AbsMTMBuffer[] arrays from begin+1 index 
   else start=prev_calculated-1;           // set start equal to the last array index
   for(int i=start;i<rates_total;i++)
     {
      MTMBuffer[i]=price[i]-price[i-1];
      AbsMTMBuffer[i]=fabs(MTMBuffer[i]);
     }

価格[0]から価格[begin-1]までの値は計算に使えないので、価格[begin]から始めます。MTMBuffer[] とAbsMTMBuffer[] 配列の計算に使う最初の値は以下のように見えます。

MTMBuffer[begin+1]=price[begin+1]-price[begin];
      AbsMTMBuffer[begin+1]=fabs(MTMBuffer[begin+1]);

このため、 for サイクル 内のstart 変数は今や 1でなく初期データstart=begin+1を持ちます。


依存配列用の begin の説明

MTMBuffer[] とAbsMTMBuffer[] 配列の指数平滑に入ります。ルールはまたシンプルです。もし 初期ポジションのベース配列がbegin バーで増えた場合、全ての依存配列の初期ポジションはまたbegin で増えるはずです。

//--- calculating the first moving average on arrays
   ExponentialMAOnBuffer(rates_total,prev_calculated,
                         1,               // index of the starting element in array 
                         r,               // period of exponential average
                         MTMBuffer,       // source buffer for average
                         EMA_MTMBuffer);  // target buffer
   ExponentialMAOnBuffer(rates_total,prev_calculated,
                         1,r,AbsMTMBuffer,EMA_AbsMTMBuffer);

ここで、MTMBuffer []とAbsMTMBuffer []は基礎配列で、これらの配列の計算値は今やbeginより大きいインデックスで始まります。 そのため、このオフセットをExponentialMAOnBuffer() 関数に追加します。

今はこのブロックはこのように見えます。:

//--- calculating the first moving average on arrays
   ExponentialMAOnBuffer(rates_total,prev_calculated,
                         begin+1,        // index of the starting element in array
                         r,               // period for exponential average
                         MTMBuffer,       // source buffer for average
                         EMA_MTMBuffer);  // target buffer
   ExponentialMAOnBuffer(rates_total,prev_calculated,
                         begin+1,r,AbsMTMBuffer,EMA_AbsMTMBuffer);

ご覧のとおり、修正全体はbeginパラメータで定義された開始データの増加に対応するためのものでした。 複雑なことは何もありません。同じように第二ブロックの平滑を変更します。

ビフォー

//--- calculating the second moving average on arrays
   ExponentialMAOnBuffer(rates_total,prev_calculated,
                         r,s,EMA_MTMBuffer,EMA2_MTMBuffer);
   ExponentialMAOnBuffer(rates_total,prev_calculated,
                         r,s,EMA_AbsMTMBuffer,EMA2_AbsMTMBuffer);

アフター

//--- calculating the second moving average on arrays
   ExponentialMAOnBuffer(rates_total,prev_calculated,
                         begin+r,s,EMA_MTMBuffer,EMA2_MTMBuffer);
   ExponentialMAOnBuffer(rates_total,prev_calculated,
                         begin+r,s,EMA_AbsMTMBuffer,EMA2_AbsMTMBuffer);

同じように最後の計算ブロックを変更 します。

ビフォー

//--- calculating values of our indicator
   if(prev_calculated==0) start=r+s-1; // set initial index for input arrays
   else start=prev_calculated-1;       // set start equal to last array index
   for(int i=start;i<rates_total;i++)
     {
      TSIBuffer[i]=100*EMA2_MTMBuffer[i]/EMA2_AbsMTMBuffer[i];
     }

アフター

//--- calculating values of our indicator
   if(prev_calculated==0) start=begin+r+s-1; // set initial index for input arrays
   else start=prev_calculated-1;              // set start equal to last array index
   for(int i=start;i<rates_total;i++)
     {
      TSIBuffer[i]=100*EMA2_MTMBuffer[i]/EMA2_AbsMTMBuffer[i];
     }

このインディケーターの調整は終わりです。これでOnCalculate() 内価格[] 入力配列の最初のbeginの空の値をスキップし、この逸脱によるオフセットを考慮します。しかし別のインディケーターがTSI値を計算に使えることを覚えておかなければなりません。このため、インディケーター空の値をEMPTY_VALUEと設定します。


インディケーターバッファー初期化は必要?

MQL5では デフォルトによって配列は定義値と初期化されません。SetIndexBuffer()関数によってインディケーターバッファーに指定される配列でも同じことが当てはまります。もし配列がインディケーターバッファーであるなら、そのサイズは値OnCalculate()関数内のrates_totalパラメータの値によります。

全てのインディケーターバッファー をArrayInitialize()関数を使ってEMPTY_VALUE 値で初期化したくなるかもしれません。 例えば、 OnCalculate()の始めで一気に。

//--- if it is the first call of OnCalculate() 
   if(prev_calculated==0)
     {
      ArrayInitialize(TSIBuffer,EMPTY_VALUE);
     }

しかし以下の理由で推奨されません。クライアントターミナルが起動中の間、 シンボルの新しい気配値を受信し、そのデータはこのインディケーターの計算に使われます。しばらくすると、バーの数が増えます。 そのためクライアントターミナルがこのインディケーターバッファーの追加メモリを貯えます。

しかし、どんな 配列のメモリ再分配の間でも 初期化が実行されないので、新しい (「添付された」) 配列 エレメント値はどのような値にもなりえます。初期の初期化は間違った確実性を与えます。明示されていない全ての配列エレメントは初期化で指定された値で記入されます。もちろんそれは真実ではありません。 変数値や配列エレメントが私たちにとって必要な値で初期化されるとは考えるべきではありません。

このインディケーターバッファーの各エレメントにおいて値を設定するべきです。バーの値がこのインディケーターアルゴリズムで定義されていない場合、空の値で明示的に設定するべきです。例えば、このインディケーターバッファーが除算で計算されると、ケースによっては除数値は0です。

MQL5において0で割ることはクリティカル実行時エラーになり、mql5-プログラムの即時終了になることを知っています。コードでこの特別なケースを取り扱い0で割ることを防ぐのではなく、バッファーエレメント値を設定する必要があります。おそらく、この 作画スタイル用に空として振当てた値を使う方がいいです。

例えば、ある作画スタイルではPlotIndexSetDouble()関数を使って、ゼロを空の値として定義しました。

PlotIndexSetDouble(plotting_style_index,PLOT_EMPTY_VALUE,0);  

この作画におけるこのインディケーターバッファーのすべての空の値は明示的にゼロの値に定義する必要があります。

if(divider==0)
      IndicatorBuffer[i]=0;
   else
      IndicatorBuffer[i]=...

その上、もしDRAW_BEGINが作画に指定されている場合、0からDRAW_BEGINまでのこのインディケーターバッファーのすべてのエレメントは自動的にゼロで記入されます。


結論

では簡単にまとめましょう。インディケーターが他のインディケーターに基づいて正しく計算されるにはいくつか必要条件があります。 (さらに他のmql5-プログラムsでの使用に適切にするため):

  1. 内蔵インディケーターの空の値は EMPTY_VALUE 定数値で記入され、ダブルタイプ (DBL_MAX)の最大値にまさに等しいです。
  2. インディケーターの重要値のスタートインデックスの詳細については、OnCalculate()内ショートフォームbegin入力パラメータを分析するべきです。
  3. 指定作画スタイル のために最初のN値の作画を禁止するため、 DRAW_BEGIN パラメータを以下のコードを使って設定します。
    PlotIndexSetInteger(plotting_style_index,PLOT_DRAW_BEGIN,N);
  4. もしDRAW_BEGINが作画に指定されている場合、インデックス0からDRAW_BEGINまでの全てのこのインディケーターバッファー エレメントは自動で空の値で記入されます。 (ディフォルトはEMPTY_VALUE)
  5. 他のインディケーターデータの正しい使用のために、自分のインディケーターのOnCalculate() 関数内にbeginバーで追加的オフセットを追加しましょう。
    //--- if it's the first call 
       if(prev_calculated==0)
         { 
          //--- increase position of data beginning by begin bars,
          //--- because other indicator's data use      
          if(begin>0)PlotIndexSetInteger(plotting_style_index,PLOT_DRAW_BEGIN,begin+N);
         }
  6. 以下のコードを使って、EMPTY_VALUEとは異なる自分の空の値をOnInit()関数内で指定できます。
    PlotIndexSetDouble(plotting_style_index,PLOT_EMPTY_VALUE,your_empty_value);
  7. 以下のコードを使い、このインディケーターバッファーのワンタイム初期化に頼ってはいけません。
    ArrayInitialize(buffer_number,value);
    空の値を含めOnCalculate() 関数のすべてのこのインディケーターバッファーを、明示的に一貫して設定 しなければなりません。

もちろん将来、インディケーターを書く経験を積んだ時、本記事の範囲外のケースに出くわすかもしれません。しかし、その時はあなたのMQL5の知識で解決できるようになっていることを願います。

MetaQuotes Software Corpによるロシア語からの翻訳