OnCalculate関数の働き・役割
OnCalculate関数は、Calculateイベントが発生して価格の変化が処理されるときに、カスタムインジケ-タで呼び出されます。この関数は、インジケータのデータ計算や更新を行うための中核的な役割を果たします。OnCalculate関数には二つの形式があり、指標の種類や計算方法に応じて使い分けます。
- データ配列に基づく計算を行う形式:
- 現在の時間枠に基づいた計算を行う形式:
- これは、時間や価格の四本値(始値、高値、安値、終値)、ティックボリュームなどを元にインジケータの値を計算する場合に使用されます。
OnCalculate関数はOnTick関数と似ていますが、いくつかの違いがあります。
OnTick関数は主にティックの変化に応じてリアルタイムで呼び出されるのに対し、OnCalculate関数は価格の変化だけでなく、時間枠の変更やインジケータの設定変更、チャートのスケール変更など、さまざまなイベントに応じて呼び出されます。
例えば、インジケータのパラメータが変更された場合や、チャートに新しい履歴データが読み込まれた場合にもOnCalculate関数が呼び出され、必要な再計算が行われます。
このようにして、OnCalculate関数は常に最新のデータに基づいてインジケータの値を提供できるようになっています。
イベントハンドラーとしてのOnCalculate関数と一般関数の違い
OnCalculate関数は、一般的な関数とは異なり、ユーザーが直接呼び出すものではなく、MetaTrader5プラットフォームによって自動的に呼び出されるイベントハンドラーです。
このため、データ型が残ったままでユーザーが実引数を入力しない形になります。以下に、これらの違いを詳しく説明します。
OnCalculate関数の引数(rates_total、prev_calculatedなど)は、関数が呼び出されたときにプラットフォームから自動的に提供されます。ユーザーはこれらの引数を明示的に設定する必要はありません。一般的な関数では、呼び出し時に実引数を提供する必要があります。
OnCalculate関数は、カスタムインジケ-タの計算と更新を行うために特別に設計されています。これは、MetaTrader5のインジケータ作成フレームワークの一部として機能します。一般的な関数は、プログラムの任意の機能を実装するために使用されます。
OnCalculate関数は、特定のシグネチャ(引数の型と数)を持つ必要があります。これは、プラットフォームが適切に関数を呼び出し、正しいデータを提供するために必要です。一般的な関数は、開発者が任意にシグネチャを定義できます。
OnCalculate関数の引数について
すべての引数に const が付いている理由は、関数内部でその引数の値が変更されないことを保証するためです。
※constについての詳細は↓の記事をご参照ください。
書式1
int OnCalculate(
const int rates_total, // price[]配列サイズ
const int prev_calculated, // 以前の呼び出しで処理されたバーの数
const int begin, // 意味のあるデータが始まるprice[]配列のインデックス番号
const double& price[] // 計算のための値の配列
);
引数説明(書式1)
- rates_total
- 計算のために指標に使用できるprice[]配列のサイズを示します。これは、現在のチャート上のバーの数に対応します。
- prev_calculated
- 以前の呼び出しで処理されたバーの数が含まれます。前回の呼び出し以降に変更されていないバーをスキップするために使用されます。
- begin
- price[]
書式2
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[] // スプレッドの配列
);
引数説明(書式2)
- rates_total
- 入力時間枠のサイズを示します。これは、計算のために指標に使用できるデータの総数です。
- prev_calculated
- 以前の呼び出しで処理されたバーの数が含まれます。前回の呼び出し以降に変更されていないバーをスキップするために使用されます。
- time[]
- バーの開いた時間の配列です。
- open[]
- バーの始値の配列です。
- high[]
- バーの高値の配列です。
- low[]
- バーの安値の配列です。
- close[]
- バーの終値の配列です。
- tick_volume[]
- ティックボリュームの配列です。ティックボリュームは、各バー内での価格変動の回数を示します。
- volume[]
- 実際の取引量の配列です。実際の取引量とは、取引が成立した実際の売買数量のことを指します。これには、ティックごとの価格変動の回数ではなく、各バー内で実際に取引された総量が含まれます。
OnCalculate関数の戻り値について
OnCalculate関数の戻り値は、次に関数が呼び出されるときにprev_calculatedパラメータとして渡されるint型の値です。この戻り値は、前回の呼び出しで処理されたバーの数を示します。
この戻り値は、効率的な計算のために重要です。次回の呼び出し時に、前回と同じデータを再計算することを避けるため、前回の計算結果を元に計算をスキップするバーを特定します。
一般的には、OnCalculate関数の戻り値としてrates_totalを返すことが多いです。これは、関数がすべてのデータを正常に処理したことを示し、次回の呼び出し時に効率的に再計算を行うためです。ただし、特定の状況やアルゴリズムによっては、異なる値を返すことが適切な場合もあります。例えば、エラーが発生した場合や、特定の条件下で計算をスキップする場合などです。
また、OnCalculate関数の戻り値がゼロの場合、クライアント端末のデータウィンドウにはインジケータ値が表示されません。これは、計算が正常に行われなかったことを示しています。
OnCalculate関数を使う際の注意点
OnCalculate関数を使用する際には、いくつかの重要な点に注意する必要があります。
まず、戻り値として適切な値を返すことが重要です。一般的には、rates_totalを返すことで、次回の呼び出し時に効率的に再計算を行うことができます。誤った値を返すと、無駄な再計算が発生したり、計算結果が正確でなくなる可能性があります。
次に、配列のインデックス付けに注意が必要です。time[]、open[]、high[]、low[]、close[]、tick_volume[]、volume[]、spread[]配列は、デフォルトでは最新のデータが配列の先頭にあります。ArraySetAsSeries関数を使用して、配列のインデックス付けを変更することができますが、これを行うと配列のインデックスが逆になるため、計算時に注意が必要です。
また、OnCalculate関数の最後の呼び出し以降に過去のデータが追加または修正された場合(履歴データの読み込みや、データのギャップが埋められた場合など)、prev_calculated入力パラメータはゼロに設定されます。この場合、すべてのデータを再計算する必要があります。
さらに、データの有効性を確認するために、price[]配列で有効なデータが始まるインデックス番号情報を持つbegin引数を使用して無効なデータや初期の不完全なデータをスキップすることが重要です。
最後に、OnCalculate関数がゼロを返した場合、クライアント端末のデータウィンドウにはインジケータ値が表示されません。これは、計算が正常に行われなかったことを示しています。正しい計算結果を得るためには、関数内で適切なエラーチェックとデータ検証を行う必要があります。
公式リファレンスのOnCalculate関数を使ったサンプルコードについて解説
以下は公式リファレンスのOnCalculate関数を使ったサンプルコードについての記述になります。
※コメントアウトは解説用にこちらで付与したものが含まれています。このサンプルコードは書式1を使ったものですが、書式2を使ったサンプルコードを確認されたい方は下記のiFractals関数に関する記事をご覧ください。
//+------------------------------------------------------------------+
//| OnCalculate_Sample.mq5 |
//| Copyright 2018, MetaQuotes Software Corp. |
//| https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2000-2024, MetaQuotes Ltd."
#property link "https://www.mql5.com"
#property version "1.00"
#property description "Sample Momentum indicator calculation"
//---- インジケータの設定
#property indicator_separate_window // インジケータを別ウィンドウに表示
#property indicator_buffers 1 // インジケータバッファ数
#property indicator_plots 1 // プロット数
#property indicator_type1 DRAW_LINE // プロットの種類を線に設定
#property indicator_color1 Blue // プロットの色を青に設定
//---- 入力パラメータ
input int MomentumPeriod=14; // モメンタム計算の期間
//---- インジケータバッファ
double MomentumBuffer[]; // モメンタム値を格納するバッファ
//--- 計算期間を格納するグローバル変数
int IntPeriod; // 計算期間
//+------------------------------------------------------------------+
//| インジケータの初期化関数 |
//+------------------------------------------------------------------+
void OnInit()
{
//--- 入力パラメータを確認し、正しくない場合はデフォルト値を設定
if(MomentumPeriod < 0)
{
IntPeriod = 14;
Print("Period parameter has an incorrect value. The following value is to be used for calculations ", IntPeriod);
}
else
IntPeriod = MomentumPeriod;
//---- バッファの設定
SetIndexBuffer(0, MomentumBuffer, INDICATOR_DATA);
//---- データウィンドウとサブウィンドウで表示されるインジケータ名の設定
IndicatorSetString(INDICATOR_SHORTNAME, "Momentum" + "(" + string(IntPeriod) + ")");
//--- 描画が始まるバーのインデックスを設定
PlotIndexSetInteger(0, PLOT_DRAW_BEGIN, IntPeriod - 1);
//--- 描画されない空の値を0.0に設定
PlotIndexSetDouble(0, PLOT_EMPTY_VALUE, 0.0);
//--- 表示されるインジケータの精度を設定
IndicatorSetInteger(INDICATOR_DIGITS, 2);
}
//+------------------------------------------------------------------+
//| モメンタム指標計算関数 |
//+------------------------------------------------------------------+
int OnCalculate(
const int rates_total, // price[]配列サイズ
const int prev_calculated, // 以前に処理されたバーの数
const int begin, // 有効なデータが始まるインデックス
const double &price[] // 計算のための値の配列
)
{
//--- 初期の計算位置を設定
int StartCalcPosition = (IntPeriod - 1) + begin;
//---- 計算データが不十分な場合、ゼロ値で終了し、インジケータは未計算
if(rates_total < StartCalcPosition)
return(0);
//--- 正しい描画を開始する
if(begin > 0)
PlotIndexSetInteger(0, PLOT_DRAW_BEGIN, StartCalcPosition + (IntPeriod - 1));
//--- 計算を開始し、開始位置を定義
int pos = prev_calculated - 1;
if(pos < StartCalcPosition)
pos = begin + IntPeriod;
//--- 計算のメインループ
for(int i = pos; i < rates_total && !IsStopped(); i++)
MomentumBuffer[i] = price[i] * 100 / price[i - IntPeriod];
//--- OnCalculateの実行が完了したので、新しいprev_calculated値を返す
return(rates_total);
}
解説1:プロパティ命令部分
//---- 指標の設定
#property indicator_separate_window
#property indicator_buffers 1
#property indicator_plots 1
#property indicator_type1 DRAW_LINE
#property indicator_color1 Blue
解説
この部分では、インジケータの基本的なプロパティを設定しています。プロパティ命令は、インジケータの表示方法やプロットの仕方を制御します。
- #property indicator_separate_window:
- #property indicator_buffers 1:
- #property indicator_plots 1:
- この命令は、インジケータがプロットする(描画する)グラフの数を指定しています。この場合、1つのグラフをプロットすることを示しています。
- #property indicator_type1 DRAW_LINE:
- この命令は、最初のプロット(plot)がどのように描画されるかを指定しています。DRAW_LINEは、線としてプロットすることを意味します。これは、インジケータの値を線で結んで表示する場合に使用されます。
- #property indicator_color1 Blue:
- この命令は、最初のプロット(plot)の色を指定しています。この場合、プロットの色は青(Blue)に設定されています。これにより、インジケータのラインが青色で表示されます。
これらのプロパティ設定は、インジケータがチャート上にどのように表示されるかを決定する重要な設定です。
解説2:グローバル領域での定義
//---- 入力パラメータ
input int MomentumPeriod=14; // モメンタム計算の期間
//---- インジケータバッファ
double MomentumBuffer[]; // モメンタム値を格納するバッファ
//--- 計算期間を格納するグローバル変数
int IntPeriod; // 計算期間
解説
この部分では、インジケータの入力パラメータやバッファ、グローバル変数を定義しています。これらの変数はインジケータ全体で使用され、インジケータの動作を制御します。
- input int MomentumPeriod=14;:
- この命令は、インジケータの入力パラメータを定義しています。ユーザーがインジケータを設定するときに変更できるパラメータです。
MomentumPeriod
はモメンタム計算の期間を示し、初期値として14が設定されています。この期間は、モメンタムの計算に使用されるバーの数を決定します。
※モメンタムについての詳細は↓の記事をご参照ください。
- double MomentumBuffer[];:
- この命令は、モメンタムの計算結果を格納するためのバッファを定義しています。
MomentumBuffer
は、計算されたモメンタム値を保存する配列です。この配列は後でプロット(描画)され、チャート上にインジケータとして表示されます。
- int IntPeriod;:
- この命令は、計算期間を格納するためのグローバル変数を定義しています。
IntPeriod
は、モメンタム計算に使用される期間を保持します。OnInit関数内でMomentumPeriodの値がこの変数に設定され、後で計算に使用されます。
これらの定義は、インジケータの設定と計算に必要な情報を保持し、インジケータ全体で利用されます。
解説3:OnInit関数の中1
//+------------------------------------------------------------------+
//| インジケータの初期化関数 |
//+------------------------------------------------------------------+
void OnInit()
{
//--- 入力パラメータを確認し、正しくない場合はデフォルト値を設定
if(MomentumPeriod < 0)
{
IntPeriod = 14;
Print("Period parameter has an incorrect value. The following value is to be used for calculations ", IntPeriod);
}
else
IntPeriod = MomentumPeriod;
解説
この部分は、インジケータの初期化関数であるOnInit関数の一部で、主に入力パラメータの確認と初期設定を行っています。
- void OnInit():
- if(MomentumPeriod < 0):
- この条件文は、入力パラメータ
MomentumPeriod
の値が正しいかどうかを確認しています。もしMomentumPeriod
が0より小さい場合、以下の処理が実行されます。
- IntPeriod = 14;:
- MomentumPeriodが負の値の場合、計算期間にデフォルト値の14を設定します。これは、安全なデフォルト値として使用され、計算が正しく行われるようにします。
- Print(“Period parameter has an incorrect value. The following value is to be used for calculations “, IntPeriod);:
- この行は、Print関数を利用してエラーメッセージをエキスパートログに出力します。ユーザーに対して、入力パラメータが不正であることと、デフォルト値が使用されることを知らせます。
- else IntPeriod = MomentumPeriod;:
MomentumPeriod
が0以上の場合、その値をIntPeriodに設定します。これにより、ユーザーが指定した期間が計算に使用されます。
この初期化処理により、インジケータは常に適切な計算期間を持つことが保証され、ユーザーが不正な値を入力した場合でも正しい動作を維持します。
解説4:OnInit関数の中2
//---- バッファの設定
SetIndexBuffer(0, MomentumBuffer, INDICATOR_DATA);
//---- データウィンドウとサブウィンドウで表示されるインジケータ名の設定
IndicatorSetString(INDICATOR_SHORTNAME, "Momentum" + "(" + string(IntPeriod) + ")");
//--- 描画が始まるバーのインデックスを設定
PlotIndexSetInteger(0, PLOT_DRAW_BEGIN, IntPeriod - 1);
//--- 描画されない空の値を0.0に設定
PlotIndexSetDouble(0, PLOT_EMPTY_VALUE, 0.0);
//--- 表示されるインジケータの精度を設定
IndicatorSetInteger(INDICATOR_DIGITS, 2);
}
解説
この部分は、インジケータの初期化関数であるOnInit関数の後半で、インジケータのバッファ設定や表示に関する設定を行っています。
- SetIndexBuffer関数:
- SetIndexBuffer関数は、インジケータのバッファを設定します。
ここでは、0番目のバッファにMomentumBufferを設定し、バッファのタイプをデータとして指定しています。
この設定により、インジケータの計算結果がMomentumBufferに保存されます。インデックス0はバッファの位置を示し、MomentumBufferはバッファの参照を示し、INDICATOR_DATAはバッファの種類を示します。
- IndicatorSetString関数:
- IndicatorSetString関数は、インジケータの文字列プロパティを設定します。ここでは、インジケータの短い名前をMomentumとし、その後に計算期間を括弧内に追加しています。この名前は、データウィンドウやサブウィンドウで表示されます。INDICATOR_SHORTNAMEはプ名前の文字列を設定します。
- PlotIndexSetInteger関数は、プロットの整数プロパティを設定します。ここでは、描画が始まるバーのインデックスを設定しています。計算期間に応じて描画開始位置を設定することで、インジケータの計算結果が正しく表示されるようにします。インデックス0はプロットの位置を示し、PLOT_DRAW_BEGINは描画開始位置を設定します。
- PlotIndexSetDouble関数は、プロットの小数プロパティを設定します。ここでは、描画されない値(空の値)を0.0に設定しています。これにより、データが存在しない箇所に対して描画を行わないようにします。インデックス0はプロットの位置を示し、PLOT_EMPTY_VALUEは設定する値を示します。
- IndicatorSetInteger関数は、インジケータの整数プロパティを設定します。ここでは、インジケータの表示精度を小数点以下2桁に設定しています。これにより、インジケータの値が適切な精度で表示されます。INDICATOR_DIGITSは精度を設定します。
これらの設定は、インジケータが正確かつ見やすく表示されるようにするために重要です。設定されたバッファや表示形式により、ユーザーはインジケータの計算結果を適切に確認することができます。
解説5:OnCalculate関数の中1
//+------------------------------------------------------------------+
//| モメンタム指標計算関数 |
//+------------------------------------------------------------------+
int OnCalculate(
const int rates_total, // price[]配列サイズ
const int prev_calculated, // 以前に処理されたバーの数
const int begin, // 有効なデータが始まるインデックス
const double &price[] // 計算のための値の配列
)
{
//--- 初期の計算位置を設定
int StartCalcPosition = (IntPeriod - 1) + begin;
//---- 計算データが不十分な場合、ゼロ値で終了し、インジケータは未計算
if(rates_total < StartCalcPosition)
return(0);
解説
この部分は、OnCalculate関数の冒頭部分で、モメンタム指標の計算を行うための初期設定をしています。
- OnCalculate関数は、カスタムインジケ-タの主要な計算関数であり、新しい価格データが到着したときや、再計算が必要な場合に呼び出されます。この関数には、計算に必要なデータやパラメータが引数として渡されます。
- rates_total:
- rates_totalは、計算のために利用できるprice配列の総サイズを示します。これは、現在のチャート上の全バーの数を表します。
- prev_calculated:
- prev_calculatedは、前回の呼び出しで処理されたバーの数を示します。これにより、前回の計算以降に追加された新しいバーのみを計算対象とすることができます。
- begin:
- price配列:
- StartCalcPositionの設定:
- StartCalcPositionは、初期の計算位置を設定します。これは、計算期間に応じて有効なデータが始まる位置を計算するためのものです。具体的には、計算期間から1を引いた値にbeginを加えた位置を示します。
- データが不十分な場合の処理:
- rates_totalがStartCalcPositionより小さい場合、計算に必要なデータが不足していることを示しています。この場合、関数は0を返し、計算を終了します。これにより、不十分なデータでの計算を避け、正確な結果を保証します。
この初期設定部分は、インジケータの計算が適切に行われるための前提条件を確認し、必要なデータが揃っているかをチェックしています。
解説6:OnCalculate関数の中2
//--- 正しい描画を開始する
if(begin > 0)
PlotIndexSetInteger(0, PLOT_DRAW_BEGIN, StartCalcPosition + (IntPeriod - 1));
//--- 計算を開始し、開始位置を定義
int pos = prev_calculated - 1;
if(pos < StartCalcPosition)
pos = begin + IntPeriod;
解説
この部分は、OnCalculate関数の中で、描画の設定と計算の開始位置を定義しています。
- 描画の開始:
- 描画を開始する条件として、beginが0より大きい場合に、PlotIndexSetInteger関数を使用して描画の開始位置を設定します。
- PlotIndexSetInteger関数は、プロットの整数プロパティを設定するために使用されます。
ここでは、0番目のプロットに対してPLOT_DRAW_BEGINプロパティを設定し、描画開始位置をStartCalcPositionに基づいて計算します。
IntPeriod – 1を追加することで、正しい開始位置が設定されます。
なお、PlotIndexSetInteger関数の第3引数にStartCalcPosition + (IntPeriod – 1)を指定する意図についてですが、そもそもStartCalcPositionは、計算が有効に開始される位置を決定するための変数です。
この位置(IntPeriod – 1) + beginとして計算されます。
IntPeriodはモメンタム計算に使用される期間であり、beginは有効なデータが始まるインデックスです。
PlotIndexSetInteger関数の第3引数にStartCalcPosition + (IntPeriod – 1)を指定することで、描画開始位置をさらに調整しています。
この調整は、インジケータの計算が完了して正しい値が得られるまでの期間を反映するために行われます。
モメンタムなどのインジケータは、計算期間内のデータがすべて揃って初めて正しい値が得られます。計算が開始される位置だけでなく、その後の期間全体が揃ってから描画を開始することで、安定した値を描画できます。
つまり、StartCalcPositionにさらにIntPeriod – 1を加えて描画開始位置を設定することで、計算に必要なデータが十分に揃った位置から描画を開始し、インジケータの表示が安定するようにしています。
- 計算の開始位置の定義:
- 計算を開始する位置を定義するために、pos変数を使用します。初期値として、前回計算されたバーの数(prev_calculated)から1を引いた値を設定します。
- 次に、posがStartCalcPositionより小さい場合、つまり初期位置が計算を開始するには不十分な場合、posをbegin + IntPeriodに設定します。これにより、計算を開始するための適切な開始位置が定義されます。
これらの設定により、描画の開始位置が正しく設定され、計算が適切な位置から開始されるようになります。これにより、インジケータの計算結果が正しく描画され、効率的に再計算が行われるようになります。
解説7:OnCalculate関数の中3
//--- 計算のメインループ
for(int i = pos; i < rates_total && !IsStopped(); i++)
MomentumBuffer[i] = price[i] * 100 / price[i - IntPeriod];
//--- OnCalculateの実行が完了したので、新しいprev_calculated値を返す
return(rates_total);
}
解説
この部分は、OnCalculate関数のメイン計算ループと、関数の終了処理を行う部分です。
- 計算のメインループ:
- このループは、posからrates_totalまでの範囲でインジケータの計算を行います。posは計算を開始する位置であり、rates_totalは利用可能なデータの総数を示します。
- ループの条件には、i < rates_totalと!IsStopped()が含まれています。i < rates_totalはループがデータ範囲内で実行されることを保証し、!IsStopped()はユーザーがスクリプトを停止した場合にループを中断するための条件です。
- モメンタムの計算:
- 各ループの反復で、モメンタム値を計算し、MomentumBufferに保存します。
- MomentumBuffer[i] = price[i] * 100 / price[i – IntPeriod]という式は、現在の価格をIntPeriod前の価格で割り、それを百分率で表しています。これにより、モメンタムが計算されます。
- OnCalculate関数の終了処理:
- 計算が完了した後、関数はrates_totalを返します。これは、次回のOnCalculate関数呼び出し時にprev_calculatedパラメータとして渡されます。
- 返される値がrates_totalであることで、前回計算されたバーの数が次回の計算に利用され、効率的な再計算が可能になります。
このメインループと終了処理により、インジケータの計算が効率的に行われ、結果がバッファに正しく保存されます。また、次回の計算がスムーズに行われるように設定されます。