峰谷指标:新鲜的方法,新颖的解决方案(基础篇)
📘

峰谷指标:新鲜的方法,新颖的解决方案(基础篇)

第 1/3 篇

峰谷指标:用极值重构趋势判定

传统趋势线依赖人工连线,主观性强;峰谷指标(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 度穿过零柱,图形像两颗飞行的彗星。再把截面的最大值、最小值、重心作为当前柱上的点绘出并留历史,就得到能预测下一节点坐标的“双彗星”指标——不过下一节点何时完成仍未知,通常只先估价格。

MQL5 / C++
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 运行时回收就行。

MQL5 / C++
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 |

常见问题

把历史高低点拆进数组,配合百条轨道线池圈出预测带,价格进带就提高警觉,不必等指标翻色再动手。
这是老指标用单根收盘确认的通病,用极值重构逻辑只认已闭合的摆动段,未闭合的不标,能避掉一半假信号。
小布盯盘的AIGC会把峰谷预测带和当前价距直接标在品种页,进带就推提醒,你不用自己数高低点数组。
EA能把拐点判定和轨道线池写成后台循环,不占图表空间、不卡帧,复盘百组参数也比手调快得多。
以最近一个已确认谷或峰外沿加缓冲点作止损,别贴预测带内缘,外汇贵金属波动大,扫损前先看带宽是否过窄。