MQL5インジケータ性能最適化|OnCalculateとCopyBuffer高速化

この記事の結論

MQL5でインジケータの性能を改善する基本は、毎回すべてのバーを再計算せず、必要な範囲だけを更新することです。
カスタムインジケータでは、OnCalculateprev_calculated を使うと、前回計算済みのバーを判定しやすくなります。
インジケータバッファ、配列方向、ハンドル管理、CopyBuffer の取得件数を適切に扱うことで、チャート表示やバックテストの負荷を抑えやすくなります。
ただし、処理を軽くするために必要な計算を省略すると、表示値やEAの判定が不正確になる場合があります。

1. インジケータ性能最適化の役割

【結論】
MQL5のインジケータ性能最適化とは、必要な計算精度を保ちながら、チャート更新時やバックテスト時の処理量を減らす設計です。
特にカスタムインジケータでは、OnCalculate の再計算範囲を小さくすることが重要です。

【定義】
インジケータ性能最適化とは、インジケータバッファ、ループ範囲、外部インジケータハンドル、データ取得回数を整理し、無駄な処理を減らす実装方針です。

MQL5のインジケータは、価格データの更新やチャートの再描画に合わせて計算されます。処理が重いインジケータを複数チャートに適用すると、表示遅延、バックテスト速度低下、EA側の判定遅延につながる場合があります。

インジケータ性能で優先すべき対象は、主に次の4つです。

  • OnCalculate 内のループ範囲
  • インジケータバッファへの書き込み回数
  • 外部インジケータハンドルの作成タイミング
  • CopyBuffer の呼び出し回数と取得件数

性能最適化は、単に処理を削ることではありません。必要なバーを正しく計算し、未確定足と確定足を区別し、EAが利用しても誤判定しにくい値を返すことが目的です。

MQL5 indicator optimization tutorial showing prev_calculated difference calculation, OnCalculate execution flow, CopyBuffer optimization, and efficient indicator buffer processing with chart and code overlay.

2. 基本構文

【結論】
カスタムインジケータの中心は OnCalculate です。
rates_total は利用可能なバー数、prev_calculated は前回までに計算されたバー数を表します。

MQL5のカスタムインジケータでは、一般的に次の構造を使います。

#property strict
#property indicator_chart_window
#property indicator_buffers 1
#property indicator_plots   1

double OutputBuffer[];

int OnInit()
{
   SetIndexBuffer(0, OutputBuffer, INDICATOR_DATA);
   ArraySetAsSeries(OutputBuffer, true);
   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[]
)
{
   if(rates_total < 2)
      return 0;

   int start = rates_total - prev_calculated;

   if(prev_calculated == 0)
      start = rates_total - 1;

   for(int i = start; i >= 0; i--)
   {
      OutputBuffer[i] = close[i];
   }

   return rates_total;
}

この例では、prev_calculated が0の場合だけ全体を初期計算します。2回目以降は、更新が必要な範囲を小さくする設計になります。

ただし、配列を時系列方向に扱う場合は、インデックス0が最新バーになります。ループ方向と計算対象を間違えると、最新足と過去足の値が逆に扱われる場合があります。

3. パラメータの意味

【結論】
インジケータ性能を理解するには、rates_totalprev_calculated、価格配列、インジケータバッファの役割を分けて考える必要があります。
再計算範囲は、利用可能なバー数と前回計算済みバー数から決めます。

OnCalculate の主要パラメータは、計算対象の範囲を決めるために使います。

パラメータ役割性能面での注意点
rates_total現在利用できるバー数バー数が多いほど全再計算の負荷が大きくなる
prev_calculated前回返した計算済みバー数差分計算の起点として使う
time[]各バーの時刻新しいバーの発生判定に使える
open[]high[]low[]close[]価格データ必要な配列だけ使う
tick_volume[]volume[]出来高データ使わない場合は処理に含めない
spread[]スプレッド情報スプレッド依存の表示を行う場合に使う

prev_calculated を使わずに毎回全バーを処理すると、バー数が増えるほど負荷が高くなります。移動平均、ATR、RSIのように一定期間の過去データを使う計算では、必要な余白分だけ再計算する設計が必要です。

3.1 初回計算と差分計算を分ける

初回計算では、すべてのバーに値を設定する必要があります。2回目以降は、最新バーと更新が必要な範囲だけを計算します。

たとえば期間20の計算では、最新バーだけでなく、計算に必要な過去19本分を考慮する場合があります。処理を軽くするために過去データを不足させると、インジケータ値が不安定になる場合があります。

4. 戻り値と判定方法

【結論】
OnCalculate は、正常に計算した後に rates_total を返すのが一般的です。
戻り値は次回呼び出し時の prev_calculated として使われるため、差分計算の基準になります。

OnCalculate の戻り値は、次回計算の開始位置に影響します。正常時に rates_total を返すと、次回の prev_calculated に現在のバー数が渡されます。

if(rates_total < RequiredBars)
   return 0;

return rates_total;

必要なバー数が不足している場合は、計算を行わず 0 を返す設計にできます。これにより、不完全なデータでバッファ値を作らないようにできます。

戻り値の扱いを誤ると、毎回初期計算が走る、更新されるべきバーが更新されない、または過去値が残る原因になります。

5. 基本的な使い方

【結論】
インジケータを軽くする基本は、ハンドル作成を初期化時に行い、計算時には必要なデータだけを取得することです。
OnCalculate 内で毎回ハンドルを作成する実装は避けるべきです。

外部インジケータの値を使うカスタムインジケータでは、OnInit でハンドルを作成し、OnDeinit で必要に応じて解放します。MQL5では、多くのインジケータ関数が値を直接返すのではなく、ハンドルを作成してから CopyBuffer で値を取得する構造になります。

#property strict
#property indicator_separate_window
#property indicator_buffers 1
#property indicator_plots   1

input int MaPeriod = 20;

double OutputBuffer[];
int maHandle = INVALID_HANDLE;

int OnInit()
{
   SetIndexBuffer(0, OutputBuffer, INDICATOR_DATA);
   ArraySetAsSeries(OutputBuffer, true);

   maHandle = iMA(_Symbol, _Period, MaPeriod, 0, MODE_SMA, PRICE_CLOSE);

   if(maHandle == INVALID_HANDLE)
   {
      Print("Failed to create MA handle");
      return INIT_FAILED;
   }

   return INIT_SUCCEEDED;
}

void OnDeinit(const int reason)
{
   if(maHandle != INVALID_HANDLE)
      IndicatorRelease(maHandle);
}

この構造では、ハンドル作成を初期化時に限定しています。チャート更新のたびにハンドルを作成しないため、計算処理を整理しやすくなります。

6. 実用コード例

【結論】
実用的な最適化では、BarsCalculated で計算済み本数を確認し、CopyBuffer の取得件数を必要最小限にします。
取得件数が不足した場合は、値を使わずに処理を終了します。

次のコードは、移動平均ハンドルから必要範囲だけを取得し、カスタムバッファに反映する例です。

#property strict
#property indicator_chart_window
#property indicator_buffers 1
#property indicator_plots   1

input int MaPeriod = 20;

double MaOutput[];
int maHandle = INVALID_HANDLE;

int OnInit()
{
   SetIndexBuffer(0, MaOutput, INDICATOR_DATA);
   ArraySetAsSeries(MaOutput, true);

   maHandle = iMA(_Symbol, _Period, MaPeriod, 0, MODE_SMA, PRICE_CLOSE);

   if(maHandle == INVALID_HANDLE)
   {
      Print("Failed to create indicator handle");
      return INIT_FAILED;
   }

   return INIT_SUCCEEDED;
}

void OnDeinit(const int reason)
{
   if(maHandle != INVALID_HANDLE)
      IndicatorRelease(maHandle);
}

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 calculated = BarsCalculated(maHandle);
   if(calculated < rates_total)
   {
      Print("Indicator data is not ready");
      return prev_calculated;
   }

   int barsToCopy;

   if(prev_calculated == 0)
      barsToCopy = rates_total;
   else
      barsToCopy = rates_total - prev_calculated + 1;

   if(barsToCopy < 1)
      barsToCopy = 1;

   double maValues[];
   ArraySetAsSeries(maValues, true);

   int copied = CopyBuffer(maHandle, 0, 0, barsToCopy, maValues);

   if(copied < barsToCopy)
   {
      Print("CopyBuffer failed or not enough data");
      return prev_calculated;
   }

   for(int i = copied - 1; i >= 0; i--)
   {
      MaOutput[i] = maValues[i];
   }

   return rates_total;
}

このサンプルは、検証用の実装例です。実際のインジケータでは、計算期間、描画開始位置、未確定足の扱い、複数バッファの同期を設計する必要があります。

6.1 最新足と確定足の違い

インデックス0は最新足です。最新足はティック更新中に値が変化する場合があります。EAがインジケータ値を売買判定に使う場合は、確定足であるインデックス1を使う設計のほうが、判定の再現性を確認しやすくなります。

7. よくある失敗

【結論】
インジケータ性能の問題は、全バー再計算、ハンドルの毎回作成、CopyBuffer の大量取得、配列方向の誤解から起きやすいです。
処理が軽く見えても、値の整合性が崩れている場合があります。

よくある失敗は次のとおりです。

  • OnCalculate のたびに全バーを再計算する
  • OnCalculate 内で毎回 iMAiCustom のハンドルを作成する
  • CopyBuffer で必要以上の本数を毎回取得する
  • CopyBuffer の戻り値を確認しない
  • BarsCalculated を確認せず未準備の値を使う
  • ArraySetAsSeries の有無を忘れてインデックスを誤る
  • 最新足の変化を確定値として扱う
  • バッファ初期化を行わず古い値を残す

7.1 CopyBufferの取得失敗を無視しない

CopyBuffer は、要求した本数を常に取得できるとは限りません。履歴データが不足している、外部インジケータがまだ計算中、ハンドルが無効、という状況では取得件数が不足する場合があります。

取得件数が不足した値をそのまま使うと、チャート表示だけでなく、EA側のシグナル判定にも影響します。

8. 他の方法との違い

【結論】
MQL5のインジケータ最適化には、差分計算、ハンドル再利用、取得本数の制限、計算条件の分離という複数の方法があります。
最も基本になる方法は、prev_calculated を使った再計算範囲の制御です。

方法メリットデメリット向いている場面
prev_calculated による差分計算全バー再計算を避けやすいループ範囲を誤ると過去値が更新されないほぼすべてのカスタムインジケータ
ハンドルの再利用外部インジケータの作成負荷を抑えやすい初期化と解放の管理が必要iMAiATRiCustom を使う場合
CopyBuffer の本数制限データ取得量を減らしやすい必要本数を少なくしすぎると計算精度が落ちる最新値中心の表示やEA連携
計算条件の分離不要な処理を避けやすい条件分岐が増えると読みにくくなる複数モードを持つインジケータ
バッファ数の整理メモリと描画処理を管理しやすい複雑な表示では不足する場合がある表示要素が少ないインジケータ

prev_calculated は計算範囲を制御する方法です。ハンドル再利用は外部インジケータの生成負荷を減らす方法です。CopyBuffer の本数制限は、データ取得量を抑える方法です。

これらは競合する方法ではありません。実用的なインジケータでは、複数の方法を組み合わせて設計します。

9. EA設計での使いどころ

【結論】
EAがカスタムインジケータを利用する場合、インジケータ側の性能は売買判定の安定性にも影響します。
EA側では、iCustom のハンドルを初期化時に作り、CopyBuffer の結果を確認してからシグナル判定を行います。

EAでインジケータを使う流れは、一般的に次の構造になります。

OnInitでインジケータハンドルを作成
↓
OnTickで必要な本数だけCopyBuffer
↓
取得件数を確認
↓
確定足の値でシグナル判定
↓
リスク確認
↓
注文前チェック
↓
注文処理

EAでは、インジケータの値が取得できない状態で注文判定を進めるべきではありません。取得失敗時は、そのティックでの売買判定を見送る設計にします。

注文処理を含むEAでは、シグナル判定後にロット、証拠金、スプレッド、ストップレベル、取引可能時間、既存ポジションを確認する必要があります。実装では、注文送信前に OrderCheck を使う設計が有効です。

9.1 EAで使う場合のCopyBuffer例

double signalBuffer[];
ArraySetAsSeries(signalBuffer, true);

int copied = CopyBuffer(customHandle, 0, 0, 2, signalBuffer);

if(copied < 2)
{
   Print("Indicator signal is not ready");
   return;
}

int confirmedBarIndex = 1;
double confirmedSignal = signalBuffer[confirmedBarIndex];

この例では、インデックス1の確定足を使っています。最新足のインデックス0はティックごとに変わる場合があるため、バックテストと実運用の判定差を小さくしたい場合は、確定足の利用を検討します。

10. 実運用での注意点

【結論】
インジケータ性能を改善しても、EAの実運用成績が保証されるわけではありません。
バックテスト、フォワードテスト、ブローカー条件、スプレッド、約定差を分けて確認する必要があります。

インジケータの処理負荷が高いと、複数チャートや複数銘柄で運用したときに遅延が出る場合があります。特に、iCustom で重いカスタムインジケータをEAから複数呼び出す設計では、処理の集中に注意が必要です。

バックテストで確認すべき項目は次のとおりです。

  • 計算速度
  • 表示値の欠落
  • 未確定足と確定足の差
  • インジケータ値を使うEAの取引回数
  • 最大ドローダウン
  • 勝率
  • 損益比
  • 連敗数
  • スプレッド条件
  • パラメータ依存性

フォワードテストで確認すべき項目は次のとおりです。

  • 実時間での表示遅延
  • CopyBuffer の取得失敗頻度
  • スプレッド拡大時の挙動
  • 約定差によるEA成績の変化
  • バックテストとの乖離
  • ブローカー差
  • VPS環境での安定性

バックテスト結果は将来の利益を保証しません。インジケータ値が正しく取得できても、売買ロジック、ロット計算、スプレッド、スリッページ、レバレッジ、口座タイプによりEAの結果は変わります。

11. まとめ

【結論】
MQL5のインジケータ性能を改善するには、OnCalculate の差分計算、ハンドル管理、CopyBuffer の取得制御、配列方向の確認が重要です。
性能と正確性の両方を確認しながら、バックテストとフォワードテストで実際の挙動を検証します。

インジケータ性能最適化で最初に見直すべき点は、毎回全バーを再計算していないかという点です。次に、外部インジケータハンドルを毎回作成していないか、CopyBuffer で必要以上の本数を取得していないかを確認します。

EAと連携する場合は、取得できなかった値を使わないことが重要です。確定足を使うか最新足を使うかは、ロジックの目的に応じて明確に分けます。

インジケータの処理速度が改善しても、売買結果の安全性や収益性は保証されません。実運用前には、スプレッド、約定、ドローダウン、ブローカー仕様、フォワードテストの結果を確認する必要があります。

FAQ

Q1. MQL5のインジケータ性能最適化で最初に確認すべき点は何ですか?

最初に確認すべき点は、OnCalculate で毎回すべてのバーを再計算していないかです。prev_calculated を使うと、前回計算済みの範囲を基準に差分計算しやすくなります。

Q2. prev_calculated は何のために使いますか?

prev_calculated は、前回までに計算されたバー数を受け取るために使います。戻り値として rates_total を返すことで、次回の計算範囲を制御しやすくなります。

Q3. CopyBuffer は毎回すべてのバーを取得したほうがよいですか?

通常は、毎回すべてのバーを取得する必要はありません。表示や判定に必要な本数だけ取得し、戻り値で取得件数を確認する設計が重要です。

Q4. OnCalculate 内で iMAiCustom を呼び出してもよいですか?

ハンドル作成を OnCalculate 内で毎回行う実装は避けるべきです。一般的には OnInit でハンドルを作成し、計算時は CopyBuffer で必要な値だけ取得します。

Q5. 最新足と確定足はどちらを使うべきですか?

EAの判定で再現性を確認しやすくしたい場合は、確定足を使う設計が有効です。最新足はティック更新中に値が変化するため、バックテストと実運用で判定がずれる場合があります。

Q6. インジケータを軽くすればEAの成績は良くなりますか?

インジケータの処理負荷を下げても、EAの成績が良くなるとは限りません。売買結果は、ロジック、スプレッド、約定条件、ロット管理、ブローカー仕様、検証条件により変わります。

Q7. バックテストでは何を確認すべきですか?

バックテストでは、計算速度、表示値の欠落、取引回数、最大ドローダウン、勝率、損益比、スプレッド条件、パラメータ依存性を確認します。バックテスト結果は将来の利益を保証しません。

Q8. フォワードテストでは何を確認すべきですか?

フォワードテストでは、実時間での表示遅延、CopyBuffer の取得失敗、スプレッド拡大時の挙動、約定差、VPS環境での安定性を確認します。デモ口座とリアル口座では約定条件が異なる場合があります。