MT4からMT5へ移行して以降、MTFの移動平均線(以下「MA」)を使っていませんでしたが、5分足チャートや15分足チャートを見ている時に「1時間足のMAも表示されている方が便利だな」と思ったので、上位足のMAを表示するインディケーターを、MQL5の練習を兼ねて作ってみました。
現在分かっている問題点
試しに作ってはみたものの、現在分かっている問題点として次のものがあります。
- 取引時間外にMAが描画されない。
- たまに(上位足ではなく)現在足のMAが表示されるケースがある。
こうした点はまだ改善の余地があります。
あと、MT4の時もそうでしたが、普通のMA(STFのMA)に比べると描画に少し時間がかかりますね。「MAを描画する対象を直近のローソク足N本にする」という機能を追加した方がよいかもしれません。
イメージ
この画像では15分足チャートに1時間足のMA(カクカクした青いライン)を表示しています。
MAの時間足を変更する
先ほどの例では1時間足のMAを表示していましたが、他の時間足に変更することもできます。たとえば、「4時間足の20本MA」を「赤色」で表示したい場合、プロパティを次のように変更します。
(備考)MAは「Moving Average」の略です。
コード
#property copyright "Copyright 2023, PHILOJUAN" #property version "1.00" #property strict #property indicator_chart_window #property indicator_buffers 1 #property indicator_plots 1 input ENUM_TIMEFRAMES maTimeFrame = PERIOD_H1; // 表示したいMAの時間足 input int maPeriod = 24; // MAの期間 input color maColor = clrBlue; // MAの色 input int maWidth = 2; // MAの太さ int chartTFminutes = 0; // チャートの時間足を「分」で表した数値 int maTFminutes = 0; // MAの時間足を「分」で表した数値 int barsToRedraw = 0; // Tick受信時に再描画するbar数(maTimeFrameの現在足に相当する部分) int barsToExclude = 0; // MAの計算対象としないbarの数(計算ができない部分) bool lessThanMATF = false; // フラグ(チャートの時間足が「MAの時間足」より下位足) // インディケーター用配列 double MA_1[]; // ハンドル変数 int h_ma1; int OnInit() { //---------------------- // パラメーターチェック //---------------------- if(maPeriod < 1) { Alert("MAの期間は1以上にしてください"); RemoveIndicator(); return INIT_FAILED; } if(maWidth < 1 || maWidth > 5) { Alert("MAの太さに指定できるのは 1 ~ 5 です"); RemoveIndicator(); return INIT_FAILED; } // チャートの時間足やMAの時間足を「分」に変換したものを用意 ENUM_TIMEFRAMES currentTF = _Period; // チャートの時間足 chartTFminutes = TimeFrameToMinutes(currentTF); // チャートの時間足を「分」にしたもの(int型) maTFminutes = TimeFrameToMinutes(maTimeFrame); // MAの時間足を「分」にしたもの(int型) // チャートの時間足が「MAの時間足と同じ OR MAの時間足よりも上位足」の時 if(chartTFminutes >= maTFminutes) { SetIndexBuffer(0, MA_1, INDICATOR_CALCULATIONS); PlotIndexSetInteger(0, PLOT_DRAW_TYPE, DRAW_NONE); // chartTFminutes と maTFminutesが同じなのにMAが描画されるケース // が見受けられたので、実験的に上記2行を記述。 return INIT_SUCCEEDED; } // [memo] // SetIndexBufferの第3引数は、通常INDICATOR_DATA。 // INDICATOR_CALCULATIONSは中間計算用で描画用ではない。 // ★★★★ チャートの時間足がMAの時間足以上なら以下のOnInit処理は省略 ★★★★ //*********************************************************************************** lessThanMATF = true; // チャートの時間足はMAの時間足よりも下位足 // バッファと配列を関連付け、MAの外観を設定 SetBufferAndStyle(0, MA_1, DRAW_LINE, STYLE_SOLID, maColor, maWidth); //-------------------------------------------- // barsToRedraw と barsToExclude の計算 //-------------------------------------------- int totalBars = Bars(_Symbol, currentTF); // チャート上に存在するローソク足の本数 // barsToRedraw (剰余があれば + 1) barsToRedraw = maTFminutes / chartTFminutes; if(MathMod(maTFminutes, chartTFminutes) != 0.0) barsToRedraw = barsToRedraw + 1; // barsToExclude (剰余があれば + 1) barsToExclude = maTFminutes * maPeriod / chartTFminutes; if(MathMod(maTFminutes * maPeriod, chartTFminutes) != 0.0) barsToExclude = barsToExclude + 1; //-------------------------------------------------------------------- // MAを描画できない場合、アラートを出してインディケーターを除去 //-------------------------------------------------------------------- // 考えられるのは、チャート上にあるローソク足の本数が少なすぎる場合や // MAの計算期間が大きすぎる場合。 bool errorA = false; bool errorB = false; if(barsToExclude > totalBars) errorA = true; if((barsToExclude + barsToRedraw - 1) > totalBars) errorB = true; if(errorA || errorB) { string symbolPeriod = "[" + _Symbol + "," + TimeFrameToString(currentTF) + "]"; Alert(symbolPeriod, " ERROR! Cannot calculate moving average."); Print("totalBars = ", IntegerToString(totalBars)); Print("maPeriod = ", IntegerToString(maPeriod)); if(errorA) Print("[TYPE A] barsToExclude = ", IntegerToString(barsToExclude)); if(errorB) Print("[TYPE B] barsToExclude + barsToRedraw - 1 = ", IntegerToString(barsToExclude + barsToRedraw - 1)); // errorAにもerrorBにも該当しないMA期間を教示 double maxPeriodA = 1.0 * totalBars * chartTFminutes / maTFminutes; double maxPeriodB = 1.0 * (totalBars - barsToRedraw + 1) * chartTFminutes / maTFminutes; Print("maximum MA period you can specify = ", DoubleToString(MathMin(maxPeriodA, maxPeriodB), 0)); RemoveIndicator(); // インディケーターを除去 return INIT_FAILED; } //-------------------------------------------- // その他 //-------------------------------------------- // MAハンドルの取得 h_ma1 = iMA(NULL, maTimeFrame, maPeriod, 0, MODE_SMA, PRICE_CLOSE); // MA_1[]のインデックスを「最新足から古い足へ」という順序で付け直す ArraySetAsSeries(MA_1, true); // 最古足から「barsToExclude - 1」の範囲はMAを描画しない(MAを計算できない) PlotIndexSetInteger(0, PLOT_DRAW_BEGIN, barsToExclude - 1); // データウィンドウの見出し string tfString = TimeFrameToString(maTimeFrame); PlotIndexSetString(0, PLOT_LABEL, "MA_" + tfString + "(" + IntegerToString(maPeriod) + ")"); // データウィンドウに表示される値の精度 IndicatorSetInteger(INDICATOR_DIGITS, 6); return(INIT_SUCCEEDED); } void OnDeinit(const int reason) { // MAハンドルを削除(MA計算に使われていた領域を解放) if(lessThanMATF) IndicatorRelease(h_ma1); } 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[]) { // チャートの時間足が「MAの時間足」と同じ又は上位足の場合、以下の処理をskip if(lessThanMATF == false) return rates_total; static bool executeFlag = false; //-------------------------------------------------------------------------------------- // インディケーター挿入直後(OR チャートの時間足を切り替えた直後) //-------------------------------------------------------------------------------------- if(prev_calculated == 0) { ; } // インディケーター挿入直後は処理なし //-------------------------------------------------------------------------------------- // インディケーター挿入直後ではなく、executeFlag が false //-------------------------------------------------------------------------------------- else if(executeFlag == false) { ArraySetAsSeries(time, true); // [失敗メモ] // インディケーター挿入直後、MTFのMA値は計算されていない(prev_calculated == 0 の // 時点で、BarsCalculated(h_ma1)の戻り値は「-1」)。 // そこで、prev_calculated == 0でない時に1回だけfor文を実行する方法を試したが、 // 一部の区間にMAが描画されないケースがあった。 // [現状] // 描画範囲のMA_1[i]が全てEMPTY_VALUEでなくなるまで、Tickのたびに、 // else if(executeFlag == false) { }ブロックを実行(実質、1~2回) bool emptyValueNotExist = true; // EMPTY_VALUEが無いことを表すフラグ for(int i = rates_total - barsToExclude; i >= 0; i--) { MA_1[i] = ValueOnSpecifiedBarTime(h_ma1, time[i]); if(MA_1[i] == EMPTY_VALUE) { emptyValueNotExist = false; break; } } if(emptyValueNotExist) executeFlag = true; } //-------------------------------------------------------------------------------------- // インディケーター挿入直後ではなく、executeFlag が true //-------------------------------------------------------------------------------------- else { //「MAの時間足における最新足」に相当する部分を再描画 for(int i = barsToRedraw - 1; i >= 0; i--) { MA_1[i] = ValueOnSpecifiedBarTime(h_ma1, iTime(_Symbol, _Period, i)); } } return(rates_total); } //-------------------------------------------------------------------- // 指定したbarの発生時刻におけるインディケーターの値を返す関数 //-------------------------------------------------------------------- double ValueOnSpecifiedBarTime(int handle, datetime startTime) { double buffer[1]; if(CopyBuffer(handle, 0, startTime, 1, buffer) > 0) return(buffer[0]); else return(EMPTY_VALUE); // else行は、CopyBufferの戻り値が -1(エラー)又は 0 の場合 } //-------------------------------------------------------------------- // バッファと配列を関連付け、インディケーターの外観を設定する関数 //-------------------------------------------------------------------- void SetBufferAndStyle(int bufNumber, double &bufArray[], ENUM_DRAW_TYPE drawType, // DRAW_LINEや DRAW_NONE ENUM_LINE_STYLE drawStyle, color drawColor, int drawWidth ) { SetIndexBuffer(bufNumber, bufArray, INDICATOR_DATA); PlotIndexSetInteger(bufNumber, PLOT_DRAW_TYPE , drawType); PlotIndexSetInteger(bufNumber, PLOT_LINE_STYLE, drawStyle); PlotIndexSetInteger(bufNumber, PLOT_LINE_COLOR, drawColor); PlotIndexSetInteger(bufNumber, PLOT_LINE_WIDTH, drawWidth); } //-------------------------------------------------------------------- // インディケーターを除去する関数 //-------------------------------------------------------------------- void RemoveIndicator() { string shortname = "indicator_to_remove"; IndicatorSetString(INDICATOR_SHORTNAME, shortname); ChartIndicatorDelete(0, 0, shortname); } //-------------------------------------------------------------------- // 時間足を「H1」等の短縮表現に変換する関数 //-------------------------------------------------------------------- // 例 PERIOD_H4 → H4 string TimeFrameToString(ENUM_TIMEFRAMES intTF) { if(intTF == PERIOD_CURRENT) intTF = Period(); return StringSubstr(EnumToString(intTF), 7); } //-------------------------------------------------------------------- // MQL5の時間軸をint型の「分」に変換する関数 //-------------------------------------------------------------------- // [備考] MQL4と違い、PERIOD_H1以上は分数に対応していない int TimeFrameToMinutes(ENUM_TIMEFRAMES timeframe) { int minutes = 0; if(timeframe == PERIOD_CURRENT) timeframe = _Period; if(timeframe < PERIOD_H1) // MT5の現行の仕様なら、単に minutes = timeframe; でもOK。 minutes = (int)StringToInteger(StringSubstr(EnumToString(timeframe), 8)); else if(timeframe < PERIOD_D1) minutes = 60 * (int)StringToInteger(StringSubstr(EnumToString(timeframe), 8)); else { switch(timeframe) { case PERIOD_D1: minutes = 1440; break; case PERIOD_W1: minutes = 10080; break; case PERIOD_MN1: minutes = 43200; // 43200分は 30日に相当(MQL4のMN1を踏襲) break; default: minutes = -1; // ERROR } } return minutes; }
iTime()関数の落とし穴?
OnCalculate()関数の中でローソク足の発生時刻を取得する際
else if(executeFlag == false) {
ブロックの中ではtime[ ]を使い、
else {
ブロックの中ではiTime()を使っています。これは以下の問題があったからです。
time[ ]を使う場合、インデックスを時系列にするためにArraySetAsSeries()を実行する必要があります。一方、iTime()を使えばその必要はありません。そこで、両方のブロックにおいてiTime()を使おうとしました。しかし、「else if」ブロック内でiTime()を使用したところ、15分足チャートではMAが普通に表示されましたが、5分足チャートでは表示されませんでした。
そこで、MAの計算に失敗したローソク足を調べると次のようになっていました。
iTime() → 1970.01.01 00:00 time[] → 2019.10.30 04:55
次に全ローソク足の発生時刻をiTime()で出力しました。すると、現在足から見て249999本前まではiTime()が適切な発生時刻を返していましたが、それよりも古いローソク足ではiTime()の戻り値が全て「1970.01.01 00:00」となっていました。
これが原因で古いローソク足時点のMAを計算できなかったようです。そこで全ローソク足に処理を行う場面ではtime[]を使い、新しいローソク足に対して処理を行う場面ではiTime()を使用することにしました。
取引時間外にMTFのMAが表示されないのは何故か?
相場が動いていない日曜日のことです。15分足チャートにこのインディケーターを入れ、1時間足のMAを表示させようとしました。しかし、チャートに1時間足のMAは表示されませんでした。
当初は「取引時間外だからTickの受信がない。だから、OnCalculate関数の中に記述されたコードが実行されず、MAが描画されないのだろう。」と考えました。しかし、MT5に標準で搭載されている「MACD」というインディケーターをチャートに入れるとMACDがチャートに描画されました。
「MACD」のコードを見たらOnCalculate関数を使っている部分があったので、取引時間外にインディケーターをチャートに入れても、少なくとも1回はOnCalculate関数が実行されるようです。
よって、「1時間足のMAが表示されなかったのはOnCalculate関数が実行されないからではない」と言えます。
考えた結果、「チャートの時間足のMA値は計算されているけれど、それ以外の時間足のMA値は計算されていないのでは?」という可能性が頭に浮かびました。この可能性を確認するために以下の実験をしてみました。
まず、h_ma2という変数を用意して、OnInit関数の中に次の記述をしました。
h_ma2 = iMA(NULL, 0, maPeriod, 0, MODE_SMA, PRICE_CLOSE); // 第2引数が 0 なのでチャートの時間足のMAを計算します
次に、OnCalculate関数の先頭に次の記述をしました。
printf("BarsCalculated of h_ma1 = %d", BarsCalculated(h_ma1)); printf("BarsCalculated of h_ma2 = %d", BarsCalculated(h_ma2));
ファイルをコンパイルしてチャートに入れてみると、エキスパートに次のように表示されました。
BarsCalculated of h_ma1 = -1 BarsCalculated of h_ma2 = 250176
この結果から2つのことが分かりました。
1. OnCalculate関数が1回実行された 2. チャートの時間足のMA値は計算されているが、1時間足のMA値はまだ計算されていない
というわけで、
・取引時間外にチャートにインディケーターを入れた場合、OnCalculateは少なくとも1回は実行される。
・この時点で、チャートの時間足のMA値は計算済みなので、画面に表示される。
・この時点で、MTFのMA値はまだ計算されていないので、画面に表示されない。
ということのようです。
なお、OnCalculate関数の実行回数については不明な部分があります。もしインディケーターをチャートに入れたままずっと放置しておけば、たとえば10分に1回とか1時間に1回とか、何らかのタイミングでOnCalculateが実行されるのかもしれません。私は数分くらい様子を見ただけで、インディケーターをチャートから除去してしまったので、その辺りが分かりません。そこで「少なくとも1回は」と表現した次第です。
ついでに、MA_1[ ]の要素数についても調べてみました。OnInit関数の最後に
printf("array size of MA_1 (OnInit) = %d", ArraySize(MA_1));
と記述して、OnCalculateの先頭に
printf("array size of MA_1 (OnCalculate) = %d", ArraySize(MA_1));
と記述したところ、次のように表示されました。
array size of MA_1 (OnInit) = 0 array size of MA_1 (OnCalculate) = 250176
これを見ると、OnInit関数の時点では、MA_1[ ]の要素数がゼロなので、MA_1[ ]を使えませんね。 仮にiMA関数を使わずにMA値を計算しMA_1[ ]にデータを入れようとしても、array out of rangeになってしまいます。
また、MA_1[ ]はSetIndexBuffer関数により0番バッファに関連づけられていますから(ターミナルの管理下に置かれていますから)、ArrayResizeで要素数を変更することもできません。
というわけで、「Tick受信が無い時間帯」に「(SetIndexBuffer関数によって)ターミナルの管理下に置かれた配列」を使って「MTFの」MAを画面に表示させるのは難しいようです。
ただ、OnCalculate関数が実行された時点ではMA_1[ ]の要素数が250176となっていますので、この段階なら、iMA関数に頼らずに上位足のMA値を計算し、その結果をMA_1[ ]に代入すればMTFのMAが表示されるかもしれません。たとえば、もし5分足チャートに1時間足のMAを表示するのであれば、〇時55分に発生した5分足の終値は1時間足の終値と同じですから、それを集計するとか。あるいは、iClose関数で1時間足の終値を取得できればそれを使っても良いかもしれません。しかし、そこまで作り込むメリットを余り感じませんね(^^;。