MQL5交易策略自动化(第二十部分):基于CCI和AO指标的多品种策略
概述
在前一篇文章(第十九部分)中,我们探讨了基于包络线的趋势反弹剥头皮策略,重点研究了交易执行和风险管理,并实现了MetaQuotes Language 5(MQL5)自动化。本文(第二十部分)将介绍一种多品种交易策略,该策略利用商品通道指数(CCI)和动量震荡指标(AO)来捕捉多个货币对的趋势反转。我们将涵盖以下主题:
到本文结尾时,您将拥有一个用于多品种交易的强大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多品种CCI与AO交易策略。我们引入“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"函数刷新指标数据,如果刷新失败则跳过当前品种的后续处理。接下来,使用SymbolInfoDouble和 SymbolInfoInteger获取当前品种的"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平台能正确识别初始化状态。初始化完成后,将呈现以下结果:

由图可见,我们已获取了所选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程序,实现了多品种CCI与AO策略的自动化交易。该系统通过“CTradingStrategy”类生成基于CCI和AO指标的交易信号,并利用“CTrade”库实现稳健的交易管理,支持在多个货币对上同时执行交易。系统采用模块化设计,结合风险控制功能(如点差验证和止损设置),提供了一个可扩展的框架,用户可通过调整参数或集成附加的过滤条件,轻松定制策略。
免责声明:本文仅用于教学目的。交易存在重大财务风险,市场剧烈波动可能导致资金损失。在实盘操作前,务必进行充分的历史回测,并建立严格的风险控制机制。
通过运用上述呈现的技能与核心概念,您可以进一步优化这套多品种交易系统,或基于其架构设计全新的交易策略,从而提升在MQL5算法交易领域的专业能力。
本文由MetaQuotes Ltd译自英文
原文地址: https://www.mql5.com/en/articles/18604
注意: MetaQuotes Ltd.将保留所有关于这些材料的权利。全部或部分复制或者转载这些材料将被禁止。
本文由网站的一位用户撰写,反映了他们的个人观点。MetaQuotes Ltd 不对所提供信息的准确性负责,也不对因使用所述解决方案、策略或建议而产生的任何后果负责。
您应当知道的 MQL5 向导技术(第 62 部分):结合 ADX 与 CCI 形态的强化学习 TRPO
MQL5自优化智能交易系统(第八部分):多策略分析(2)
MQL5 简介(第 19 部分):沃尔夫波浪自动检测
从新手到专家:使用 MQL5 制作动画新闻标题(六)—— 新闻交易的挂单策略