MQL5 酷宝书 - 移动通道交易信号·综合运用
📈

MQL5 酷宝书 - 移动通道交易信号·综合运用

(3/3)·从指标重构到基础策略再到综合实战,打通移动通道信号的最后一块拼图

含代码示例偏理论 第 3/3 篇

不少交易者把移动通道当成纯画线工具,以为肉眼看边界就够了,结果回测时什么信号都抓不到。MQL5 里图形对象在优化模式下直接归零,靠对象判边界的 EA 一进测试器就失明。本文接上两篇,把通道信号真正落到指标与策略代码里。

「通道宽度分级与边界冗余的实盘参数」

价格通道不是只画两条线就完事,宽度本身就能过滤掉一大批噪音信号。代码里把通道分成窄、平均、宽三档:CHANNEL_WIDTH_NARROW=0、CHANNEL_WIDTH_MID=1、CHANNEL_WIDTH_BROAD=2,对应枚举 ENUM_CHANNEL_WIDTH_TYPE。 外部输入给了四个关键 pip 值:内部冗余 InpPipsInside=100、外部冗余 InpPipsOutside=150、窄通道阈值 InpNarrowPips=250、宽通道阈值 InpBroadPips=1200。注意窄必须严格小于宽,否则初始化直接 INIT_FAILED 并打错误日志,这是写 EA 时容易踩的坑。 实际判定在运行时算 ch_width_pnt = (upper_vals[1]-lower_vals[1])/(1.0+pr_speed_pnt),再拿它和 m_ch_narrow_tol、m_ch_broad_tol 比。低于窄容差归为窄通道,高于宽容差归为宽通道,中间就是平均。 开 MT5 把 InpNarrowPips 从 250 调到 400 试一次,你会看到原本被标成「窄」的欧美国盘整区间改判成「平均」,触发的条件分支随之变化。外汇与贵金属杠杆高,参数微调可能显著改变信号频率,请先在策略测试器验证。

MQL5 / C++
if(m_base_low_price>class="num">0.)
      class=class="str">"cmt">//--- 如果通道非降势
      if(m_ch_type!=CHANNEL_TYPE_DESCENDING)
         class=class="str">"cmt">//--- 如果最低价格位于下边界
         if((m_base_low_price<=m_lower_zone[class="num">0]) && (m_base_low_price>=m_lower_zone[class="num">1]))
            {
            if(IS_PATTERN_USAGE(class="num">0))
               result=m_pattern_0;
            }
class=class="str">"cmt">//---
   class="kw">return result;
   }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 通道宽度                                                                 |
class=class="str">"cmt">//+------------------------------------------------------------------+
enum ENUM_CHANNEL_WIDTH_TYPE
  {
  CHANNEL_WIDTH_NARROW=class="num">0,   class=class="str">"cmt">// 窄
  CHANNEL_WIDTH_MID=class="num">1,      class=class="str">"cmt">// 平均
  CHANNEL_WIDTH_BROAD=class="num">2,    class=class="str">"cmt">// 宽
  };
class=class="str">"cmt">//---
sinput class="type">class="kw">string Info_channels="+===-- 通道 --====+"; class=class="str">"cmt">// +===-- 通道 --====+
class="kw">input class="type">int InpPipsInside=class="num">100;          class=class="str">"cmt">// 内部冗余, pips
class="kw">input class="type">int InpPipsOutside=class="num">150;         class=class="str">"cmt">// 外部冗余, pips
class="kw">input class="type">int InpNarrowPips=class="num">250;          class=class="str">"cmt">// 窄通道, pips
class="kw">input class="type">int InpBroadPips=class="num">1200;          class=class="str">"cmt">// 宽通道, pips
...
class=class="str">"cmt">//--- 过滤参数
   filter0.PointsInside(_Point*InpPipsInside);
   filter0.PointsOutside(_Point*InpPipsOutside);
   if(InpNarrowPips>=InpBroadPips)
     {
      PrintFormat(__FUNCTION__+": 指定的窄通道和边界数值错误");
      class="kw">return INIT_FAILED;
     }
   filter0.NarrowTolerance(_Point*InpNarrowPips);
   filter0.BroadTolerance(_Point*InpBroadPips);
class=class="str">"cmt">//--- 通道宽度 
   m_ch_width=CHANNEL_WIDTH_MID;                class=class="str">"cmt">// 平均
   class="type">class="kw">double ch_width_pnt=((upper_vals[class="num">1]-lower_vals[class="num">1])/(class="num">1.0+pr_speed_pnt));
class=class="str">"cmt">//--- 如果指定了窄通道准则
   if(m_ch_narrow_tol!=EMPTY_VALUE)
      if(ch_width_pnt<=m_ch_narrow_tol)
         m_ch_width=CHANNEL_WIDTH_NARROW;       class=class="str">"cmt">// 窄      
class=class="str">"cmt">//--- 如果指定了宽通道准则
   if(m_ch_narrow_tol!=EMPTY_VALUE)
      if(ch_width_pnt>=m_ch_broad_tol)
         m_ch_width=CHANNEL_WIDTH_BROAD;        class=class="str">"cmt">// 宽 
class=class="str">"cmt">//+------------------------------------------------------------------+

◍ 等距通道里的两种做多触发逻辑

等距通道信号类里,LongCondition 的返回值是 0 到 100 的权重整数,非 0 即代表满足了某一类做多模式。通道宽度状态直接决定走突破还是反弹分支,这是写 EA 时最容易看错的地方。 窄通道(CHANNEL_WIDTH_NARROW)走突破上边界:只有当 m_base_high_price 大于 0,且这根基准最高价 >= m_upper_zone[1](前一根上轨区)时,才用 IS_PATTERN_USAGE(1) 判断「通道边界突破」模式是否启用,启用则 result=m_pattern_1。若当前无持仓(!PositionSelect)才进一步放行,避免重复加仓。 宽通道(CHANNEL_WIDTH_BROAD)走下边界反弹:m_base_low_price 须 >0 且落在 m_lower_zone[0] 与 m_lower_zone[1] 之间,此时 IS_PATTERN_USAGE(0) 对应的「自通道边界反弹」模式生效,result=m_pattern_0。外汇与贵金属杠杆高,窄通道假突破概率不低,建议把 m_pattern_1 权重调低于 m_pattern_0 再回测。 开 MT5 把这段贴进 CSignalEquidChannel 派生类,改 m_ch_width_type 和 IS_PATTERN_USAGE 的索引,就能直接对比两种通道宽度下的做多信号分布。

MQL5 / C++
class="type">int CSignalEquidChannel::LongCondition(class="type">void)
  {
   class="type">int result=class="num">0;
   class="type">bool is_position=PositionSelect(m_symbol.Name());
class=class="str">"cmt">//--- 如果通道较窄 - 突破上边界时交易
   if(m_ch_width_type==CHANNEL_WIDTH_NARROW)
     {
      class=class="str">"cmt">//--- 如果设置了最高价格
      if(m_base_high_price>class="num">0.)
        class=class="str">"cmt">//--- 如果最高价格位于上边界
        if(m_base_high_price>=m_upper_zone[class="num">1])
          {
           if(IS_PATTERN_USAGE(class="num">1))
             {
              result=m_pattern_1;
              class=class="str">"cmt">//--- 如果无持仓
              if(!is_position)
                result=m_pattern_1; class=class="str">"cmt">// 实际应在此作用域内处理无持仓放行
             }
          }
     }
class=class="str">"cmt">//--- 或者通道较宽 - 在下边界反弹时交易
   else if(m_ch_width==CHANNEL_WIDTH_BROAD)
     {
      class=class="str">"cmt">//--- 如果设置了最低价格
      if(m_base_low_price>class="num">0.)
        class=class="str">"cmt">//--- 如果最低价格位于下边界
        if((m_base_low_price<=m_lower_zone[class="num">0]) && (m_base_low_price>=m_lower_zone[class="num">1]))
          {
           if(IS_PATTERN_USAGE(class="num">0))
             result=m_pattern_0;
          }
     }
   class="kw">return result;
  }

通道宽窄决定边界交易逻辑

当通道判定为较宽或处于平均状态,边界策略就从“突破追入”切换成“下边界反弹接多”。代码里用 else 分支处理这种情况:先确认 m_base_low_price 大于 0,再判断它是否落在 m_lower_zone[0] 与 m_lower_zone[1] 构成的区间内,只有同时满足才进入模式 0 的判定。 IS_PATTERN_USAGE(0) 返回真时,result 被赋为 m_pattern_0;若当前无持仓(is_position 为假)且开启了日志开关 m_to_log,终端会打印“通道边界反弹”买入模式,并带出最低价、区域上边际 m_upper_zone[0] 与下边际 m_upper_zone[1],精度跟随品种 Digits()。 注意这里日志里写的是 m_upper_zone 而非 m_lower_zone,变量命名和实际下边界判断存在错位,复制代码后建议在 MT5 里跑一遍 XAUUSD 的 M15,观察打印值是否真的对应下边界,避免误把上轨数据当支撑参考。外汇与贵金属杠杆高,边界反弹仅为概率性信号,实盘须控仓。 CheckCloseLong 函数开头直接把 to_close_long 置为 true、result 置 0,说明平仓判定默认放行,后续逻辑会在此基础上做条件剔除,这一默认态值得在回测里重点验证。

MQL5 / C++
   class=class="str">"cmt">//--- 输出到日志
            if(m_to_log)
              {
               Print("\触发 \"通道边界突破\" 买入模式。");
               PrintFormat("最高价: %class="num">0."+IntegerToString(m_symbol.Digits())+"f",m_base_high_price);
               PrintFormat("触发价格: %class="num">0."+IntegerToString(m_symbol.Digits())+"f",m_upper_zone[class="num">1]);
              }
           }
        }
     }
class=class="str">"cmt">//--- 或者如果通道较宽或是平均 - 在下边界反弹时交易
   else
     {
      class=class="str">"cmt">//--- 如果设置了最低价格
      if(m_base_low_price>class="num">0.)
        class=class="str">"cmt">//--- 如果最低价格位于下边界
        if((m_base_low_price<=m_lower_zone[class="num">0]) && (m_base_low_price>=m_lower_zone[class="num">1]))
          {
           if(IS_PATTERN_USAGE(class="num">0))
             {
              result=m_pattern_0;
              class=class="str">"cmt">//--- 如果无持仓
              if(!is_position)
                class=class="str">"cmt">//--- 输出到日志
                if(m_to_log)
                  {
                   Print("\n触发 \"通道边界反弹\" 买入模式。");
                   PrintFormat("最低价: %class="num">0."+IntegerToString(m_symbol.Digits())+"f",m_base_low_price);
                   PrintFormat("区域上边际: %class="num">0."+IntegerToString(m_symbol.Digits())+"f",m_upper_zone[class="num">0]);
                   PrintFormat("区域下边际: %class="num">0."+IntegerToString(m_symbol.Digits())+"f",m_upper_zone[class="num">1]);
                  }
             }
          }
     }
class=class="str">"cmt">//---
   class="kw">return result;
   }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 检查平多头仓位条件                                                                 |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CSignalEquidChannel::CheckCloseLong(class="type">class="kw">double &price) class="kw">const
  {
   class="type">bool to_close_long=true;
   class="type">int result=class="num">0;

「等距通道尾随止损的类骨架」

等距通道尾随止损的核心由 CTrailingEquidChannel 类承担,它继承自 CExpertTrailing,专门处理基于通道边界的移动止损逻辑。类内protected区定义了 m_sl_distance、m_tp_distance 以及 m_upper_val、m_lower_val 四组double变量,分别对应止损距离、止盈距离和通道上下边界值,另有一个 ENUM_CHANNEL_WIDTH_TYPE 枚举控制通道宽度计算方式。 公开接口里除了常规的多空尾随检查虚函数 CheckTrailingStopLong / CheckTrailingStopShort,还暴露了 RefreshData 方法,入参是 CSignalEquidChannel 信号类指针,作用是把最新通道数据同步进尾随类。实盘里若你改了信号层的通道周期,必须确认这行 RefreshData 被每帧调用,否则尾随边界会用旧值。 前面那段未命名逻辑是通道突破平仓判断的残片:当 IS_PATTERN_USAGE(2) 命中且 result 超过 m_threshold_close,且处于新通道状态、to_close_long 为真时,会用 NormalizeDouble 按品种小数位对齐 Bid 价,并视 m_to_log 开关打印平仓价到日志。外汇与贵金属杠杆高,这类自动平多仓触发可能在大波动中滑点放大,建议在 MT5 策略测试器用真实点差回测确认阈值。

MQL5 / C++
if(IS_PATTERN_USAGE(class="num">2))
   result=m_pattern_2;
if(result>=m_threshold_close)
  {
   if(m_is_new_channel)
     class=class="str">"cmt">//--- 如果多仓已平
     if(to_close_long)
       {
        price=NormalizeDouble(m_symbol.Bid(),m_symbol.Digits());
        class=class="str">"cmt">//--- 输出到日志
        if(m_to_log)
          {
           Print("\触发 \"新通道\" 平多仓模式。");
           PrintFormat("平仓价格: %class="num">0."+IntegerToString(m_symbol.Digits())+"f",price);
          }
       }
  }
class=class="str">"cmt">//---
class="kw">return to_close_long;
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 类 CTrailingEquidChannel。                                      | 
class=class="str">"cmt">//| 目的: 基于等距通道尾随停止类。                                  | 
class=class="str">"cmt">//|     派生自类 CExpertTrailing。                                  | 
class=class="str">"cmt">//+------------------------------------------------------------------+
class CTrailingEquidChannel : class="kw">public CExpertTrailing
  {
class="kw">protected:
   class="type">class="kw">double            m_sl_distance;     class=class="str">"cmt">// 止损距离
   class="type">class="kw">double            m_tp_distance;     class=class="str">"cmt">// 止盈距离
   class="type">class="kw">double            m_upper_val;       class=class="str">"cmt">// 上边界
   class="type">class="kw">double            m_lower_val;       class=class="str">"cmt">// 下边界
   ENUM_CHANNEL_WIDTH_TYPE m_ch_wid_type; class=class="str">"cmt">// 通道宽度类型
   class=class="str">"cmt">//---
class="kw">public:
   class="type">void              CTrailingEquidChannel(class="type">void);
   class="type">void             ~CTrailingEquidChannel(class="type">void){};
   class=class="str">"cmt">//--- 保护数据的初始化方法
   class="type">void              SetTradeLevels(class="type">class="kw">double _sl_distance,class="type">class="kw">double _tp_distance);
   class=class="str">"cmt">//---
   class="kw">virtual class="type">bool      CheckTrailingStopLong(CPositionInfo *position,class="type">class="kw">double &sl,class="type">class="kw">double &tp);
   class="kw">virtual class="type">bool      CheckTrailingStopShort(CPositionInfo *position,class="type">class="kw">double &sl,class="type">class="kw">double &tp);
   class=class="str">"cmt">//---
   class="type">bool              RefreshData(class="kw">const CSignalEquidChannel *_ptr_ch_signal);
  };

◍ 等距通道 EA 的类骨架与处理流

把等距通道策略做成 EA,第一步是继承标准 CExpert 类,扩展出 CEquidChannelExpert。它只多持有一个指向通道信号对象的常量指针 m_ptr_ch_signal,其余开平仓逻辑靠重写虚函数挂接,不碰底层交易管道。 构造与析构为空实现,真正的信号注入靠 EquidChannelSignal() 的 setter/getter 双向方法,把外部 CSignalEquidChannel 实例绑进来。这样信号模块和专家类解耦,换通道参数不用改 EA 主体。 Processing() 是每 tick 的入口。先 m_signal.SetDirection() 算方向,无持仓就跑 CheckOpen() 尝试开仓;有持仓则先 CheckClose() 判平,平不掉再走 CheckTrailingStop() 移损。返回 true 代表本周期已处理动作,false 即空转。 在 MT5 里把这段类抄进 include 目录,配一个返回上下轨突破的 CSignalEquidChannel,就能直接编译出可回测的通道 EA。外汇与贵金属杠杆高,通道假突破概率不低,实盘前务必用历史数据验证信号阈值。

MQL5 / C++
class=class="str">"cmt">//| 目的: 基于等距通道交易的 EA 类。                                   |
class=class="str">"cmt">//| 派生自 CExper 类。                                                                |
class=class="str">"cmt">//+------------------------------------------------------------------+
class CEquidChannelExpert : class="kw">public CExpert
  {
  class=class="str">"cmt">//--- === 数据成员 === --- 
class="kw">private:
  class="kw">const CSignalEquidChannel *m_ptr_ch_signal;
  class=class="str">"cmt">//--- === 方法 === --- 
class="kw">public:
  class=class="str">"cmt">//--- 构造器/析构器
  class="type">void              CEquidChannelExpert(class="type">void);
  class="type">void             ~CEquidChannelExpert(class="type">void);
  class=class="str">"cmt">//--- 指向通道信号对象
  class="type">void              EquidChannelSignal(class="kw">const CSignalEquidChannel *_ptr_ch_signal){m_ptr_ch_signal=_ptr_ch_signal;};
  class="kw">const CSignalEquidChannel *EquidChannelSignal(class="type">void) class="kw">const {class="kw">return m_ptr_ch_signal;};
class="kw">protected:
  class="kw">virtual class="type">bool      Processing(class="type">void);
  class=class="str">"cmt">//--- 交易平仓检查
  class="kw">virtual class="type">bool      CheckClose(class="type">void);
  class="kw">virtual class="type">bool      CheckCloseLong(class="type">void);
  class="kw">virtual class="type">bool      CheckCloseShort(class="type">void);
  class=class="str">"cmt">//--- 尾随停止检查
  class="kw">virtual class="type">bool      CheckTrailingStop(class="type">void);
  class="kw">virtual class="type">bool      CheckTrailingStopLong(class="type">void);
  class="kw">virtual class="type">bool      CheckTrailingStopShort(class="type">void);
  };
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 主模块                                                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CEquidChannelExpert::Processing(class="type">void)
  {
class=class="str">"cmt">//--- 计算方向
   m_signal.SetDirection();
class=class="str">"cmt">//--- 如果无持仓
   if(!this.SelectPosition())
     {
     class=class="str">"cmt">//--- 开仓模块
     if(this.CheckOpen())
       class="kw">return true;
     }
class=class="str">"cmt">//--- 如果存在仓位
   else
     {
     if(!this.CheckClose())
       {
       class=class="str">"cmt">//--- 价差仓位修改可行性
       if(this.CheckTrailingStop())
         class="kw">return true;
       class=class="str">"cmt">//---
       class="kw">return class="kw">false;
       }
     else
       {
       class="kw">return true;
       }
     }
class=class="str">"cmt">//--- 是否没有交易操作
   class="kw">return class="kw">false;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+

等距通道类的边界与余地

这套基于移动通道的信号类已经把每个变体都配上了交易策略和测试结果,附件里的 channelstrader.zip 有 253.85 KB,equidistantchannels.zip 10.16 KB,可以直接拖进 MT5 的 MQL5/Experts 目录编译验证。 整篇文章的固定值设置贯穿始终——等距通道的偏移、平滑周期都是写死的。所以文里说的哪个因子有效,只对那组参数成立;换一组手数或通道宽度,结论大概率要重算。 性能还有别的可撬动空间,本文只是把寻找这种可能性的工作流程摆了出来,并不是终版方案。外汇和贵金属波动大、杠杆高,这类信号在历史样本上表现好,实盘仍可能失效,别直接当仓位依据。 想接着挖,就打开 base_signal.set(1.54 KB)改几个输入,跑一遍策略测试器里的 99% 质量 tick,看曲线会不会塌。

让小布替你跑这套通道诊断
这些通道边界信号的判定逻辑,小布盯盘已内置成对应品种页的自动诊断,打开即可看实时触碰与忽略重复信号的状态,你只管定仓位大小。

常见问题

测试和优化期间不会绘制图形对象,引用其属性会得到零值,只能改用指标缓冲区来承载通道与分形数据。
若已有同方向持仓,后续触碰边界的重复入场信号会被忽略,止损止盈按固定点数自动放置。
因为通道显示方式变了,其 Calculate 方法需重新计算指标通道缓冲区,适配新指标结构。
小布盯盘内置了通道边界诊断,但不直接代跑 EA;你可把本文指标逻辑对照品种页信号状态做人工决策。
倾向先接入标准库现成交易类管理仓位与风控,再叠加过滤条件降低边界假突破概率。