今回作成するインジケータの概要
今回は、新規ローソク足が発生したタイミングのみ計算処理を動かすインジケータを作ろうと思います。
MT5(MQL5)でインジケータを作成する場合、OnCalculate()関数の中に実装をしていくのですが、この関数は「ローソク足に変化があった場合」に呼び出されるので、ローソク足が未確定の状態で価格変動(ティック変動)があった場合(下図の青丸箇所)、全てのタイミングで呼び出されてしまいます。
インジケータを裁量トレードの補助として利用するのであれば、それでも問題ないと思いますが、過去データでの検証やEAに組み込む場合に不都合が多いので、新規ローソク足が発生した時(下図の赤丸箇所)のみ、計算処理が動くインジケータを作っていきます。
【サンプル1】全てのローソク足変更時にインジケータ値を計算
サンプルコード
まずは、動作を比較するために、全てのローソク足変更時(「<図1>チャート説明」の青丸箇所)に計算処理をするインジケータを作ってみます。サンプルコードは以下の通りです。
//+------------------------------------------------------------------+
//| CalculateAllBar.mq5 |
//| Copyright 2023, MetaQuotes Ltd. |
//| https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2023, MetaQuotes Ltd."
#property link "https://www.mql5.com"
#property version "1.00"
#property indicator_chart_window
#property indicator_buffers 1
#property indicator_plots 1
#property indicator_type1 DRAW_LINE
#property indicator_style1 STYLE_SOLID
#property indicator_color1 clrRed
#property indicator_width1 1
double bufHigh[];
//+------------------------------------------------------------------+
//| 【初期化関数】
//| ・チャートの初期表示時、または時間足変更等のチャート初期化が必要な
//| タイミングで呼び出される。
//+------------------------------------------------------------------+
int OnInit()
{
ArraySetAsSeries(bufHigh, true);
ArrayInitialize(bufHigh, 0);
SetIndexBuffer(0, bufHigh, INDICATOR_DATA);
PlotIndexSetDouble(0, PLOT_EMPTY_VALUE, 0.0);
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[])
{
//+------------------------------------------------------------------+
//| 過去チャートのインジケータ値を全て計算する。
//+------------------------------------------------------------------+
for(int i=rates_total-1; i>=0; i--)
{
// 現在Bar位置に、1つ前のBarの高値をプロットする。
bufHigh[i] = iHigh(Symbol(),PERIOD_CURRENT,i+1);
}
//+------------------------------------------------------------------+
//| 今回の処理でインジケータ値を計算したBar数を返却する。
//| → このreturn値は、次回OnCalculate関数が呼び出された際、
//| 引数のprev_calculatedに設定される。
//+------------------------------------------------------------------+
return(rates_total);
}
//+------------------------------------------------------------------+
コード解説
短いコードですが、簡単に解説していきます。
53~57行目
過去チャートのBar(ローソク足)数分、全てのインジケータ値を計算しています。このサンプルでは、現在Bar位置に前Barの高値をプロットしています。
「高値を使うのであれば引数のhigh[]を使えばいいのでは?」という声が聞こえてきそうですが、MQL5のhigh[]引数は、時系列データにも関わらず何故かインデックス順が逆(インデックス0のデータは最も過去のデータ)になっています。(昔、結構ハマりました・・)
MT4と同じようにhigh[]を使う場合は、ArraySetAsSeries()関数でインデックス順を変更してあげれば良いのですが、結構忘れるし面倒なので、いつもiHigh系統の関数を使用しています。
65行目
おまじないのようなコードですが、初回なので解説しておきます。
OnCalculate()関数は、returnで「今回計算したBar数」を返却する仕様になっています。次回OnCalculate()関数が呼び出された際、引数のprev_calculatedに「前回計算されたBar数」が設定されてくるため、次にどのBarから計算すればよいか分かるようになっています。
実行結果
サンプル1のインジケータを実行すると以下のように表示されます。現在Bar位置に、前Barの高値がプロットされています。ただ、前述の通り全てのティック変動時に全過去データ分の計算が実行されてしまうので、非常に重いです。相場が動いている時にインジケータを設定してみれば、いかに処理が重いかが実感できると思います。
【サンプル2】新規ローソク足発生時のみインジケータ値を計算
サンプルコード
次は、新規ローソク足が発生した時(「<図1>チャート説明」の赤丸箇所)のみ、計算処理が動くインジケータを作ります。サンプルコードは以下の通りです。
//+------------------------------------------------------------------+
//| CalculateOnlyNewBar.mq5 |
//| Copyright 2023, MetaQuotes Ltd. |
//| https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2023, MetaQuotes Ltd."
#property link "https://www.mql5.com"
#property version "1.00"
#property indicator_chart_window
#property indicator_buffers 1
#property indicator_plots 1
#property indicator_type1 DRAW_LINE
#property indicator_style1 STYLE_SOLID
#property indicator_color1 clrRed
#property indicator_width1 1
double bufHigh[];
//+------------------------------------------------------------------+
//| 【初期化関数】
//| ・チャートの初期表示時、または時間足変更等のチャート初期化が必要な
//| タイミングで呼び出される。
//+------------------------------------------------------------------+
int OnInit()
{
ArraySetAsSeries(bufHigh, true);
ArrayInitialize(bufHigh, 0);
SetIndexBuffer(0, bufHigh, INDICATOR_DATA);
PlotIndexSetDouble(0, PLOT_EMPTY_VALUE, 0.0);
Print("OnInit Function Called.");
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[])
{
//+------------------------------------------------------------------+
//| 1. 新規チャートのインジケータ値がまだ計算されていない場合
//| → 過去チャートのインジケータ値を計算する。
//+------------------------------------------------------------------+
if(prev_calculated == 0)
{
for(int i=rates_total-1; i>=0; i--)
{
setBufferValue(i,false);
}
}
//+------------------------------------------------------------------+
//| 2. 過去チャートのインジケータ値は計算済で、
//| 前回計算済Bar数 < チャートの最大Bar数 の場合 (Liveで新規Barが発生した)
//| → 新規Barのインジケータ値を計算する。
//+------------------------------------------------------------------+
else
{
if(prev_calculated < rates_total)
{
setBufferValue(0,true);
}
}
//+------------------------------------------------------------------+
//| 3. 同Bar内でのティック変更によって呼び出された場合
//| → インジケータ値は計算しない
//+------------------------------------------------------------------+
// NOP
//+------------------------------------------------------------------+
//| 今回の処理でインジケータ値を計算したBar数を返却する。
//| → このreturn値は、次回OnCalculate関数が呼び出された際、
//| 引数のprev_calculatedに設定される。
//+------------------------------------------------------------------+
return(rates_total);
}
//+------------------------------------------------------------------+
//| 【インデックスバッファ設定関数】
//| ・指定されたBar位置のインジケータ値を計算する。
//+------------------------------------------------------------------+
void setBufferValue(int iCurrentBar, bool bIsLiveNewBar)
{
// 現在Bar位置に、1つ前のBarの高値をプロットする。
bufHigh[iCurrentBar] = iHigh(Symbol(),PERIOD_CURRENT,iCurrentBar+1);
// Liveで新規Barが発生した場合のみ、Logを出力する。
// 過去チャートのインジケータ値を計算している時は出力しない。
if(bIsLiveNewBar)
{
Print("Live New Bar Created!");
}
}
//+------------------------------------------------------------------+
コード解説
57行目
引数のprev_calculatedを見て、今表示しているチャートでインジケータ計算が実行されたかどうかを調べています。この値が0の場合、チャートが表示されてからまだ一度もインジケータ計算が実行されていない事になるため、過去データに対してインジケータ値を計算します。
70~76行目
過去チャートのインジケータ値は計算済(prev_calculated > 0)かつ、計算済Bar数(prev_calculated)よりも、チャートの最大Bar数(rates_total)のほうが大きい場合、新規ローソク足が発生したと判断します。
新規ローソク足が発生した場合、最新bar位置(最新Bar位置は常に0になります)を引数に設定して、インデックスバッファ設定関数(setBufferValue)を呼び出します。
102行目
引数で渡されたBar位置(iCurrentBar)に、前Barの高値を設定します。
32, 107~110行目
Liveで新規ローソク足が発生した場合のみ、関数が呼び出される事を確認しています。109行目のログは、過去チャートのインジケータ値を計算している時は出力されず、Liveで新規ローソク足が発生した場合のみ出力されます。
実行結果
サンプル2のインジケータを実行すると以下のように表示されます。インジケータの表示内容自体はサンプル1と同じです。
チャートが初期化されたOnInit()関数呼び出し時のログ”OnInit Function Called.”の直後に、1回だけ”Live New Bar Created!”のログが表示されている事から、新規ローソク足が発生した場合のみ計算処理が動いているのが分かります。
こちらも、相場が動いている時にインジケータを設定してみれば、サンプル1との処理速度の違いが実感できると思います。(ただ、最近のPCは性能が良くなってきているので、CPUパワーが凄い場合は実感できないかもしれませんが・・・)
まとめ
今回は、新規ローソク足が発生したタイミングのみ計算処理を動かすインジケータを作りました。
繰り返しになりますが、過去データ検証やEAに組み込む場合には、今回の技術はかなり有効なテクニックだと思うので、お役に立てれば幸いです。
MT5(MQL5)は、MetaQutes社が力を入れているにも関わらず、なかなか普及が進まず、Googleで検索しても情報がなかなか見つからない状況で苦労しますが、サンプルコードを含んだノウハウを今後も公開していければと思います。
コメント