通用的之字转向指标(基础篇)
之字转向在 MT5 里的原生形态
MT5 自带之字转向(ZigZag)指标,属通用型回调识别工具,不依赖外部 DLL,直接在终端插入指标即可调用。 它的核心是以最小价格变动幅度(Deviation)过滤杂波,把满足条件的拐点连成折线。默认参数下,许多品种在 H1 周期上会漏掉小于 5 点的毛刺,但仍可能把中级回调误判为趋势反转。 Dmitry Fedoseev 在 2017 年 1 月 9 日发布的示例版本,累计被查看 17 558 次、收到 33 条讨论,说明该基础实现仍是多数自定义 ZigZag 的母本。外汇与贵金属杠杆高,直接照用默认参数易在震荡段频繁重绘,实盘前务必在策略测试器用历史数据回放验证。
「先看清这套指标的骨架」
做之字转向(ZigZag)类工具前,先把模块拆开看。核心就三块:源数据怎么取、方向怎么判、图形怎么画。原文把目录列成了 16 个节点,从简介一路到附件,说明这不是单文件脚本,而是分层构造。 对 MT5 用户来说,这种目录结构意味着你后面会碰到「用于源数据的类」「用于方向的类」「用于绘图的类」三段独立代码,最后再整合。先记住这一点,比直接抄代码更有用:你改参数时改的是源数据类,动视觉的是绘图类,别混在一起调。 外汇和贵金属波动大、滑点频繁,之字转向对拐点敏感,复盘看清晰但实盘可能重绘,高风险品种上别把它当确认信号独用。
◍ 为什么多数之字转向指标难进EA
之字转向(ZigZag)在 MT5 用户里算最常被加载的指标之一,但现存变种五花八门,痛点很实:一部分计算太慢,直接挡死在 EA 实时调用的路上;另一部分频繁重绘出错,连肉眼观察都不可靠。 即便挑到又快又稳的那类,想把它接进 EA 或二次开发指标依然麻烦——核心障碍是之字转向的数据展开与解释并不直观,普通数组取法很容易写出脆弱逻辑。 本文给出的路线是先统一一套绘制算法,在属性窗口里切换不同之字转向类型,底层用 OOP 把各绘制阶段拆成基类与子类。这样后续新增类型只改子类,不碰框架。 顺带的目标是把取数接口做轻:让其他指标或 EA 能以低成本从之字转向里拿拐点数据,而不是每次都重新解析缓冲区。外汇与贵金属波动剧烈,任何指标信号都只是概率参考,实盘前务必在 MT5 策略测试器跑一遍。
之字转向的滞后与噪音过滤两副面孔
之字转向在图上是一条连接局部高点与低点的折线。新手常幻想靠它在底买顶卖,但那只是历史回看里的幻觉——实盘里只要再走出几根 K 线,就可能刷新前高或前低,让最后一段折线的方向被迫改写。 图2到图4演示了一种典型反复:价格先向上反转、之字最后段暂停延伸,几根后又跌回去、最后段继续向下,直到再走过 10 根柱才确认新向上段、真正画出底部。也就是说,极值点要滞后若干根才能被指标坐实。 尽管有这种滞后,它仍是可视化分析里的常用骨架。它能压住杂波、标定主方向,也可直接拿来描支撑阻力、识形态,或像趋势线、斐波那契工具那样手绘参考。外汇与贵金属波动大、滑点频发,用它判方向只是概率倾向,别当确认信号独用。
「之字转向的两种画法内核」
之字转向在任意时刻只有两个状态:方向向上时盯价格是否出新高,方向向下时等新低。要画出来,核心就三件事——取源数据、列方向反转的条件、持续监视高低点。 源数据可以只用收盘价这类单序列,也可以用最高价加最低价双序列;若接指标,还能拆成「基于最高价的指标值」和「基于最低价的指标值」两组输入。 决定类型的关键是第二点:反转条件。经典写法是用当前值与最近 n 根柱的最高/最低比,触极值就翻转;另一种是按固定回撤量,价格里用点数、指标里用约定单位。还能接随机震荡指标——K线上破50向上、跌破50向下;或看 ADX:PDI 在 MDI 上方向上,反之下。 把第一点和第二点随意组合,就能拼出不同之字转向。比如第一点喂 RSI、方向判据用随机,完全可行。第三点只在形态接近传统之字转向时有用,绘制细节差异很大。 做通用指标要把算法拆两层:所有之字转向共用的绘图层,以及随类型变的个体层。个体层往缓冲区填源数据(价格或指标),再往方向缓冲区写 1 或 -1,三个缓冲区交共用层出图。 先落一个具体实现:只吃最高/最低价,按最近 n 根极值翻转方向,单独成指标最直观。
◍ 用高低价重构之字转向的绘制逻辑
在 MetaEditor 里新建指标,命名 iHighLowZigZag,挂一个 int 型外部参数 period 默认 12,事件函数选 OnCalculate(...,open,high,low,close)。主图缓冲区叫 ZigZag 用红色画线,再开 Direction、LastHighBar、LastLowBar 三个辅助缓冲区,类型 line 但颜色 none。 辅助缓冲区必须在 OnInit() 里用 SetIndexBuffer() 把 INDICATOR_DATA 改成 INDICATOR_CALCULATIONS,同时把文件顶部 indicator_plots 改成 1。这样只渲染 ZigZag 一条线,其余缓冲区的数仍可被 iCustom() 读取,不会在图上画出杂线。 启动阶段 prev_calculated==0 时,把三个辅助缓冲首元素清 0,start 从 1 起算;之后运行期 start=prev_calculated-1,只在每根新柱补算,避免全量重跑。方向判定用 ArrayMaximum(high,ps,period) 和 ArrayMinimum(low,ps,period),ps=i-period+1,若最高价柱恰为当前 i 且非最低价则 Direction 写 1,反之写 -1,同根柱两头都触极值时保留旧方向。 绘图按 Direction 当前与前一根的值分四支:续涨、续跌、由跌转涨、由涨转跌。转涨时 ZigZagBuffer[i]=high[i] 且 LastHighBarBuffer[i]=i;续涨中若 high[i] 大于 LastHighBar 记录的高点,先将该旧点置 EMPTY_VALUE 再写新点,保证线段末端跟着新高走。外汇与贵金属波动剧烈,这种基于固定 period 的转向对参数敏感,信号滞后与假突破概率均不低,实盘前务必在策略测试器可视化模式跑一遍。 剩下由涨转跌与续跌两支对称处理即可,完整代码见原工程附件。把 period 从 12 调到 5 或 20,在 XAUUSD 的 M15 上对比折线密度,能直观看出转向灵敏度边界。
class="type">int start; class=class="str">"cmt">// 开始计算的柱的索引 if(prev_calculated==class="num">0) { DirectionBuffer[class="num">0]=class="num">0; LastHighBarBuffer[class="num">0]=class="num">0; LastLowBarBuffer[class="num">0]=class="num">0; start=class="num">1; } else { start=prev_calculated-class="num">1; } for(class="type">int i=start;i<rates_total;i++) { DirectionBuffer[i]=DirectionBuffer[i-class="num">1]; class="type">int ps=i-period+class="num">1; class="type">int hb=ArrayMaximum(high,ps,period); class="type">int lb=ArrayMinimum(low,ps,period); if(hb==i && lb!=i) { DirectionBuffer[i]=class="num">1; } else if(lb==i && hb!=i) { DirectionBuffer[i]=-class="num">1; } LastHighBarBuffer[i]=LastHighBarBuffer[i-class="num">1]; LastLowBarBuffer[i]=LastLowBarBuffer[i-class="num">1]; ZigZagBuffer[i]=EMPTY_VALUE; class="kw">switch((class="type">int)DirectionBuffer[i]) { case class="num">1: class="kw">switch((class="type">int)DirectionBuffer[i-class="num">1]) { case class="num">1: class=class="str">"cmt">// 继续向上的移动 if(high[i]>high[(class="type">int)LastHighBarBuffer[i]]) { ZigZagBuffer[(class="type">int)LastHighBarBuffer[i]]=EMPTY_VALUE; ZigZagBuffer[i]=high[i]; LastHighBarBuffer[i]=i; } break; case -class="num">1: class=class="str">"cmt">// 开始新的向上移动 ZigZagBuffer[i]=high[i]; LastHighBarBuffer[i]=i; break; } break; case -class="num">1: class="kw">switch((class="type">int)DirectionBuffer[i-class="num">1]) { case -class="num">1: class=class="str">"cmt">// 继续向下的移动 break; case class="num">1: class=class="str">"cmt">// 开始新的向下移动 break; } break; } }