トップ  >  自作してみる >  MQL4未経験者向け(インジケータ作成の基本)  >  メインウインドウ  >  M4.トレンド系インジケータ作成(マルチタイムフレームMA)
M4.トレンド系インジケータ作成(マルチタイムフレームMA)


今回は違う時間軸の移動平均線を表示するインジケータを作成します。

いつも通りに今回も他カスタムインジケータをベースにして新しいカスタムインジケータを作成します。
それでは以前作成した移動平均線のカスタムインジケータのソースコードのTestMALine.mq4をコピペして、
ファイル名をTestMAPeriodLine.mq4に変更しましょう。

インジケータマルチフレーム移動平均


ENUM列挙型インプットパラメータ追加


表示したい時間軸の移動平均線を指定できるように、インプットパラメータで時間軸を指定出来るようにします。

ソースコード:
// インプットパラメータ
input int               _InputMAPeriod   = 25;                  // 移動平均期間
input ENUM_TIMEFRAMES _InputTimePeriod = PERIOD_CURRENT;   // 移動平均の時間軸

インジケータマルチフレーム移動平均

パラメータ入力タブを選択して、インプットパラメータが追加された事を確認します。

変数の型はENUM_TIMEFRAMESという事前定義済みのENUM列挙型を使用し、
初期値はPERIOD_CURRENT(現在のチャートの時間軸)を設定します。

ちなみにこのENUM列挙型というのは、整数(int)型の定数リストです。
この定数リストの値は全て重複していないユニーク値になります。


ロジックについて考える


時間軸のインプットパラメータを追加したので、
察しの良い人は「iMA関数の引数に設定するんだろう?」と思うでしょう。
半分合っていますが、半分しか合っていません。

半分だけ合った状態で実際に動作させるとどうなるか見てみましょう。

インジケータマルチフレーム移動平均

15分足チャートと30分足チャートですが、インプットパラメータを30分設定で移動平均を表示させた例です。
15分足チャート上に単純に30分足チャートの移動平均線が表示されている事に気がついたでしょうか?
これは意図した動作ではありません。

MQL4プログラミングに慣れていない人が異なる時間軸のデータを取り扱う時に、頻繁にこの手の不具合をやらかしてしまいます。
インジケータ等の目視で異常に気が付けている人ならまだ良いのですが、インジケータ作成経験の少ない人がEA作成していた時等にこの手の不具合に気がついてさえいない事もあります。
この手の不具合の質問も結構ありました。

まずはプログラミングの前にロジックを考えて脳内で設計しましょう。
インジケータマルチフレーム移動平均

15分足チャートで一番大きな陽線が現れた時間は2019/12/12 3:30でした。
30分足チャートで同じ時間の位置を確認すると、インデックスが倍近く違います。
時間軸の大きさが2倍違うのでインデックスも2倍になるのは当然です。

これを理解できていれば何をすれば良いのか分かりますよね?
15分足チャートのインデックスが、30分足チャートのどのインデックスに相当するのかを調べる必要があります。
移動平均線を算出する時のインデックスは、そのインデックスを指定すれば良いだけです。

プログラミング経験者であればいつも考えている事なので当たり前の事ですが、
プログラミング未経験者の方は、プログラマーはこのようなロジックを頭の中でいつも考えているという事を知っておいて下さい。
早い話が、基本的に自分で状況判断して、自分で考えて、自分で設計して、自分で試行錯誤してプログラムを作成しています。
「分からないからとりあえず誰かに聞く」では無く、「こうやれば実現できるかも?」と試行錯誤して下さい。
試行錯誤してどうしても分からなくてギブアップという状態になってからやっと人に聞くようにして下さい。
ここまで読んでくれている人ならきっと出来る筈です!!

試行錯誤もせずに「何も分からないから答えだけ教えて」では一生他人に頼り続ける事になります。
実際のIT企業でも何年もプログラマーとして働いてきているのに、新卒社員と一緒に同じような事を何度も何度も聞きに来る社員や外注プログラマーがいました。
一緒に質問に来ていた新卒社員の方はどんどん学習して一人で仕事が出来るようになったにも関わらず、何度も同じような事を聞きに来る社員は成長せず、 いつの間にか新卒社員の方が優秀になっていたという事がありました。


関数を自作する


さて先程のロジックをプログラミングしてみましょう。
しなければならない事をまとめると、

 ①現在の時間軸のインデックスから日時を取得する。
 ②取得した日時から、指定した時間軸のインデックスを取得する
 ③取得したインデックスから移動平均を算出する

これだけですが、これを一発で行ってくれる関数は用意されていません。
なので、これを一発で行ってくれる関数を自作して定義しましょう。

ソースコード:
//+------------------------------------------------------------------+
//| 指定した時間軸の移動平均を算出する
//+------------------------------------------------------------------+
double CalOtherPeriodMA( 
                        ENUM_TIMEFRAMES in_period , // 時間軸
                        int in_time_period ,          // MAの平均期間
                        int in_index                  // インデックス
                       )
{
    double ret = 0;   // 戻り値

    return ret; // 戻り値を返す
}



先ずはCalOtherPeriodMAという自作関数を定義しましょう。
これはイベント関数と同様に他関数の外に書く必要があります。
当たり前ですが自作関数なので、リファレンスを幾ら調べても出てきません。

算出した移動平均を返すので、戻り値の型はdoubleにします。

仮引数は時間軸と、MAの平均期間と、インデックスの3つにします。

戻り値が必要な関数なので、内部変数のretを宣言して、retを戻り値に返すようにします。

とりあえず未だ中身の無い関数ですが、ここで一度コンパイルしてエラーが発生しない事を確認して下さい。

以前、イベント関数の説明で関数の定義について解説済みなのですが、
念の為簡単に説明します。
これ以降は、一度解説した内容は二度と解説しません。
ここまで見てきた方はリファレンスについても見慣れて来たと思いますので、細かい説明は不要になってきていると思います。

インジケータマルチフレーム移動平均

これで思い出せたと思いますので、
「戻り値って何?戻り値の型って何?戻り値を返すって何?仮引数って何?」
とはならないでしょう。


関数定義が完了した所で、次は関数の中身を実装しましょう。
まずは関数内部で使用するローカル変数を追加します。

ソースコード:
    double   ret          = 0;            // 戻り値
    double   get_other_ma = 0;            // 他時間軸の移動平均値
    int      other_index   = 0;            // 指定した時間軸のインデックス
    datetime other_time   = 0;            // 指定した時間軸の日時


必要なローカル変数を宣言した所で、最初に考えたロジックをプログラミングしていきましょう。
ローカル変数宣言した所の下に

 ①現在の時間軸のインデックスから日時を取得する。

の処理を追加します。

ソースコード:
    other_time  = Time[in_index];  // 現在の時間軸のインデックス時間を取得


これは今までやってきた内容と同じで、コメントの内容を見ても分かるので詳細説明は割愛します。

続いて、

 ②取得した日時から、指定した時間軸のインデックスを取得する

の処理を追加します。


ソースコード:
    other_index = iBarShift(                // 指定した時間から、他時間軸でのインデックスを取得する
                            Symbol(),       // 通貨ペア
                            in_period,       // 時間軸
                            other_time,      // 日時
                            false            // 検索モード(指定した日時に近いインデックスを取得する)
                    );


iBarShift()関数を使って、指定した日時で指定した時間軸のインデックスを取得します。
異なる時間軸を検索するので検索モードを完全一致にすると上手く動作しない為、false指定して曖昧検索します。
ここで指定する日時は先程取得したother_timeを使います。

関数やリファレンスも見慣れて来たと思いますので、関数の詳細説明も割愛します。
詳細はiBarShift()関数のリファレンスを確認して下さい。


続いて、

 ③取得したインデックスから移動平均を算出する

の処理を追加します。


ソースコード:
    if ( other_index >= 0) {               // インデック取得していた場合

        get_other_ma = iMA (               // 移動平均算出
                            Symbol(),     // 通貨ペア
                            in_period,      // 時間軸
                            in_time_period, // MAの平均期間
                            0,              // MAシフト
                            MODE_SMA,     // MAの平均化メソッド
                            PRICE_CLOSE, // 適用価格
                            other_index    // シフト
                        );

        ret = get_other_ma; // 戻り値に算出した移動平均を設定
    }


最初のif文判定は、 iBarShift()がインデックス取得失敗した時に-1を返すので0以上の時だけ処理するようにする為です。
後は取得したインデックスと、インプットパラメータで指定した値をお馴染みのiMA()関数に渡して、
指定した時間軸の移動平均を算出します。

算出した結果をローカル変数のretに代入します。

これで関数の中身実装は完了です。
ここでコンパイルしてエラーが無い事を確認して下さい。

自作関数を呼び出す


自作した関数を使って違う時間軸の移動平均を算出しましょう。
事前定義された関数も自作関数も使い方は同じですので、今まで通りやってみましょう。

OnCalculate()イベント関数内の処理を変更して、
iMA関数を呼び出していた所を自作関数のCalOtherPeriodMA()関数に置き換えます。

ソースコード:
    for( int icount = 0 ; icount < end_index ; icount++ ) {
        double get_ma = CalOtherPeriodMA(              // 指定した時間軸の移動平均線を算出する
                            _InputTimePeriod,           // 時間軸
                            _InputMAPeriod,             // MAの平均期間
                            icount                      // インデックス
                        );



コンパイルして動作チェックしてみましょう。
インプットパラメータは1Hourに指定して、1時間足チャートと15分足チャートで確認します。


インジケータマルチフレーム移動平均

インジケータマルチフレーム移動平均


15分足チャートに1時間足チャートの移動平均線が表示されるようになりました。

未確定足の移動平均線表示不具合


実はまだ問題があります。
実際に動作させてみると分かるのですが、
インジケータマルチフレーム移動平均

時間経過と共に未確定足部分の移動平均線表示がフラフラしてしまいます。
理由は簡単で、直近2本の足しか再描画させていない為、指定した時間軸の最新の足が確定する前の値で算出した移動平均線が残ってしまうからです。
インジケータマルチフレーム移動平均

理由が分かればどう修正すれば良いのかが分かります。
指定した時間軸がチャートの時間軸よりも大きい場合は、再描画する範囲を拡大すれば良いだけです。
という事で再描画範囲を計算する関数を追加します。

ソースコード:
//+------------------------------------------------------------------+
//| 最小更新バー数を取得
//+------------------------------------------------------------------+
int GetMinIndex() {
    int ret = IND_MIN_INDEX;

    if ( _InputTimePeriod > Period() ) { // 指定した時間軸がチャートの時間軸よりも大きい場合
        ret = IND_MIN_INDEX + _InputTimePeriod / Period();
    }
    
    return ret;
}


これで計算した値を使って再描画範囲を変更します。

ソースコード:
    int min_index = GetMinIndex();          // 最小描画数取得

    int end_index = Bars - prev_calculated;  // バー数取得(未計算分)
    if ( end_index <= min_index ) {    // 直近数本は常時更新
        end_index = min_index;
    }

    if ( Bars <= min_index ) {         // ヒストリカルデータ不足時
        return 0;                            // 全て再計算が必要なので、計算済みバー数を0にして終了する
    }


これで動作チェックしてみましょう。
インジケータマルチフレーム移動平均

大丈夫ですね。
未確定足なので確定するまでは上下しますが、途中経過の軌跡は残らないようになりました。
異なる時間軸のデータを扱う場合は注意しましょう。

ここまでの説明で分からない所があるという人は・・・読み飛ばしてここのページだけを見ていませんか?
既に知っていて分かっているから読み飛ばしたんですよね?

完成したサンプルソースコード


最後に出来上がったソースコードをまとめると、

ソースコード:
//+------------------------------------------------------------------+
//|                                             TestMAPeriodLine.mq4 |
//|                                                             yuki |
//|                                      https://yukifx.web.fc2.com/ |
//+------------------------------------------------------------------+
#property copyright "yuki"
#property link      "https://yukifx.web.fc2.com/"
#property version   "1.00"
#property strict // strictは絶対に削除しない事
#property indicator_chart_window // カスタムインジケータをチャートウインドウに表示する

// インジケータプロパティ設定
#property  indicator_buffers    1               // カスタムインジケータのバッファ数
#property  indicator_color1     clrWhite      // インジケータ1の色
#property  indicator_width1     2               // インジケータ1の太さ

// マクロ定義
#define    IND_MIN_INDEX         2               // 最小バー数

// インジケータ表示用動的配列
double     _IndBuffer1[];                          // インジケータ1表示用動的配列

// インプットパラメータ
input int               _InputMAPeriod   = 25;                  // 移動平均期間
input ENUM_TIMEFRAMES _InputTimePeriod = PERIOD_CURRENT;   // 移動平均の時間軸

//+------------------------------------------------------------------+
//| OnInit(初期化)イベント
//+------------------------------------------------------------------+
int OnInit()
{
   SetIndexBuffer( 0, _IndBuffer1 );     // インジケータ1表示用動的配列をインジケータ1にバインドする

   return( INIT_SUCCEEDED );      // 戻り値:初期化成功
}

//+------------------------------------------------------------------+
//| OnCalculate(tick受信)イベント
//| カスタムインジケータ専用のイベント関数
//+------------------------------------------------------------------+
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[],   // Tick出来高
                const long     &volume[],        // Real出来高
                const int      &spread[])        // スプレッド
{
    int min_index = GetMinIndex();          // 最小描画数取得

    int end_index = Bars - prev_calculated;  // バー数取得(未計算分)
    if ( end_index <= min_index ) {    // 直近数本は常時更新
        end_index = min_index;
    }

    if ( Bars <= min_index ) {         // ヒストリカルデータ不足時
        return 0;                            // 全て再計算が必要なので、計算済みバー数を0にして終了する
    }

    for( int icount = 0 ; icount < end_index ; icount++ ) {
        double get_ma = CalOtherPeriodMA(              // 指定した時間軸の移動平均線を算出する
                            _InputTimePeriod,           // 時間軸
                            _InputMAPeriod,             // MAの平均期間
                            icount                      // インデックス
                        );

        _IndBuffer1[icount] = get_ma;       // インジケータ1に移動平均を設定

    }

   return( rates_total ); // 戻り値設定:次回OnCalculate関数が呼ばれた時のprev_calculatedの値に渡される
}


//+------------------------------------------------------------------+
//| 指定した時間軸の移動平均を算出する
//+------------------------------------------------------------------+
double CalOtherPeriodMA( 
                        ENUM_TIMEFRAMES in_period , // 時間軸
                        int in_time_period ,          // MAの平均期間
                        int in_index                  // インデックス
                       )
{
    double   ret          = 0;            // 戻り値
    double   get_other_ma = 0;            // 他時間軸の移動平均値
    int      other_index   = 0;            // 指定した時間軸のインデックス
    datetime other_time   = 0;            // 指定した時間軸の日時

    other_time  = Time[in_index];          // 現在の時間軸のインデックス時間を取得

    other_index = iBarShift(                // 指定した時間から、他時間軸でのインデックスを取得する
                            Symbol(),       // 通貨ペア
                            in_period,       // 時間軸
                            other_time,      // 日時
                            false            // 検索モード(指定した日時に近いインデックスを取得する)
                    );

    if ( other_index >= 0) {               // インデック取得していた場合

        get_other_ma = iMA (                // 移動平均算出
                            Symbol(),      // 通貨ペア
                            in_period,      // 時間軸
                            in_time_period, // MAの平均期間
                            0,              // MAシフト
                            MODE_SMA,      // MAの平均化メソッド
                            PRICE_CLOSE,  // 適用価格
                            other_index     // シフト
                        );

        ret = get_other_ma; // 戻り値に算出した移動平均を設定
    }

    return ret; // 戻り値を返す
}

//+------------------------------------------------------------------+
//| 最小更新バー数を取得
//+------------------------------------------------------------------+
int GetMinIndex() {
    int ret = IND_MIN_INDEX;

    if ( _InputTimePeriod > Period() ) { // 指定した時間軸がチャートの時間軸よりも大きい場合
        ret = IND_MIN_INDEX + _InputTimePeriod / Period();
    }
    
    return ret;
}


スポンサーリンク
検索

↑の検索エンジンが表示されない人は、
↓の古い検索エンジンを使用して下さい。
カスタム検索
MQL4リファレンスツリー
スポンサーリンク


Copyright ©2015 MT4でEA自作しちゃお~ All Rights Reserved.


Top

inserted by FC2 system