【検証#14】インジケータが重い?「全期間再計算」の罪¶

図1:全期間計算モード(激重)の結果

図2:差分更新モード(高速)の結果
この記事の3行まとめ
- 🐢 毎回
0からループすると、Tickごとに全過去データを再計算することになる - ⚡
prev_calculatedを使った差分更新なら、計算は最新の1〜2本だけで済む - 🛑 「重いインジケータ」の正体は、このループ処理の書き方ひとつにあるかもしれない
はじめに¶
この記事は、「とあるMetaTraderの備忘秘録」様のブログ記事を検証・紹介するシリーズの第14弾です。
自作インジケータを作ってみたら「MT5がカクカクする」「CPU使用率が跳ね上がる」という経験はありませんか? もしかすると、既に計算し終わった過去のデータを、何度も何度も計算し直しているからかもしれません。
元ネタ
IndicatorCounted() の深層
https://fai-fx.hatenadiary.org/entry/20101014/1287019183
(2010年10月14日 公開)
差分更新の仕組み¶
インジケータ関数 OnCalculate には、prev_calculated(前回計算済みバー数)という引数があります。これを使うかどうかが運命の分かれ道です。
❌ ダメな書き方(全期間再計算)¶
これだと、新しいTickが来るたびに 過去数万本のバー 全てに対して計算処理が走ります。無駄です。
✅ 正しい書き方(差分更新)¶
int limit;
if(prev_calculated == 0)
limit = 0; // 初回は全部
else
limit = prev_calculated - 1; // 2回目以降はラスト1本から
// 必要な部分だけループ
for(int i = limit; i < rates_total; i++)
{
Buffer[i] = ...;
}
これなら、Tick更新時は 最新の1本だけ 計算すれば済みます。負荷は数万分の1です。
MQL5での検証コード¶
この負荷の違いを体感するためのインジケータを作成しました。
ファイル名: 14_Indi_SpeedTest.mq5
保存先: MQL5\Indicators
パラメータ: UseSmartCalc(true=高速 / false=激重)
```mql5 //+------------------------------------------------------------------+ //| 14_Indi_SpeedTest.mq5 | //| ※動作原理を示すための簡易版コードです | //| 完全版は mql5_codes/14_Indi_SpeedTest.mq5 を参照してください | //+------------------------------------------------------------------+
property indicator_separate_window¶
property indicator_buffers 1¶
property indicator_plots 1¶
input bool UseSmartCalc = false; // true=高速(差分), false=低速(全期間) input int LoadWeight = 100; // 計算負荷係数
double Buffer[];
int OnInit() { SetIndexBuffer(0, Buffer, INDICATOR_DATA); 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[]) { ulong start = GetMicrosecondCount();
int limit;
if(UseSmartCalc) { // スマート計算:差分更新 if(prev_calculated == 0) limit = 0; else limit = prev_calculated - 1; } else { // 非効率計算:毎回最初から limit = 0; }
// 計算ループ for(int i = limit; i < rates_total; i++) { // 少し重い計算を回す(負荷シミュレーション) double sum = 0; for(int k=0; k < LoadWeight; k++) { sum += MathSqrt(close[i] * high[i]) / (k+1); } Buffer[i] = sum; }
ulong elapsed = GetMicrosecondCount() - start;
string mode = UseSmartCalc ? "高速(差分)" : "低速(全期間)"; Comment(StringFormat("モード: %s\n計算時間: %.3f ms\n計算対象: %d 本", mode, elapsed/1000.0, rates_total - limit));
return(rates_total); }
結果イメージ¶
手元の環境(ストラテジーテスター・ビジュアルモード)での実測結果です:
| モード | 計算時間(目安) |
|---|---|
| 全期間(False) | 27.070 ms (毎回発生) |
| 差分更新(True) | 0.001 ms (一瞬) |
「27msなら大したことない」と思うかもしれませんが、これが複数のチャート、複数のインジケータで同時に動くと、MT5全体の動作がもっさりしてきます。 一方、差分更新なら 0.001ms という驚異的な軽さです。
まとめ¶
| ポイント | 内容 |
|---|---|
| prev_calculated | これを使わない手はない |
| 全期間ループ | 致命的なパフォーマンス低下の原因 |
| 例外 | 過去データを参照して値が変わる特殊なインジケータ(ZigZagなど)は全計算が必要な場合もある |
基本的には「差分更新」をテンプレートとして使いましょう。
ソースコードのダウンロード¶
この記事で紹介したコードをダウンロードできます。
ファイルの種類:インジケータ
保存先: MQL5/Indicators/ フォルダ
使い方: MT5のナビゲーターから「インディケータ」を展開し、チャートにドラッグ&ドロップで適用