English Deutsch 日本語
preview
MQL5交易策略自动化(第二十部分):基于CCI和AO指标的多品种策略

MQL5交易策略自动化(第二十部分):基于CCI和AO指标的多品种策略

MetaTrader 5交易 |
76 0
Allan Munene Mutiiria
Allan Munene Mutiiria

概述

前一篇文章(第十九部分)中,我们探讨了基于包络线的趋势反弹剥头皮策略,重点研究了交易执行和风险管理,并实现了MetaQuotes Language 5(MQL5)自动化。本文(第二十部分)将介绍一种多品种交易策略,该策略利用商品通道指数(CCI)和动量震荡指标(AO)来捕捉多个货币对的趋势反转。我们将涵盖以下主题:

  1. 策略规划与架构
  2. 在MQL5中的实现
  3. 回测与优化
  4. 结论

到本文结尾时,您将拥有一个用于多品种交易的强大MQL5交易系统,并且已准备好进行优化和部署——让我们开始深入探讨吧!


策略规划与架构

在第十九部分中,我们构建了基于包络线的趋势反弹短线交易策略,重点关注交易执行和风险管理,旨在根据价格与包络线指标互动产生的信号进行交易,并以趋势过滤器加以确认。如今,在第二十部分中,我们将转向一种多品种交易策略,该策略通过CCI和AO,在两个时间周期(M5用于生成信号,H1用于确认趋势)上识别多个货币对的趋势反转。本文的规划重点在于设计一个可扩展的系统,能够高效地处理信号、执行交易并管理多个品种的风险。

我们的架构设计强调模块化和稳健性,利用MQL5中基于类的结构来规划策略的各个组件。我们的目标是创建一个通用类,用于处理指标计算(CCI和AO)、基于预定义阈值的信号生成以及带有止损和止盈设置的交易执行,同时确保在放置新交易之前某个品种不存在未平仓订单。该设计融入了诸如点差检查等保障措施,并支持在新K线或逐笔成交上进行交易,为各种市场条件提供了灵活性,最终交付一个连贯的多品种交易系统。简而言之,这就是我们的实现目标。

策略规划


在MQL5中的实现

要在MQL5中创建该程序,请打开MetaEditor,在导航器中找到“指标”文件夹,点击“新建”选项卡,并按照向导提示创建文件。完成代码编写环境的准备后,我们将首先声明一些结构和类,以采用面向对象编程(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
};

我们从搭建稳健的交易执行基础组件开始,实现MQL5多品种CCIAO交易策略。我们引入“Trade.mqh”库,借助其中“CTrade”类提供的方法来实现开仓和平仓等交易操作。引入该库可确保我们具备在多个交易品种上执行买卖订单所需的核心交易功能。 

接下来,我们创建“TradingParameters”结构体,用于整理该策略的所有输入参数。该结构体包含关键变量,例如用于存储以逗号分隔的货币对列表的“symbols”、用于设置CCI指标周期的“per_signal_cci”和“per_trend_cci”,以及用于指定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”、用于跟踪最新K线时间戳的“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”来实现该策略的核心逻辑,该类以模块化且有条理的方式封装了所有交易功能。我们定义了私有成员变量来管理策略的状态和操作,首先引入的是“m_trade”,它是来自交易库的“CTrade”类的一个实例,用于处理诸如开仓和平仓等交易执行任务。接下来,我们纳入“m_params”,它是“TradingParameters”结构体的一个实例,用于存储所有配置设置,如交易品种列表、指标周期和风险参数等,确保能够集中访问用户自定义的输入。

我们还声明了“m_symbols”,这是一个“SymbolData”结构体的数组,用于存储每个交易品种的数据,包括指标句柄和数据缓冲区,从而便于进行多品种处理。为了跟踪交易品种的数量,我们使用“m_array_size”,而“m_last_day”和“m_is_new_day”则用于管理每日时间戳的更新,以检测新的交易日。此外,我们设置“m_candle_shift”以根据“trade_anytime”参数控制是在当前K线还是上一根K线上生成信号。最后,我们定义了常量“CCI_TREND_BUY_VALUE”和“CCI_TREND_SELL_VALUE”,以确立用于趋势确认的固定CCI阈值,确保整个策略中的信号逻辑保持一致。现在,我们可以在私有访问修饰符下添加更多实用方法,如下所示:

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策略的调试及交易品种的准备工作。我们创建了“PrintDebug”函数,当“m_params.debug_mode”启用且智能交易系统(EA)未处于优化模式时,该函数会使用Print函数输出诊断信息,记录“text”参数内容以供故障排查。同样地,我们定义了“PrintMessage”函数用于记录信息性消息,通过检查“MQLInfoInteger(MQL_OPTIMIZATION)”确保仅在非优化模式下输出信息,并使用“Print”函数记录“text”输入内容。

此外,我们开发了“PrepareSymbolsList”函数,用于初始化交易品种数据数组。我们声明一个临时数组“symbols_array”来存放拆分后的交易品种,通过StringGetCharacter函数获取逗号分隔符并赋值给“sep”,然后运用StringSplit函数将“m_params.symbols”字符串解析到“symbols_array”中。我们根据“m_array_size”的值,使用ArrayResize函数调整“m_symbols”数组的大小,并通过循环将每个“m_symbols[i].name”设置为对应的交易品种,将“m_symbols[i].last_bar_time”初始化为0,并调用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”(信号周期CCI指标句柄)和“m_symbols[index].cci_trend_handle”(趋势周期CCI指标句柄),同时使用iAO函数设置“m_symbols[index].ao_signal_handle”(信号周期AO指标句柄)和“m_symbols[index].ao_trend_handle”(趋势周期AO指标句柄)。如果指标句柄为INVALID_HANDLE,则通过“Print”记录错误信息。此外,我们使用ArraySetAsSeries函数将数据数组配置为时间序列形式。

我们还开发了“UpdateIndicatorData”函数,该函数使用CopyBuffer函数将三个数据点分别复制到“m_symbols[index].cci_signal_data”(信号周期CCI指标数据)、“m_symbols[index].cci_trend_data”(趋势周期CCI指标数据)、“m_symbols[index].ao_signal_data”(信号周期AO指标数据)和“m_symbols[index].ao_trend_data”(趋势周期AO指标数据)中。如果获取的数据不足,则通过“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”初始化为0,然后通过“PositionsTotal”遍历所有持仓,并使用PositionGetTicket获取持仓的订单编号。接下来,再通过PositionSelectByTicket选中特定持仓,检查其“PositionGetInteger(POSITION_MAGIC)”、“PositionGetString(POSITION_SYMBOL)”和“PositionGetInteger(POSITION_TYPE)”是否与输入参数匹配。如果匹配,则将计数器“count”加1。最后,返回持仓数量。

我们还开发了“OpenOrder”函数,用于针对指定的“symbol”和“type”执行交易,该函数接受"price"、"sl"(止损)、"tp"(止盈)、"lots"、"magic"和"comment"等参数。我们调用“m_trade.PositionOpen”方法开仓,并将返回的订单编号存入“ticket”中。如果“ticket”为负值(表示开仓失败),我们使用“PrintMessage”函数结合StringFormat输出错误信息,其中包含通过GetLastError获取的错误详情;如果下单成功,则记录成功交易的详细信息,并返回“ticket”以跟踪订单状态。完成以上这些后,我们可以定义一个公有类,并在其中初始化所有成员变量。

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
   }

在公有访问修饰符中,我们实现了初始化逻辑。我们定义构造函数“CTradingStrategy”,将常量“CCI_TREND_BUY_VALUE”和“CCI_TREND_SELL_VALUE”分别初始化为-114和134,同时将“m_last_day”设置为0、“m_is_new_day”设置为true、“m_array_size”设置为0,以完成初始状态管理。

我们还创建了"Init"函数,用于配置策略的参数和资源。我们为“m_params”的成员赋予默认值,包括货币对相关的“m_params.symbols”、CCI信号周期参数“m_params.per_signal_cci”和趋势周期参数“m_params.per_trend_cci”、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(5分钟时间周期),将“m_params.tf_trend”设置为“PERIOD_H1”(1小时时间周期),并配置风险参数,如“m_params.take”(止盈点数)、“m_params.stop”(止损点数)和“m_params.lots”(交易手数)。我们根据“m_params.trade_anytime”的值配置“m_candle_shift”,调用“m_trade.SetExpertMagicNumber”方法设置magic数字为“m_params.magic”,并调用“PrepareSymbolsList”函数完成交易品种设置。随后,我们遍历“m_array_size”范围内的所有交易品种,为每个品种调用“InitializeIndicators”初始化指标。如果初始化失败,则返回“false”;成功初始化后,使用“PrintMessage”函数记录当前点差信息,并返回“true”。

最后,在完成所有初始化工作后,我们仍需在类中定义OnTick事件处理器,以确保该方法仅在实际的事件触发时被调用。这也是此处需要将其声明为公有的原因所在。当然,您也可以将其声明为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获取当前K线的日期时间戳,存入“new_day”变量,并通过更新“m_is_new_day”和“m_last_day”来跟踪每日变化。对于“m_array_size”范围内的每个交易品种,我们初始化以下标识位:"is_new_bar"、"buy_signal"、"sell_signal"、"buy_trend"和"sell_trend" flags,接下来,我们再次使用"iTime"检测新K线,如果"m_params.trade_anytime"为false则更新“m_symbols[i].last_bar_time”为当前K线时间,以确保后续逻辑仅在新K线时触发。

我们调用"UpdateIndicatorData"函数刷新指标数据,如果刷新失败则跳过当前品种的后续处理。接下来,使用SymbolInfoDoubleSymbolInfoInteger获取当前品种的"ask"(卖出价)、"bid"(买入价)、"point"(最小变动价位)和"spread"(点差)。通过"CountOrders"统计当前品种的买入订单和卖出订单总数"total_orders"。对于交易信号的生成,我们根据"m_symbols[i].cci_signal_data"与"m_params.cci_signal_buy_value"的比对结果,并结合"m_symbols[i].ao_signal_data"的确认,判断是否触发买入信号"buy_signal" ;根据"m_symbols[i].cci_signal_data"与"m_params.cci_signal_sell_value"的比对结果,判断是否触发卖出信号"sell_signal" 。我们通过“m_symbols[i].cci_trend_data”、“CCI_TREND_BUY_VALUE”、“CCI_TREND_SELL_VALUE”以及可选的“m_symbols[i].ao_trend_data”来确定趋势方向("buy_trend"或"sell_trend")。

如果"spread"低于"m_params.max_spread","total_orders"为0,且允许交易,我们使用"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
}

我们通过定义全局和核心EA函数关键方法,最终完成了多品种CCI与AO策略的实现。我们将"g_strategy"声明为"CTradingStrategy"类的全局实例,统一管理所有交易品种的交易操作,这种设计确保了在EA生命周期内,策略逻辑与状态的访问的高度集中化。

我们还创建了"OnInit"函数,用于处理EA的初始化流程。我们调用"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
}

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

我们仅需调用全局对象"g_strategy"("CTradingStrategy"类的实例)的"Init"函数,即可初始化交易参数、品种列表和指标。如果初始化成功,返回INIT_SUCCEEDED;反之如果遇到错误,则返回"INIT_FAILED",确保MetaTrader 5平台能根据返回状态决定继续执行或终止程序。编译后,呈现如下效果:

最终交易结果

由图可见,我们已经能够基于各品种的确认信号开仓,并对每笔交易进行独立管理。目前仅剩的步骤是对程序进行回测,相关内容将在下一章节展开说明。


回测与优化

经过全面回测后,我们得到以下结果:

回测图:

图表

回测报告:

报告


结论

综上所述,我们开发了一款MQL5程序,实现了多品种CCIAO策略的自动化交易。该系统通过“CTradingStrategy”类生成基于CCI和AO指标的交易信号,并利用“CTrade”库实现稳健的交易管理,支持在多个货币对上同时执行交易。系统采用模块化设计,结合风险控制功能(如点差验证和止损设置),提供了一个可扩展的框架,用户可通过调整参数或集成附加的过滤条件,轻松定制策略。

免责声明:本文仅用于教学目的。交易存在重大财务风险,市场剧烈波动可能导致资金损失。在实盘操作前,务必进行充分的历史回测,并建立严格的风险控制机制。

通过运用上述呈现的技能与核心概念,您可以进一步优化这套多品种交易系统,或基于其架构设计全新的交易策略,从而提升在MQL5算法交易领域的专业能力。

本文由MetaQuotes Ltd译自英文
原文地址: https://www.mql5.com/en/articles/18604

附加的文件 |
您应当知道的 MQL5 向导技术(第 62 部分):结合 ADX 与 CCI 形态的强化学习 TRPO 您应当知道的 MQL5 向导技术(第 62 部分):结合 ADX 与 CCI 形态的强化学习 TRPO
ADX 振荡器和 CCI 振荡器是趋势跟踪和动量指标,可在开发智能系统时配对。我们延续上一篇文章未竟的话题,实证如何得益于强化学习来实际运用训练、并更新我们已开发的模型。我们正在使用的算法尚未在本系列中涵盖,其称为可信区域政策优化。一如既往,由 MQL5 向导汇编的智能系统令我们能够更快地搭建测试模型,且可配合不同类型信号进行测试、并派发。
MQL5自优化智能交易系统(第八部分):多策略分析(2) MQL5自优化智能交易系统(第八部分):多策略分析(2)
欢迎继续阅读本系列文章,我们将把前两个交易策略合并为一个集成交易策略。本文将展示多种合并多个策略的可行方案,并介绍如何控制参数空间,确保即使在参数数量增加的情况下,仍能进行有效的优化。
MQL5 简介(第 19 部分):沃尔夫波浪自动检测 MQL5 简介(第 19 部分):沃尔夫波浪自动检测
本文展示了如何使用 MQL5 以编程方式识别看涨和看跌的沃尔夫波浪形态并进行交易。我们将探索如何通过编程方式识别沃尔夫波浪结构,并使用 MQL5 根据这些结构执行交易。这包括检测关键的波动点、验证形态规则,以及让 EA 根据它发现的信号采取行动。
从新手到专家:使用 MQL5 制作动画新闻标题(六)—— 新闻交易的挂单策略 从新手到专家:使用 MQL5 制作动画新闻标题(六)—— 新闻交易的挂单策略
在本文中,我们将重点转移到整合新闻驱动的订单执行逻辑 —— 使 EA 能够采取行动,而不仅仅是提供信息。加入我们,一起探索如何在 MQL5 中实现自动交易执行,并将 News Headline EA 扩展为一个完全响应式的交易系统。由于 EA 交易支持多种功能,因此为算法开发人员提供了显著优势。到目前为止,我们一直专注于构建新闻和日历事件展示工具,其中包含集成的 AI 洞察通道和技术指标洞察。