开发基于振荡器的之字折线 (ZigZag) 指标。 执行需求规范的示例(基础篇)
📘

开发基于振荡器的之字折线 (ZigZag) 指标。 执行需求规范的示例(基础篇)

第 1/3 篇

「从振荡器里长出一条之字折线」

在 MT5 里做一条带振荡器信号的 ZigZag,第一步不是画图,而是先把『转折阈值』定死。原文示例用固定点数确认拐点,避免每根 K 线抖动都重绘,这是之字折线不洗数据的根基。 Dmitry Fedoseev 在 2018-07-19 发布的示例帖,至统计时累计阅读 6 245、评论 6,属于低噪高实操的参考实现。其阶段拆法是:先独立开发 ZigZag,再叠加形态涂色,随后接振荡器,最后才做 GUI——顺序不能反,否则调试时信号层和绘制层会互相污染。 外汇与贵金属品种上跑这类指标,要先认同一件事:点差和滑点会让拐点确认滞后,属于高风险验证环境,任何『拐点已现』都只是概率倾向,不是确定性。

从振荡器到之字折线指标的落地路径

如果你手头有基于各类振荡器开发之字折线指标的需求,先去翻那篇《订购指标时如何准备需求规范》里的规范样本,原文里带了一份可直接参照的模板。 这篇内容不空谈概念,而是把“按规范把振荡器转成之字折线指标”拆成一步步能照做的动作。 在动手前务必先把上面那篇的规范链接点开读一遍,否则后面的实现步骤会缺上下文,MT5 里建文件时容易漏字段。

◍ 之字折线接振荡器的三个坑

把之字折线绑到 WPR 这类振荡器上,方向切换逻辑和标准 ZigZag 完全不同:它不是等局部极值确认才拐头,而是振荡器数值穿过超买/超卖线时才转向,新极值仍由价格图表的最高/最低价决定。 由此带来第一个坑:指标拐点对应的高低点,未必等于那根 K 线的最高价/最低价。图例里 WPR 在箭头1柱离开超买,但之字折线新段只画到箭头2柱——验证时要在 MT5 里比对该柱 H/L 是否更早出现。 第二个坑是之字折线方向随柱线动态变化。振荡器触发转向后,若本根柱收盘把信号吞掉,方向可能取消,必须保证指标重算时状态回滚正确。 第三个坑出在重叠形态着色。段落1-4构成上行形态本应蓝,但已被下行形态染红;等段落6出现新上行形态(3-6)时,只能清掉5、6段的颜色,不能动3、4段,否则历史信息量骤降。MT5 的彩色 ZigZag 样式虽能垂直画段,但无法独立绘相邻倾斜段,默认仍用最常见方案。 外汇与贵金属波动剧烈,这类动态重算指标在实盘可能出现重绘,高风险,建议先用历史数据回放验证拐点逻辑。

「在 MT5 里搭 OscZigZag 的第一层骨架」

用 MetaEditor 新建自定义指标 OscZigZagStep1,向导里只勾 OnCalculate(...,open,high,low,close),显示参数开两个缓冲区:HighLow 用彩色箭头(金、柠檬绿),ZigZag 用彩色线段(灰、矢车菊蓝、红)。点近柱线的先画,所以缓冲区顺序上 HighLow 排前。 外部参数里删掉自动生成的 Input1,换成 WPR 的周期与边界:WPRperiod=14、WPRmax=-20、WPRmin=-80。OnInit 开头用 iWPR 拿句柄,OnDeinit 里 IndicatorRelease 释放,避免句柄泄漏。 向导默认 4 个缓冲区不够,把 indicator_buffers 改成 5,多开一个 wpr[] 做振荡器数值暂存,用 SetIndexBuffer(4,wpr,INDICATOR_CALCULATIONS) 挂上。OnCalculate 里标准计数范围从 prev_calculated-1 起步,CopyBuffer 把 WPR 值搬进 wpr[],再循环标出超买/超卖点:wpr> -20 写 high 且色号 0,wpr< -80 写 low 且色号 1。 挂到图表对比标准 WPR,能看见金/绿箭头落在极值位,第一阶段验证通过。继续加 dir[]、lhb[]、llb[] 三个辅助缓冲区,indicator_buffers 提到 8,分别管方向、最后顶/底柱线索引。 彩色线段缓冲区必须设空值 0,否则 ZigZag 会画成一坨乱线而非折线。在 OnInit 末尾 return 前加 PlotIndexSetDouble(1,PLOT_EMPTY_VALUE,0),参数 1 对应 ZigZagBuffer 的显示索引。 之字折线方向由 dir[] 承载:上行 1、下行 -1。转上行时在最后底到当前柱(不含底)搜最高价,于该柱设端点、记索引进 lhb[]、中性色写 ZigZagColor[];若重算发现不该是端点,用 NewDotTime/NewDotBar 记录并回删。上行延续段若新高超过旧端点,删旧设新,循环开头先复原再算。全局把 lhb[]、llb[]、NewDotTime、NewDotBar 初始化为零,躲开关数组越界。

MQL5 / C++
class="macro">#class="kw">property indicator_color1  clrGold,clrLimeGreen
class="kw">input class="type">int      Input1;
class="kw">input class="type">int       WPRperiod   =  class="num">14;
class="kw">input class="type">class="kw">double    WPRmax      =  -class="num">20;
class="kw">input class="type">class="kw">double    WPRmin      =  -class="num">80;
class="type">int h;
h=iWPR(Symbol(),Period(),WPRperiod);
if(h==INVALID_HANDLE){
   Print("无法加载指标");
   class="kw">return(INIT_FAILED);
}  
class="type">void OnDeinit(class="kw">const class="type">int reason){
   if(h!=INVALID_HANDLE){
      IndicatorRelease(h);
   }
}  
class="macro">#class="kw">property indicator_buffers class="num">5
class="type">class="kw">double        wpr[];
SetIndexBuffer(class="num">4,wpr,INDICATOR_CALCULATIONS);
class="type">int start;
if(prev_calculated==class="num">0){
   start=class="num">0;
}
else{
   start=prev_calculated-class="num">1;
}
if(CopyBuffer(h,class="num">0,class="num">0,rates_total-start,wpr)==-class="num">1){
   class="kw">return(class="num">0);
}
for(class="type">int i=start;i<rates_total;i++){
   HighLowBuffer[i]=EMPTY_VALUE;
   if(wpr[i]>WPRmax){
      HighLowBuffer[i]=high[i];
      HighLowColors[i]=class="num">0;
   }
   else if(wpr[i]<WPRmin){
      HighLowBuffer[i]=low[i];
      HighLowColors[i]=class="num">1;      
   }       
}
class="type">class="kw">double        dir[]; class=class="str">"cmt">// 方向
class="type">class="kw">double        lhb[]; class=class="str">"cmt">// 最后的顶端柱线索引
class="type">class="kw">double        llb[]; class=class="str">"cmt">// 最后的底端柱线索引
class="macro">#class="kw">property indicator_buffers class="num">8

方向翻转时怎么重算拐点

这段逻辑是 ZigZag 类指标在 MT5 里真正落地的部分:当 WPR 突破 WPRmax 或跌破 WPRmin,dir 数组在 1 与 -1 之间切换,拐点缓冲才重算,否则沿用前一柱的值。 SetIndexBuffer 把索引 5/6/7 绑定到 dir、lhb、llb 并标记为 INDICATOR_CALCULATIONS,意味着它们不参与直接绘图,只做中间状态;PlotIndexSetDouble(1,PLOT_EMPTY_VALUE,0) 则让副图序列的空值显示为 0。 方向由 -1 翻成 1 时,代码从当前柱 i 往回扫到上一低点 llb[i],找这段区间里的最高 high[j],把该柱号记进 hb,再写 ZigZagBuffer[hb]=high[hb] 并标色 0。若后续 high[i] 高过已记下的 lhb 端点,旧端点被清 0、新端点移到 i,拐点在跳动行情里会实时前移。 prev_calculated==0 时 start 置 1 且 lhb[0]/llb[0] 归零,重算则从 prev_calculated-1 开始,避免每 tick 全量跑。外汇与贵金属波动大,这种回看重算在跳空时可能漏掉极端影线,上机把 WPRmax/WPRmin 调小一档就能看到拐点更敏感。

MQL5 / C++
SetIndexBuffer(class="num">5,dir,INDICATOR_CALCULATIONS);
SetIndexBuffer(class="num">6,lhb,INDICATOR_CALCULATIONS);
SetIndexBuffer(class="num">7,llb,INDICATOR_CALCULATIONS);
PlotIndexSetDouble(class="num">1,PLOT_EMPTY_VALUE,class="num">0);
lhb[i]=lhb[i-class="num">1];
llb[i]=llb[i-class="num">1];
dir[i]=dir[i-class="num">1];
if(wpr[i]>WPRmax){
   HighLowBuffer[i]=high[i];
   HighLowColors[i]=class="num">0;
   dir[i]=class="num">1;
}
else if(wpr[i]<WPRmin){
   HighLowBuffer[i]=low[i];
   HighLowColors[i]=class="num">1;
   dir[i]=-class="num">1;
}
if(dir[i]==class="num">1){
   if(dir[i-class="num">1]==-class="num">1){
      class=class="str">"cmt">// 方向转变上行
   }
   else{
      class=class="str">"cmt">// 上行走势延续
   }
}
else if(dir[i]==-class="num">1){
   if(dir[i-class="num">1]==class="num">1){
      class=class="str">"cmt">// 方向转变下行
   }
   else{
      class=class="str">"cmt">// 下行走势延续
   }
}
if(dir[i]==class="num">1){
   if(dir[i-class="num">1]==-class="num">1){
      class=class="str">"cmt">// 方向转变上行
      class=class="str">"cmt">// 搜索最大值
      class="type">int hb=i;
      for(class="type">int j=i;j>llb[i];j--){
         if(high[j]>high[hb]){
            hb=j;
         }
      }
      class=class="str">"cmt">//...
   }
   else{
      class=class="str">"cmt">// 上行走势延续
   }
}
ZigZagBuffer[hb]=high[hb];
lhb[i]=hb;
ZigZagColors[hb]=class="num">0;
ZigZagBuffer[i]=class="num">0;
NewDotTime=time[i];
NewDotBar=hb;
if(NewDotTime==time[i]){
   ZigZagBuffer[NewDotBar]=class="num">0;
}
if(high[i]>ZigZagBuffer[(class="type">int)lhb[i]]){
   class=class="str">"cmt">// 删除旧端点
   ZigZagBuffer[(class="type">int)lhb[i]]=class="num">0;
   class=class="str">"cmt">// 设置新端点
   ZigZagBuffer[i]=high[i];
   ZigZagColors[i]=class="num">0;
   lhb[i]=i;
}
ZigZagBuffer[(class="type">int)lhb[i]]=high[(class="type">int)lhb[i]];
ZigZagBuffer[(class="type">int)llb[i]]=low[(class="type">int)llb[i]];
if(prev_calculated==class="num">0){
   start=class="num">1;
   lhb[class="num">0]=class="num">0;
   llb[class="num">0]=class="num">0;
   NewDotTime=class="num">0;
}
else{
   start=prev_calculated-class="num">1;
}

常见问题

把振荡器的峰谷当成价格拐点来源,越过前高前低才确认转折,连起来就是之字折线。
方向翻转时没重算拐点,会留下错误连线;必须在翻转条件触发时清空上一方向未确认点。
可以,小布能实时标注振荡器之字折线的方向翻转并推送提醒,你只需打开对应品种页看诊断。
至少存当前方向、上一个拐点值、上一个拐点位置三样,缺一个就无法续画。
差很多,震荡市拐点密、假突破多,趋势市线段长;外汇贵金属高风险,建议先回看历史验证。