English Русский
preview
バックテスト結果を改善するための生のコードの最適化と調整

バックテスト結果を改善するための生のコードの最適化と調整

MetaTrader 5 |
18 0
Hlomohang John Borotho
Hlomohang John Borotho

はじめに

アルゴリズム取引における信頼性の高いバックテスト結果の追求は、堅牢な戦略ロジックだけでなく、基盤となるコードの効率性と正確性にも依存しています。コードの最適化や調整は、エキスパートアドバイザー(EA)が意図した通りに動作し、計算コストを最小化しつつ実行精度を最大化するために非常に重要です。最適化されていないコードは、注文の実行遅延、誤ったシグナル検出、リソースの枯渇などを引き起こし、戦略の本来の性能を正しく評価できない原因となります。

戦略開発の過程では、EAが機能的に強固であり、技術的にも安定していることを保証するため、いくつかの重要なステップを踏みます。まず、操作を効率化し、冗長なコードを避けるために、カスタムヘルパー関数や再利用可能なロジックブロックを追加します。次に、コードの可読性を向上させ、パラメータ調整を容易にするために、構造化された変数や定数を導入します。これらの基礎的な調整により、コードの保守性が向上し、バックテスト時や複数銘柄でのテスト時の実行時間も改善されます。

もうひとつの重要な改善領域は、テクニカル指標のより効率的な活用です。すべてのティックやバーごとにインジケーターを盲目的に計算するのではなく、負荷や遅延を減らすスマートな更新ロジックを実装します。また、EAのロジックでより良い意思決定をサポートするために、異なるインジケーターの組み合わせも試行します。構造的なコード改善、パフォーマンスを意識した関数、インジケーターの最適化と組み合わせることで、バックテストの質と速度を大幅に向上させ、安定して利益を上げられる、実運用可能な戦略により近づけます。



戦略開発

私たちのアルゴリズム取引戦略の開発は、パターン認識とシグナル検証に対する構造化された体系的なアプローチから始まります。本戦略の核となるのは、確率の高い反転シナリオを特定することを目的とした、ローソク足ベースのフレームワークです。ロングポジションの場合、ロジックは3つのパターンを体系的に検出します。連続する3本の強気ローソク足、それに続く1~2本の調整的な弱気ローソク足、インデックス1(直近で確定したバー)における確認用の強気ローソク足です。

一方、ショートポジションの場合は逆のパターンがトリガーとなります。つまり、連続する3本の弱気ローソク足、それに続く1~2本の戻しの強気ローソク足、インデックス1で確認用の弱気ローソク足です。この構成により、シグナルは新しいバーが形成された時点でのみ検証されるため、バー内の値動きによる誤検出を避け、確定した価格動向に基づいて実行されます。

このロジックを実際に運用可能にするため、戦略のアーキテクチャはモジュール化されたコード設計と計算効率を優先します。まず、ヘルパー関数を実装して、繰り返しおこなわれるタスクを抽象化します。具体的には、ローソク足の分類(強気/弱気判定)や連続ローソク足パターンの検証などです。これらの関数は、iOpenやiCloseなどMQL5のネイティブな価格データ取得メソッドを活用しつつ、静的変数によるキャッシュで冗長な計算を最小化します。

次に、イベント駆動型の実行モデルを採用し、EAがシグナルを処理するのは、インデックス1のタイムスタンプ比較によって新しいバーが形成されたときのみとします。この手法により、不要な計算負荷を削減し、バー形成中の誤トリガーを防止します。

開発の最終段階では、パフォーマンス最適化と堅牢性テストに重点を置きます。ボリュームフィルターやボラティリティ調整済みのストップロスなどのテクニカル指標を統合して、エントリー/エグジットの精度を向上させます。さらに、ローソク足パターンを格納するために履歴バッファ配列を事前に確保し、実行時のメモリ割り当てによるオーバーヘッドを削減します。これらの要素、つまりモジュール化されたコード設計、イベント駆動型実行、戦略的な指標統合を組み合わせることで、EAは応答性とリソース効率のバランスを実現します。

以下がコードです。

//+------------------------------------------------------------------+
//|                           Includes                               |
//+------------------------------------------------------------------+
#include <Trade/Trade.mqh>
MqlTick CTick, PTick;
CTrade trade;

enum InLot{
   Lot_fixed,
   Lot_dynamic,
};

//+------------------------------------------------------------------+
//|                           Inputs                                 |
//+------------------------------------------------------------------+
input group "--------------General Inputs--------------"
input int RSI_Period = 14;
input int    TakeProfit   = 100;     // TP in points
input ulong  MagicNumber  = 888888;  // EA identifier
input int    MaxBarsCheck = 8;       // Historical bars to analyze
input InLot Lot_mode = Lot_fixed;
input double  In_Lot = 0.01;
input bool TrailYourStop = true;
input int trailingStop = 50;
//input int StopLoss = 234;

//+------------------------------------------------------------------+
//|                      Global Variables                            |
//+------------------------------------------------------------------+
int rsiHandle, macdHanlde;
datetime lastBarTime;

double pointMultiplier;
double StopLoss     = 150;

bool bullish_pattern_met = false;
bool bearish_pattern_met = false;

double first_bullish_low = 0.0;
double first_bearish_high = 0.0;

ENUM_TIMEFRAMES TimeFrame = PERIOD_CURRENT;

EAのアーキテクチャは、スムーズな取引機能を実現するために、必須のライブラリやデータ構造を統合しています。<Trade/Trade.mqh>ライブラリの導入により、CTradeクラスへのアクセスが可能になり、ポジションのオープン、修正、クローズといった注文管理操作が効率化されます。2つのMqlTick構造体、CTickPTickはリアルタイムの価格データを追跡するために使用され、実行タイミングや市場状況の分析における精度を確保します。また、列挙型InLotはEAのポジションサイズ計算方法を定義しており、固定ロット(Lot_fixed)と動的ロット(Lot_dynamicの2つのモードを提供します。後者は将来的なリスク調整済みロット計算に対応することを想定しています。

ユーザーは専用の入力インターフェイスを通じて、コアコードを変更することなくパラメータを調整できます。主な設定項目には、指標の感度を制御するRSI_Period(デフォルト:14)、リスクリワードの閾値を定義するTakeProfitおよびtrailingStop(ポイント単位)があります。MagicNumberは取引の識別を保証し、MaxBarsCheckはパターン分析に使用する過去データの深さを決定します。ポジションサイズの柔軟性はLot_modeIn_Lotにより実現され、トレーリングストップ機能(TrailYourStoptrailingStop)により、利益が順調に伸びている取引を自動的に保護します。

内部的には、グローバル変数が複雑な運用フローを管理します。rsiHandlemacdHandleといったインジケーターハンドルは、ランタイム中にインジケーターインスタンスを格納・参照するために使用されます。lastBarTimeは直近で分析されたローソク足のタイムスタンプを記録することで時間同期をサポートします。ブール型フラグ(bullish_pattern_metbearish_pattern_met)やスイングレベルマーカー(first_bullish_lowfirst_bearish_high)は市場状況を動的に監視します。TimeFrameはEAの稼働周期を指定し、デフォルトでは即時利用可能なようにアクティブチャートの時間枠が設定されています。

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit(){

   // Create RSI and MACD indicator handles
   rsiHandle = iRSI(_Symbol, TimeFrame, RSI_Period, PRICE_CLOSE);
   macdHanlde = iMACD(_Symbol, TimeFrame, 12, 26, 9, PRICE_CLOSE);

   return(INIT_SUCCEEDED);
}

Oninit関数は、RSIおよびMACDインジケーターを正しく初期化します。それぞれの期間や価格タイプの設定を適切に割り当て、将来的に取引シグナル最適化で使用できるようメモリ上に準備します。初期化が正常に完了した場合は、INIT_SUCCEEDEDを返し、EAが実行可能な状態であることを確認します。

void OnTick(){

   if(!NewBarTrigger()) return;
   ThreeBar();
}
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
//| Detects new bar formation                                        |
//+------------------------------------------------------------------+
bool NewBarTrigger(){

   datetime currentTime = iTime(_Symbol, PERIOD_CURRENT, 0);
   if(currentTime != lastBarTime){
      lastBarTime = currentTime;
      return true;
   }
   return false;
}

double getHigh(int index) {
    return iHigh(_Symbol, _Period, index);
}

double getLow(int index) {
    return iLow(_Symbol, _Period, index);
}

//+------------------------------------------------------------------+
//| Execute trade with risk parameters                               |
//+------------------------------------------------------------------+
void ExecuteTrade(ENUM_ORDER_TYPE tradeType)
{
   double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
   double price = (tradeType == ORDER_TYPE_BUY) ? SymbolInfoDouble(_Symbol, SYMBOL_ASK) :                                                  SymbolInfoDouble(_Symbol, SYMBOL_BID);

   // Convert StopLoss and TakeProfit from pips to actual price distances
   double sl_distance = StopLoss * point;
   double tp_distance = TakeProfit * point;
   
   double sl = (tradeType == ORDER_TYPE_BUY) ? price - sl_distance :
                                               price + sl_distance;   
   double tp = (tradeType == ORDER_TYPE_BUY) ? price + tp_distance :
                                               price - tp_distance;
   trade.PositionOpen(_Symbol, tradeType, In_Lot, price, sl, tp, NULL);
}

OnTick関数はEAの心臓部として機能し、受信するすべての市場ティックで実行されます。過剰な処理や不要な計算を避けるため、まずNewBarTriggerを使って新しいローソク足の発生を確認します。 新しいバーが形成されていれば、その後で取引ロジックとパターン検出ルーチンを含むThreeBar関数を呼び出します。

NewBarTrigger関数は、新しいローソク足の開始を検出する役割を担います。具体的には、現在のローソク足の開始時間を以前に保存されたlastBarTimeと比較します。新しいタイムスタンプが検出された場合、lastBarTimeを更新しtrueを返すことで、OnTickが処理を進められるようにします。これにより、バーごとに1回だけ取引判断がおこなわれ、バー確定時のシグナルを基にした戦略において重要な処理精度が確保されます。getHighおよびgetLowヘルパー関数は、指定したバーインデックスの高値・安値を取得する簡易ユーティリティです。

ExecuteTrade関数は、適切なリスクパラメータを用いて取引を実行します。まず取引タイプ(買い/売り)に応じて現在の市場価格を取得し、ユーザー定義のpips/ポイント値を銘柄のPoint値で換算して、実際の価格単位でのストップロスおよびテイクプロフィットを計算します。計算された水準は、CTradeクラスのPositionOpenメソッドに渡され、各取引が正確かつ一貫したリスク管理のもとで実行されることを保証します。

//+------------------------------------------------------------------+
//| Candle pattern detection Function                                |
//+------------------------------------------------------------------+
void ThreeBar() {
   // Settings
   string symbol = Symbol();              // Current symbol
   ENUM_TIMEFRAMES timeframe = PERIOD_M5; // Timeframe (e.g., 1-hour)

   // Pattern detection variables
   int min_bullish_count = 3;
   int min_bearish_count = 3;
   int check_candles = 6;  // Check the last 6 candles for patterns

   bool bullish_pattern = false;
   bool bearish_pattern = false;
   static bool bullish_pattern_detected = false;
   static bool bearish_pattern_detected = false;

   // Loop through recent candles to search for patterns
   for (int i = check_candles; i >= min_bullish_count; i--) {
      
      // Reset pattern flags for each loop iteration
      bullish_pattern = false;
      bearish_pattern = false;

      // 1. Check for Bullish Trend Pattern
      int bullish_count = 0;
      int bearish_count = 0;

      // Count initial bullish candles
      for (int j = i; j >= i - min_bullish_count + 1; j--) {
         if (iClose(symbol, timeframe, j) > iOpen(symbol, timeframe, j)){
            bullish_count++;
            first_bullish_low = getLow(5);
         }
         else
            break;
      }

      // Check for 1 or 2 bearish candles followed by a bullish candle
      if (bullish_count >= min_bullish_count) {
         for (int j = i - bullish_count; j >= i - bullish_count - 1; j--) {
            if (iClose(symbol, timeframe, j) < iOpen(symbol, timeframe, j))
               bearish_count++;
            else
               break;
         }
         if ((bearish_count == 1 || bearish_count == 2) &&
             iClose(symbol, timeframe, i - bullish_count - bearish_count) > iOpen(symbol, timeframe, i - bullish_count - bearish_count)) {
            bullish_pattern = true;
         }
      }

      // 2. Check for Bearish Trend Pattern
      int bearish_candles_count = 0;
      int bullish_candles_count = 0;

      // Count initial bearish candles
      for (int j = i; j >= i - min_bearish_count + 1; j--) {
         if (iClose(symbol, timeframe, j) < iOpen(symbol, timeframe, j)){
            bearish_candles_count++;
            first_bearish_high = getHigh(5);
         }
         else
            break;
      }

      // Check for 1 or 2 bullish candles followed by a bearish candle
      if (bearish_candles_count >= min_bearish_count) {
         for (int j = i - bearish_candles_count; j >= i - bearish_candles_count - 1; j--) {
            if (iClose(symbol, timeframe, j) > iOpen(symbol, timeframe, j))
               bullish_candles_count++;
            else
               break;
         }
         if ((bullish_candles_count == 1 || bullish_candles_count == 2) &&
             iClose(symbol, timeframe, i - bearish_candles_count - bullish_candles_count) < iOpen(symbol, timeframe, i - bearish_candles_count - bullish_candles_count)) {
            bearish_pattern = true;
         }
      }

      // Print result and call functions only once for each pattern
      if (bullish_pattern && !bullish_pattern_detected) {
         Print("Bullish pattern conditions are met");
         ExecuteTrade(ORDER_TYPE_BUY); // Call to buyer function
         bullish_pattern_detected = true;
      } else if (!bullish_pattern) {
         bullish_pattern_detected = false; // Reset when conditions no longer met
      }

      if (bearish_pattern && !bearish_pattern_detected) {
         Print("Bearish pattern conditions are met");
         ExecuteTrade(ORDER_TYPE_SELL); // Call to seller function
         bearish_pattern_detected = true;
      } else if (!bearish_pattern) {
         bearish_pattern_detected = false; // Reset when conditions no longer met
      }   
   }
}

ThreeBar関数は、EAの中核となるパターン検出ロジックです。この関数は、M5時間枠またはEAを適用した任意の時間枠で直近のローソク足形成を解析し、強気または弱気のトレンド反転の可能性があるセットアップを検出します。ロジックは、直近6本のローソク足をスキャンし、少なくとも3本の連続した強気または弱気ローソク足の後に、1〜2本の逆方向のプルバックが続き、さらに元のトレンド方向に確認用ローソク足が出現するパターンを識別します。こうした構造が確認されると、有効なパターンの存在をフラグで示します。

スキャンループの各ステップでは、強気スイングの安値や弱気スイングの高値などの主要な価格ポイントを取得します。これらは、将来的な構造のブレイク確認や取引フィルターとして利用可能です。

現在のEAでは、強気ローソク足の後に1本の弱気ローソク足がある場合に2つのポジションを開き、ローソク足確定後にもう1つの取引を開始します。一方、2本の弱気ローソク足の後に強気ローソク足が出た場合は1つのポジションしか開きません。売りシグナルでも同様の問題が発生しています。この問題を解決するためには、パターンが取引を発動した時点でループを即座に停止するbreakを追加する必要があります。これにより、EAがさらに過去のローソク足までループして同じパターンを異なる位置で誤検出することを防げます。特に、構造上1本および2本のプルバックの両方が存在する場合に重要です。

// Print result and call functions only once for each pattern
if (bullish_pattern && !bullish_pattern_detected) {
   Print("Bullish pattern conditions are met");
   ExecuteTrade(ORDER_TYPE_BUY);
   bullish_pattern_detected = true;
   break; // prevent further detection in this run
} else if (!bullish_pattern) {
   bullish_pattern_detected = false;
}
      
if (bearish_pattern && !bearish_pattern_detected) {
   Print("Bearish pattern conditions are met");
   ExecuteTrade(ORDER_TYPE_SELL);
   bearish_pattern_detected = true;
   break; // prevent further detection in this run
} else if (!bearish_pattern) {
   bearish_pattern_detected = false;
}

インジケーターフィルターの組み込み

// Indicator buffers
double rsi_value[1];    // Use array for buffer storage
double macd_main[1];    
double macd_signal[1];
   
// Get indicator values - with error checking
if (CopyBuffer(rsiHandle, 0, 0, 1, rsi_value) <= 0) {
    Print("RSI CopyBuffer error: ", GetLastError());
    return;
}
   
if (CopyBuffer(macdHandle, 0, 0, 1, macd_main) <= 0) { 
    Print("MACD Main Line error: ", GetLastError());
    return;
}
   
if (CopyBuffer(macdHandle, 1, 0, 1, macd_signal) <= 0) { 
    Print("MACD Signal Line error: ", GetLastError());
    return;
}

取引ロジックにインジケーターフィルターを組み込む前に、CopyBufferを使用してRSIおよびMACDインジケーターの値を取得し、検証する必要があります。これにより、EAはインジケーターハンドルから最新のデータを正確に取得できます。上記のコードでは、各インジケーターバッファから1つの値を取得して配列に格納しています。また、各バッファコピー後にエラーハンドリングを組み込むことで、ハンドルへのアクセス失敗やデータ欠損などの問題を検出できるようにしています。

いずれかのバッファがデータ取得に失敗した場合、関数は早期に終了し、対応するエラーをログに記録します。これにより、EAが無効または欠損したインジケーター入力に基づいて取引判断をおこなうことを防止します。

// Filter + Trade Logic
if (bullish_pattern && !bullish_pattern_detected &&
    rsi_value[0] > 50 && macd_main[0] > macd_signal[0])  
{
    Print("Bullish pattern confirmed with RSI(", rsi_value[0], ") and MACD(", 
          macd_main[0], "/", macd_signal[0], ")");
    ExecuteTrade(ORDER_TYPE_BUY);
    bullish_pattern_detected = true;
    break;
 }
else if (!bullish_pattern)
    bullish_pattern_detected = false;
      
if (bearish_pattern && !bearish_pattern_detected &&
    rsi_value[0] < 50 && macd_main[0] < macd_signal[0])  
{
    Print("Bearish pattern confirmed with RSI(", rsi_value[0], ") and MACD(", 
          macd_main[0], "/", macd_signal[0], ")");
    ExecuteTrade(ORDER_TYPE_SELL);
    bearish_pattern_detected = true;
    break;
}
else if (!bearish_pattern)
    bearish_pattern_detected = false;

3本ローソク足パターンだけでは、全体の市場トレンドを考慮していないため、バックテストの結果が不安定で誤シグナルが多く発生しやすくなります。パフォーマンスを改善するために、取引判断ブロックにRSIおよびMACDインジケーターを組み込み、トレンド確認を導入します。

強気シグナルが有効であるためには、RSIが50以上であること(強気モメンタムを示す)と、MACDのメインラインがシグナルラインより上にあること(上昇トレンドを確認)を条件とします。弱気シグナルでは、RSIが50未満、MACDのメインラインがシグナルラインより下にあることが条件となります。このフィルタリングにより、エントリーが市場全体のモメンタムと一致するため、取引精度が向上します。

これらのフィルターを組み込むことで、三本ローソク足パターンによって発生する取引は、現行の市場方向を反映したテクニカル指標に裏付けられた場合のみ実行されます。この二重確認アプローチにより、レンジ相場やノイズが多い相場での不要な取引の発生を抑え、勢いのある取引にエントリーする確率を高めます。

最終的に、この改良によりEAの意思決定能力が向上し、バックテスト結果の安定性や好結果が期待できるようになります。また、ローソク足パターンだけに頼ることで生じるホイップソー(逆方向の急反発)を回避することにもつながります。



XAU/USDのバックテスト結果

バックテストは、XAUUSD(ゴールド/米ドル)の1時間足を対象に、2021年11月26日から2022年3月17日までの4か月間の過去価格データを用いて評価されました。主なパラメータは以下の通りです。

  • RSI期間:14
  • リスク管理:テイクプロフィット(TP)500ポイント、ストップロス(SL)150ポイント、固定ロットサイズ
  • 分析構成:8本分のローソク足を遡った履歴ルックバック

市場環境

テスト期間中、金価格は平均$1,841.59で推移し、価格の振れ幅は$1,752.95(安値)から$2,070.48(高値)まで広がりました。この上昇傾向は強い強気バイアスを示しており、特に2022年初頭には価格が約18%上昇して3月の高値に達しています。この期間の顕著な上昇トレンドは、マクロ経済要因とも整合しています。



結論

本最適化プロセスでは、3本ローソク足パターンのみを取引エントリーの基準とすることの限界を解消するために、RSIおよびMACDといった主要テクニカル指標を組み込みました。まず、インジケーターハンドルとバッファコピーを用いた堅牢なデータ取得を実装し、エラーチェックをおこないました。その後、これらのインジケーターを取引実行ロジック内の確認フィルターとして適用し、取引が現在の市場トレンドに沿うように調整しました。この手法により、レンジ相場や逆トレンド時の低確率シグナルを除外できるため、取引の質とバックテストの一貫性が大幅に向上します。

要約すると、ローソク足パターンとRSIやMACDなどのモメンタム系インジケーターを組み合わせることで、市場状況を考慮したよりコンテキスト認識型の取引システムが構築できます。この多層的アプローチにより、EAは高品質の取引セットアップと低品質のセットアップを区別する能力が向上します。ローソク足パターンと広範なトレンドシグナルの両方が一致することを条件とすることで、EAはより規律ある判断をおこない、短期的なノイズに過剰反応することが減少します。その結果、バックテストおよび実運用の両面でパフォーマンスと堅牢性が向上します。

さらなる改善と研究

エリア
 提案
メリット
ダイナミックストップロスまたはテイクプロフィット
ATRまたは最近のボラティリティに基づいてSL/TPを調整する
市場の状況に合わせてリスク管理を適応させる
トレンドフィルターの強化
長期トレンドを定義するために移動平均(例:200EMA)を含める
進入方向の精度を向上
取引頻度制御
取引間のクールダウン時間や1日あたりの最大取引回数の制限を追加する
過剰取引と取引の集中を軽減
多時間枠分析
RSIとMACDのトレンドをより長い時間枠(例:H1、H4)で確認する
時間枠をまたいで調整することでシグナルの信頼性を向上
ニュースフィルタリング
経済指標カレンダーやニュース時間検出を統合する
影響の大きいイベント中の取引を回避

MetaQuotes Ltdにより英語から翻訳されました。
元の記事: https://www.mql5.com/en/articles/17702

添付されたファイル |
EAのサンプル EAのサンプル
一般的なMACDを使ったEAを例として、MQL4開発の原則を紹介します。
初級から中級まで:テンプレートとtypename(II) 初級から中級まで:テンプレートとtypename(II)
この記事では、最も難しいプログラミング状況のひとつである、同じ関数または手続きのテンプレート内で異なる型を使用する方法について説明します。これまで私たちは主に関数に焦点を当ててきましたが、ここで扱う内容はすべて手続きにも役立ち、応用可能です。
エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法 エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法
この記事では、MT4において複数のEAの衝突をさける方法を扱います。ターミナルの操作、MQL4の基本的な使い方がわかる人にとって、役に立つでしょう。
初級から中級まで:テンプレートとtypename(I) 初級から中級まで:テンプレートとtypename(I)
この記事では、多くの初心者が避けがちな概念の1つを取り上げます。これはテンプレートに関連する話題で、多くの人がテンプレートの基本原理を理解していないため、決して簡単なテーマではありません。その基本原理とは、関数や手続きのオーバーロードです。