初心者のための複数インディケータバッファの作成

Nikolay Kositsin | 5 10月, 2015

はじめに

前稿、『初心者のためのMQL5のカスタムインディケータ』および『初心者のためのMQL5へのデジタルフィラーの実践的実装』でひとつのインディケータバッファを使いインディケータのストラクチャ詳細に着目しました。

あきらかに、そういう手法はカスタムインディケータを書くのに広く採用されています。しかし、現実にはそれを使用する制限はほとんどなく、そのためより複雑な手法でインディケータコードを構築する方法を考える時がきたのではないでしょうか。さいわいにも、MQL5の能力は尽きることなく、 RAMやわれわれの PCが制限を持つにすぎません。


コードをコピーする例としてのアルーン指標

このインディケータの式には2つの構成部位があります。bullishと bearishインディケータです。これらは個別のチャートウィンドウにプロットされます。

BULLS = (1 - (bar - SHIFT(MAX(HIGH(), AroonPeriod)))/AroonPeriod) * 100
BEARS = (1 - (bar - SHIFT(MIN (LOW (), AroonPeriod)))/AroonPeriod) * 100

ここで

まさにインディケータの式からインディケータを構築するのに必要なインディケータバッファは2つだけだと結論づけることができます。インディケータの構築は前稿で考察したSMA_1.mq5の構築とさほど変わるところはありません。

それは、単純に、異なるインディケータ番号を持つ同じ重複コードです。MetaEditorでこのインディケータコードを開いて、Aroon.mq5として保存しましょう。最初の11行は、著作権とそのバージョン番号に関する内容ですが、そこにはインディケータの名前を置き換えるだけです。

//+------------------------------------------------------------------+
//|                                                        Aroon.mq5 |
//|                        Copyright 2010, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
//---- copyright
#property copyright "2010, MetaQuotes Software Corp."
//---- link to the author's site
#property link      "http://www.mql5.com"
//---- version number
#property version   "1.00"

次は12行目です。基本のチャートウィンドウから個別のウィンドウにインディケータのプロットを変更する必要があります。

//---- plot indicator in the separate window
#property indicator_separate_window

このインディケータには完全に異なる値の範囲が入っているので、そのプロットは別のウィンドウで作成されます。

その後の4行(一般的インディケータプロパティ)では、使用されるインディケータ数を2に変更します。

//---- two buffers are used
#property indicator_buffers 2
//---- two plots are used 
#property indicator_plots   2

続く10行は特別なインディケータバッファからのインディケータのプロットに関する部分です。そのラベルは重複する必要があり、その後インデックスをすべて1から2に置き換えます。また、インディケータバッファのレベルをすべて変更する必要があります。

//+----------------------------------------------+
//| bullish strength indicator parameters        |
//+----------------------------------------------+
//---- drawing style = line
#property indicator_type1   DRAW_LINE
//---- drawing color = Lime
#property indicator_color1  Lime
//---- line style = solid line
#property indicator_style1  STYLE_SOLID
//---- line width = 1
#property indicator_width1  1
//---- label of the BullsAroon indicator
#property indicator_label1  "BullsAroon"
//+----------------------------------------------+
//|  bearish strength indicator parameters       |
//+----------------------------------------------+
//---- drawing style = line
#property indicator_type2   DRAW_LINE
//---- drawing color = Red
#property indicator_color2  Red
//---- line style = solid line
#property indicator_style2  STYLE_SOLID
//---- line width = 1
#property indicator_width2  1
//---- label of the BearsAroon indicator
#property indicator_label2  "BearsAroon"

このインディケータは30、50、70、と3つの水平レベルを使用しています。

この3つのレベルをプロットするには、インディケータコードにもう5行追加する必要があります。

//+----------------------------------------------+
//| Horizontal levels                            |
//+----------------------------------------------+
#property indicator_level1 70.0
#property indicator_level2 50.0
#property indicator_level3 30.0
#property indicator_levelcolor Gray
#property indicator_levelstyle STYLE_DASHDOTDOT

インディケータの入力パラメータについては、前のインディケータと比べると、タイトルのちょっとした変更以外はすべて同じです。

//+----------------------------------------------+
//| Indicator input parameters                   |
//+----------------------------------------------+
input int AroonPeriod = 9; // Period 
input int AroonShift = 0// Horizontal shift of the indicator in bars 

ただし、配列が2つあります。どちらもインディケータバッファに使用され、適切な名前が付けられます。

//--- declare the dynamic arrays used further as indicator buffers
double BullsAroonBuffer[];
double BearsAroonBuffer[];

OnInit()関数のコードとまったく同じように進めていきます。

まず、0番目のバッファのコード行に変更を加えます。

//--- set BullsAroonBuffer dynamic array as indicator buffer 
SetIndexBuffer(0, BullsAroonBuffer, INDICATOR_DATA);
//--- horizontal shift (AroonShift) of the indicator 1
PlotIndexSetInteger(0, PLOT_SHIFT, AroonShift);
//--- plot draw begin (AroonPeriod) of the indicator 1
PlotIndexSetInteger(0, PLOT_DRAW_BEGIN, AroonPeriod);
//--- label shown in DataWindow
PlotIndexSetString(0, PLOT_LABEL, "BearsAroon");

そののち、このコード全体をウィンドウズのクリップボードにコピーし同じコードのすぐ後ろに貼り付けます。

そして、貼りつけたコード上でインディケータバッファの値を0から1に変え、インディケータ配列の名前とインディケータラベルを変更します。

//--- set BearsAroonBuffer dynamic array as indicator buffer 
SetIndexBuffer(1, BearsAroonBuffer, INDICATOR_DATA); 
//--- horizontal shift (AroonShift) of the indicator 2 
PlotIndexSetInteger(1, PLOT_SHIFT, AroonShift); 
//--- plot draw begin (AroonPeriod) of the indicator 2 
PlotIndexSetInteger(1, PLOT_DRAW_BEGIN, AroonPeriod); 
//--- label shown in DataWindow 
PlotIndexSetString(1, PLOT_LABEL, "BullsAroon");  

インディケータの短縮名もちょっとした変更です。

//--- initialization of the variable for a short indicator name
string shortname;
StringConcatenate(shortname, "Aroon(", AroonPeriod, ", ", AroonShift, ")");

それでは、インディケータのプロットの正確性について考察します。実際のインディケータ範囲は1~100で、この範囲は常に表示されています。

今回の場合、チャートにプロットされたインディケータの整数値のみを使用するのは可能です。この理由により、インディケータプロットとして、小数点の後に0を使います。

//--- set accuracy of drawing of indicator values
IndicatorSetInteger(INDICATOR_DIGITS, 0);

SMA_1.mq5インディケータではOnCalculate()関数呼び出しの最初のフォームを使いました。

アルーンインディケータではそれは適切ではありません。high[]とlow[]の価格配列がないからです。これら配列はこの関数を二度目に呼び出したとき使用可能となります。そのため、関数のヘッダを変更する必要があります。

int OnCalculate( 
                const int rates_total,    // total bars on the current tick
                const int prev_calculated,// total bars on the previous tick
                const datetime& time[],
                const double& open[],    
                const double& high[],     // price array of the maximum prices for the indicator calculations
                const double& low[],      // price array of the minimum prices for the indicator calculations
                const double& close[],
                const long& tick_volume[],
                const long& volume[],
                const int& spread[]
              )

この変更後、始まりのパラメータ使用は意味をなさなくなります。そのためコードから削除します。

処理サイクルの変数変更制限を計算するコードは、計算の有効性を検証するデータですが、変更なしでそのまま残します。

//--- checking the number of bars
if (rates_total < AroonPeriod - 1) return(0);
   
//--- declare the local variables 
int first, bar;
double BULLS, BEARS; 

//--- calculation of the first (staring index) for the main loop
if (prev_calculated == 0)          // checking for the first call of the OnCalculate function
    first = AroonPeriod - 1;       // starting index for calculating all of the bars 
else first = prev_calculated - 1// starting index for calculating new bars

ただしインディケータ値を計算するアルゴリズムにはある種の問題が発生します。その問題とはMQL5には、減少インデックスの方向で現在バーからの期間に対する最大、最小のインデックスを判断する関数が組み込まれていないことです。

解決法のひとつは自分でこの関数を書くことです。さいわいにも、その関数はすでカスタムインディケータのZigZag.mq5インディケータに存在しており、"MetaTrader5\MQL5\Indicators\Examples"フォルダにあります。

一番簡単な解決法は、ZigZag.mq5インディケータ内でこれら関数のコードを検索し、ウィンドウズのクリップボードにコピーし、 グローバルレベルのOnInit()関数のすぐあとに貼り付けることです。

//+------------------------------------------------------------------+
//|  searching index of the highest bar                              |
//+------------------------------------------------------------------+
int iHighest(const double &array[], // array for searching for the index of the maximum element
             int count,            // number of the elements in the array (in the decreasing order), 
             int startPos          // starting index
             )                     
  {
//---+
   int index = startPos;
   
   //---- checking the starting index
   if (startPos < 0)
     {
      Print("Incorrect value in the function iHighest, startPos = ", startPos);
      return (0);
     } 
   //---- checking the startPos values
   if (startPos - count < 0) count = startPos;
    
   double max = array[startPos];
   
   //---- index search
   for(int i = startPos; i > startPos - count; i--)
     {
      if(array[i] > max)
        {
         index = i;
         max = array[i];
        }
     }
//---+ return of the index of the largest bar
   return(index);
  }
//+------------------------------------------------------------------+
//|  searching index of the lowest bar                               |
//+------------------------------------------------------------------+
int iLowest(
            const double &array[], // array for searching for the index of the maximum element
            int count,            // number of the elements in the array (in the decreasing order),
            int startPos          // starting index
            ) 
{
//---+
   int index = startPos;
   
   //--- checking the stating index
   if (startPos < 0)
     {
      Print("Incorrect value in the iLowest function, startPos = ",startPos);
      return(0);
     }
     
   //--- checking the startPos value
   if (startPos - count < 0) count = startPos;
    
   double min = array[startPos];
   
   //--- index search
   for(int i = startPos; i > startPos - count; i--)
     {
      if (array[i] < min)
        {
         index = i;
         min = array[i];
        }
     }
//---+ return of the index of the smallest bar
   return(index);
  }

そののち、OnCalculate() 関数のコードは以下のようになります。

//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate( const int rates_total,    // total number of bars on the current tick
               const int prev_calculated,// number of calculated bars on the previous tick
               const datetime& time[],
               const double& open[],    
               const double& high[],     // price array for the maximum price for the indicator calculation
               const double& low[],      // price array for the minimum price for the indicator calculation
               const double& close[],
               const long& tick_volume[],
               const long& volume[],
               const int& spread[]
             )
  {
//---+   
   //--- checking the number of bars
   if (rates_total < AroonPeriod - 1)
    return(0);
   
   //--- declare the local variables 
   int first, bar;
   double BULLS, BEARS;
   
   //--- calculation of the starting bar number
   if (prev_calculated == 0// checking for the first start of the indicator calculation
     first = AroonPeriod - 1; // starting number for the calculation of all of the bars

   else first = prev_calculated - 1; // starting number for the calculation of new bars

   //--- main loop
   for(bar = first; bar < rates_total; bar++)
    {
     //--- calculation of values
     BULLS = 100 - (bar - iHighest(high, AroonPeriod, bar) + 0.5) * 100 / AroonPeriod;
     BEARS = 100 - (bar - iLowest (low,  AroonPeriod, bar) + 0.5) * 100 / AroonPeriod;

     //--- filling the indicator buffers with the calculated values 
     BullsAroonBuffer[bar] = BULLS;
     BearsAroonBuffer[bar] = BEARS;
    }
//---+     
   return(rates_total);
  }
//+------------------------------------------------------------------+

軸の対称性のため、コードをすこし修正し、インディケータの垂直移動を追加しました。元のものにくらべると値を0.5としています。

チャート上にこのインディケータの動作による結果が表示されています

図1 アルーンインディケータ動作結果のグラフ表示

現在バーからAroonPeriod以上遠くない距離にある最高または最小の値を伴うエレメントの位置を見つけるには、MQL5内臓のArrayMaximum()またはArrayMinimum()を使用することができます。それはまた極値の検索をしますが、これら関数は昇順でのみ検索を行います。

ただ、検索はインデックスの降順でも行う必要があります。この最も簡単な解決法は、インディケータと価格バッファ内のインデックスの方向を変えることです。このとき、ArraySetAsSeries()関数を使用します。

しかし、計算ループにあるバー順序の方向も変える必要があり、最初の変数計算のアルゴリズムも変えます。

この場合、結果OnCalculate() 関数は以下のように見えます。

//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(
                const int rates_total,    // total number of bars on the current tick
                const int prev_calculated,// number of calculated bars on the previous tick
                const datetime& time[],
                const double& open[],    
                const double& high[],     // price array for the maximum price for the indicator calculation
                const double& low[],      // price array for the minimum price for the indicator calculation
                const double& close[],
                const long& tick_volume[],
                const long& volume[],
                const int& spread[]
              )
  {
//---+   
   //--- checking the number of bars
   if (rates_total < AroonPeriod - 1)
    return(0);
    
   //--- set indexation as timeseries
   ArraySetAsSeries(high, true);
   ArraySetAsSeries(low,  true);
   ArraySetAsSeries(BullsAroonBuffer, true);
   ArraySetAsSeries(BearsAroonBuffer, true);
   
   //--- declare the local variables 
   int limit, bar;
   double BULLS, BEARS;
   
   //--- calculation of the starting bar index
   if (prev_calculated == 0)                      // check for the first call of OnCalculate function
       limit = rates_total - AroonPeriod - 1// starting index for the calculation of all of the bars
   else limit = rates_total - prev_calculated; // starting index for the calculation of new bars
   
   //--- main loop
   for(bar = limit; bar >= 0; bar--)
    {
     //--- calculation of the indicator values
     BULLS = 100 + (bar - ArrayMaximum(high, bar, AroonPeriod) - 0.5) * 100 / AroonPeriod;
     BEARS = 100 + (bar - ArrayMinimum(low,  bar, AroonPeriod) - 0.5) * 100 / AroonPeriod;

     //--- filling the indicator buffers with the calculated values 
     BullsAroonBuffer[bar] = BULLS;
     BearsAroonBuffer[bar] = BEARS;
    }
//----+     
   return(rates_total);
  }
//+------------------------------------------------------------------+

変数名を『一番目』から『限定』に変えました。今の場合その方が適切だからです。

ここでは、メインループのコードMQL4のときと似ています。それで、この OnCalculate() 関数の書き方は、SMQL4からMQL5にインディケータを変換するのにコードの最小変更として使うことができます。


おわりに

もう終わりました!2種類のバージョンでインディケータは書かれました。

右の場合には、保守的でスマートな方法での問題解決でしたが、子供のおもちゃ、レゴを使って何かを作ることよりもほんの少し複雑なだけでした。