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;
input int maPeriod = 24;
input color maColor = clrBlue;
input int maWidth = 2;
int chartTFminutes = 0;
int maTFminutes = 0;
int barsToRedraw = 0;
int barsToExclude = 0;
bool lessThanMATF = false;
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;
}
ENUM_TIMEFRAMES currentTF = _Period;
chartTFminutes = TimeFrameToMinutes(currentTF);
maTFminutes = TimeFrameToMinutes(maTimeFrame);
if(chartTFminutes >= maTFminutes) {
SetIndexBuffer(0, MA_1, INDICATOR_CALCULATIONS);
PlotIndexSetInteger(0, PLOT_DRAW_TYPE, DRAW_NONE);
return INIT_SUCCEEDED;
}
lessThanMATF = true;
SetBufferAndStyle(0, MA_1, DRAW_LINE, STYLE_SOLID, maColor, maWidth);
int totalBars = Bars(_Symbol, currentTF);
barsToRedraw = maTFminutes / chartTFminutes;
if(MathMod(maTFminutes, chartTFminutes) != 0.0) barsToRedraw = barsToRedraw + 1;
barsToExclude = maTFminutes * maPeriod / chartTFminutes;
if(MathMod(maTFminutes * maPeriod, chartTFminutes) != 0.0) barsToExclude = barsToExclude + 1;
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));
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;
}
h_ma1 = iMA(NULL, maTimeFrame, maPeriod, 0, MODE_SMA, PRICE_CLOSE);
ArraySetAsSeries(MA_1, true);
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)
{
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[])
{
if(lessThanMATF == false) return rates_total;
static bool executeFlag = false;
if(prev_calculated == 0) { ; }
else if(executeFlag == false) {
ArraySetAsSeries(time, true);
bool emptyValueNotExist = true;
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;
}
else {
for(int i = barsToRedraw - 1; i >= 0; i--) {
MA_1[i] = ValueOnSpecifiedBarTime(h_ma1, iTime(_Symbol, _Period, i));
}
}
return(rates_total);
}
double ValueOnSpecifiedBarTime(int handle, datetime startTime)
{
double buffer[1];
if(CopyBuffer(handle, 0, startTime, 1, buffer) > 0)
return(buffer[0]);
else return(EMPTY_VALUE);
}
void SetBufferAndStyle(int bufNumber,
double &bufArray[],
ENUM_DRAW_TYPE drawType,
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);
}
string TimeFrameToString(ENUM_TIMEFRAMES intTF)
{
if(intTF == PERIOD_CURRENT) intTF = Period();
return StringSubstr(EnumToString(intTF), 7);
}
int TimeFrameToMinutes(ENUM_TIMEFRAMES timeframe)
{
int minutes = 0;
if(timeframe == PERIOD_CURRENT) timeframe = _Period;
if(timeframe < PERIOD_H1)
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;
break;
default:
minutes = -1;
}
}
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);
次に、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時間足の終値を取得できればそれを使っても良いかもしれません。しかし、そこまで作り込むメリットを余り感じませんね(^^;。