价格行为分析工具包开发(第19部分):ZigZag分析器·综合运用
📐

价格行为分析工具包开发(第19部分):ZigZag分析器·综合运用

(3/3)·从趋势线骨架到ZigZag波段顶点,手把手把价格行为工具包收口落地

含代码示例偏理论 第 3/3 篇
很多交易者画趋势线全靠肉眼连点,行情一跳就重画,信号滞后还带情绪。ZigZag分析器的思路是把波段顶点交给算法识别,线随结构走而不是随鼠标走。

◍ OnTick 里先把旧图形清干净

EA 在每根新 K 线触发时,第一件事不是画新线,而是把上一根残留的支撑阻力与趋势线对象全删掉。代码里用 ObjectDelete 按名字逐个清除 Downtrend_HighLine、Uptrend_LowLine、Major/Minor_Resistance、Major/Minor_Support,避免历史对象叠在图表上干扰判读。 清场后通过 CopyBuffer 拉取 ZigZag 指标句柄的缓冲区,若返回值 ≤0 就 Print 报错并 return,这一步能拦掉九成以上的句柄失效闪退。LookBackBars 决定回看深度,默认取值若偏小,摆点提取会漏掉早期波段。 随后 ArraySetAsSeries(zzBuffer, true) 把缓冲区倒序,让索引 0 对应最新柱,再分别调用 DrawZigZagTrendlines 与 DrawSupportResistance。DrawZigZagTrendlines 内部用两个长度为 10 的数组存.swing 高/低点,循环里只认 zzBuffer[i] 不等于 0 的位置,且 highCount/lowCount 上限锁死 10,超出就不再收录。 外汇与贵金属波动剧烈,ZigZag 回调参数设错会让趋势线频繁重绘,实盘前务必在 MT5 策略测试器用历史数据跑一遍看对象刷新节奏。

MQL5 / C++
ObjectDelete(class="num">0, "Major_Resistance");
ObjectDelete(class="num">0, "Major_Support");
ObjectDelete(class="num">0, "Minor_Resistance");
ObjectDelete(class="num">0, "Minor_Support");
IndicatorRelease(zzHandle);
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Tick function                                                     |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTick()
  {
   class="type">class="kw">datetime currentBarTime = iTime(_Symbol, InpTimeFrame, class="num">0);
   if(currentBarTime == lastBarTime)
      class="kw">return;
   lastBarTime = currentBarTime;
class=class="str">"cmt">// Remove previous objects
   ObjectDelete(class="num">0, "Downtrend_HighLine");
   ObjectDelete(class="num">0, "Uptrend_LowLine");
   ObjectDelete(class="num">0, "Major_Resistance");
   ObjectDelete(class="num">0, "Major_Support");
   ObjectDelete(class="num">0, "Minor_Resistance");
   ObjectDelete(class="num">0, "Minor_Support");
   if(CopyBuffer(zzHandle, class="num">0, class="num">0, LookBackBars, zzBuffer) <= class="num">0)
     {
      Print("Failed to copy ZigZag data");
      class="kw">return;
     }
   ArraySetAsSeries(zzBuffer, true);
   DrawZigZagTrendlines();
   DrawSupportResistance();
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Draw ZigZag-based Trendlines                                      |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void DrawZigZagTrendlines()
  {
   class="type">class="kw">double highPrices[class="num">10], lowPrices[class="num">10];
   class="type">class="kw">datetime highTimes[class="num">10], lowTimes[class="num">10];
   class="type">int highCount = class="num">0, lowCount = class="num">0;
class=class="str">"cmt">// Extract swing points from the ZigZag buffer
   for(class="type">int i = class="num">0; i < LookBackBars - class="num">1; i++)
     {
      if(zzBuffer[i] != class="num">0)
        {
         if(iHigh(_Symbol, InpTimeFrame, i) == zzBuffer[i] && highCount < class="num">10)
           {
            highPrices[highCount] = zzBuffer[i];
            highTimes[highCount] = iTime(_Symbol, InpTimeFrame, i);
            highCount++;
           }
         else
         if(iLow(_Symbol, InpTimeFrame, i) == zzBuffer[i] && lowCount < class="num">10)
           {
            lowPrices[lowCount] = zzBuffer[i];

「高低点分别做线性回归求通道斜率」

把前几步收集到的摆动高低点时间数组,先做一次裁剪:若高点或低点数量 ≥4,就丢弃最新一个摆动,避免右端被未闭合的毛刺带偏。代码中 usedHighCount / usedLowCount 正是这个意图,阈值 4 是硬判断。 随后对保留下来的点分别做一元线性回归。以高点为例,用 highTimes[i] 作自变量 t、highPrices[i] 作因变量 p,累加 sumT、sumP、sumTP、sumT2 四个量;斜率 mHigh = (N*sumTP - sumT*sumP) / (N*sumT2 - sumT*sumT),截距 bHigh = (sumP - mHigh*sumT)/N。当分母为零(所有 t 相同,几乎不可能但需防御)时退化为水平线 bHigh = sumP/N。低点完全同构,得到 mLow、bLow。 回归有效的前提是 usedHighCount≥3 且 usedLowCount≥3,否则 validHigh / validLow 保持 false,后面不画任何线。 最后取 pastTime = iTime(_Symbol, InpTimeFrame, LookBackBars-1) 定左边界,futureTime = lastBarTime + ExtendFutureBars * PeriodSeconds() 定右边界,为后续画趋势线框出时间窗。外汇与贵金属杠杆高,回归通道仅描述已有摆动的概率倾向,不等于价格会沿该方向运行。

MQL5 / C++
  class="type">int usedHighCount = (highCount >= class="num">4) ? highCount - class="num">1 : highCount;
  class="type">int usedLowCount  = (lowCount  >= class="num">4) ? lowCount - class="num">1  : lowCount;
  class="type">class="kw">double mHigh = class="num">0, bHigh = class="num">0, mLow = class="num">0, bLow = class="num">0;
  class="type">bool validHigh = false, validLow = false;
class=class="str">"cmt">// Regression for highs
  if(usedHighCount >= class="num">3)
    {
      class="type">class="kw">double sumT = class="num">0, sumP = class="num">0, sumTP = class="num">0, sumT2 = class="num">0;
      for(class="type">int i = class="num">0; i < usedHighCount; i++)
        {
         class="type">class="kw">double t = (class="type">class="kw">double)highTimes[i];
         class="type">class="kw">double p = highPrices[i];
         sumT += t;
         sumP += p;
         sumTP += t * p;
         sumT2 += t * t;
        }
      class="type">int N = usedHighCount;
      class="type">class="kw">double denominator = N * sumT2 - sumT * sumT;
      if(denominator != class="num">0)
        {
         mHigh = (N * sumTP - sumT * sumP) / denominator;
         bHigh = (sumP - mHigh * sumT) / N;
        }
      else
         bHigh = sumP / N;
      validHigh = true;
    }
class=class="str">"cmt">// Regression for lows
  if(usedLowCount >= class="num">3)
    {
      class="type">class="kw">double sumT = class="num">0, sumP = class="num">0, sumTP = class="num">0, sumT2 = class="num">0;
      for(class="type">int i = class="num">0; i < usedLowCount; i++)
        {
         class="type">class="kw">double t = (class="type">class="kw">double)lowTimes[i];
         class="type">class="kw">double p = lowPrices[i];
         sumT += t;
         sumP += p;
         sumTP += t * p;
         sumT2 += t * t;
        }
      class="type">int N = usedLowCount;
      class="type">class="kw">double denominator = N * sumT2 - sumT * sumT;
      if(denominator != class="num">0)
        {
         mLow = (N * sumTP - sumT * sumP) / denominator;
         bLow = (sumP - mLow * sumT) / N;
        }
      else
         bLow = sumP / N;
      validLow = true;
    }
class=class="str">"cmt">// Define time limits for trendlines
  class="type">class="kw">datetime pastTime   = iTime(_Symbol, InpTimeFrame, LookBackBars - class="num">1);
  class="type">class="kw">datetime futureTime = lastBarTime + ExtendFutureBars * PeriodSeconds();
class=class="str">"cmt">// Draw trendlines if both regressions are valid
  if(validHigh && validLow)
    {
      class=class="str">"cmt">// When slopes have the same sign, use average slope

平行通道的上下轨绘制逻辑

当 mHigh 与 mLow 同号(乘积大于 0)时,说明高低点斜率方向一致,此时可用两者均值 mParallel 构造一条平行通道,而非独立的两组斜线。 代码先算出平行斜率 mParallel = (mHigh + mLow) / 2.0,再分别用 highPrices[0]、lowPrices[0] 与对应时间求出高低轨的截距 bHighParallel、bLowParallel,得到两条永不相交的平行趋势线。 高低轨都从 pastTime 延伸到 futureTime,调用 ObjectCreate 以 OBJ_TREND 类型画图;失败则 Print 报错,成功则将颜色设为红/绿,并开启 OBJPROP_RAY_LEFT 与 OBJPROP_RAY_RIGHT,让射线向左右双向延伸。 若 mHigh * mLow <= 0,则走 else 分支,仅用各自斜率 mHigh 与截距 bHigh 绘制单侧高位趋势线,不再强制平行。外汇与贵金属波动剧烈,这类自动画线仅作结构参考,实际突破倾向需结合成交量与多周期验证。

MQL5 / C++
if(mHigh * mLow > class="num">0)
    {
     class="type">class="kw">double mParallel = (mHigh + mLow) / class="num">2.0;
     class="type">class="kw">double bHighParallel = highPrices[class="num">0] - mParallel * (class="type">class="kw">double)highTimes[class="num">0];
     class="type">class="kw">double bLowParallel  = lowPrices[class="num">0] - mParallel * (class="type">class="kw">double)lowTimes[class="num">0];
     class="type">class="kw">datetime highStartTime = pastTime;
     class="type">class="kw">double highStartPrice  = mParallel * (class="type">class="kw">double)highStartTime + bHighParallel;
     class="type">class="kw">double highEndPrice    = mParallel * (class="type">class="kw">double)futureTime + bHighParallel;
     if(!ObjectCreate(class="num">0, "Downtrend_HighLine", OBJ_TREND, class="num">0, highStartTime, highStartPrice, futureTime, highEndPrice))
        Print("Failed to create High Trendline");
     else
       {
       ObjectSetInteger(class="num">0, "Downtrend_HighLine", OBJPROP_COLOR, clrRed);
       ObjectSetInteger(class="num">0, "Downtrend_HighLine", OBJPROP_RAY_LEFT, true);
       ObjectSetInteger(class="num">0, "Downtrend_HighLine", OBJPROP_RAY_RIGHT, true);
       }
     class="type">class="kw">datetime lowStartTime = pastTime;
     class="type">class="kw">double lowStartPrice  = mParallel * (class="type">class="kw">double)lowStartTime + bLowParallel;
     class="type">class="kw">double lowEndPrice    = mParallel * (class="type">class="kw">double)futureTime + bLowParallel;
     if(!ObjectCreate(class="num">0, "Uptrend_LowLine", OBJ_TREND, class="num">0, lowStartTime, lowStartPrice, futureTime, lowEndPrice))
        Print("Failed to create Low Trendline");
     else
       {
       ObjectSetInteger(class="num">0, "Uptrend_LowLine", OBJPROP_COLOR, clrGreen);
       ObjectSetInteger(class="num">0, "Uptrend_LowLine", OBJPROP_RAY_LEFT, true);
       ObjectSetInteger(class="num">0, "Uptrend_LowLine", OBJPROP_RAY_RIGHT, true);
       }
    }
  else
    {
     class="type">class="kw">datetime highStartTime = pastTime;
     class="type">class="kw">double highStartPrice  = mHigh * (class="type">class="kw">double)highStartTime + bHigh;
     class="type">class="kw">double highEndPrice    = mHigh * (class="type">class="kw">double)futureTime + bHigh;
     if(!ObjectCreate(class="num">0, "Downtrend_HighLine", OBJ_TREND, class="num">0, highStartTime, highStartPrice, futureTime, highEndPrice))

◍ 通道边界的射线绘制与失败处理

在回归通道的上下轨生成后,代码把斜率截距换算成具体时间坐标上的价格点,再用 OBJ_TREND 画趋势线。高点线命名为 Downtrend_HighLine 并染红,低点线命名为 Uptrend_LowLine 并染绿,两条线都开启左右射线(RAY_LEFT/RAY_RIGHT 为 true),方便在 MT5 主图上向后延伸观察价格触碰。 若 ObjectCreate 返回 false,说明对象名冲突或图表上下文异常,此时直接 Print 输出 "Failed to create High/Low Trendline" 到专家日志,不会中断 EA 运行。开 MT5 跑这段时,先确认没有同名手工趋势线占着坑,否则创建必然失败。 价格点的计算用到了 mHigh*(double)highStartTime + bHigh 这类线性表达式,pastTime 与 futureTime 分别是通道回看起点和向前投射终点。外汇与贵金属波动剧烈,射线延伸仅作概率参考,实际触碰后反转或突破都可能发生,属高风险场景。

MQL5 / C++
Print("Failed to create High Trendline");
   else
      {
      ObjectSetInteger(class="num">0, "Downtrend_HighLine", OBJPROP_COLOR, clrRed);
      ObjectSetInteger(class="num">0, "Downtrend_HighLine", OBJPROP_RAY_LEFT, true);
      ObjectSetInteger(class="num">0, "Downtrend_HighLine", OBJPROP_RAY_RIGHT, true);
      }
      class="type">class="kw">datetime lowStartTime = pastTime;
      class="type">class="kw">double lowStartPrice  = mLow * (class="type">class="kw">double)lowStartTime + bLow;
      class="type">class="kw">double lowEndPrice    = mLow * (class="type">class="kw">double)futureTime + bLow;
      if(!ObjectCreate(class="num">0, "Uptrend_LowLine", OBJ_TREND, class="num">0, lowStartTime, lowStartPrice, futureTime, lowEndPrice))
         Print("Failed to create Low Trendline");
      else
         {
         ObjectSetInteger(class="num">0, "Uptrend_LowLine", OBJPROP_COLOR, clrGreen);
         ObjectSetInteger(class="num">0, "Uptrend_LowLine", OBJPROP_RAY_LEFT, true);
         ObjectSetInteger(class="num">0, "Uptrend_LowLine", OBJPROP_RAY_RIGHT, true);
         }
         }
      }
   else
      {
      if(validHigh)
         {
         class="type">class="kw">datetime highStartTime = pastTime;
         class="type">class="kw">double highStartPrice  = mHigh * (class="type">class="kw">double)highStartTime + bHigh;
         class="type">class="kw">double highEndPrice    = mHigh * (class="type">class="kw">double)futureTime + bHigh;
         if(!ObjectCreate(class="num">0, "Downtrend_HighLine", OBJ_TREND, class="num">0, highStartTime, highStartPrice, futureTime, highEndPrice))
            Print("Failed to create High Trendline");
         else
            {
            ObjectSetInteger(class="num">0, "Downtrend_HighLine", OBJPROP_COLOR, clrRed);
            ObjectSetInteger(class="num">0, "Downtrend_HighLine", OBJPROP_RAY_LEFT, true);
            ObjectSetInteger(class="num">0, "Downtrend_HighLine", OBJPROP_RAY_RIGHT, true);
            }
         }
      if(validLow)
         {
         class="type">class="kw">datetime lowStartTime = pastTime;
         class="type">class="kw">double lowStartPrice  = mLow * (class="type">class="kw">double)lowStartTime + bLow;

「用 ZigZag 节点画支撑阻力与上升底线」

这段逻辑承接前面的 ZigZag 识别,把已确认的高低点转成可视的支撑阻力线,并补一条向右延伸的上升底线。外汇与贵金属波动剧烈,这类画线只描述历史结构,后续价格触碰后反转还是破位都只是概率事件。 上升底线的终点价格由斜率推算:lowEndPrice = mLow * (double)futureTime + bLow,其中 mLow 是前面拟合出的底线斜率,futureTime 是向右延伸到的柱索引。若 ObjectCreate 返回失败会向日志打印 Failed to create Low Trendline,成功则把颜色设为绿、左右都开射线,方便在 MT5 上持续观察。 支撑阻力的提取在 DrawSupportResistance 里完成:用两个长度 10 的数组存 confirmedHighs / confirmedLows,遍历 LookBackBars-1 根 K 线,把 zzBuffer 非空且与 iHigh / iLow 吻合的节点分别收进去,最多各存 10 个。 计数上有个细节——若确认高/低点 ≥4,实际使用数会减 1(usedHighCount / usedLowCount 的写法),相当于留最后一个节点不参与主级判定。主级支撑取所有 used 低点里的最小值(初始化 1e9),主级阻力取最大值(初始化 -1e9),次级的 tempHigh / tempLow 则承接被挤下来的次高/次低值。

MQL5 / C++
class="type">class="kw">double lowEndPrice     = mLow * (class="type">class="kw">double)futureTime + bLow;
if(!ObjectCreate(class="num">0, "Uptrend_LowLine", OBJ_TREND, class="num">0, lowStartTime, lowStartPrice, futureTime, lowEndPrice))
   Print("Failed to create Low Trendline");
else
   {
   ObjectSetInteger(class="num">0, "Uptrend_LowLine", OBJPROP_COLOR, clrGreen);
   ObjectSetInteger(class="num">0, "Uptrend_LowLine", OBJPROP_RAY_LEFT, true);
   ObjectSetInteger(class="num">0, "Uptrend_LowLine", OBJPROP_RAY_RIGHT, true);
   }
   }
 }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Draw Support and Resistance Levels                               |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void DrawSupportResistance()
  {
  class="type">class="kw">double confirmedHighs[class="num">10], confirmedLows[class="num">10];
  class="type">int confHighCount = class="num">0, confLowCount = class="num">0;
  for(class="type">int i = class="num">0; i < LookBackBars - class="num">1; i++)
    {
    if(zzBuffer[i] != class="num">0)
      {
      if(iHigh(_Symbol, InpTimeFrame, i) == zzBuffer[i] && confHighCount < class="num">10)
        {
        confirmedHighs[confHighCount] = zzBuffer[i];
        confHighCount++;
        }
      else
        if(iLow(_Symbol, InpTimeFrame, i) == zzBuffer[i] && confLowCount < class="num">10)
          {
           confirmedLows[confLowCount] = zzBuffer[i];
           confLowCount++;
          }
      }
    }
  class="type">int usedHighCount = (confHighCount >= class="num">4) ? confHighCount - class="num">1 : confHighCount;
  class="type">int usedLowCount  = (confLowCount  >= class="num">4) ? confLowCount - class="num">1  : confLowCount;
  class="type">class="kw">double majorResistance = -class="num">1e9, majorSupport = class="num">1e9;
  class="type">class="kw">double minorResistance = -class="num">1e9, minorSupport = class="num">1e9;
  class="type">class="kw">double tempHigh = -class="num">1e9, tempLow = -class="num">1e9;
  for(class="type">int i = class="num">0; i < usedHighCount; i++)
    {
    if(confirmedHighs[i] > majorResistance)
      {
      tempHigh = majorResistance;
      majorResistance = confirmedHighs[i];
      }
    else
      if(confirmedHighs[i] > tempHigh)
        {

把高低点数组画成四级水平线

这段逻辑紧接前面已确认的波峰波谷数组,负责把 major/minor 支撑阻力算出来并直接往 MT5 图表上甩水平线。先扫 confirmedHighs:比当前 majorResistance 高的就顶替成主阻力,否则记到 tempHigh 当次阻力,最后 tempHigh 大于 -1e9 才写进 minorResistance。 低点侧同理,遍历 confirmedLows 找比 majorSupport 更小的更新主支撑,其余落进 tempLow;tempLow 小于 1e9 才认定为 minorSupport。注意判断里用了 1e9 / -1e9 当初始哨兵值,没碰到有效值就不会误画。 画线部分用 ObjectCreate 建 OBJ_HLINE,主阻力品红(clrMagenta)、次阻力紫红(clrFuchsia)、主支撑青色(clrAqua)、次支撑蓝色(clrBlue)。次线只在「确实比主线更靠近现价」时才建——阻力要低于主阻力,支撑要高于主支撑,避免重叠乱图。 开 MT5 把这段接在你自己的波峰波谷识别后跑一遍,图表上若没出线先查 Print 里的 Failed 提示;外汇和贵金属波动快,这类静态水平线仅作概率参考,实战须自担高风险。

MQL5 / C++
   tempHigh = confirmedHighs[i];
     }
   }
  if(tempHigh > -class="num">1e9)
     minorResistance = tempHigh;
  for(class="type">int i = class="num">0; i < usedLowCount; i++)
   {
     if(confirmedLows[i] < majorSupport)
       {
        tempLow = majorSupport;
        majorSupport = confirmedLows[i];
       }
     else
       if(confirmedLows[i] < tempLow)
         {
          tempLow = confirmedLows[i];
         }
   }
  if(tempLow < class="num">1e9)
     minorSupport = tempLow;
  if(usedHighCount > class="num">0)
   {
     if(!ObjectCreate(class="num">0, "Major_Resistance", OBJ_HLINE, class="num">0, class="num">0, majorResistance))
        Print("Failed to create Major Resistance");
     else
        ObjectSetInteger(class="num">0, "Major_Resistance", OBJPROP_COLOR, clrMagenta);
     if(minorResistance > -class="num">1e9 && minorResistance < majorResistance)
       {
        if(!ObjectCreate(class="num">0, "Minor_Resistance", OBJ_HLINE, class="num">0, class="num">0, minorResistance))
           Print("Failed to create Minor Resistance");
        else
           ObjectSetInteger(class="num">0, "Minor_Resistance", OBJPROP_COLOR, clrFuchsia);
       }
   }
  if(usedLowCount > class="num">0)
   {
     if(!ObjectCreate(class="num">0, "Major_Support", OBJ_HLINE, class="num">0, class="num">0, majorSupport))
        Print("Failed to create Major Support");
     else
        ObjectSetInteger(class="num">0, "Major_Support", OBJPROP_COLOR, clrAqua);
     if(minorSupport < class="num">1e9 && minorSupport > majorSupport)
       {
        if(!ObjectCreate(class="num">0, "Minor_Support", OBJ_HLINE, class="num">0, class="num">0, minorSupport))
           Print("Failed to create Minor Support");
        else
           ObjectSetInteger(class="num">0, "Minor_Support", OBJPROP_COLOR, clrBlue);
       }
   }
}

◍ 把趋势线摆到图上验证

先往图表里挂上 ZigZag 指标,再跑分析器,不然结果没法对照看。阶梯指数那张图里,EA 接手后高低线都向下倾,主次要支撑阻力一并标出,低线高线同步下压就是下降结构的可视证据。 波段顶点看得更细:更低的低点、更低的高点连成趋势线,线和主要水平的交叉点正是反转高发区——最高的更低高点碰主阻力、最低的更低低点碰主支撑,这两处都出现了折返。 USDCHF 上趋势线不抢戏但市场照常尊重,触碰波动顶点的位置高亮后,系统在外汇品种上算站住了脚。波动率 25 指数也画出连贯波段连线,多种行情下线条都贴着运动路径,说明这套画法不是单品种巧合。 外汇与指数差价合约波动剧烈、杠杆风险高,上述图形结论仅代表历史片段表现,后续走势仍可能背离。

「把这条线请下神坛」

ZigZag 分析器在趋势市里能把拐点连成趋势线,测试显示支撑阻力区识别效率明显高于纯肉眼描线,但它终归只是价格行为的投影,不是决策本身。外汇与贵金属杠杆高、滑点跳空频繁,把它当唯一依据大概率会吃亏,老手也该用其他指标交叉确认。 下面这段初始化代码是工具跑起来的命门:先建 ZigZag 句柄,失败就退初始化,成功才挂到主图。若你编译后图表空白,九成是自定义指标没放进 Examples\ZigZag 路径——社区里已有两人踩过这坑。 int OnInit() 创建句柄并 ChartIndicatorAdd 挂图,逻辑干净,复制进 MT5 改 ZZ_Depth 参数就能看不同灵敏度下的线形差异。工具链从 24 年 1 月图表投影仪到 25 年 3 月 ZigZag 分析器共 19 个,挑顺手的改两行比追新更有用。

MQL5 / C++
class="type">int OnInit()
  {
   zzHandle = iCustom(_Symbol, InpTimeFrame, "Examples\\ZigZag", ZZ_Depth, ZZ_Deviation, ZZ_Backstep);
   if(zzHandle == INVALID_HANDLE)
     {
       Print("Error creating ZigZag handle");
       class="kw">return(INIT_FAILED);
     }
     
   ChartIndicatorAdd(class="num">0, class="num">0, zzHandle);
   class="kw">return(INIT_SUCCEEDED);
  }
把波段识别交给小布盯盘
这些诊断小布盯盘的AIGC已内置,打开对应品种页即可看到ZigZag自动标出的高低点连线,你只管判断破位与回踩。

常见问题

算法按阈值过滤噪声,手动线常混入次级波动;以ZigZag结构为骨架,手动线仅作微观确认,外汇贵金属波动大时优先看结构。
可以,小布盯盘品种页内置了类似波段顶点诊断,不需要自己写MQL5也能看到实时趋势线骨架,适合快速复核。
跨品种用百分比更稳,贵金属和外汇点值差异大;日内短周期可试点数,但需回测避免频繁重绘。
算法不清代码必乱,ZigZag分析器先定顶点判定与连线规则,MQL5只是载体,这样后续改阈值不伤架构。