今回は違う時間軸の移動平均線を表示するインジケータを作成します。
いつも通りに今回も他カスタムインジケータをベースにして新しいカスタムインジケータを作成します。
それでは
以前作成した移動平均線のカスタムインジケータのソースコードのTestMALine.mq4をコピペして、
ファイル名をTestMAPeriodLine.mq4に変更しましょう。
ENUM列挙型インプットパラメータ追加
表示したい時間軸の移動平均線を指定できるように、インプットパラメータで時間軸を指定出来るようにします。
パラメータ入力タブを選択して、インプットパラメータが追加された事を確認します。
変数の型は
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企業でも何年もプログラマーとして働いてきているのに、新卒社員と一緒に同じような事を何度も何度も聞きに来る社員や外注プログラマーがいました。
一緒に質問に来ていた新卒社員の方はどんどん学習して一人で仕事が出来るようになったにも関わらず、何度も同じような事を聞きに来る社員は成長せず、
いつの間にか新卒社員の方が優秀になっていたという事がありました。
関数を自作する
さて先程のロジックをプログラミングしてみましょう。
しなければならない事をまとめると、
①現在の時間軸のインデックスから日時を取得する。
②取得した日時から、指定した時間軸のインデックスを取得する
③取得したインデックスから移動平均を算出する
これだけですが、これを一発で行ってくれる
関数は用意されていません。
なので、これを一発で行ってくれる
関数を自作して定義しましょう。
先ずはCalOtherPeriodMAという自作
関数を定義しましょう。
これは
イベント関数と同様に他関数の外に書く必要があります。
当たり前ですが自作関数なので、リファレンスを幾ら調べても出てきません。
算出した移動平均を返すので、戻り値の型は
doubleにします。
仮引数は時間軸と、MAの平均期間と、インデックスの3つにします。
戻り値が必要な関数なので、内部変数のretを宣言して、retを戻り値に返すようにします。
とりあえず未だ中身の無い関数ですが、ここで一度コンパイルしてエラーが発生しない事を確認して下さい。
以前、
イベント関数の説明で関数の定義について解説済みなのですが、
念の為簡単に説明します。
これ以降は、一度解説した内容は二度と解説しません。
ここまで見てきた方はリファレンスについても見慣れて来たと思いますので、細かい説明は不要になってきていると思います。
これで思い出せたと思いますので、
「戻り値って何?戻り値の型って何?戻り値を返すって何?
仮引数って何?」
とはならないでしょう。
関数定義が完了した所で、次は関数の中身を実装しましょう。
まずは関数内部で使用するローカル変数を追加します。
必要なローカル変数を宣言した所で、最初に考えたロジックをプログラミングしていきましょう。
ローカル変数宣言した所の下に
①現在の時間軸のインデックスから日時を取得する。
の処理を追加します。
ソースコード:
other_time = Time[in_index];
これは今までやってきた内容と同じで、コメントの内容を見ても分かるので詳細説明は割愛します。
続いて、
②取得した日時から、指定した時間軸のインデックスを取得する
の処理を追加します。
iBarShift()関数を使って、指定した日時で指定した時間軸のインデックスを取得します。
異なる時間軸を検索するので検索モードを完全一致にすると上手く動作しない為、
false指定して曖昧検索します。
ここで指定する日時は先程取得したother_timeを使います。
関数やリファレンスも見慣れて来たと思いますので、関数の詳細説明も割愛します。
詳細は
iBarShift()関数のリファレンスを確認して下さい。
続いて、
③取得したインデックスから移動平均を算出する
の処理を追加します。
ソースコード:
if ( other_index >= 0) {
get_other_ma = iMA (
Symbol(),
in_period,
in_time_period,
0,
MODE_SMA,
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,
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;
}
これで動作チェックしてみましょう。
大丈夫ですね。
未確定足なので確定するまでは上下しますが、途中経過の軌跡は残らないようになりました。
異なる時間軸のデータを扱う場合は注意しましょう。
ここまでの説明で分からない所があるという人は・・・読み飛ばしてここのページだけを見ていませんか?
既に知っていて分かっているから読み飛ばしたんですよね?
完成したサンプルソースコード
最後に出来上がったソースコードをまとめると、
ソースコード:
#property copyright "yuki"
#property link "https://yukifx.web.fc2.com/"
#property version "1.00"
#property strict
#property indicator_chart_window
#property indicator_buffers 1
#property indicator_color1 clrWhite
#property indicator_width1 2
#define IND_MIN_INDEX 2
double _IndBuffer1[];
input int _InputMAPeriod = 25;
input ENUM_TIMEFRAMES _InputTimePeriod = PERIOD_CURRENT;
int OnInit()
{
SetIndexBuffer( 0, _IndBuffer1 );
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[])
{
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;
}
for( int icount = 0 ; icount < end_index ; icount++ ) {
double get_ma = CalOtherPeriodMA(
_InputTimePeriod,
_InputMAPeriod,
icount
);
_IndBuffer1[icount] = get_ma;
}
return( rates_total );
}
double CalOtherPeriodMA(
ENUM_TIMEFRAMES in_period ,
int in_time_period ,
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,
0,
MODE_SMA,
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;
}