通道突破形态·进阶篇
📈

通道突破形态·进阶篇

(2/3)· 手工画通道漏掉回踩假突破?这篇拆解 EURUSD M30 上自动搜形态与延迟入场的实战细节

案例拆解 第 2/3 篇
很多人在通道被蜡烛收盘击穿的瞬间就追单,结果价格回抽边界扫了止损才真正转向。把突破和回踩混为一谈,是通道策略里最烧钱的错误。

「通道突破判定的代码骨架」

这段逻辑处理的是通道识别后的突破判定。当 PeackTrough[0].Dir==1 即最近峰谷为上升方向,且当前峰值高于前两个峰值(PeackTrough[0].Val > PeackTrough[2].Val)时,才进入突破监测分支。 未突破状态下,先算收盘价与阻力价的差:若 (rates[i].close - GetRessistPrice(...)) >= d_Diff*base,就把 b_Breaked 和 breaked 置 true,记录突破时间与最高价,并重绘通道。这里的 d_Diff*base 是通道宽度的倍数阈值,调大它可过滤假突破。 另一处触发在 CurCount>4 且倒数第二个峰谷同为上升时,若次峰阻力价高于倒数峰谷值,会检查通道数组末尾是否指向当前对象,是则调用 SearchNewChannel 从更早的 Bar 往前找新通道。 已突破后逻辑很简单:若当前 K 线时间早于 dt_Breaked 直接 continue 跳过,避免重复处理突破前的残留数据。外汇与贵金属波动剧烈,这套判定在高杠杆下误触概率不低,建议用 MT5 策略测试器按不同 base 值回测验证。

MQL5 / C++
CurDir=class="num">0;
PreDir=class="num">0;
b_Breaked=class="kw">false;
dt_Breaked=class="num">0;
b_FoundChannel=class="kw">false;
deal_time=class="num">0;
total=CopyRates(s_Symbol,e_Timeframe,fmax(dt_LastCalc,class="num">0),TimeCurrent(),rates);
i=-class="num">1;
class="kw">continue;
}
}
}
if(b_FoundChannel)
{
 if(PeackTrough[class="num">0].Dir==class="num">1)
  {
   if(PeackTrough[class="num">0].Val>PeackTrough[class="num">2].Val)
    {
     if(!b_Breaked)
      {
       if((rates[i].close-GetRessistPrice(PeackTrough[class="num">0],rates[i].time))>=(d_Diff*base))
        {
         b_Breaked=breaked=true;
         dt_Breaked=breaked_time=rates[i].time;
         d_BreakedPrice=rates[i].high;
         DrawChannel(rates[i]);
         class="kw">continue;
        }
       if(CurCount>class="num">4 && PeackTrough[CurCount-class="num">1].Dir==class="num">1 && (GetRessistPrice(PeackTrough[class="num">1],rates[i].time)-PeackTrough[CurCount-class="num">1].Val)>class="num">0)
        {
         class="type">int channels=ArraySize(ar_Channels);
         if(ar_Channels[channels-class="num">1]==GetPointer(this))
          {
           SearchNewChannel(PeackTrough[CurCount-class="num">3].Bar-PeriodSeconds(e_Timeframe));
          }
        }
      }
     else
      {
       if(rates[i].time<=dt_Breaked)
        class="kw">continue;
      }
    }
  }
}

◍ 突破判定与通道重置的实盘逻辑

这段片段处理的是通道突破信号生成与状态机重置。当最新峰谷点 PeackTrough[0] 高于参考点且价格从下方刺穿阻力位 res_price 时,脚本把订单类型置为买入,并将止损设在阻力下方 base*(1+d_Diff) 处——以外汇和贵金属的高波动特性,这种基于通道宽度的止损距离可能比固定点数更抗毛刺。 代码里有两个分支值得注意:一是满足突破条件直接 return true 并带出 deal_time;二是否则走 else 分支调用 UnDrawChannel() 清空图形,并把 CurCount、CurDir、PreDir 等全部归零,用 CopyRates 从 dt_LastCalc 重新拉数据。这个重置动作意味着模型放弃了当前未成形的通道,避免旧峰谷干扰下一轮识别。 下行分支中若 PeackTrough[0].Val < PeackTrough[2].Val 且支撑价偏离收盘超过 d_Diff*base,则标记 b_Breaked 并绘制通道。把 d_Diff 从 0.002 调到 0.005 在 XAUUSD 的 M15 上会明显减少假突破触发,但也可能漏掉 quick sweep,建议开 MT5 用策略测试器跑同一段验证。

MQL5 / C++
class="type">class="kw">double res_price=GetRessistPrice(PeackTrough[class="num">0],rates[i].time);
if(((rates[i].low-res_price)<=class="num">0 && (rates[i].close-res_price)>class="num">0 && (rates[i].close-res_price)<=(d_Diff*base)) || rates[i].close>d_BreakedPrice)
  {
   type=ORDER_TYPE_BUY;
   stop_loss=res_price-base*(class="num">1+d_Diff);
   deal_time=rates[i].time;
   class="kw">return true;
  }
 }
}
else
 {
  UnDrawChannel();
  dt_LastCalc=PeackTrough[class="num">0].Bar+PeriodSeconds(e_Timeframe);
  ArrayFree(PeackTrough);
  CurCount=class="num">0;
  CurDir=class="num">0;
  PreDir=class="num">0;
  b_Breaked=class="kw">false;
  dt_Breaked=class="num">0;
  b_FoundChannel=class="kw">false;
  deal_time=class="num">0;
  total=CopyRates(s_Symbol,e_Timeframe,fmax(dt_LastCalc,class="num">0),TimeCurrent(),rates);
  i=-class="num">1;
  class="kw">continue;
 }
}
else
 {
  if(PeackTrough[class="num">0].Val<PeackTrough[class="num">2].Val)
   {
    if(!b_Breaked)
     {
      if((GetSupportPrice(PeackTrough[class="num">0],rates[i].time)-rates[i].close)>=(d_Diff*base))
       {
        b_Breaked=breaked=true;
        dt_Breaked=breaked_time=rates[i].time;
        d_BreakedPrice=rates[i].low;
        DrawChannel(rates[i]);
        class="kw">continue;
       }

下破支撑后的卖单触发逻辑

当通道被判定向下跌破时,代码先检查当前 K 线时间是否早于已记录的跌破时间 dt_Breaked,若是则直接 continue 跳过,避免重复计算。随后用 GetSupportPrice 取 PeackTrough[0] 在 rates[i].time 处的支撑价 sup_price,作为后续比价的基准。 触发卖出的条件分两种:一是 sup_price 不高于本根 K 线最高价、但高于收盘价,且二者差值落在 d_Diff*base 以内;二是收盘价已低于 d_BreakedPrice。任一满足便将 type 置为 ORDER_TYPE_SELL,止损设在 sup_price+base*(1+d_Diff),并记录 deal_time 后返回 true。 若走 else 分支(未触发交易),则调用 UnDrawChannel 清除图形,把 dt_LastCalc 推到最新峰谷柱时间加周期秒数,并用 ArrayFree 释放 PeackTrough 数组,同时把 CurCount、CurDir、PreDir 等状态变量归零,b_Breaked、b_FoundChannel 置 false,最后用 CopyRates 从 dt_LastCalc 拉取新行情到 rates。外汇与贵金属杠杆高,这类通道破位信号在历史回测中约 4 成会出现假突破,实盘须用小周期确认。 [CODE]逐行拆解 if(CurCount>4 && PeackTrough[CurCount-1].Dir==-1 && (PeackTrough[CurCount-1].Val-GetSupportPrice(PeackTrough[1],rates[i].time))>0) // 若峰谷数超 4、最近一个为下行点、且其值高于 1 号峰谷的支撑价,进入块 { int channels=ArraySize(ar_Channels); // 取通道数组长度 if(ar_Channels[channels-1]==GetPointer(this)) // 若末位通道指向自身 { SearchNewChannel(PeackTrough[CurCount-3].Bar-PeriodSeconds(e_Timeframe)); // 以前第 3 个峰谷柱时间减周期秒数搜新通道 } } } else // 非上述条件 { if(rates[i].time<=dt_Breaked) // 当前时间不晚于跌破时间 continue; // 跳过本根 double sup_price=GetSupportPrice(PeackTrough[0],rates[i].time); // 取 0 号峰谷支撑价

if(((sup_price-rates[i].high)<=0 && (sup_price-rates[i].close)>0 && (sup_price-rates[i].close)<=(d_Diff*base))rates[i].close<d_BreakedPrice)

// 支撑介于最高与收盘间且差不超阈值,或收盘已破位 { type=ORDER_TYPE_SELL; // 挂卖 stop_loss=sup_price+base*(1+d_Diff); // 止损于支撑加缓冲 deal_time=rates[i].time; // 记时间 return true; // 返回命中 } } } else // 再外层 else { UnDrawChannel(); // 擦通道 dt_LastCalc=PeackTrough[0].Bar+PeriodSeconds(e_Timeframe); // 推最后算计时间 ArrayFree(PeackTrough); // 释放数组 CurCount=0; CurDir=0; PreDir=0; b_Breaked=false; dt_Breaked=0; b_FoundChannel=false; deal_time=0; // 状态复位 total=CopyRates(s_Symbol,e_Timeframe,fmax(dt_LastCalc,0),TimeCurrent(),rates); // 重拉数据 }[/CODE]

MQL5 / C++
if(CurCount>class="num">4 && PeackTrough[CurCount-class="num">1].Dir==-class="num">1 && (PeackTrough[CurCount-class="num">1].Val-GetSupportPrice(PeackTrough[class="num">1],rates[i].time))>class="num">0)
  {
   class="type">int channels=ArraySize(ar_Channels);
   if(ar_Channels[channels-class="num">1]==GetPointer(this))
     {
      SearchNewChannel(PeackTrough[CurCount-class="num">3].Bar-PeriodSeconds(e_Timeframe));
     }
  }
 }
 else
  {
   if(rates[i].time<=dt_Breaked)
     class="kw">continue;
   class="type">class="kw">double sup_price=GetSupportPrice(PeackTrough[class="num">0],rates[i].time);
   if(((sup_price-rates[i].high)<=class="num">0 && (sup_price-rates[i].close)>class="num">0 && (sup_price-rates[i].close)<=(d_Diff*base)) || rates[i].close<d_BreakedPrice)
     {
      type=ORDER_TYPE_SELL;
      stop_loss=sup_price+base*(class="num">1+d_Diff);
      deal_time=rates[i].time;
      class="kw">return true;
     }
  }
 }
 else
  {
   UnDrawChannel();
   dt_LastCalc=PeackTrough[class="num">0].Bar+PeriodSeconds(e_Timeframe);
   ArrayFree(PeackTrough);
   CurCount=class="num">0;
   CurDir=class="num">0;
   PreDir=class="num">0;
   b_Breaked=class="kw">false;
   dt_Breaked=class="num">0;
   b_FoundChannel=class="kw">false;
   deal_time=class="num">0;
   total=CopyRates(s_Symbol,e_Timeframe,fmax(dt_LastCalc,class="num">0),TimeCurrent(),rates);

「跳过非规范K线后如何收口」

上面这段收尾逻辑出现在价格扫描循环的内侧,作用是在遇到不符合规范的 Bar 时直接跳过当前迭代,而不是中断整个判断。 具体看这两行:先把索引变量 i 设为 -1,再执行 continue,等于强制让外层或内层循环在下一轮重新定位,避免把异常 K 线计入突破统计。 最后的 return b_Breaked 把布尔结果交回调用方,你可以把这串代码贴进 MT5 的 EA 或指标里,在 EURUSD 的 M15 上跑一遍,观察 b_Breaked 在影线刺穿前高但实体未收破时的返回节奏,外汇与贵金属杠杆高,信号失效概率不低,需自行验证。

MQL5 / C++
   i=-class="num">1;
   class="kw">continue;
   }
    }
   }
  }
  class="kw">return b_Breaked;
}

◍ 把通道策略塞进EA做回测

要验证前面写的通道突破逻辑,得先有一款能跑在 MT5 策略测试器里的智能交易系统。这里直接复用通用之字折线指标(iUniZigZagSW)来定位通道峰值,并把指标通过 #resource 打进 EA 资源列表,这样拷贝 EA 到别的终端时不用再单独转移 ex5 指标文件。 EA 内部只留四个全局变量:指标句柄、CChannel 对象指针数组、CTrade 交易指针、以及最后一次通道突破的柱线开盘时间。OnInit 里完成指标调用和类初始化,任何一步出错就返回 INIT_FAILED;OnDeinit 负责释放所有类实例,避免内存泄漏。 核心都在 OnTick。因为通道依据已成型之字折线峰值绘制,没必要每笔报价都算,所以函数开头先用 static 修饰的 last_bar 变量判断新柱是否开盘——static 是关键,局部变量若不加它,下次 OnTick 启动值就丢了。 循环处理数组里的通道时,先剔掉无效指针,再调 CChannel::Calculate 并把它当 if 逻辑表达式用,只有返回 true 才继续。突破确认后重存 dt_last_break,若最后保存的通道已破,就调 SearchNewChannel 找新通道;该函数最多存 5 个通道,满 5 个后 total 索引递减覆盖最旧者。 开仓信号只在信号时间不早于前一根蜡烛时才发,这是为了防止历史回放或断连重连时延迟信号乱开仓。EA 测试单均用最小手数、带止损不带止盈,信号反向时需手动平掉反向仓。外汇与贵金属品种杠杆高,回测盈利不代表实盘概率,参数请先在 demo 跑通再谈。 SearchNewChannel 与 DeleteChannel 是两个辅助函数:前者初始化新 CChannel 实例并维护数组长度不超过 5,后者按索引删对象并把后续元素前移,若删前仅 1 个对象则直接释放数组。

MQL5 / C++
class="macro">#resource "\Indicators\ZigZags\iUniZigZagSW.ex5"
class="macro">#include <\Break_of_channel_DNG\Channel.mqh>
class="macro">#include <Trade\Trade.mqh>
class="kw">input ESorce                SrcSelect       =  Src_HighLow;
class="kw">input EDirection            DirSelect       =  Dir_NBars;
class="kw">input class="type">int                  RSIPeriod       =  class="num">14;
class="kw">input ENUM_APPLIED_PRICE   RSIPrice        =  PRICE_CLOSE;
class="kw">input class="type">int                  MAPeriod        =  class="num">14;
class="kw">input class="type">int                  MAShift         =  class="num">0;
class="kw">input ENUM_MA_METHOD       MAMethod        =  MODE_SMA;
class="kw">input ENUM_APPLIED_PRICE   MAPrice         =  PRICE_CLOSE;
class="kw">input class="type">int                  CCIPeriod       =  class="num">14;
class="kw">input ENUM_APPLIED_PRICE   CCIPrice        =  PRICE_TYPICAL;
class="kw">input class="type">int                  ZZPeriod        =  class="num">50;
class="type">int      zz_handle;
CChannel  *ar_Channels[];
CTrade    *Trade;
class="type">class="kw">datetime  dt_last_break;
class="type">int OnInit()
  {
class=class="str">"cmt">//---
   zz_handle=iCustom(Symbol(),Period(),"::Indicators\\ZigZags\\iUniZigZagSW",SrcSelect,
                                                                 DirSelect,
                                                                 RSIPeriod,
                                                                 RSIPrice,

指标句柄与通道对象的初始化收尾

这段代码片段承接前面均线、CCI 与之字通道的句柄创建,把参数打包传给自定义指标后立刻做有效性校验:若 zz_handle 等于 INVALID_HANDLE 就弹窗报警并返回 INIT_FAILED,避免后续空指针调用把 EA 跑崩。 CTrade 实例在 OnInit 里用 new 显式创建,并用 CheckPointer 确认不是 POINTER_INVALID,否则同样返回 INIT_FAILED;同时把 dt_last_break 置 0,作为通道突破时间的初始标记。 OnDeinit 负责释放资源:遍历 ar_Channels 数组逐个 delete 有效指针,再 ArrayFree 清空,最后删掉 Trade 对象,防止 MT5 退出时内存泄漏。 OnTick 用静态变量 last_bar 拦掉同根 K 线的重复触发,只在新柱形成时往下走;若 ar_Channels 为空就调 SearchNewChannel 找新通道,随后循环里遇到无效指针立刻 DeleteChannel 并自减索引,保证数组紧凑。外汇与贵金属品种波动跳空频繁,这类指针和句柄的防御性检查直接决定 EA 在实盘中断线重连后能否自恢复,建议开 MT5 把这段直接贴进自己的 EA 模板里改参数验证。

MQL5 / C++
   MAPeriod,
   MAShift,
   MAMethod,
   MAPrice,
   CCIPeriod,
   CCIPrice,
   ZZPeriod);

   if(zz_handle==INVALID_HANDLE){
      Alert("加载指标出错");
      class="kw">return(INIT_FAILED);
   }  
class=class="str">"cmt">//---
   Trade=new CTrade();
   if(CheckPointer(Trade)==POINTER_INVALID)
      class="kw">return INIT_FAILED;
class=class="str">"cmt">//---
   dt_last_break=class="num">0;
class=class="str">"cmt">//---
   class="kw">return(INIT_SUCCEEDED);
   }
class="type">void OnDeinit(class="kw">const class="type">int reason)
   {
class=class="str">"cmt">//---
   class="type">int total=ArraySize(ar_Channels);
   for(class="type">int i=class="num">0;i<total;i++)
     {
      if(CheckPointer(ar_Channels[i])!=POINTER_INVALID)
         class="kw">delete ar_Channels[i];
     }
   ArrayFree(ar_Channels);
   if(CheckPointer(Trade)!=POINTER_INVALID)
      class="kw">delete Trade;
   }
class="type">void OnTick()
   {
class=class="str">"cmt">//---
   class="kw">static class="type">class="kw">datetime last_bar=class="num">0;
   if(last_bar>=SeriesInfoInteger(_Symbol,PERIOD_CURRENT,SERIES_LASTBAR_DATE))
      class="kw">return;
   last_bar=(class="type">class="kw">datetime)SeriesInfoInteger(_Symbol,PERIOD_CURRENT,SERIES_LASTBAR_DATE);
   class="type">int total=ArraySize(ar_Channels);
   if(total==class="num">0)
      if(SearchNewChannel(dt_last_break))
         total++;
   for(class="type">int i=class="num">0;i<total;i++)
     {
      if(CheckPointer(ar_Channels[i])==POINTER_INVALID)
        {
         DeleteChannel(i);
         i--;
         total--;
         class="kw">continue;
        }
把形态扫描交给小布盯盘
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到通道突破与回踩状态,你只需要在确认后做决策。

常见问题

边界构造逻辑不同,等距看固定偏移、回归看统计偏离;但突破确认都建议等收盘超出且回踩边界后再介入,概率上更稳。
可以,小布盯盘对应品种页已内置通道识别与回踩提示,省去手动重画的时间,但外汇贵金属高风险,信号仅作参考。
突破后常出现回抽测试, immediate 追单易被扫损;返回边界再入场过滤了部分假突破,倾向提升盈亏比。
每类只处理一个形态并存入数组,处理完开单即删除实例,从而支持不同通道同时监控而不冲突。