OnCalculate関数とは?
OnCalculate()は、カスタムインジケータ専用のイベント関数で、チャートに新しい価格データ(ティック)が到着するたびに自動的に呼び出されます。ロウソク足の値動きが発生するたびに実行されるため、移動平均線やRSIなどのテクニカル指標の計算処理をこの関数の中に記述します。
EA(エキスパートアドバイザー)における OnTick() に相当する役割を持ちますが、OnCalculate()はインジケータバッファへの描画計算に特化した設計になっています。
OnCalculate関数の2つの書式
OnCalculate()には2つの書式があります。1つのインジケータで使えるのはどちらか一方のみです。
書式1:短縮形(シンプル版)
チャートの価格データ(始値・高値・安値・終値など)を直接受け取る形式です。最も一般的に使われます。
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:OnInit()でのSetIndexBuffer連携版
他のインジケータの値を入力として使う場合に利用します。#property indicator_separate_windowを使うインジケータなどで使われます。
int OnCalculate(const int rates_total, // 利用可能なデータ数
const int prev_calculated, // 前回呼び出し時の計算済みデータ数
const int begin, // 意味のあるデータの開始位置
const double &price[]) // 計算対象の価格配列
引数の詳細解説
| 引数 | 型 | 説明 |
|---|---|---|
| rates_total | int | チャート上の全バー数。Barsと同等の値 |
| prev_calculated | int | 前回のOnCalculate()呼び出しで返した戻り値。初回は0 |
| time[] | datetime | 各バーの開始時刻の配列 |
| open[] | double | 各バーの始値の配列 |
| high[] | double | 各バーの高値の配列 |
| low[] | double | 各バーの安値の配列 |
| close[] | double | 各バーの終値の配列 |
| tick_volume[] | long | 各バーのティックボリューム |
| volume[] | long | 各バーの実ボリューム(対応サーバーのみ) |
| spread[] | int | 各バーのスプレッド(ポイント単位) |
| begin | int | (書式2のみ)有効データの開始インデックス |
| price[] | double | (書式2のみ)計算に使う価格データ配列 |
戻り値
OnCalculate()はint型の値を返します。この戻り値は、次回呼び出し時に prev_calculated 引数として渡されます。
- 通常は
rates_totalを返します(すべてのバーを計算済みという意味) - 0を返すと、次回の呼び出しで全バーを再計算します
- 初回呼び出し時は
prev_calculatedが0になるため、全バーの計算が行われます
prev_calculatedの仕組み(重要)
prev_calculated は、OnCalculate()のパフォーマンス最適化の鍵となる引数です。毎回すべてのバーを計算し直すのではなく、新しいバーだけを計算することで処理を高速化します。
// prev_calculatedの動作イメージ
//
// 1回目の呼び出し: prev_calculated = 0 → 全バーを計算
// 戻り値として rates_total(例:1000)を返す
//
// 2回目の呼び出し: prev_calculated = 1000
// rates_total = 1000(新しいバーなし)→ 最後のバーだけ更新
// rates_total = 1001(新しいバーあり)→ 新しいバーだけ計算
プログラム例1:最もシンプルな終値ラインインジケータ
まずは最も基本的な例として、終値をそのままラインとして描画するインジケータを作成します。OnCalculate()の基本構造とprev_calculatedの使い方を理解するのに最適です。
//+------------------------------------------------------------------+
//| 終値ラインインジケータ(OnCalculateの基本構造を学ぶ) |
//+------------------------------------------------------------------+
#property indicator_chart_window // メインチャートに表示
#property indicator_buffers 1 // バッファ数:1本
#property indicator_color1 clrDodgerBlue // ラインの色
#property indicator_width1 2 // ラインの太さ
// インジケータバッファ
double LineBuffer[];
//+------------------------------------------------------------------+
//| 初期化関数 |
//+------------------------------------------------------------------+
int OnInit()
{
// バッファをインジケータラインとして割り当て
SetIndexBuffer(0, LineBuffer);
SetIndexStyle(0, DRAW_LINE);
SetIndexLabel(0, "終値ライン");
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| OnCalculate - ティックごとに呼ばれるメイン関数 |
//+------------------------------------------------------------------+
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[])
{
// 計算開始位置を決定
// prev_calculated == 0 なら全バーを計算(初回 or 再計算)
// それ以外なら、最後のバーから計算(効率化)
int start;
if(prev_calculated == 0)
start = 0; // 初回:最初のバーから
else
start = prev_calculated - 1; // 2回目以降:最後のバーから
// 計算ループ
for(int i = start; i < rates_total; i++)
{
LineBuffer[i] = close[i]; // 終値をそのままバッファに格納
}
// rates_totalを返すことで、次回のprev_calculatedに反映される
return(rates_total);
}
このコードのポイントは、prev_calculatedを活用した計算開始位置の決定です。初回はすべてのバーを計算し、2回目以降は最後のバーだけを更新するため、処理が非常に軽くなります。
プログラム例2:単純移動平均(SMA)インジケータ
次に、実用的な単純移動平均線(SMA)を自作します。外部パラメータで期間を変更でき、prev_calculatedによる最適化も実装しています。
//+------------------------------------------------------------------+
//| 自作SMA(単純移動平均線)インジケータ |
//+------------------------------------------------------------------+
#property indicator_chart_window
#property indicator_buffers 1
#property indicator_color1 clrRed
#property indicator_width1 2
// 外部パラメータ(ユーザーが変更可能)
input int MaPeriod = 20; // 移動平均の期間
input int MaShift = 0; // 表示をシフトするバー数
// インジケータバッファ
double MaBuffer[];
//+------------------------------------------------------------------+
//| 初期化関数 |
//+------------------------------------------------------------------+
int OnInit()
{
// パラメータチェック
if(MaPeriod < 1)
{
Print("エラー: 期間は1以上を指定してください");
return(INIT_PARAMETERS_INCORRECT);
}
SetIndexBuffer(0, MaBuffer);
SetIndexStyle(0, DRAW_LINE);
SetIndexShift(0, MaShift); // ラインの水平シフト
SetIndexLabel(0, "SMA(" + IntegerToString(MaPeriod) + ")");
// 最初のMaPeriod-1本は描画しない(データ不足のため)
SetIndexDrawBegin(0, MaPeriod);
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| OnCalculate - SMAの計算処理 |
//+------------------------------------------------------------------+
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(rates_total < MaPeriod)
return(0);
// 計算開始位置の決定
int start;
if(prev_calculated == 0)
start = MaPeriod - 1; // 初回:期間分のデータが揃った位置から
else
start = prev_calculated - 1; // 2回目以降:最後のバーから
// SMAの計算ループ
for(int i = start; i < rates_total; i++)
{
double sum = 0.0;
// 現在のバーからMaPeriod本分の終値を合計
for(int j = 0; j < MaPeriod; j++)
{
sum += close[i - j];
}
// 平均値をバッファに格納
MaBuffer[i] = sum / MaPeriod;
}
return(rates_total);
}
SetIndexDrawBegin() を使って、データが不足する最初の数本のバーでは描画を行わないようにしている点に注目してください。
プログラム例3:高値・安値バンドインジケータ(複数バッファ)
複数のインジケータバッファを使い、指定期間の高値ラインと安値ラインを同時に描画する例です。ドンチャンチャネルに似たバンド型インジケータを作成します。
//+------------------------------------------------------------------+
//| 高値・安値バンドインジケータ(ドンチャンチャネル風) |
//+------------------------------------------------------------------+
#property indicator_chart_window
#property indicator_buffers 3 // バッファ3本(高値・安値・中間線)
#property indicator_color1 clrDeepSkyBlue // 高値バンド
#property indicator_color2 clrOrangeRed // 安値バンド
#property indicator_color3 clrGray // 中間線
#property indicator_width1 2
#property indicator_width2 2
#property indicator_style3 STYLE_DOT // 中間線は点線
// 外部パラメータ
input int BandPeriod = 20; // バンドの期間
// インジケータバッファ
double HighBand[]; // 高値バンド
double LowBand[]; // 安値バンド
double MidLine[]; // 中間線
//+------------------------------------------------------------------+
//| 初期化関数 |
//+------------------------------------------------------------------+
int OnInit()
{
if(BandPeriod < 2)
{
Print("エラー: 期間は2以上を指定してください");
return(INIT_PARAMETERS_INCORRECT);
}
// バッファの割り当て
SetIndexBuffer(0, HighBand);
SetIndexBuffer(1, LowBand);
SetIndexBuffer(2, MidLine);
// 描画スタイルの設定
SetIndexStyle(0, DRAW_LINE);
SetIndexStyle(1, DRAW_LINE);
SetIndexStyle(2, DRAW_LINE);
// ラベル設定
SetIndexLabel(0, "高値バンド(" + IntegerToString(BandPeriod) + ")");
SetIndexLabel(1, "安値バンド(" + IntegerToString(BandPeriod) + ")");
SetIndexLabel(2, "中間線");
// 描画開始位置
SetIndexDrawBegin(0, BandPeriod);
SetIndexDrawBegin(1, BandPeriod);
SetIndexDrawBegin(2, BandPeriod);
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| OnCalculate - 高値・安値バンドの計算 |
//+------------------------------------------------------------------+
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(rates_total < BandPeriod)
return(0);
// 計算開始位置
int start;
if(prev_calculated == 0)
start = BandPeriod - 1;
else
start = prev_calculated - 1;
// バンドの計算
for(int i = start; i < rates_total; i++)
{
double highestHigh = high[i]; // 最高値の初期値
double lowestLow = low[i]; // 最安値の初期値
// 過去BandPeriod本のバーから最高値・最安値を探す
for(int j = 0; j < BandPeriod; j++)
{
if(high[i - j] > highestHigh)
highestHigh = high[i - j];
if(low[i - j] < lowestLow)
lowestLow = low[i - j];
}
// バッファに格納
HighBand[i] = highestHigh; // 期間内の最高値
LowBand[i] = lowestLow; // 期間内の最安値
MidLine[i] = (highestHigh + lowestLow) / 2.0; // 中間値
}
return(rates_total);
}
3本のバッファを同時に使うことで、チャート上にバンド(帯域)を視覚的に表示できます。SetIndexStyle()で各ラインの線種を変えることで、見分けやすくなります。
プログラム例4:書式2を使った価格モメンタムインジケータ(サブウインドウ)
書式2(price[]配列を使う形式)の例です。サブウインドウに表示するモメンタム系インジケータを作成します。ユーザーがチャート上で「適用価格」を選択でき、その値がprice[]として渡されます。
//+------------------------------------------------------------------+
//| モメンタムインジケータ(OnCalculate書式2を使用) |
//+------------------------------------------------------------------+
#property indicator_separate_window // サブウインドウに表示
#property indicator_buffers 2
#property indicator_color1 clrLimeGreen // モメンタムライン
#property indicator_color2 clrRed // ゼロライン用(水平線)
#property indicator_width1 2
#property indicator_level1 0.0 // ゼロラインを自動描画
#property indicator_levelcolor clrSilver
// 外部パラメータ
input int MomentumPeriod = 14; // モメンタム期間
// インジケータバッファ
double MomentumBuffer[];
double SignalBuffer[]; // シグナル用(SMA平滑化)
//+------------------------------------------------------------------+
//| 初期化関数 |
//+------------------------------------------------------------------+
int OnInit()
{
if(MomentumPeriod < 1)
{
Print("エラー: 期間は1以上を指定してください");
return(INIT_PARAMETERS_INCORRECT);
}
SetIndexBuffer(0, MomentumBuffer);
SetIndexBuffer(1, SignalBuffer);
SetIndexStyle(0, DRAW_LINE);
SetIndexStyle(1, DRAW_LINE);
SetIndexLabel(0, "Momentum(" + IntegerToString(MomentumPeriod) + ")");
SetIndexLabel(1, "Signal(9)");
SetIndexDrawBegin(0, MomentumPeriod);
SetIndexDrawBegin(1, MomentumPeriod + 9);
// インジケータの短縮名(サブウインドウのタイトル)
IndicatorShortName("My Momentum(" + IntegerToString(MomentumPeriod) + ")");
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| OnCalculate - 書式2:price[]配列を使う形式 |
//| ※ユーザーがインジケータの「適用価格」で選択した値がprice[]に入る |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
const int prev_calculated,
const int begin, // 有効データの開始位置
const double &price[]) // 適用価格の配列
{
// データ不足チェック
if(rates_total < MomentumPeriod + 1)
return(0);
// ===== モメンタムの計算 =====
int start;
if(prev_calculated == 0)
start = MomentumPeriod + begin; // beginを考慮して開始位置を決定
else
start = prev_calculated - 1;
// モメンタム = 現在価格 - N期間前の価格
for(int i = start; i < rates_total; i++)
{
MomentumBuffer[i] = price[i] - price[i - MomentumPeriod];
}
// ===== シグナルライン(モメンタムの9期間SMA)=====
int signalPeriod = 9;
int signalStart;
if(prev_calculated == 0)
signalStart = MomentumPeriod + begin + signalPeriod - 1;
else
signalStart = prev_calculated - 1;
// signalStartがデータ範囲内か確認
if(signalStart < MomentumPeriod + begin + signalPeriod - 1)
signalStart = MomentumPeriod + begin + signalPeriod - 1;
for(int i = signalStart; i < rates_total; i++)
{
double sum = 0.0;
for(int j = 0; j < signalPeriod; j++)
{
sum += MomentumBuffer[i - j];
}
SignalBuffer[i] = sum / signalPeriod;
}
return(rates_total);
}
書式2では、ユーザーがインジケータのプロパティで「適用価格」(終値、始値、高値、安値、中間値など)を選択でき、その値がprice[]として自動的に渡されます。beginパラメータは、他のインジケータの出力を入力にチェーンする場合に、有効なデータの開始位置を示します。
よくある使い方のポイント・注意事項
1. prev_calculatedの正しい活用
OnCalculate()で最も重要なのはprev_calculatedの活用です。毎回全バーを再計算すると、バー数が多い場合に処理が重くなります。
// ◎ 正しい使い方:差分計算
int start;
if(prev_calculated == 0)
start = 0; // 初回のみ全バー計算
else
start = prev_calculated - 1; // -1 は最後のバー(形成中)を再計算するため
// ✕ 悪い使い方:毎回全バー計算(非効率)
for(int i = 0; i < rates_total; i++) // 毎回全部計算してしまう
2. 配列のインデックス方向に注意
OnCalculate()の引数で渡される配列(close[], high[]など)は、デフォルトで時系列ではない点に注意が必要です。
// OnCalculate内の配列のインデックス
// close[0] → 最も古いバーの終値
// close[rates_total - 1] → 最新(現在形成中の)バーの終値
//
// ※通常のClose[0]が最新バーを指すのとは逆!
//
// もし時系列順にしたい場合は以下を使う(ただし通常は不要)
// ArraySetAsSeries(close, true);
// ※ただし const 配列なので変更できない場合がある
3. 戻り値は必ずrates_totalを返す
// 正常な計算が完了した場合
return(rates_total); // 次回のprev_calculatedにこの値が入る
// エラーや計算不能の場合
return(0); // 次回は全バーを再計算する
4. OnCalculate()はEAやスクリプトでは使えない
OnCalculate()はカスタムインジケータ(.mq4ファイルの#property indicator_~ がある場合)でのみ使用できます。EAではOnTick()、スクリプトではOnStart()を使用してください。
5. rates_totalの変化を検知して新しいバーを判定
int OnCalculate(const int rates_total,
const int prev_calculated,
/* 省略 */)
{
// 新しいバーが形成されたかどうかの判定
if(rates_total > prev_calculated)
{
// 新しいバーが追加された(または初回呼び出し)
Print("新しいバーが追加されました。バー総数: ", rates_total);
}
else
{
// 既存のバー(最新バー)のティック更新
// 最新バーの終値だけが変わっている
}
return(rates_total);
}
6. EMPTY_VALUEで描画をスキップ
特定の条件でラインを描画しない場合は、バッファにEMPTY_VALUEを設定します。
// 条件を満たさない場合は描画しない
if(close[i] < open[i])
LineBuffer[i] = EMPTY_VALUE; // この位置はラインが途切れる
else
LineBuffer[i] = close[i];
まとめ
- OnCalculate()はカスタムインジケータ専用のイベント関数で、ティックごとに自動実行される
- 2つの書式があり、用途に応じて使い分ける(書式1がメイン、書式2は他インジケータの値を入力にする場合)
prev_calculatedを活用して差分計算を行い、パフォーマンスを最適化するのが基本- 引数の配列は時系列ではない(インデックス0が最古、rates_total-1が最新)
- 戻り値として
rates_totalを返すことで、次回のprev_calculatedに計算済みバー数を伝える - 描画をスキップしたい場合は
EMPTY_VALUEを使用する


