峰谷指标:新鲜的方法,新颖的解决方案(基础篇)
峰谷指标:用极值重构趋势判定
传统趋势线依赖人工连线,主观性强;峰谷指标(ZigZag 类扩展)改用算法自动捕捉价格序列中的局部极值,把「波峰—波谷」结构直接画在图表上。2014 年 3 月发布的这一版本在 MT5 上累计被查看 5688 次,说明手动画线的痛点确实普遍。 它的核心逻辑是设定最小波动幅度(Deviation),只有反向波动超过该阈值才确认前一个点为峰或谷。这样能过滤掉噪音级毛刺,但参数过大会漏掉有效转折,过小则频繁重绘。 外汇与贵金属波动受消息驱动明显,该指标仅作结构参考,实际进出场仍需结合量价与风控,杠杆品种高风险,信号失效概率不低。
「峰谷指标的底层逻辑与老毛病」
峰谷指标本质是对给定或更大级别价格波动的折线化:节点钉在图表的最高价和最低价上,用来划分行情的转折结构。市面上变体极多,光常见实现就有至少 16 种,但大量 MQL5 开发者仍执着于搓自己的“理想峰谷”。 这类指标有三个老毛病绕不开:信号滞后、外柱(最右侧未稳定 K 线)节点误标、以及运行效率一般。个人经验上,Yuri Kulikov(Yurich)的那版实现相对最稳,另有一些 MQL4 老文对 ZigZag 机理讲得透,但整体主题早被挖烂了,却依旧有折腾价值。 本文落点是用轨道线指标反推高级峰谷:核心假设是存在一组轨道线输入参数组合,能让绝大多数峰谷节点落进轨道线带的边界内。外汇与贵金属波动剧烈,这类结构识别工具仅作概率参考,实操须自担高风险。
◍ 用百条轨道线池锁定峰谷预测带
目标是拿到两类坐标:当前节点和预测节点。当前节点永远贴在零号柱上、还没定型;预测节点则给出下一处峰或谷的估测价位,相当于把轨道线池当成了峰谷指标的底层骨架。 具体做法不是挂一两条 Envelopes,而是各起一个约 100 条指标的池子——高峰池和低谷池分开。每条轨道线主线 ENV 与 iMA 逻辑一致,但在平均周期和取价(高峰用 High、低谷用 Low)上错开。对池内每条指标,算它与 ZZ(High) 或 ZZ(Low) 的偏差算术平均,公式层面就是 n_high 个高峰各自减 ENV(High) 再平均,低谷同理。 这个平均值决定了轨道线该平移到什么价位:高峰偏差均值画上轨,低谷偏差均值画下轨。把上百条结果线投到价格图表平面,会看到图 4 那种“带”,若按 3D 看则是一个被零柱垂直切开的曲面。 切点处的横截面曲线,取三个特征就够了:最大值、最小值、重心(全部点算术平均)。把它们投回零柱并写进历史,就是当前与下一峰谷的基底。高峰预测取上轨截面特征点,低谷预测取下轨截面特征点——外汇与贵金属波动剧烈,这种统计式估测只代表概率倾向,实盘前请在 MT5 用多周期样本自行验证。
用 EA 跑峰谷指标比指标本体更顺手
方法定义完后,真正落地要借助 AdvancedZigZag 类。它内部两套逻辑:计数法扫指定柱数内的所有峰谷存进区分高低点的数组,方便后续轨道线运算;读取法只抓最后节点进单一数组,专供可视化。底层节点搜索依赖 Yury Kulikov 的 GetExtremums 库。 为什么塞进 EA 而不是做成指标?纯属习惯,但 EA 各跑各的数据流,同品种指标却挤在一条流里,EA 在性能上更占优。原文把 iEnvelopes 换成 iMA——两者主线本就一致,用均线反而省事。 实测中用了两个均线池各 227 条,共 454 个 iMA。数量偏多,但统计需要:找几个节点没意义,至少得百来个才够算分布。数据加载放在 OnTick() 而非 OnInit(),否则容易延迟漏载,等 err 变正才跳出该块。 最终指标标出最后 7 个峰谷,其余历史节点坐标只算一次。外汇与贵金属波动剧烈,这种单次计算在实盘高风险环境下可能跟不上跳空,必要时你得改成定期刷新。 横截面点按均线周期大小区分尺寸:峰向右转 90 度、谷向左转 90 度穿过零柱,图形像两颗飞行的彗星。再把截面的最大值、最小值、重心作为当前柱上的点绘出并留历史,就得到能预测下一节点坐标的“双彗星”指标——不过下一节点何时完成仍未知,通常只先估价格。
class="macro">#include <GetExtremums.mqh> class=class="str">"cmt">// author of the code Yurich class="kw">struct MqlZigZag { class="type">class="kw">double price; class=class="str">"cmt">// Node coordinate class="type">class="kw">datetime t; class=class="str">"cmt">// Time }; class AdvancedZigZag { class="kw">private: class="type">MqlRates rt[]; dextremum zz[]; class="type">int history; class="type">class="kw">double amplitude;
「把拐点拆进高低数组里」
AdvancedZigZag 类的构造只做两件事:把回看柱数 history 记下来,振幅 amplitude 先置 0。真正喂数据靠 Read 和 Count 两个方法,前者按节点数取极值,后者按振幅阈值取并拆分高低。 Read 里 CopyRates 拉的是 NULL 品种、周期 0(当前图表)、从 TimeCurrent 往回 history 根 K 线,再调 GetExtremums 填 zHL。Count 多一步:把返回的极值按 type 正负分到 zzH 和 zzL,高点为 type>0。 注意 ArrayResize 用了两次——先按 cnt 扩,循环完再按实际 h、l 缩。外汇和贵金属波动跳变多,amplitude 设太小会让 zzH/zzL 塞满噪声拐点,回测时建议先用 1~2 倍 ATR 试。 析构函数是空的,说明类内没申请非托管资源,直接交给 MT5 运行时回收就行。
class="kw">public: dextremum zHL[]; MqlZigZag zzH[],zzL[]; class="type">int Count(class="kw">const class="type">class="kw">double range); class="type">int Read(class="kw">const class="type">int nodes); AdvancedZigZag(class="kw">const class="type">int bars); ~AdvancedZigZag(); }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Class constructor | class=class="str">"cmt">//+------------------------------------------------------------------+ AdvancedZigZag::AdvancedZigZag(class="kw">const class="type">int bars) { history=bars; amplitude=class="num">0; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| The Read method of the class | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int AdvancedZigZag::Read(class="kw">const class="type">int nodes) { CopyRates(NULL,class="num">0,TimeCurrent(),history,rt); class="type">int cnt=GetExtremums(amplitude,rt,zHL,nodes); class="kw">return(cnt); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| The Count method of the class | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int AdvancedZigZag::Count(class="kw">const class="type">class="kw">double range) { amplitude=range; CopyRates(NULL,class="num">0,TimeCurrent(),history,rt); class="type">int cnt=GetExtremums(amplitude,rt,zz); ArrayResize(zzH,cnt); ArrayResize(zzL,cnt); class="type">int h=class="num">0; class="type">int l=class="num">0; for(class="type">int i=class="num">0; i<cnt; i++) { if(zz[i].type>class="num">0) { zzH[h]=(MqlZigZag)zz[i]; h++; } else { zzL[l]=(MqlZigZag)zz[i]; l++; } } ArrayResize(zzH,h); ArrayResize(zzL,l); class="kw">return(cnt); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| | class=class="str">"cmt">//+------------------------------------------------------------------+ AdvancedZigZag::~AdvancedZigZag() { } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| two_Comets.mq5 |