通道突破形态·进阶篇
(2/3)· 手工画通道漏掉回踩假突破?这篇拆解 EURUSD M30 上自动搜形态与延迟入场的实战细节
「通道突破判定的代码骨架」
这段逻辑处理的是通道识别后的突破判定。当 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 值回测验证。
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 用策略测试器跑同一段验证。
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]
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 在影线刺穿前高但实体未收破时的返回节奏,外汇与贵金属杠杆高,信号失效概率不低,需自行验证。
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 个对象则直接释放数组。
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 模板里改参数验证。
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; }