
MQL5での取引戦略の自動化(第20回):CCIとAOを使用した多銘柄戦略
はじめに
前回の記事(第19回)では、Envelopes Trend Bounce Scalping戦略を取り上げ、MetaQuotes Language 5 (MQL5)での自動化を完成させるために、取引執行とリスク管理に焦点を当てました。第20回では、複数の通貨ペアにおけるトレンド反転を捉える多銘柄取引戦略を紹介します。本戦略では、商品チャンネル指数(CCI: Commodity Channel Index)とオーサムオシレータ(AO: Awesome Oscillator)を利用します。次のトピックについて説明します。
記事を読み終える頃には、多銘柄取引に対応した堅牢な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は設定されたパラメータや銘柄に基づいて取引を開始する準備が整います。コンパイルすると、次の結果が得られます。
画像から、選択した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
警告: これらの資料についてのすべての権利はMetaQuotes Ltd.が保有しています。これらの資料の全部または一部の複製や再プリントは禁じられています。
この記事はサイトのユーザーによって執筆されたものであり、著者の個人的な見解を反映しています。MetaQuotes Ltdは、提示された情報の正確性や、記載されているソリューション、戦略、または推奨事項の使用によって生じたいかなる結果についても責任を負いません。





- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索