2018年7月7日土曜日

[MQL4] INDICATOR_CALCULATIONS を有効にするには IndicatorBuffers を使う

@MetaTrader 4 (Version 4.00 Build 1090)

MT4 のカスタムインジケーターでは計算用のバッファーを用意する機能があり、計算の途中結果等を保存しておくことが出来ます。
当然、それは非表示になる…はずなのですが、普通に作ると何故か表示されてしまうのです。

これを想定通りに動作させるには少し工夫が必要なので書いておきます。



### 途中計算が表示されてしまうバージョン
途中計算を必要とするインジケーターを真面目に作ると複雑になってしまうので、単純移動平均(SMA)から少し値を引くだけのインジケーターを作ってみました当然、途中計算バッファーを使う意味はありません。メモリの無駄遣いですが、あくまで確認用ということで。。

```mq4
`highlight: [3, 4, 22];
#property strict
#property indicator_chart_window
#property indicator_buffers 2
#property indicator_plots   1
//--- plot Label1
#property indicator_type1   DRAW_LINE
#property indicator_color1  clrRed
#property indicator_style1  STYLE_SOLID
#property indicator_width1  1
//--- input parameters
input int      MaPeriod=14; // Period
//--- indicator buffers
double         Line0[];
double         Dummy[];
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
{
    SetIndexLabel(0, StringFormat("Test (%d)", MaPeriod));
    SetIndexBuffer(0, Line0);
    SetIndexBuffer(1, Dummy, INDICATOR_CALCULATIONS);
   
    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[])
{
    if (rates_total <= MaPeriod) {
        return 0;
    }
 
    int num_uncalculated = rates_total - prev_calculated;
    if (prev_calculated > 0 && num_uncalculated == 0) {
        num_uncalculated = 1; // 最新の足は必ず更新する
    }
    for (int i = num_uncalculated - 1; i >= 0; i--) {
        if (i + MaPeriod + 1 > rates_total) {
            Line0[i] = EMPTY_VALUE;
        } else {
            double sum = 0;
            if (i + MaPeriod + 1 == rates_total) {
                for (int j = 0; j < MaPeriod; j++) {
                    sum += Close[i + j];
                }    
            } else {
                sum = Line0[i + 1] * MaPeriod;
                sum -= Close[i + MaPeriod];
                sum += Close[i];
            }    
            Line0[i] = sum / MaPeriod;
            Dummy[i] = Line0[i] - 0.01;
        }
    }

    //--- return value of prev_calculated for next call
    return rates_total;
}
```

ポイントは `indicator_buffers` と `indicator_plots` で用意するバッファーの数と表示するラインの数を指定しているところです。
さらに、`SetIndexBuffer` で `INDICATOR_CALCULATIONS` を指定して、計算用バッファであることを明示していますMQL4 ではこの値は無視されているようにみえるのですが、念のため。

これで `Dummy` の値は表示されないはずなのですが、実際にチャートに適用させてみると以下のようになりました。

よく見るとわかるのですが、実際の SMA(赤線)の下に黒い線が描かれています。 さらに、Data Window にも以下のように Dummy の値が表示されてしまいました。
### 表示させないようにする いろいろ検証してみた結果、`indicator_buffers` と `indicator_plots` が内部で混同されているようです。 なので、以下のように `indicator_buffers` の値を `indicator_plots` と同じにします。 ```mq4 `first-line: 3; highlight: 3; #property indicator_buffers 1 #property indicator_plots 1 ``` このままだとバッファが足りなくて以下のエラーが出てしまいます。 > array out of range in 'Test.mq4' そこで、`OnInit()` の中で動的にバッファの数を増やします。 ```mq4 `first-line: 18; highlight: 20; int OnInit() { IndicatorBuffers(2); SetIndexLabel(0, StringFormat("Test (%d)", MaPeriod)); SetIndexBuffer(0, Line0); SetIndexBuffer(1, Dummy, INDICATOR_CALCULATIONS); return(INIT_SUCCEEDED); } ``` これで、想定通り Dummy が表示されなくなります。
### MQL5 由来の機能は疑ってみる MQL4 は Build 600 以降、MQL5 の構文を取り込んで来たわけですが、一部の動作は完全移植とはいかないようです。 特に `#property` の動作はかなり不安定な部分があり、動作を疑ってかかった方がよいところも多々あります。