2018年6月8日金曜日

[MQL4] マルチタイムフレーム対応インジケーターの作り方

iBarShift() を使うと比較的簡単に作ることが出来ます。

iBarShift() の基本的な使い方

まずは、iBarShift() の動作を確認するために、MA(移動平均線)のマルチタイムフレーム対応版を作ってみます。

ただし、このインジケーターは正しく動作しません。 修正は後ほどしますので、まずは一度見てみてください。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#property strict
#property indicator_chart_window
#property indicator_buffers 1
#property indicator_plots   1
//--- plot
#property indicator_type1   DRAW_LINE
#property indicator_color1  clrAqua
#property indicator_style1  STYLE_SOLID
#property indicator_width1  1
//--- input parameters
input ENUM_TIMEFRAMES    TimeFrame; // 対象の時間軸
input int                MaPeriod;
input int                MaShift;
input ENUM_MA_METHOD     MaMethod;
input ENUM_APPLIED_PRICE MaAppliedPrice;
//--- indicator buffers
double Buffer1[];
 
int OnInit()
{
    // indicator buffers mapping
    SetIndexBuffer(0, Buffer1);
    SetIndexLabel(0, StringFormat("MA(%d) [%s]", MaPeriod, EnumToString(TimeFrame)));
 
    return(INIT_SUCCEEDED);
}
 
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 == 0) {
        return 0;
    }
 
    int num_uncalculated = rates_total - prev_calculated;
 
    for (int i = num_uncalculated - 1; i >= 0; i--) {
        int shift = iBarShift(NULL, TimeFrame, time[i]);
        Buffer1[i] = iMA(NULL, TimeFrame, MaPeriod, MaShift, MaMethod, MaAppliedPrice, shift);
    }
 
    return rates_total;
}

iBarShift() で「対象のタイムフレーム(TimeFrame)における shift」を計算し、その値を「対象のタイムフレームのインジケーターiMA()」に適用します。

以下が実際の動作画面です。(4時間チャートに日足のMAを表示)

6本毎に値が更新されているのがわかるかと思います。

なお、iMA() の代わりに iCustom() を使用する事もできるので、あらゆるインジケーターのマルチタイムフレーム化が可能です。

最新値が更新されない問題を修正する

カスタムインジケーターを作り慣れた人だと気づいたと思いますが、上記の方法だと最新値が更新されません。

参考

[MQL4] カスタムインジケーターを最新値で更新する方法2種 | Strategy of C

そこで、最新値を更新するように修正します。 参考リンクでは最新の1本だけ更新していますが、マルチタイムフレームだと複数本更新しなければいけないので、少し工夫が必要です。

28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
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[])
{
    int num_need_recalculate = TimeFrame / Period(); // 簡易バージョン
 
    if (rates_total < num_need_recalculate) {
        return 0;
    }
 
    int num_uncalculated = rates_total - prev_calculated;
 
    for (int i = MathMax(num_need_recalculate - 1, num_uncalculated - 1); i >= 0; i--) {
        int shift = iBarShift(NULL, TimeFrame, time[i]);
        Buffer1[i] = iMA(NULL, TimeFrame, MaPeriod, MaShift, MaMethod, MaAppliedPrice, shift);
    }
 
    return rates_total;
}

num_need_recalculate が毎回必ず更新する足の本数です1。 例えば、「4時間チャートに日足」の場合だと 24 ÷ 4 = 6本 ということになります。

この方法、常に最大本数を更新してしまうので少し効率が悪いです。 例えば、上記画像の瞬間を考えると、本来最新の2本だけ更新すれば良いのに、さらに4本更新してしまうわけです。

さらに厳密に改造してみる

上記の例でも実用上問題ないのですが、せっかくなのでさらに改造してみます。

28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
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[])
{
    int shift_need_recalculate = iBarShift(NULL, Period(), iTime(NULL, TimeFrame, 0));
 
    if (rates_total <= shift_need_recalculate) {
        return 0;
    }
 
    int num_uncalculated = rates_total - prev_calculated;
 
    for (int i = MathMax(shift_need_recalculate, num_uncalculated - 1); i >= 0; i--) {
        int shift = iBarShift(NULL, TimeFrame, time[i]);
        Buffer1[i] = iMA(NULL, TimeFrame, MaPeriod, MaShift, MaMethod, MaAppliedPrice, shift);
    }
 
    return rates_total;
}

iBarShift() を使うと、対象の最新足に現在の時間軸で何本入るのかも求める事が出来ます2。 これにより、shift_need_recalculate を求める事が出来ます。

なお、あえて変数の役割を変更し、名前も変更しました。shiftnum より1小さい値をとるのでご注意ください。

この方法、より厳密にはなりましたが、実はそれほどパフォーマンスには影響しません。 iBarShift()iTime() を使用している分、場合によっては遅くなるかもしれない3ので、どちらを使用するかは判断の難しいところです。

  1. TimeFrame > Period() だった場合0になってしまうという問題があるのですが、その状況はそもそも意味をなさないので今回は無視します。  
  2. つまり、違う時間軸の足数を相互に変換できるということ 
  3. どちらにせよ、最近のPCだと誤差範囲でしょうが 
?