English Deutsch
preview
MQL5での取引戦略の自動化(第20回):CCIとAOを使用した多銘柄戦略

MQL5での取引戦略の自動化(第20回):CCIとAOを使用した多銘柄戦略

MetaTrader 5トレーディング |
15 0
Allan Munene Mutiiria
Allan Munene Mutiiria

はじめに

前回の記事(第19回)では、Envelopes Trend Bounce Scalping戦略を取り上げ、MetaQuotes Language 5 (MQL5)での自動化を完成させるために、取引執行とリスク管理に焦点を当てました。第20回では、複数の通貨ペアにおけるトレンド反転を捉える多銘柄取引戦略を紹介します。本戦略では、商品チャンネル指数(CCI: Commodity Channel Index)とオーサムオシレータ(AO: Awesome Oscillator)を利用します。次のトピックについて説明します。

  1. 戦略ロードマップとアーキテクチャ
  2. MQL5での実装
  3. バックテストと最適化
  4. 結論

記事を読み終える頃には、多銘柄取引に対応した堅牢なMQL5取引システムを完成させ、最適化および実運用に向けて準備できるようになります。さっそく始めましょう。


戦略ロードマップとアーキテクチャ

第19回では、Envelopes Trend Bounce Scalping戦略を構築し、価格がEnvelopesインジケーターと相互作用する際に発生するシグナルに基づく取引執行とリスク管理に焦点を当てました。今回の第20回では、複数通貨ペアのトレンド反転を捉える多銘柄取引戦略に焦点を移します。本戦略では、CCIとAOを使用し、2つの時間足(M5でのシグナル生成、H1でのトレンド確認)を組み合わせます。今回のロードマップでは、スケーラブルで効率的にシグナルを処理し、取引を実行し、リスク管理を行うシステム設計に重点を置きます。

アーキテクチャ設計は、モジュール化と堅牢性を重視しており、MQL5のクラスベース構造を活用して戦略のコンポーネントを整理します。共通クラスを作成し、CCIとAOのインジケーター計算、事前定義された閾値に基づくシグナル生成、ストップロスおよびテイクプロフィット設定を伴う取引執行を管理します。また、既存のオープンポジションがない場合のみ新規取引を行うといった安全策も組み込みます。本設計は、スプレッドチェックや、新しいバー発生時やティック単位での取引に対応可能な柔軟性を備え、さまざまな市場状況に対応できる、統合的な多銘柄取引システムを提供することを目的としています。以下のようなイメージを目指しています。

戦略計画


MQL5での実装

MQL5でプログラムを作成するには、まずMetaEditorを開き、ナビゲータに移動して、Indicatorsフォルダを見つけ、[新規作成]タブをクリックして、表示される手順に従ってファイルを作成します。作成が完了したら、コーディング環境では、まず使用する構造体やクラスを宣言します。本戦略では、オブジェクト指向プログラミング(OOP)アプローチを適用したいと考えているためです。

//+------------------------------------------------------------------+
//|                                          MultiSymbolCCIAO_EA.mq5 |
//|                           Copyright 2025, Allan Munene Mutiiria. |
//|                                   https://t.me/Forex_Algo_Trader |
//+------------------------------------------------------------------+
#property copyright "Copyright 2025, Allan Munene Mutiiria."
#property link      "https://t.me/Forex_Algo_Trader"
#property version   "1.00"
#property strict
#property description "Multi-symbol trading strategy using CCI and AO indicators"

#include <Trade/Trade.mqh> //--- Include the Trade library for trading operations

//+------------------------------------------------------------------+
//| Input Parameters Structure                                       | //--- Define a structure for trading parameters
//+------------------------------------------------------------------+
struct TradingParameters {
   string            symbols;                  //--- Store comma-separated symbol list
   int               per_signal_cci;           //--- Set period for CCI signal
   int               per_trend_cci;            //--- Set period for CCI trend
   ENUM_APPLIED_PRICE price_cci;               //--- Specify applied price for CCI
   int               cci_signal_buy_value;     //--- Define CCI buy signal threshold
   int               cci_signal_sell_value;    //--- Define CCI sell signal threshold
   ENUM_TIMEFRAMES   tf_signal;                //--- Set timeframe for signal
   ENUM_TIMEFRAMES   tf_trend;                 //--- Set timeframe for trend
   bool              use_ao_for_trend;         //--- Enable AO for trend confirmation
   int               take;                     //--- Set take-profit in points
   int               stop;                     //--- Set stop-loss in points
   double            lots;                     //--- Specify trade lot size
   int               slip;                     //--- Set maximum slippage
   int               max_spread;               //--- Define maximum allowed spread
   int               magic;                    //--- Set magic number for trades
   bool              trade_anytime;            //--- Allow trading at any time
   string            comment;                  //--- Store trade comment
   int               tester_max_balance;       //--- Set tester balance limit
   bool              debug_mode;               //--- Enable debug mode
};

//+------------------------------------------------------------------+
//| Symbol Data Structure                                            | //--- Define a structure for symbol-specific data
//+------------------------------------------------------------------+
struct SymbolData {
   string            name;                     //--- Store symbol name
   datetime          last_bar_time;            //--- Track last bar timestamp
   int               cci_signal_handle;        //--- Hold CCI signal indicator handle
   int               cci_trend_handle;         //--- Hold CCI trend indicator handle
   int               ao_signal_handle;         //--- Hold AO signal indicator handle
   int               ao_trend_handle;          //--- Hold AO trend indicator handle
   double            cci_signal_data[];        //--- Store CCI signal data
   double            cci_trend_data[];         //--- Store CCI trend data
   double            ao_signal_data[];         //--- Store AO signal data
   double            ao_trend_data[];          //--- Store AO trend data
};

多銘柄のCCIおよびAO取引戦略の実装を開始するにあたり、まず堅牢な取引実行のための基盤コンポーネントを設定します。取引操作を可能にするTrade.mqhライブラリをインクルードし、CTradeクラスを利用できるようにします。このクラスは、ポジションのオープンおよびクローズのメソッドを提供し、複数の銘柄に対して売買注文を実行するために必要な基本的取引機能にアクセスできるようにします。 

次に、戦略のすべての入力パラメータを整理するためのTradingParameters構造体を作成します。この構造体には、通貨ペアのカンマ区切りリストを格納するsymbols、CCIインジケーターの期間を指定するper_signal_cciとper_trend_cci、適用価格タイプを指定するprice_cciなどの重要な変数が含まれます。また、シグナルの閾値を設定するcci_signal_buy_valueとcci_signal_sell_value、時間足を指定するtf_signalとtf_trend、リスク管理に関連するtake、stop、lots、max_spreadも定義しています。さらに、magicにより取引の一意識別をおこない、trade_anytimeで取引タイミングを制御し、debug_modeにより診断ログを有効化できます。これにより、戦略の設定を集中管理できる構造となっています。

最後に、取引システムで各銘柄のデータを管理するSymbolData構造体を定義します。この構造体には、銘柄の識別子を保持するname、最新バーのタイムスタンプを追跡するlast_bar_time、シグナルおよびトレンドの時間足に対応したCCIやAOインジケーターのハンドルcci_signal_handle、ao_trend_handleを含めます。また、cci_signal_dataやao_trend_dataといった配列も含め、インジケーター値を格納して多銘柄処理の効率を向上させます。これらの構造体は、モジュール化され拡張性の高い取引システムの基盤を形成します。次におこなうべきは、制御ロジックを担う取引クラスを宣言することです。

//+------------------------------------------------------------------+
//| Trading Strategy Class                                           | //--- Implement a class for trading strategy logic
//+------------------------------------------------------------------+
class CTradingStrategy {
private:
   CTrade            m_trade;                  //--- Initialize trade object for trading operations
   TradingParameters m_params;                 //--- Store trading parameters
   SymbolData        m_symbols[];              //--- Store array of symbol data
   int               m_array_size;             //--- Track number of symbols
   datetime          m_last_day;               //--- Store last day timestamp
   bool              m_is_new_day;             //--- Indicate new day detection
   int               m_candle_shift;           //--- Set candle shift for signal calculation
   const int         CCI_TREND_BUY_VALUE;      //--- Define constant for CCI trend buy threshold
   const int         CCI_TREND_SELL_VALUE;     //--- Define constant for CCI trend sell threshold
}

ここでは、CTradingStrategyクラスを作成して戦略のコアロジックを実装します。このクラスは、すべての取引機能をモジュール化し整理された形でカプセル化します。まず、戦略の状態や操作を管理するためのprivateメンバー変数を定義します。最初に、TradeライブラリのCTradeクラスのインスタンスであるm_tradeを定義し、ポジションのオープンやクローズなどの取引実行タスクを担当させます。次に、TradingParameters構造体のインスタンスであるm_paramsを用意し、銘柄リスト、インジケーター期間、リスクパラメータなど、すべての設定を格納します。これにより、ユーザー定義の入力に集中してアクセスできるようになります。

さらに、m_symbolsとしてSymbolData構造体の配列を宣言し、各銘柄のデータを保持します。これにはインジケーターハンドルやデータバッファが含まれ、多銘柄処理を効率的におこなえるようにします。銘柄の数を管理するためにm_array_sizeを使用し、m_last_dayおよびm_is_new_dayで日次タイムスタンプを管理し、新しい取引日の検出をおこないます。加えて、m_candle_shiftを設定し、trade_anytimeパラメータに基づいてシグナルを現在のバーまたは前のバーで生成するかを制御します。最後に、CCI_TREND_BUY_VALUEおよびCCI_TREND_SELL_VALUEという定数を定義し、トレンド確認用の固定CCI閾値を設定します。これにより、戦略全体で一貫したシグナルロジックを確保できます。これらの設定を基に、privateアクセス修飾子の下にさらにユーティリティ用のメソッドを追加していくことができます。

void PrintDebug(string text) {              //--- Define method to print debug messages
   if(m_params.debug_mode && !MQLInfoInteger(MQL_OPTIMIZATION)) { //--- Check debug mode and optimization status
      Print(text);                          //--- Output debug message
   }
}

void PrintMessage(string text) {            //--- Define method to print informational messages
   if(!MQLInfoInteger(MQL_OPTIMIZATION)) {  //--- Check if not in optimization mode
      Print(text);                          //--- Output message
   }
}

void PrepareSymbolsList() {                 //--- Define method to prepare symbol list
   string symbols_array[];                  //--- Initialize temporary array for symbols
   ushort sep = StringGetCharacter(",", 0); //--- Get comma separator character
   m_array_size = StringSplit(m_params.symbols, sep, symbols_array); //--- Split symbols string into array
   ArrayResize(m_symbols, m_array_size);    //--- Resize symbol data array
   for(int i = 0; i < m_array_size; i++) {  //--- Iterate through symbols
      m_symbols[i].name = symbols_array[i]; //--- Set symbol name
      m_symbols[i].last_bar_time = 0;       //--- Initialize last bar time
      SymbolSelect(m_symbols[i].name, true); //--- Ensure symbol is in market watch
   }
}

CTradingStrategyクラス内に、デバッグ処理や多銘柄CCI・AO戦略向けの銘柄準備を行うユーティリティメソッドを実装します。まず、m_params.debug_modeが有効で、EAが最適化モードでない場合に診断メッセージを出力するPrintDebug関数を作成します。Printを使用して、トラブルシューティング用にtextパラメータをログに記録します。同様に、PrintMessage関数を定義し、情報メッセージを出力します。ここでもMQLInfoInteger(MQL_OPTIMIZATION)で最適化モードでないことを確認し、text入力をPrint関数でログに出力します。

さらに、PrepareSymbolsList関数を作成し、銘柄データ配列を初期化します。まず、文字列を分割するための一時配列symbols_arrayを宣言し、文字列区切り文字sepとしてStringGetCharacterでカンマを取得します。その後、StringSplitを使用してm_params.symbolsをsymbols_arrayに分割します。m_symbolsをArrayResizeでm_array_sizeに合わせてサイズ変更し、各要素に対してm_symbols[i].nameに銘柄名を設定し、m_symbols[i].last_bar_timeをゼロに初期化し、SymbolSelectを呼び出して各銘柄が気配値表示に表示され、取引可能であることを確認します。次のステップでは、インジケーターの値を初期化および更新する処理を実装する必要があります。

bool InitializeIndicators(int index) {      //--- Define method to initialize indicators
   m_symbols[index].cci_signal_handle = iCCI(m_symbols[index].name, m_params.tf_signal, m_params.per_signal_cci, m_params.price_cci); //--- Create CCI signal indicator
   if(m_symbols[index].cci_signal_handle == INVALID_HANDLE) { //--- Check for invalid handle
      Print("INITIALIZATION OF CCI SIGNAL FAILED: ", m_symbols[index].name); //--- Log error
      return false;                         //--- Return failure
   }
   m_symbols[index].cci_trend_handle = iCCI(m_symbols[index].name, m_params.tf_trend, m_params.per_trend_cci, m_params.price_cci); //--- Create CCI trend indicator
   if(m_symbols[index].cci_trend_handle == INVALID_HANDLE) { //--- Check for invalid handle
      Print("INITIALIZATION OF CCI TREND FAILED: ", m_symbols[index].name); //--- Log error
      return false;                         //--- Return failure
   }
   m_symbols[index].ao_signal_handle = iAO(m_symbols[index].name, m_params.tf_signal); //--- Create AO signal indicator
   if(m_symbols[index].ao_signal_handle == INVALID_HANDLE) { //--- Check for invalid handle
      Print("INITIALIZATION OF AO SIGNAL FAILED: ", m_symbols[index].name); //--- Log error
      return false;                         //--- Return failure
   }
   m_symbols[index].ao_trend_handle = iAO(m_symbols[index].name, m_params.tf_trend); //--- Create AO trend indicator
   if(m_symbols[index].ao_trend_handle == INVALID_HANDLE) { //--- Check for invalid handle
      Print("INITIALIZATION OF AO TREND FAILED: ", m_symbols[index].name); //--- Log error
      return false;                         //--- Return failure
   }
   ArraySetAsSeries(m_symbols[index].cci_signal_data, true); //--- Set CCI signal data as series
   ArraySetAsSeries(m_symbols[index].cci_trend_data, true); //--- Set CCI trend data as series
   ArraySetAsSeries(m_symbols[index].ao_signal_data, true); //--- Set AO signal data as series
   ArraySetAsSeries(m_symbols[index].ao_trend_data, true); //--- Set AO trend data as series
   return true;                             //--- Return success
}
   
bool UpdateIndicatorData(int index) {       //--- Define method to update indicator data
   if(CopyBuffer(m_symbols[index].cci_signal_handle, 0, 0, 3, m_symbols[index].cci_signal_data) < 3) { //--- Copy CCI signal data
      Print("UNABLE TO COPY CCI SIGNAL DATA: ", m_symbols[index].name); //--- Log error
      return false;                         //--- Return failure
   }
   if(CopyBuffer(m_symbols[index].cci_trend_handle, 0, 0, 3, m_symbols[index].cci_trend_data) < 3) { //--- Copy CCI trend data
      Print("UNABLE TO COPY CCI TREND DATA: ", m_symbols[index].name); //--- Log error
      return false;                         //--- Return failure
   }
   if(CopyBuffer(m_symbols[index].ao_signal_handle, 0, 0, 3, m_symbols[index].ao_signal_data) < 3) { //--- Copy AO signal data
      Print("UNABLE TO COPY AO SIGNAL DATA: ", m_symbols[index].name); //--- Log error
      return false;                         //--- Return failure
   }
   if(CopyBuffer(m_symbols[index].ao_trend_handle, 0, 0, 3, m_symbols[index].ao_trend_data) < 3) { //--- Copy AO trend data
      Print("UNABLE TO COPY AO TREND DATA: ", m_symbols[index].name); //--- Log error
      return false;                         //--- Return failure
   }
   return true;                             //--- Return success
}

ここでは、CTradingStrategyクラス内でインジケーターの設定と更新を実装します。まず、InitializeIndicators関数を作成し、指定したindexの銘柄に対して、iCCI関数を使用してm_symbols[index].cci_signal_handleおよびm_symbols[index].cci_trend_handleを設定し、iAO関数を使用してm_symbols[index].ao_signal_handleおよびm_symbols[index].ao_trend_handleを設定します。ハンドルがINVALID_HANDLEの場合は、Print関数でエラーをログに出力します。また、データ配列はArraySetAsSeriesを使用して時系列として設定します。

さらに、UpdateIndicatorData関数を作成し、CopyBuffer関数を使用して3つのデータポイントをm_symbols[index].cci_signal_data、m_symbols[index].cci_trend_data、m_symbols[index].ao_signal_data、m_symbols[index].ao_trend_dataにコピーします。取得したデータが不足している場合は、Print関数でエラーをログに出力します。次のステップでは、ポジションの閾値を管理するために、注文数をカウントする必要があります。

int CountOrders(string symbol, int magic, ENUM_POSITION_TYPE type) { //--- Define method to count orders
   int count = 0;                           //--- Initialize order counter
   for(int i = PositionsTotal() - 1; i >= 0; i--) { //--- Iterate through positions
      ulong ticket = PositionGetTicket(i);   //--- Get position ticket
      if(PositionSelectByTicket(ticket)) {   //--- Select position by ticket
         if(PositionGetInteger(POSITION_MAGIC) == magic && //--- Check magic number
            PositionGetString(POSITION_SYMBOL) == symbol && //--- Check symbol
            PositionGetInteger(POSITION_TYPE) == type) { //--- Check position type
            count++;                        //--- Increment counter
         }
      }
   }
   return count;                            //--- Return order count
}

long OpenOrder(string symbol, ENUM_ORDER_TYPE type, double price, double sl, double tp, double lots, int magic, string comment) { //--- Define method to open orders
   long ticket = m_trade.PositionOpen(symbol, type, lots, price, sl, tp, comment); //--- Execute position open
   if(ticket < 0) {                         //--- Check for order failure
      PrintMessage(StringFormat("Info - OrderSend %s %d_%s_%.5f error %.5f_%.5f_%.5f_#%d", //--- Log error
         comment, type, symbol, price, price, sl, tp, GetLastError()));
   } else {                                 //--- Handle successful order
      PrintMessage(StringFormat("Info - OrderSend done. Comment:%s, Type:%d, Sym:%s, Price:%.5f, SL:%.5f, TP:%.5f", //--- Log success
         comment, type, symbol, price, sl, tp));
   }
   return ticket;                           //--- Return order ticket
}

ここでは、CTradingStrategyクラス内で取引管理機能を実装し、注文のカウントや実行をおこないます。まず、CountOrders関数を作成し、指定したsymbolおよびmagic番号、さらにENUM_POSITION_TYPE型のポジションタイプに対して、オープンポジションの数を集計します。countをゼロで初期化し、PositionsTotalとPositionGetTicketを用いてポジションを順に確認します。PositionSelectByTicketを使用して、PositionGetInteger(POSITION_MAGIC)、PositionGetString(POSITION_SYMBOL)、およびPositionGetInteger(POSITION_TYPE)が入力値と一致するかをチェックし、一致した場合はcountをインクリメントして、最終的にcountを返します。

次に、OpenOrder関数を作成し、指定したsymbolとtypeの取引を実行します。価格(price)、ストップロス(sl)、テイクプロフィット(tp)、ロット数(lots)、magic、コメント(comment)をパラメータとして受け取ります。m_trade.PositionOpenを呼び出してポジションをオープンし、結果をticketに格納します。ticketが負の値の場合は、GetLastErrorを含めてStringFormatでエラーをPrintMessageに出力し、注文が正常に発注された場合は成功時の内容をログに出力します。最終的にticketを返すことで、発注後の追跡が可能となります。これらの準備が整ったところで、メンバーを初期化できるpublicクラスを定義することができます。

public:
   CTradingStrategy() : CCI_TREND_BUY_VALUE(-114), CCI_TREND_SELL_VALUE(134) { //--- Initialize constructor with constants
      m_last_day = 0;                          //--- Set initial last day
      m_is_new_day = true;                     //--- Set new day flag
      m_array_size = 0;                        //--- Set initial array size
   }
   
   bool Init() {                               //--- Define initialization method
      m_params.symbols = "EURUSDm,GBPUSDm,AUDUSDm"; //--- Set default symbols
      m_params.per_signal_cci = 20;            //--- Set CCI signal period
      m_params.per_trend_cci = 24;             //--- Set CCI trend period
      m_params.price_cci = PRICE_TYPICAL;      //--- Set CCI applied price
      m_params.cci_signal_buy_value = -90;     //--- Set CCI buy signal threshold
      m_params.cci_signal_sell_value = 130;    //--- Set CCI sell signal threshold
      m_params.tf_signal = PERIOD_M5;          //--- Set signal timeframe
      m_params.tf_trend = PERIOD_H1;           //--- Set trend timeframe
      m_params.use_ao_for_trend = false;       //--- Disable AO trend by default
      m_params.take = 200;                     //--- Set take-profit
      m_params.stop = 300;                     //--- Set stop-loss
      m_params.lots = 0.01;                    //--- Set lot size
      m_params.slip = 5;                       //--- Set slippage
      m_params.max_spread = 20;                //--- Set maximum spread
      m_params.magic = 123456789;              //--- Set magic number
      m_params.trade_anytime = false;          //--- Disable trade anytime
      m_params.comment = "EA_AO_BP";           //--- Set trade comment
      m_params.tester_max_balance = 0;         //--- Set tester balance limit
      m_params.debug_mode = false;             //--- Disable debug mode
      
      m_candle_shift = m_params.trade_anytime ? 0 : 1; //--- Set candle shift based on trade mode
      m_trade.SetExpertMagicNumber(m_params.magic); //--- Set magic number for trade object
      
      PrepareSymbolsList();                    //--- Prepare symbol list
      for(int i = 0; i < m_array_size; i++) {  //--- Iterate through symbols
         if(!InitializeIndicators(i)) {         //--- Initialize indicators
            return false;                      //--- Return failure on error
         }
      }
      PrintMessage("Current Spread on " + Symbol() + ": " + //--- Log current spread
         IntegerToString((int)SymbolInfoInteger(Symbol(), SYMBOL_SPREAD)));
      return true;                             //--- Return success
   }

publicアクセス修飾子内では、初期化ロジックを実装します。まず、コンストラクタ「CTradingStrategy」を定義し、CCI_TREND_BUY_VALUEおよびCCI_TREND_SELL_VALUEをそれぞれ-114と134に設定して定数として初期化します。また、m_last_dayをゼロ、m_is_new_dayをtrue、m_array_sizeをゼロに設定し、初期状態を管理します。

次に、Init関数を作成し、戦略のパラメータやリソースを設定します。m_paramsメンバーにデフォルト値を代入します。具体的には、通貨ペアを格納するm_params.symbols、CCI期間を指定するm_params.per_signal_cciおよびm_params.per_trend_cci、m_params.price_cciをPRICE_TYPICALに設定し、シグナル閾値としてm_params.cci_signal_buy_valueおよびm_params.cci_signal_sell_valueを設定します。

さらに、m_params.tf_signalをPERIOD_M5、m_params.tf_trendをPERIOD_H1に設定し、リスクパラメータとしてm_params.take、m_params.stop、m_params.lotsを構成します。m_candle_shiftはm_params.trade_anytimeに基づいて設定し、m_trade.SetExpertMagicNumberでm_params.magicを指定します。その後、PrepareSymbolsListを呼び出して銘柄を準備します。m_array_size分ループし、各銘柄に対してInitializeIndicatorsを呼び出し、失敗した場合はfalseを返します。最後に、現在のスプレッドをPrintMessageでログ出力し、成功時はtrueを返します。

すべての初期化が完了した後、クラス内でOnTickイベントハンドラを定義できます。これにより、実際のイベントハンドラ内でのみメソッドを呼び出すことができます。そのため、このメソッドはpublicにする必要があります。必要に応じてvirtualとしても設定可能ですが、今回はシンプルに保持します。

void OnTick() {                             //--- Define tick handling method
   datetime new_day = iTime(Symbol(), PERIOD_D1, 0); //--- Get current day timestamp
   m_is_new_day = (m_last_day != new_day);  //--- Check for new day
   if(m_is_new_day) {                       //--- Handle new day
      m_last_day = new_day;                 //--- Update last day
   }
   
   for(int i = 0; i < m_array_size; i++) {  //--- Iterate through symbols
      bool is_new_bar = false;              //--- Initialize new bar flag
      bool buy_signal = false, sell_signal = false; //--- Initialize signal flags
      bool buy_trend = false, sell_trend = false; //--- Initialize trend flags
      
      datetime new_time = iTime(m_symbols[i].name, m_params.tf_signal, 0); //--- Get current bar time
      if(!m_params.trade_anytime && m_symbols[i].last_bar_time != new_time) { //--- Check for new bar
         is_new_bar = true;                 //--- Set new bar flag
         m_symbols[i].last_bar_time = new_time; //--- Update last bar time
      }
      
      if(!UpdateIndicatorData(i)) continue; //--- Update indicators, skip on failure
      
      double ask = SymbolInfoDouble(m_symbols[i].name, SYMBOL_ASK); //--- Get ask price
      double bid = SymbolInfoDouble(m_symbols[i].name, SYMBOL_BID); //--- Get bid price
      double point = SymbolInfoDouble(m_symbols[i].name, SYMBOL_POINT); //--- Get point value
      long spread = SymbolInfoInteger(Symbol(), SYMBOL_SPREAD); //--- Get current spread
      
      int total_orders = CountOrders(m_symbols[i].name, m_params.magic, POSITION_TYPE_BUY) + //--- Count buy orders
                        CountOrders(m_symbols[i].name, m_params.magic, POSITION_TYPE_SELL); //--- Count sell orders
      
      // Generate signals
      buy_signal = m_symbols[i].cci_signal_data[m_candle_shift+1] < m_params.cci_signal_buy_value && //--- Check CCI buy signal condition
                  m_symbols[i].cci_signal_data[m_candle_shift] > m_params.cci_signal_buy_value && //--- Confirm CCI buy signal
                  m_symbols[i].ao_signal_data[m_candle_shift+1] < m_symbols[i].ao_signal_data[m_candle_shift]; //--- Confirm AO buy signal
                  
      sell_signal = m_symbols[i].cci_signal_data[m_candle_shift+1] > m_params.cci_signal_sell_value && //--- Check CCI sell signal condition
                   m_symbols[i].cci_signal_data[m_candle_shift] < m_params.cci_signal_sell_value && //--- Confirm CCI sell signal
                   m_symbols[i].ao_signal_data[m_candle_shift+1] > m_symbols[i].ao_signal_data[m_candle_shift]; //--- Confirm AO sell signal
      
      buy_trend = m_symbols[i].cci_trend_data[m_candle_shift+1] < m_symbols[i].cci_signal_data[m_candle_shift] && //--- Check CCI trend buy condition
                 m_symbols[i].cci_trend_data[m_candle_shift] > CCI_TREND_BUY_VALUE && //--- Confirm CCI trend buy threshold
                 m_symbols[i].cci_trend_data[m_candle_shift] < CCI_TREND_SELL_VALUE && //--- Confirm CCI trend sell threshold
                 (!m_params.use_ao_for_trend || //--- Check AO trend condition
                  (m_symbols[i].ao_trend_data[m_candle_shift+1] < m_symbols[i].ao_trend_data[m_candle_shift])); //--- Confirm AO trend buy
                  
      sell_trend = m_symbols[i].cci_trend_data[m_candle_shift+1] > m_symbols[i].cci_signal_data[m_candle_shift] && //--- Check CCI trend sell condition
                  m_symbols[i].cci_trend_data[m_candle_shift] > CCI_TREND_BUY_VALUE && //--- Confirm CCI trend buy threshold
                  m_symbols[i].cci_trend_data[m_candle_shift] < CCI_TREND_SELL_VALUE && //--- Confirm CCI trend sell threshold
                  (!m_params.use_ao_for_trend || //--- Check AO trend condition
                   (m_symbols[i].ao_trend_data[m_candle_shift+1] > m_symbols[i].ao_trend_data[m_candle_shift])); //--- Confirm AO trend sell
      
      // Execute trades
      if(spread < m_params.max_spread && total_orders == 0 && //--- Check spread and open orders
         (m_params.trade_anytime || is_new_bar)) { //--- Check trade timing
         if(buy_signal && buy_trend) {         //--- Check buy conditions
            double sl = m_params.stop == 0 ? 0 : ask - m_params.stop * point; //--- Calculate stop-loss
            double tp = m_params.take == 0 ? 0 : ask + m_params.take * point; //--- Calculate take-profit
            OpenOrder(m_symbols[i].name, ORDER_TYPE_BUY, ask, sl, tp, m_params.lots, //--- Open buy order
                     m_params.magic, "Open BUY " + m_params.comment);
         }
         if(sell_signal && sell_trend) {       //--- Check sell conditions
            double sl = m_params.stop == 0 ? 0 : bid + m_params.stop * point; //--- Calculate stop-loss
            double tp = m_params.take == 0 ? 0 : bid - m_params.take * point; //--- Calculate take-profit
            OpenOrder(m_symbols[i].name, ORDER_TYPE_SELL, bid, sl, tp, m_params.lots, //--- Open sell order
                     m_params.magic, "Open SELL " + m_params.comment);
         }
      }
      
      // Debug output
      if((m_params.trade_anytime || is_new_bar) && (buy_signal || sell_signal)) { //--- Check debug conditions
         PrintDebug(StringFormat("Debug - IsNewBar: %b - candle_shift: %d - buy_signal: %b - " //--- Log debug information
                               "sell_signal: %b - buy_trend: %b - sell_trend: %b",
                               is_new_bar, m_candle_shift, buy_signal, sell_signal, 
                               buy_trend, sell_trend));
      }
   }
}

ここでは、CTradingStrategyクラス内にOnTick関数を作成し、多銘柄CCI・AO戦略の取引ロジックを実装します。まず、iTimeを使用して当日のタイムスタンプを取得し、new_dayに格納します。これにより、m_is_new_dayおよびm_last_dayを更新し、日次の変化を追跡します。m_array_size分ループして各銘柄を処理し、is_new_bar、buy_signal、sell_signal、buy_trend、sell_trendのフラグを初期化します。iTimeを使用して新しいバーを検出し、m_params.trade_anytimeがfalseの場合はm_symbols[i].last_bar_timeを更新します。

次に、UpdateIndicatorDataを呼び出してインジケーターを更新します。更新に失敗した場合は処理をスキップします。その後、SymbolInfoDoubleおよびSymbolInfoIntegerを使用してask、bid、point、spreadを取得します。CountOrdersを用いて買いおよび売りポジションのtotal_ordersを算出します。buy_signalは、m_symbols[i].cci_signal_dataがm_params.cci_signal_buy_valueを満たしているか、さらにm_symbols[i].ao_signal_dataで確認して設定します。sell_signalはm_params.cci_signal_sell_valueを基準に判定します。buy_trendおよびsell_trendは、m_symbols[i].cci_trend_data、CCI_TREND_BUY_VALUE、CCI_TREND_SELL_VALUE、必要に応じてm_symbols[i].ao_trend_dataを使用して決定します。

spreadがm_params.max_spread未満で、total_ordersがゼロ、かつ取引が許可されている場合、m_params.stopおよびm_params.takeを使用してslとtpを計算し、OpenOrderを呼び出して買いまたは売りの取引を実行します。デバッグモードが有効な場合は、PrintDebugとStringFormatを使用してシグナル状態をログに出力し、取引を効果的に監視できるようにします。ポジションをクローズするための処理は、以下の関数で実装します。

bool CloseAllTrades() {                     //--- Define method to close all trades
   for(int i = PositionsTotal() - 1; i >= 0; i--) { //--- Iterate through positions
      ulong ticket = PositionGetTicket(i);   //--- Get position ticket
      if(PositionSelectByTicket(ticket) &&   //--- Select position
         PositionGetInteger(POSITION_MAGIC) == m_params.magic) { //--- Check magic number
         ENUM_POSITION_TYPE type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); //--- Get position type
         if(type == POSITION_TYPE_BUY || type == POSITION_TYPE_SELL) { //--- Check position type
            m_trade.PositionClose(ticket);   //--- Close position
            PrintMessage("Position close " + IntegerToString(ticket)); //--- Log closure
         }
      }
   }
   for(int i = OrdersTotal() - 1; i >= 0; i--) { //--- Iterate through orders
      ulong ticket = OrderGetTicket(i);      //--- Get order ticket
      if(OrderSelect(ticket) && OrderGetInteger(ORDER_MAGIC) == m_params.magic) { //--- Select order and check magic
         ENUM_ORDER_TYPE type = (ENUM_ORDER_TYPE)OrderGetInteger(ORDER_TYPE); //--- Get order type
         if(type >= ORDER_TYPE_BUY_STOP && type <= ORDER_TYPE_SELL_LIMIT) { //--- Check order type
            m_trade.OrderDelete(ticket);   //--- Delete order
            PrintMessage("Order delete " + IntegerToString(ticket)); //--- Log deletion
         }
      }
   }
   return true;                             //--- Return success
}

ここでは、CTradingStrategyクラス内にCloseAllTrades関数を作成し、多銘柄CCI・AO戦略の取引クローズ機能を実装します。まず、PositionsTotalを使用してすべてのオープンポジションを順に処理し、各ポジションのチケットをPositionGetTicketでticketに取得します。PositionSelectByTicketで各ポジションを選択し、PositionGetInteger(POSITION_MAGIC)がm_params.magicと一致するか確認します。さらに、PositionGetInteger(POSITION_TYPE)がPOSITION_TYPE_BUYまたはPOSITION_TYPE_SELLの場合、m_trade.PositionCloseを呼び出してポジションをクローズし、PrintMessageとIntegerToStringで操作内容をログに記録します。

加えて、OrdersTotalを使用して未決注文をループ処理します。OrderGetTicketで各注文のチケットをticketに取得し、OrderSelectが成功した場合にOrderGetInteger(ORDER_MAGIC)がm_params.magicと一致するか確認します。さらに、OrderGetInteger(ORDER_TYPE)がORDER_TYPE_BUY_STOPからORDER_TYPE_SELL_LIMITの範囲にある場合、m_trade.OrderDeleteを呼び出して注文を削除し、PrintMessageとIntegerToStringでログに記録します。すべての関連するポジションおよび注文のクローズが完了したら、trueを返して処理が正常に完了したことを示します。以上で実装は完了です。あとは、このクラスを使用して実際に戦略を動かすだけです。

//+------------------------------------------------------------------+
//| Global Variables and Functions                                   | //--- Define global variables and functions
//+------------------------------------------------------------------+
CTradingStrategy g_strategy;                   //--- Initialize global strategy object

//+------------------------------------------------------------------+
//| Expert Advisor Functions                                         | //--- Define EA core functions
//+------------------------------------------------------------------+
int OnInit() {                                 //--- Define initialization function
   return g_strategy.Init() ? INIT_SUCCEEDED : INIT_FAILED; //--- Initialize strategy and return status
}

多銘柄CCI・AO戦略の実装を締めくくるために、グローバルおよびコアのエキスパートアドバイザー(EA)関数を定義します。まず、CTradingStrategyクラスのグローバルインスタンスとしてg_strategyを宣言します。これにより、すべての銘柄に対する取引操作を管理でき、EAのライフサイクル全体で戦略のロジックと状態に集中してアクセスできるようになります。

次に、EAの初期化を担当するOnInit関数を作成します。ここでg_strategyのInit関数を呼び出し、パラメータ、銘柄、インジケーターを設定します。初期化が成功した場合はINIT_SUCCEEDEDを返し、エラーが発生した場合はINIT_FAILEDを返すことで、MetaTrader 5プラットフォーム上で正しい初期化状態を反映させます。初期化の結果、EAは設定されたパラメータや銘柄に基づいて取引を開始する準備が整います。コンパイルすると、次の結果が得られます。

EAの初期化

画像から、選択した3つの銘柄に関するすべてのデータが揃っていることが確認できます。これで、OnTickイベントハンドラを呼び出して主要な処理を実行する準備が整いました。これを実行すれば、戦略の処理は完了となります。

//+------------------------------------------------------------------+
//| Expert Advisor Functions                                         | //--- Define EA core functions
//+------------------------------------------------------------------+
int OnInit() {                                                         //--- Define initialization function
   return g_strategy.Init() ? INIT_SUCCEEDED : INIT_FAILED;            //--- Initialize strategy and return status
}

//+------------------------------------------------------------------+

ここでは、CTradingStrategyクラスのインスタンスであるg_strategyグローバルオブジェクトのInit関数を呼び出し、取引パラメータ、銘柄リスト、およびインジケーターを初期化します。初期化が成功した場合はINIT_SUCCEEDEDを返し、エラーが発生した場合はINIT_FAILEDを返すことで、MetaTrader 5プラットフォームに適切な状態を通知し、実行を継続するか停止するかを判断させます。コンパイルすると、次の結果が得られます。

最終取引結果

画像から、各銘柄の確認済みシグナルに基づき取引をオープンし、それぞれ個別に管理できていることが確認できます。 残された作業はプログラムのバックテストであり、それについては次のセクションで取り扱います。


バックテストと最適化

徹底的なバックテストの結果、次の結果が得られました。

バックテストグラフ

グラフ

バックテストレポート

レポート


結論

本記事では、MQL5を用いて多銘柄CCIおよびAO戦略を自動化するプログラムを開発しました。CTradingStrategyクラスを活用し、CCIおよびAOインジケーターに基づくシグナル生成と、CTradeライブラリによる堅牢な取引管理を組み合わせ、複数の通貨ペアに対して取引を実行できる仕組みを構築しています。モジュール化されたコンポーネントと、スプレッド検証やストップロス設定などのリスク管理機能により、パラメータを調整したり追加のフィルターを統合したりすることでカスタマイズ可能な拡張性の高いフレームワークを提供します。

免責条項:本記事は教育目的のみを意図したものです。取引には重大な財務リスクが伴い、市場の変動によって損失が生じる可能性があります。本プログラムを実際の市場で運用する前に、十分なバックテストと慎重なリスク管理が不可欠です。

本記事で紹介したスキルや概念を活用することで、この多銘柄取引システムをさらに改善したり、アーキテクチャを応用して新しい戦略を作成したりすることが可能となり、MQL5によるアルゴリズム取引の専門性を高めることができます。

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

添付されたファイル |
ログレコードをマスターする(第9回):ビルダーパターンの実装とデフォルト設定の追加 ログレコードをマスターする(第9回):ビルダーパターンの実装とデフォルト設定の追加
本記事では、Logifyライブラリの利用をビルダーパターンと自動的なデフォルト設定によって大幅に簡単にする方法をご紹介します。ここでは、専用ビルダーの構造、スマートな補完機能を活用した利用方法、手動で設定をおこなわなくても動作するログ確保方法について解説します。さらに、MetaTrader 5ビルド5100向けの調整についても触れます。
MQL5で自己最適化エキスパートアドバイザーを構築する(第8回):複数戦略分析(2) MQL5で自己最適化エキスパートアドバイザーを構築する(第8回):複数戦略分析(2)
次のフォローアップディスカッションにぜひご参加ください。今回は、これまでの2つの取引戦略を統合し、アンサンブル取引戦略(複合戦略)を作成する方法を解説します。複数の戦略を組み合わせる際のさまざまな手法を紹介するとともに、パラメータ空間の制御方法についても説明します。これにより、パラメータの数が増えても、効果的な最適化が可能な状態を保つことができます。
エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法 エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法
この記事では、MT4において複数のEAの衝突をさける方法を扱います。ターミナルの操作、MQL4の基本的な使い方がわかる人にとって、役に立つでしょう。
MQL5からDiscordへのメッセージの送信、Discord-MetaTrader 5ボットの作成 MQL5からDiscordへのメッセージの送信、Discord-MetaTrader 5ボットの作成
Telegramと同様に、Discordもその通信APIを使用してJSON形式の情報やメッセージを受信することができます。本記事では、MetaTrader5からDiscordの取引コミュニティに取引シグナルやアップデートを送信するためにDiscord APIをどのように利用できるかを探っていきます。