コンテンツにスキップ

【検証#07】MQL⅘コードの高速化テクニック

この記事の3行まとめ

  • ⏱️ GetTickCount でコードの実行時間を計測できる
  • 🚀 static 変数で計算結果をキャッシュして高速化
  • ⚡ 関数呼び出しを減らし、OnInit に移動できる処理は移動

はじめに

この記事は、「とあるMetaTraderの備忘秘録」様のブログ記事を検証・紹介するシリーズの第7弾です。

2010年に公開された「MQL4の高速化」連載は、現代のMQL5開発でも有効な知見が満載です。

元ネタ

MQL4の高速化 連載記事
- MQL4の高速化(1) 速度測定(2010-02-14)
- MQL4の高速化(2) バックテスト時間(2010-02-15)
- MQL4の高速化(3) 1秒でも速く(2010-02-17)

時代背景に関する注意

この記事の元となるブログ記事は2010年頃に公開されたものです。
MQL5のコンパイラ最適化技術の向上により、当時の高速化テクニックの一部は効果が薄れている可能性があります。


なぜ高速化が重要なのか

シナリオ 高速化の効果
バックテスト テスト時間短縮 → 多くの条件を検証可能
最適化 パラメータ探索範囲を広げられる
リアルタイム ティック処理の遅延を減らす
MTF計算 複数時間足の計算負荷を軽減

特にMT5の最適化機能では、1回のバックテストが0.1秒速くなるだけで、1000回の最適化で100秒(約2分)の節約になります。


ベンチマーク手法

コードの速度を計測するには GetTickCount を使用します。

基本的な計測方法

MQL
void OnStart()
{
    int iterations = 1000000;  // 100万回

    // 計測開始
    uint start = GetTickCount();

    // 計測対象のコード
    for(int i = 0; i < iterations; i++)
    {
        // テストしたい処理
        double result = MathSqrt(12345.6789);
    }

    // 計測終了
    uint elapsed = GetTickCount() - start;

    PrintFormat("処理時間: %d ms (%d 回)", elapsed, iterations);
}

より精密な計測

MQL5では GetMicrosecondCount も使用可能です:

MQL
ulong start = GetMicrosecondCount();
// 処理
ulong elapsed = GetMicrosecondCount() - start;
PrintFormat("処理時間: %d μs", elapsed);

高速化テクニック集

1. static変数で計算結果をキャッシュ

Before(遅い):

MQL
void OnTick()
{
    // 毎ティックで同じ計算を繰り返している
    double pipValue = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE);
    double lotStep = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);

    // 使用...
}

After(速い):

MQL
void OnTick()
{
    // static変数にキャッシュ
    static double pipValue = 0;
    static double lotStep = 0;
    static bool initialized = false;

    if(!initialized)
    {
        pipValue = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE);
        lotStep = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
        initialized = true;
    }

    // 使用...
}

さらに良い方法

変化しない値は OnInit で取得し、グローバル変数に格納するのがベスト。


2. OnInitに移動できる処理を移動

Before:

MQL
int indicatorHandle;

void OnTick()
{
    // 毎ティックでハンドル取得(非効率)
    indicatorHandle = iMA(_Symbol, PERIOD_CURRENT, 20, 0, MODE_SMA, PRICE_CLOSE);

    double buffer[];
    CopyBuffer(indicatorHandle, 0, 0, 1, buffer);
    // 使用...

    IndicatorRelease(indicatorHandle);  // 毎回解放
}

After:

MQL
int indicatorHandle;

int OnInit()
{
    // 初期化時に一度だけ取得
    indicatorHandle = iMA(_Symbol, PERIOD_CURRENT, 20, 0, MODE_SMA, PRICE_CLOSE);
    return INIT_SUCCEEDED;
}

void OnTick()
{
    double buffer[];
    CopyBuffer(indicatorHandle, 0, 0, 1, buffer);
    // 使用...
}

void OnDeinit(const int reason)
{
    // 終了時に解放
    IndicatorRelease(indicatorHandle);
}


3. 文字列連結を避ける

Before(遅い):

MQL
string message = "";
for(int i = 0; i < 100; i++)
{
    message = message + "Item " + IntegerToString(i) + ", ";
}
Print(message);

After(速い):

MQL
string message = "";
for(int i = 0; i < 100; i++)
{
    message += StringFormat("Item %d, ", i);
}
Print(message);

StringConcatenate

MQL4では StringConcatenate 関数を使うとさらに高速でした。
MQL5では StringFormat+= 演算子が最適化されています。


4. 短絡評価を活用

Before:

MQL
// 両方の条件を常に評価
if(IsTradeAllowed() && CheckComplexCondition())
{
    Trade();
}

After:

MQL
// 左の条件がfalseなら右は評価されない(短絡評価)
if(CheckComplexCondition() && IsTradeAllowed())  // 重い処理を後に
{
    Trade();
}

順序に注意

軽い条件を先に、重い条件を後にすることで、
軽い条件で弾かれた場合に重い計算をスキップできます。


5. 配列の事前確保

Before:

MQL
double buffer[];
for(int i = 0; i < 1000; i++)
{
    ArrayResize(buffer, ArraySize(buffer) + 1);  // 毎回リサイズ(遅い)
    buffer[i] = i * 1.5;
}

After:

MQL
double buffer[];
ArrayResize(buffer, 1000);  // 最初に確保
for(int i = 0; i < 1000; i++)
{
    buffer[i] = i * 1.5;
}


ベンチマーク用スクリプト

以下は各テクニックの効果を計測するスクリプトです:

MQL
//+------------------------------------------------------------------+
//|                                        Benchmark_Sample.mq5       |
//| 「とあるMetaTraderの備忘秘録」様の記事を検証                        |
//+------------------------------------------------------------------+
#property copyright "FXおもしろラボ"
#property link      "https://fx-omoshiro-lab.com/"
#property version   "1.00"

void OnStart()
{
    int iterations = 100000;

    Print("=== 高速化テクニック ベンチマーク ===");
    Print("反復回数: ", iterations);
    Print("");

    // テスト1: SymbolInfoDouble キャッシュなし vs あり
    uint start1 = GetTickCount();
    for(int i = 0; i < iterations; i++)
    {
        double tickSize = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
    }
    uint time1 = GetTickCount() - start1;

    static double cachedTickSize = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
    uint start2 = GetTickCount();
    for(int i = 0; i < iterations; i++)
    {
        double tickSize = cachedTickSize;
    }
    uint time2 = GetTickCount() - start2;

    PrintFormat("SymbolInfoDouble 毎回呼び出し: %d ms", time1);
    PrintFormat("キャッシュ使用            : %d ms", time2);
    PrintFormat("高速化率: %.1f 倍", (double)time1 / MathMax(time2, 1));
    Print("");

    // テスト2: 文字列連結
    uint start3 = GetTickCount();
    string s1 = "";
    for(int i = 0; i < 1000; i++)
    {
        s1 = s1 + "test";
    }
    uint time3 = GetTickCount() - start3;

    uint start4 = GetTickCount();
    string s2 = "";
    for(int i = 0; i < 1000; i++)
    {
        s2 += "test";
    }
    uint time4 = GetTickCount() - start4;

    PrintFormat("文字列連結 (+ 演算子)   : %d ms", time3);
    PrintFormat("文字列連結 (+= 演算子)  : %d ms", time4);
    Print("");

    Print("=== ベンチマーク完了 ===");
}

まとめ

テクニック 効果 適用場面
static変数キャッシュ 変化しない値の再計算防止
OnInit移動 ハンドル取得、初期設定
文字列連結最適化 ログ出力、文字列処理
短絡評価 複合条件
配列事前確保 動的配列の使用時

オリジナル記事への謝辞

この記事は「とあるMetaTraderの備忘秘録」様の貴重な連載記事をもとに、
MQL5での検証と解説を加えたものです。
オリジナル記事に心より感謝いたします。


ソースコードのダウンロード

この記事で紹介した高速化テクニックの効果を 実際に数値で確認できる ベンチマークスクリプトを用意しました。

ファイルの種類:スクリプト

保存先: MQL5/Scripts/ フォルダ
使い方: MT5のナビゲーターから「スクリプト」を展開し、チャートにドラッグ&ドロップで実行

07_Benchmark.mq5 をダウンロード

ベンチマーク結果の見方

スクリプトを実行すると、エキスパートログに以下のような結果が出力されます。

Text Only
=== 高速化テクニック ベンチマーク ===
反復回数: 100000

SymbolInfoDouble 毎回呼び出し: 94 ms
キャッシュ使用            : 0 ms
高速化率: 94.0 倍

文字列連結 (+ 演算子)   : 0 ms
文字列連結 (StringAdd) : 0 ms
高速化率: 0.0 倍

=== テスト完了 ===

結果の解釈

テスト項目 意味
SymbolInfoDouble比較 SymbolInfoDouble を毎回呼び出す vs 一度取得してキャッシュする比較。94倍の高速化は、キャッシュの威力を示しています。
文字列連結比較 + 演算子 vs StringAdd の比較。現代のMQL5では最適化が進んでおり、10000回程度では差が出にくくなっています。

ベンチマークのポイント

  • SymbolInfoDouble などの関数呼び出しコストは依然として大きい
  • 現代のMQL5コンパイラは文字列処理を最適化しているため、昔ほどの差は出ない
  • 重要なのは「キャッシュの考え方」— 変化しない値は何度も取得しない

関連用語

この記事で登場した用語は用語集でも解説しています。