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

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

第 3/3 篇

「多指标共振下的开仓闸门」

这段逻辑把 CCI 与 AO 两条趋势线叠在一起做过滤,只有当 CCI 当前值卡在买入阈值与卖出阈值之间、且上一根 K 线的 AO 值相对当前发生方向性偏转时,才把对应方向的 trend 标志置真。注意 m_candle_shift+1 与 m_candle_shift 的错位比较——它实际是在拿前一根确认的 AO 斜率,去佐证当前 CCI 区间内的趋势倾向,属于典型的「慢线定方向、快线定区间」写法。 真正下单前还有三道硬性闸门:点差必须低于 m_params.max_spread、同品种当前挂单与成交单总数 total_orders 必须等于 0、且要么允许任意时间交易要么卡在新 K 线诞生瞬间。外汇与贵金属杠杆高,点差跳变常在数据行情出现,total_orders==0 这条能避免同源信号在同一波行情里叠仓。 SL/TP 的计算很直白:stop 或 take 参数为 0 就传 0 给 broker 代表不挂,否则用 ask/bid 加减点数乘 point。下面这段是趋势判定与执行核心,可直接丢进 MT5 的 EA 主循环里对照验证。

MQL5 / C++
m_symbols[i].cci_trend_data[m_candle_shift] > CCI_TREND_BUY_VALUE && class=class="str">"cmt">//--- Confirm CCI trend buy threshold
m_symbols[i].cci_trend_data[m_candle_shift] < CCI_TREND_SELL_VALUE && class=class="str">"cmt">//--- Confirm CCI trend sell threshold
(!m_params.use_ao_for_trend || class=class="str">"cmt">//--- Check AO trend condition
 (m_symbols[i].ao_trend_data[m_candle_shift+class="num">1] < m_symbols[i].ao_trend_data[m_candle_shift])); class=class="str">"cmt">//--- Confirm AO trend buy

sell_trend = m_symbols[i].cci_trend_data[m_candle_shift+class="num">1] > m_symbols[i].cci_signal_data[m_candle_shift] && class=class="str">"cmt">//--- Check CCI trend sell condition
 m_symbols[i].cci_trend_data[m_candle_shift] > CCI_TREND_BUY_VALUE && class=class="str">"cmt">//--- Confirm CCI trend buy threshold
 m_symbols[i].cci_trend_data[m_candle_shift] < CCI_TREND_SELL_VALUE && class=class="str">"cmt">//--- Confirm CCI trend sell threshold
 (!m_params.use_ao_for_trend || class=class="str">"cmt">//--- Check AO trend condition
  (m_symbols[i].ao_trend_data[m_candle_shift+class="num">1] > m_symbols[i].ao_trend_data[m_candle_shift])); class=class="str">"cmt">//--- Confirm AO trend sell

class=class="str">"cmt">// Execute trades
if(spread < m_params.max_spread && total_orders == class="num">0 && class=class="str">"cmt">//--- Check spread and open orders
 (m_params.trade_anytime || is_new_bar)) { class=class="str">"cmt">//--- Check trade timing
  if(buy_signal && buy_trend) { class=class="str">"cmt">//--- Check buy conditions
    class="type">class="kw">double sl = m_params.stop == class="num">0 ? class="num">0 : ask - m_params.stop * point; class=class="str">"cmt">//--- Calculate stop-loss
    class="type">class="kw">double tp = m_params.take == class="num">0 ? class="num">0 : ask + m_params.take * point; class=class="str">"cmt">//--- Calculate take-profit
    OpenOrder(m_symbols[i].name, ORDER_TYPE_BUY, ask, sl, tp, m_params.lots, class=class="str">"cmt">//--- Open buy order
         m_params.magic, "Open BUY " + m_params.comment);
  }
  if(sell_signal && sell_trend) { class=class="str">"cmt">//--- Check sell conditions
    class="type">class="kw">double sl = m_params.stop == class="num">0 ? class="num">0 : bid + m_params.stop * point; class=class="str">"cmt">//--- Calculate stop-loss
    class="type">class="kw">double tp = m_params.take == class="num">0 ? class="num">0 : bid - m_params.take * point; class=class="str">"cmt">//--- Calculate take-profit
    OpenOrder(m_symbols[i].name, ORDER_TYPE_SELL, bid, sl, tp, m_params.lots, class=class="str">"cmt">//--- Open sell order
         m_params.magic, "Open SELL " + m_params.comment);
  }
}

class=class="str">"cmt">// Debug output
if((m_params.trade_anytime || is_new_bar) && (buy_signal || sell_signal)) { class=class="str">"cmt">//--- Check debug conditions
  PrintDebug(StringFormat("Debug - IsNewBar: %b - candle_shift: %d - buy_signal: %b - " class=class="str">"cmt">//--- Log debug information

一键清仓的底层实现

EA 在切换信号或退出前,往往需要把当前策略开出的所有持仓和挂单清掉。下面这段 CloseAllTrades 就是干这件事的,逻辑上分两步:先扫持仓(PositionsTotal),再扫挂单(OrdersTotal),两者都倒序遍历以避免删除元素后索引错位。 外汇与贵金属杠杆高,一键平仓若 magic 号匹配范围过宽,可能误杀手动单,实盘前务必在策略测试器用历史数据跑一遍确认只清自己的单。 代码里用 PositionGetInteger(POSITION_MAGIC) == m_params.magic 做过滤,说明只有本 EA 的 magic 单才会被 PositionClose;挂单侧则用 OrderGetInteger(ORDER_MAGIC) 比对,且类型限定在 BUY_STOP 到 SELL_LIMIT 区间内才 OrderDelete。PrintMessage 把 ticket 号打出来,方便你回看日志核对哪笔被平了。

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

◍ EA初始化函数的两种写法对照

在 MQL5 的 EA 骨架里,OnInit 是整个策略生命周期的入口。下面这段把初始化逻辑完全委托给 g_strategy.Init(),靠返回值决定 INIT_SUCCEEDED 还是 INIT_FAILED,写法干净,适合把策略对象化管理的老手。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert Advisor Functions                                          | //--- Define EA core functions
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit() {                                                      class=class="str">"cmt">//--- Define initialization function
   class="kw">return g_strategy.Init() ? INIT_SUCCEEDED : INIT_FAILED;         class=class="str">"cmt">//--- Initialize strategy and class="kw">return status
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert Advisor Functions                                          | //--- Define EA core functions
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit() {                                                      class=class="str">"cmt">//--- Define initialization function
   class="kw">return g_strategy.Init() ? INIT_SUCCEEDED : INIT_FAILED;         class=class="str">"cmt">//--- Initialize strategy and class="kw">return status
}
class=class="str">"cmt">//+------------------------------------------------------------------+
逐行拆一下:第 1 行注释框只是分隔符,无执行意义;int OnInit() 是 MT5 规定的初始化回调,返回整型状态码。函数体只有一行——调用 g_strategy.Init(),若返回 true 则向外递 INIT_SUCCEEDED,否则递 INIT_FAILED,终端据此决定是否允许 EA 运行。 注意原文里出现了两次 OnInit 定义,这是同一段代码的重复粘贴,实际编译会报重定义错误;真要写进 mt5 得删掉其中一份。外汇与贵金属杠杆高,EA 初始化失败可能让实盘跳过风控装载,先在策略测试器跑通再上真户。 回测环节如果只靠 OnInit 返回成功就以为策略稳了,容易漏掉 Init() 内部加载指标句柄失败这类隐蔽问题。建议你在 Init() 里打印具体错误码,回测报告里看是否有 zero deals 的异常。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert Advisor Functions                                          | //--- Define EA core functions
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit() {                                                      class=class="str">"cmt">//--- Define initialization function
   class="kw">return g_strategy.Init() ? INIT_SUCCEEDED : INIT_FAILED;         class=class="str">"cmt">//--- Initialize strategy and class="kw">return status
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert Advisor Functions                                          | //--- Define EA core functions
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit() {                                                      class=class="str">"cmt">//--- Define initialization function
   class="kw">return g_strategy.Init() ? INIT_SUCCEEDED : INIT_FAILED;         class=class="str">"cmt">//--- Initialize strategy and class="kw">return status
}
class=class="str">"cmt">//+------------------------------------------------------------------+

「拿这套架构继续往下改」

这套多品种 CCI 与 AO 系统把信号生成和订单管理拆成了两层:CTradingStrategy 类只管出信号,CTrade 库专门接执行,点差校验和止损挂在模块边界上,所以你在 MT5 里换货币对或者加过滤条件,不用动核心循环。 想验证就直接把附带的 MultiSymbolCCIAO_EA.mq5(22.21 KB)拖进 MetaEditor 编译,默认同时跑多个符号,先在策略测试器用 2020—2023 年 H1 数据回测,看 CCI 阈值和 AO 零轴穿越的组合在哪些品种上夏普掉得厉害。 外汇和贵金属杠杆高、跳空频繁,实测里点差超过预设值会自动跳过入场,但这不杜绝滑点吞噬止损,真上实盘前先把最大持仓数和单笔风险百分比写死。 顺着这个骨架,你把 CCI 周期从 14 调到 20 或者接一个时段过滤器,就能衍生出完全不同的多品种策略,架构本身不用重写。

常见问题

当CCI上穿-100且AO由负转正、两者同周期同向时视为共振,可在EA里用双条件闸门过滤,避免单指标假信号。
遍历持仓池按品种分组,用CTrade::PositionClose逐个平仓并校验返回值,循环里加重试计数防部分成交残留。
小布可加载对应品种页自动跑多周期回看,标出CCI+AO共振触发点并统计胜率分布,省去手动翻图。
OnInit在加载时返回初始化状态便于报错退出,构造函数仅分配内存不报终端级错误,正式EA推荐用OnInit做参数检查。
在开仓闸门函数里插入ATR阈值判断,低于均值时不发单,改完用单品种先测再扩到多品种防止互相干扰。