English Deutsch 日本語
preview
价格行为分析工具包开发(第19部分):ZigZag分析器

价格行为分析工具包开发(第19部分):ZigZag分析器

MetaTrader 5示例 |
103 5
Christian Benjamin
Christian Benjamin

引言

趋势线构成了技术分析和现代价格行为的基石。它们是价格形态的“骨架”,外汇、加密货币、大宗商品、股票和衍生品的交易者都依赖于此。当绘制正确时,趋势线能有效地揭示市场趋势,并帮助交易者发现潜在的机会。

无论您是日内交易者还是短线交易者,趋势线在许多交易系统和策略中都扮演着关键角色。在我们的价格行为分析工具包开发系列中,我们介绍一款名为ZigZag分析器的工具。该工具专注于使用ZigZag指标绘制趋势线,通过识别构成趋势线基础的波段顶点来实现。MQL5是一种强大的自动化交易系统语言,它允许我们构建像ZigZag分析器这样的高级工具,这些工具能够适应市场条件并支持实时决策。

我们将首先探讨趋势线,然后研究Zigzag指标,概述系统算法,展示MQL5代码,审查结果,最后以我们的最终见解作结。让我们看看下面的目录:



趋势线

趋势线是一条连接重要价格点(通常是更高的低点或更低的高点)的倾斜线,并作为支撑或阻力水平向未来延伸。它指明了价格变动的可能方向和动量。要绘制一条趋势线,首先需要确定市场的整体方向。上升趋势的特征是一系列更高的高点和更高的低点,而下降趋势则以更低的高点和更低的低点为标志。为了便于说明,考虑一个显示下降形态的图表,您需要连接下降的波段高点来建立趋势线。在价格盘整期间,多条趋势线的形成可以揭示出能够强化交易信号的形态。下面是一个图示。

上行趋势

图 1. 趋势线

该图表说明了两个关键概念:支撑与阻力。支撑位是价格下行运动常常停滞的区域,因为买方变得足够活跃以吸收卖压。当应用趋势线时,支撑通常通过连接重要低点的向上倾斜线来显示。相反,阻力位是价格上行运动因卖压加剧而频繁暂停的区域。对于趋势线,阻力由连接重要高点的向下倾斜线表示。交易者经常跨越多个时间周期绘制趋势线,以识别市场在短期、中期或长期内是向上还是向下移动。
趋势线是技术分析中的一个宝贵工具,因为它们帮助市场参与者衡量资产价格的整体方向。通过在选定的时间框架内连接重要的高点或低点,这些线条直观地展示了市场是在向上、向下还是横盘。这一信息对于依赖价格趋势来指导决策的交易者和短期投资者尤其有用。



ZigZag指标

尽管Zig Zag指标的确切起源没有明确记载,但一些资料将其发现归功于比尔·沃尔夫,一位标普500(S&P 500)交易员,他以开发沃尔夫波浪而闻名,这是一种与艾略特波浪有些相似但图表技术独特的方法。沃尔夫波浪由五个波浪组成,描绘了供需向均衡价格移动的过程,请参阅。Zig Zag指标突显了超过指定阈值(通常以百分比表示)的重大价格反转。当价格波动超过此阈值时,该指标会绘制一个新点,并从上一个点画一条直线,从而有效地过滤掉较小的波动。通过这样做,它使得潜在趋势在不同时间框架上更容易被发现。

交易者可以调整百分比阈值(例如4%或5%)来捕捉或多或少的波动,具体取决于资产的波动性或他们的个人交易风格。这种灵活性有助于完善Zig Zag指标识别潜在转折点的方式。在基于波浪的分析中,如艾略特波浪理论,Zig Zag可以帮助阐明每个波浪的结构。最终,通常需要通过试验不同的设置,以在过滤噪音和检测有意义的价格变动之间找到最佳平衡。

ZigZag指标

图 2. ZigZag指标

上面的图2展示了一个带有Zig Zag指标的图表。它演示了如何根据其波段顶点来构建趋势线。Zig Zag指标通过过滤掉次要的价格波动并突出关键的转折点,简化了趋势分析。它标记了重要的高点和低点,这些点作为绘制趋势线的锚点。在上升趋势中,连接这些波段低点会形成一条支撑线;而在下降趋势中,连接波段高点则会形成一条阻力线。这种方法通过消除噪音,强调了市场的整体方向。调整指标的灵敏度可以进一步完善这些趋势线的准确性,以获得更好的交易洞察。



系统算法

1. 全局声明和输入参数

在初始化部分,我们设置了关键的输入参数和全局变量,让您无需深入研究代码即可自定义分析器。我们定义了诸如图表时间周期等参数,以及 ZigZag 指标的特定属性,例如深度、偏差和 backstep(回溯步长)。这些设置决定了分析器如何挑选波段顶点。我们还声明了用于存储 ZigZag 数据以及记录关键转折点时间和价格的数组。

我们赞赏这种设置的简洁性和模块化设计。例如,通过更改时间周期参数,您可以轻松地在短期和长期趋势之间切换。ZigZag 的属性让您可以调整指标的灵敏度,因此您可以使分析适应不同的市场条件或资产。我们使用全局变量和数组作为所有动态数据的中心枢纽。这种方法提高了性能,并使代码更易于维护。当新数据到达时,它被高效地存储并准备好进行分析,从而使自定义和调试变得更加简单。

总而言之,通过集中这些关键的输入和声明,我们创建了一个灵活且清晰的工具,使您能够根据自己的需求定制分析器,同时保持底层过程的稳健和可靠。

// Input parameters
input ENUM_TIMEFRAMES InpTimeFrame    = PERIOD_CURRENT; // Timeframe to analyze
input int             ZZ_Depth         = 12;  // ZigZag depth
input int             ZZ_Deviation     = 5;   // ZigZag deviation
input int             ZZ_Backstep      = 3;   // ZigZag backstep
input int             LookBackBars     = 200; // Bars to search for pivots
input int             ExtendFutureBars = 100; // Bars to extend trendlines into the future

// Global indicator handle for ZigZag
int zzHandle;

// Arrays for ZigZag data and pivot storage
double   zzBuffer[];
datetime pivotTimes[];
double   pivotPrices[];
bool     pivotIsPeak[];

// Variable to detect new bars
datetime lastBarTime = 0;

  • InpTimeFrame:分析的时间周期。
  • ZZ_Depth, ZZ_Deviation, ZZ_Backstep: 影响 ZigZag 指标灵敏度和行为的参数。

zzBuffer 这样的数组用于存储 ZigZag 指标的输出。额外的数组(pivotTimes, pivotPrices, pivotIsPeak)则用于保存每个检测到的转折点的详细信息。一个变量(lastBarTime)用于帮助判断是否开始了新的K线,确保分析仅在必要时更新。

2. 初始化函数(OnInit

在初始化函数中,我们在指标加载时执行一次代码。我们的主要目标是使用用户输入来创建和配置 ZigZag 指标。我们调用 iCustom 函数来实例化 ZigZag 指标并存储其句柄。如果指标初始化失败并返回无效句柄,我们会记录错误并停止进一步处理。

我们使用此函数来确保在开始任何分析之前,所有设置都已正确配置。它扮演着“守门员”的角色,如果指标设置存在问题,就阻止其余代码运行。这种早期检查节省了时间,并避免了执行后期出现不必要的错误。通过将初始化集中在此处,我们使系统在出现问题时更易于管理和调试。该函数还确保所有用户定义的设置从一开始就得到应用,为后续的分析奠定了坚实的基础。

int OnInit()
  {
   zzHandle = iCustom(_Symbol, InpTimeFrame, "ZigZag", ZZ_Depth, ZZ_Deviation, ZZ_Backstep);
   if(zzHandle == INVALID_HANDLE)
     {
      Print("Error creating ZigZag handle");
      return(INIT_FAILED);
     }
   return(INIT_SUCCEEDED);
  }

  • 指标创建:使用 iCustom 加载带有特定参数的 ZigZag 指标。
  • 错误处理:检查返回的句柄是否有效。如果初始化失败,则记录错误消息并中止。

3. 反初始化函数 (OnDeinit)

当指标被移除或平台关闭时,清理所有对象并释放已分配的资源至关重要。OnDeinit 函数负责此任务,它会删除在图表上绘制的任何图形对象(如趋势线或水平线),并释放 ZigZag 指标的句柄。这确保了图表上不会留下任何不需要的元素,并且系统的资源得到了妥善管理。

资源清理:删除所有创建的图表对象以避免杂乱。

句柄释放:使用 IndicatorRelease 释放 ZigZag 指标句柄。

void OnDeinit(const int reason)
  {
   ObjectDelete(0, "Downtrend_HighLine");
   ObjectDelete(0, "Uptrend_LowLine");
   ObjectDelete(0, "Major_Resistance");
   ObjectDelete(0, "Major_Support");
   ObjectDelete(0, "Minor_Resistance");
   ObjectDelete(0, "Minor_Support");
   IndicatorRelease(zzHandle);
  }

4. 主执行函数(OnTick)

这是算法的核心,在每个新的报价变动时运行。该函数首先通过比较当前K线的时间戳与上次存储的时间来检查是否形成了新的K线。如果没有检测到新的K线,函数会提前退出以节省处理能力。一旦确认有新的K线,它会移除旧的图表对象,使用 CopyBuffer 检索最新的 ZigZag 数据,然后调用两个辅助函数:一个用于绘制趋势线,另一个用于绘制支撑和阻力水平。

void OnTick()
  {
   datetime currentBarTime = iTime(_Symbol, InpTimeFrame, 0);
   if(currentBarTime == lastBarTime)
      return;
   lastBarTime = currentBarTime;

   // Remove previous objects
   ObjectDelete(0, "Downtrend_HighLine");
   ObjectDelete(0, "Uptrend_LowLine");
   ObjectDelete(0, "Major_Resistance");
   ObjectDelete(0, "Major_Support");
   ObjectDelete(0, "Minor_Resistance");
   ObjectDelete(0, "Minor_Support");

   if(CopyBuffer(zzHandle, 0, 0, LookBackBars, zzBuffer) <= 0)
     {
      Print("Failed to copy ZigZag data");
      return;
     }
   ArraySetAsSeries(zzBuffer, true);

   DrawZigZagTrendlines();
   DrawSupportResistance();
  }

  • 新K线检查:使用 iTime 函数检测K线的变化。
  • 缓冲区更新:用最新的 ZigZag 数据刷新 zzBuffer
  • 图表更新:清除先前的图形对象,为绘制新对象做准备。调用函数来绘制趋势线和支撑/阻力线。

5. 绘制基于ZigZag的趋势线 (DrawZigZagTrendlines)

此函数负责从 ZigZag 数据中识别重要的波段顶点,并用它们来计算趋势线。它会遍历缓冲区,根据当前的 ZigZag 值是否匹配K线的高点或低点来收集高点和低点。一旦收集到波段顶点,代码就会使用线性回归来确定每组点的趋势线。回归计算采用以下公式:

1. 斜率(m)

图 3. 回归公式

2. 截距(b)

截距(b)

图 4. 截距

  • (t)代表时间值(或自变量)。
  • (p)代表价格值(或因变量)。
  • (N)是回归中使用的数据点的数量。

波段顶点提取:遍历 zzBuffer 以收集最多10个高点和低点。
排除最新波段顶点:舍弃最近的顶点以减少噪音。
回归分析:使用上述公式计算斜率和截距。

void DrawZigZagTrendlines()
  {
   double highPrices[10], lowPrices[10];
   datetime highTimes[10], lowTimes[10];
   int highCount = 0, lowCount = 0;

   // Extract swing points from the ZigZag buffer
   for(int i = 0; i < LookBackBars - 1; i++)
     {
      if(zzBuffer[i] != 0)
        {
         if(iHigh(_Symbol, InpTimeFrame, i) == zzBuffer[i] && highCount < 10)
           {
            highPrices[highCount] = zzBuffer[i];
            highTimes[highCount] = iTime(_Symbol, InpTimeFrame, i);
            highCount++;
           }
         else if(iLow(_Symbol, InpTimeFrame, i) == zzBuffer[i] && lowCount < 10)
              {
               lowPrices[lowCount] = zzBuffer[i];
               lowTimes[lowCount] = iTime(_Symbol, InpTimeFrame, i);
               lowCount++;
              }
        }
     }

   // Exclude the most recent swing if possible
   int usedHighCount = (highCount >= 4) ? highCount - 1 : highCount;
   int usedLowCount  = (lowCount  >= 4) ? lowCount - 1  : lowCount;

   double mHigh = 0, bHigh = 0, mLow = 0, bLow = 0;
   bool validHigh = false, validLow = false;

   // Regression for highs
   if(usedHighCount >= 3)
     {
      double sumT = 0, sumP = 0, sumTP = 0, sumT2 = 0;
      for(int i = 0; i < usedHighCount; i++)
        {
         double t = (double)highTimes[i];
         double p = highPrices[i];
         sumT += t;
         sumP += p;
         sumTP += t * p;
         sumT2 += t * t;
        }
      int N = usedHighCount;
      double denominator = N * sumT2 - sumT * sumT;
      if(denominator != 0)
        {
         mHigh = (N * sumTP - sumT * sumP) / denominator;
         bHigh = (sumP - mHigh * sumT) / N;
        }
      else
         bHigh = sumP / N;
      validHigh = true;
     }

   // Regression for lows
   if(usedLowCount >= 3)
     {
      double sumT = 0, sumP = 0, sumTP = 0, sumT2 = 0;
      for(int i = 0; i < usedLowCount; i++)
        {
         double t = (double)lowTimes[i];
         double p = lowPrices[i];
         sumT += t;
         sumP += p;
         sumTP += t * p;
         sumT2 += t * t;
        }
      int N = usedLowCount;
      double denominator = N * sumT2 - sumT * sumT;
      if(denominator != 0)
        {
         mLow = (N * sumTP - sumT * sumP) / denominator;
         bLow = (sumP - mLow * sumT) / N;
        }
      else
         bLow = sumP / N;
      validLow = true;
     }

   // Define time limits for trendlines
   datetime pastTime   = iTime(_Symbol, InpTimeFrame, LookBackBars - 1);
   datetime futureTime = lastBarTime + ExtendFutureBars * PeriodSeconds();

   // Draw trendlines if both regressions are valid
   if(validHigh && validLow)
     {
      // When slopes have the same sign, use average slope for parallel lines
      if(mHigh * mLow > 0)
        {
         double mParallel = (mHigh + mLow) / 2.0;
         double bHighParallel = highPrices[0] - mParallel * (double)highTimes[0];
         double bLowParallel  = lowPrices[0] - mParallel * (double)lowTimes[0];

         datetime highStartTime = pastTime;
         double highStartPrice  = mParallel * (double)highStartTime + bHighParallel;
         double highEndPrice    = mParallel * (double)futureTime + bHighParallel;
         if(!ObjectCreate(0, "Downtrend_HighLine", OBJ_TREND, 0, highStartTime, highStartPrice, futureTime, highEndPrice))
            Print("Failed to create High Trendline");
         else
           {
            ObjectSetInteger(0, "Downtrend_HighLine", OBJPROP_COLOR, clrRed);
            ObjectSetInteger(0, "Downtrend_HighLine", OBJPROP_RAY_LEFT, true);
            ObjectSetInteger(0, "Downtrend_HighLine", OBJPROP_RAY_RIGHT, true);
           }

         datetime lowStartTime = pastTime;
         double lowStartPrice  = mParallel * (double)lowStartTime + bLowParallel;
         double lowEndPrice    = mParallel * (double)futureTime + bLowParallel;
         if(!ObjectCreate(0, "Uptrend_LowLine", OBJ_TREND, 0, lowStartTime, lowStartPrice, futureTime, lowEndPrice))
            Print("Failed to create Low Trendline");
         else
           {
            ObjectSetInteger(0, "Uptrend_LowLine", OBJPROP_COLOR, clrGreen);
            ObjectSetInteger(0, "Uptrend_LowLine", OBJPROP_RAY_LEFT, true);
            ObjectSetInteger(0, "Uptrend_LowLine", OBJPROP_RAY_RIGHT, true);
           }
        }
      else // Draw separate trendlines if slopes differ
        {
         datetime highStartTime = pastTime;
         double highStartPrice  = mHigh * (double)highStartTime + bHigh;
         double highEndPrice    = mHigh * (double)futureTime + bHigh;
         if(!ObjectCreate(0, "Downtrend_HighLine", OBJ_TREND, 0, highStartTime, highStartPrice, futureTime, highEndPrice))
            Print("Failed to create High Trendline");
         else
           {
            ObjectSetInteger(0, "Downtrend_HighLine", OBJPROP_COLOR, clrRed);
            ObjectSetInteger(0, "Downtrend_HighLine", OBJPROP_RAY_LEFT, true);
            ObjectSetInteger(0, "Downtrend_HighLine", OBJPROP_RAY_RIGHT, true);
           }

         datetime lowStartTime = pastTime;
         double lowStartPrice  = mLow * (double)lowStartTime + bLow;
         double lowEndPrice    = mLow * (double)futureTime + bLow;
         if(!ObjectCreate(0, "Uptrend_LowLine", OBJ_TREND, 0, lowStartTime, lowStartPrice, futureTime, lowEndPrice))
            Print("Failed to create Low Trendline");
         else
           {
            ObjectSetInteger(0, "Uptrend_LowLine", OBJPROP_COLOR, clrGreen);
            ObjectSetInteger(0, "Uptrend_LowLine", OBJPROP_RAY_LEFT, true);
            ObjectSetInteger(0, "Uptrend_LowLine", OBJPROP_RAY_RIGHT, true);
           }
        }
     }
   else
     {
      // Draw only available regression if only one set of points is valid
      if(validHigh)
        {
         datetime highStartTime = pastTime;
         double highStartPrice  = mHigh * (double)highStartTime + bHigh;
         double highEndPrice    = mHigh * (double)futureTime + bHigh;
         if(!ObjectCreate(0, "Downtrend_HighLine", OBJ_TREND, 0, highStartTime, highStartPrice, futureTime, highEndPrice))
            Print("Failed to create High Trendline");
         else
           {
            ObjectSetInteger(0, "Downtrend_HighLine", OBJPROP_COLOR, clrRed);
            ObjectSetInteger(0, "Downtrend_HighLine", OBJPROP_RAY_LEFT, true);
            ObjectSetInteger(0, "Downtrend_HighLine", OBJPROP_RAY_RIGHT, true);
           }
        }
      if(validLow)
        {
         datetime lowStartTime = pastTime;
         double lowStartPrice  = mLow * (double)lowStartTime + bLow;
         double lowEndPrice    = mLow * (double)futureTime + bLow;
         if(!ObjectCreate(0, "Uptrend_LowLine", OBJ_TREND, 0, lowStartTime, lowStartPrice, futureTime, lowEndPrice))
            Print("Failed to create Low Trendline");
         else
           {
            ObjectSetInteger(0, "Uptrend_LowLine", OBJPROP_COLOR, clrGreen);
            ObjectSetInteger(0, "Uptrend_LowLine", OBJPROP_RAY_LEFT, true);
            ObjectSetInteger(0, "Uptrend_LowLine", OBJPROP_RAY_RIGHT, true);
           }
        }
     }
  }

趋势线绘制(如果两种趋势都有效)

斜率同号:使用平均斜率来绘制平行线。 斜率异号:绘制各自独立的趋势线。使用 ObjectCreateObjectSetInteger 来放置和设置线条样式。

6. 绘制支撑和阻力水平 (DrawSupportResistance)

代码中关于支撑和阻力的部分,旨在根据ZigZag指标提供的已确认波段顶点,动态地识别关键的水平价格水平。该算法不仅仅是简单地将最高的确认高点指定为主要阻力,将最低的确认低点指定为主要支撑,而是通过筛选重要的高点和低点来确定主要和次要水平,从而进行更深入的分析。这个过程至关重要,因为支撑和阻力是技术分析中的基本概念;支撑代表价格因需求增加而趋于停止下跌的水平,而阻力则表明价格通常因供应压力而难以继续上涨的水平。

在此实现中,代码会检查所有已确认的波段顶点,仔细地对它们进行排序和比较,以分离出最极端的数值。这确保了在图表上绘制的水平线并非短暂的波动,而是代表了历史上曾影响价格反转的有意义区域。通过将第二高点和第二低点确定为次要阻力和次要支撑,该算法提供了额外的信息。这些次要水平可以作为早期信号或次要目标,在价格达到更关键的主要水平之前,为交易者提供对潜在价格反应的细致入微的视角。

这种方法在瞬息万变的市场中非常有用。随着新价格数据的涌入,ZigZag 指标会捕捉到新的波段顶点,并自动更新支撑和阻力水平以反映最新的市场结构。这种实时调整对交易者至关重要,帮助他们与可能影响其入场或出场决策的、不断变化的价格障碍保持一致。这些水平会以不同颜色的水平线直观地映射到图表上,使其一目了然。这种额外的清晰度使交易者能够快速识别价格可能做出反应的关键区域,帮助他们更有信心地放置止损单或设定盈利目标。最终,这部分代码不仅仅是计算支撑和阻力,它将原始的市场数据转化为清晰、可操作的洞察,使其成为任何使用技术工具的交易者的必备工具。

void DrawSupportResistance()
  {
   double confirmedHighs[10], confirmedLows[10];
   int confHighCount = 0, confLowCount = 0;

   for(int i = 0; i < LookBackBars - 1; i++)
     {
      if(zzBuffer[i] != 0)
        {
         if(iHigh(_Symbol, InpTimeFrame, i) == zzBuffer[i] && confHighCount < 10)
           {
            confirmedHighs[confHighCount] = zzBuffer[i];
            confHighCount++;
           }
         else if(iLow(_Symbol, InpTimeFrame, i) == zzBuffer[i] && confLowCount < 10)
              {
               confirmedLows[confLowCount] = zzBuffer[i];
               confLowCount++;
              }
        }
     }

   int usedHighCount = (confHighCount >= 4) ? confHighCount - 1 : confHighCount;
   int usedLowCount  = (confLowCount  >= 4) ? confLowCount - 1  : confLowCount;

   double majorResistance = -1e9, majorSupport = 1e9;
   double minorResistance = -1e9, minorSupport = 1e9;
   double tempHigh = -1e9, tempLow = -1e9;
   for(int i = 0; i < usedHighCount; i++)
     {
      if(confirmedHighs[i] > majorResistance)
        {
         tempHigh = majorResistance;
         majorResistance = confirmedHighs[i];
        }
      else if(confirmedHighs[i] > tempHigh)
           {
            tempHigh = confirmedHighs[i];
           }
     }
   if(tempHigh > -1e9)
      minorResistance = tempHigh;
   for(int i = 0; i < usedLowCount; i++)
     {
      if(confirmedLows[i] < majorSupport)
        {
         tempLow = majorSupport;
         majorSupport = confirmedLows[i];
        }
      else if(confirmedLows[i] < tempLow)
           {
            tempLow = confirmedLows[i];
           }
     }
   if(tempLow < 1e9)
      minorSupport = tempLow;

   if(usedHighCount > 0)
     {
      if(!ObjectCreate(0, "Major_Resistance", OBJ_HLINE, 0, 0, majorResistance))
         Print("Failed to create Major Resistance");
      else
         ObjectSetInteger(0, "Major_Resistance", OBJPROP_COLOR, clrMagenta);

      if(minorResistance > -1e9 && minorResistance < majorResistance)
        {
         if(!ObjectCreate(0, "Minor_Resistance", OBJ_HLINE, 0, 0, minorResistance))
            Print("Failed to create Minor Resistance");
         else
            ObjectSetInteger(0, "Minor_Resistance", OBJPROP_COLOR, clrFuchsia);
        }
     }
   if(usedLowCount > 0)
     {
      if(!ObjectCreate(0, "Major_Support", OBJ_HLINE, 0, 0, majorSupport))
         Print("Failed to create Major Support");
      else
         ObjectSetInteger(0, "Major_Support", OBJPROP_COLOR, clrAqua);

      if(minorSupport < 1e9 && minorSupport > majorSupport)
        {
         if(!ObjectCreate(0, "Minor_Support", OBJ_HLINE, 0, 0, minorSupport))
            Print("Failed to create Minor Support");
         else
            ObjectSetInteger(0, "Minor_Support", OBJPROP_COLOR, clrBlue);
        }
     }
  }

数据收集遍历缓冲区以提取已确认的高点和低点。水平确定通过比较来分离出极值。主要阻力/支撑被设为最极端的数值,而次极端的数值则成为次要水平。图形表示为每个识别出的水平创建水平线 (OBJ_HLINE)。为每条线应用颜色以便于区分。


MQL5代码

//+------------------------------------------------------------------+
//|                                               ZigZag Analyzer.mq5|
//|                                   Copyright 2025, MetaQuotes Ltd.|
//|                           https://www.mql5.com/en/users/lynnchris|
//+------------------------------------------------------------------+
#property copyright "Copyright 2025, MetaQuotes Ltd."
#property link      "https://www.mql5.com/en/users/lynnchris"
#property version   "1.0"
#property strict

// Input parameters
input ENUM_TIMEFRAMES InpTimeFrame    = PERIOD_CURRENT; // Timeframe to analyze
input int             ZZ_Depth         = 12;  // ZigZag depth
input int             ZZ_Deviation     = 5;   // ZigZag deviation
input int             ZZ_Backstep      = 3;   // ZigZag backstep
input int             LookBackBars     = 200; // Bars to search for pivots
input int             ExtendFutureBars = 100; // Bars to extend trendlines into the future

// Global indicator handle for ZigZag
int zzHandle;

// Arrays for ZigZag data and pivot storage
double   zzBuffer[];
datetime pivotTimes[];
double   pivotPrices[];
bool     pivotIsPeak[];

// Variable to detect new bars
datetime lastBarTime = 0;

//+------------------------------------------------------------------+
//| Initialization function                                          |
//+------------------------------------------------------------------+
int OnInit()
  {
   zzHandle = iCustom(_Symbol, InpTimeFrame, "ZigZag", ZZ_Depth, ZZ_Deviation, ZZ_Backstep);
   if(zzHandle == INVALID_HANDLE)
     {
      Print("Error creating ZigZag handle");
      return(INIT_FAILED);
     }
   return(INIT_SUCCEEDED);
  }

//+------------------------------------------------------------------+
//| Deinitialization function                                        |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   ObjectDelete(0, "Downtrend_HighLine");
   ObjectDelete(0, "Uptrend_LowLine");
   ObjectDelete(0, "Major_Resistance");
   ObjectDelete(0, "Major_Support");
   ObjectDelete(0, "Minor_Resistance");
   ObjectDelete(0, "Minor_Support");
   IndicatorRelease(zzHandle);
  }

//+------------------------------------------------------------------+
//| Tick function                                                    |
//+------------------------------------------------------------------+
void OnTick()
  {
   datetime currentBarTime = iTime(_Symbol, InpTimeFrame, 0);
   if(currentBarTime == lastBarTime)
      return;
   lastBarTime = currentBarTime;

// Remove previous objects
   ObjectDelete(0, "Downtrend_HighLine");
   ObjectDelete(0, "Uptrend_LowLine");
   ObjectDelete(0, "Major_Resistance");
   ObjectDelete(0, "Major_Support");
   ObjectDelete(0, "Minor_Resistance");
   ObjectDelete(0, "Minor_Support");

   if(CopyBuffer(zzHandle, 0, 0, LookBackBars, zzBuffer) <= 0)
     {
      Print("Failed to copy ZigZag data");
      return;
     }
   ArraySetAsSeries(zzBuffer, true);

   DrawZigZagTrendlines();
   DrawSupportResistance();
  }

//+------------------------------------------------------------------+
//| Draw ZigZag-based Trendlines                                     |
//+------------------------------------------------------------------+
void DrawZigZagTrendlines()
  {
   double highPrices[10], lowPrices[10];
   datetime highTimes[10], lowTimes[10];
   int highCount = 0, lowCount = 0;

// Extract swing points from the ZigZag buffer
   for(int i = 0; i < LookBackBars - 1; i++)
     {
      if(zzBuffer[i] != 0)
        {
         if(iHigh(_Symbol, InpTimeFrame, i) == zzBuffer[i] && highCount < 10)
           {
            highPrices[highCount] = zzBuffer[i];
            highTimes[highCount] = iTime(_Symbol, InpTimeFrame, i);
            highCount++;
           }
         else
            if(iLow(_Symbol, InpTimeFrame, i) == zzBuffer[i] && lowCount < 10)
              {
               lowPrices[lowCount] = zzBuffer[i];
               lowTimes[lowCount] = iTime(_Symbol, InpTimeFrame, i);
               lowCount++;
              }
        }
     }

// Exclude the most recent swing if possible
   int usedHighCount = (highCount >= 4) ? highCount - 1 : highCount;
   int usedLowCount  = (lowCount  >= 4) ? lowCount - 1  : lowCount;

   double mHigh = 0, bHigh = 0, mLow = 0, bLow = 0;
   bool validHigh = false, validLow = false;

// Regression for highs
   if(usedHighCount >= 3)
     {
      double sumT = 0, sumP = 0, sumTP = 0, sumT2 = 0;
      for(int i = 0; i < usedHighCount; i++)
        {
         double t = (double)highTimes[i];
         double p = highPrices[i];
         sumT += t;
         sumP += p;
         sumTP += t * p;
         sumT2 += t * t;
        }
      int N = usedHighCount;
      double denominator = N * sumT2 - sumT * sumT;
      if(denominator != 0)
        {
         mHigh = (N * sumTP - sumT * sumP) / denominator;
         bHigh = (sumP - mHigh * sumT) / N;
        }
      else
         bHigh = sumP / N;
      validHigh = true;
     }

// Regression for lows
   if(usedLowCount >= 3)
     {
      double sumT = 0, sumP = 0, sumTP = 0, sumT2 = 0;
      for(int i = 0; i < usedLowCount; i++)
        {
         double t = (double)lowTimes[i];
         double p = lowPrices[i];
         sumT += t;
         sumP += p;
         sumTP += t * p;
         sumT2 += t * t;
        }
      int N = usedLowCount;
      double denominator = N * sumT2 - sumT * sumT;
      if(denominator != 0)
        {
         mLow = (N * sumTP - sumT * sumP) / denominator;
         bLow = (sumP - mLow * sumT) / N;
        }
      else
         bLow = sumP / N;
      validLow = true;
     }

// Define time limits for trendlines
   datetime pastTime   = iTime(_Symbol, InpTimeFrame, LookBackBars - 1);
   datetime futureTime = lastBarTime + ExtendFutureBars * PeriodSeconds();

// Draw trendlines if both regressions are valid
   if(validHigh && validLow)
     {
      // When slopes have the same sign, use average slope
      if(mHigh * mLow > 0)
        {
         double mParallel = (mHigh + mLow) / 2.0;
         double bHighParallel = highPrices[0] - mParallel * (double)highTimes[0];
         double bLowParallel  = lowPrices[0] - mParallel * (double)lowTimes[0];

         datetime highStartTime = pastTime;
         double highStartPrice  = mParallel * (double)highStartTime + bHighParallel;
         double highEndPrice    = mParallel * (double)futureTime + bHighParallel;
         if(!ObjectCreate(0, "Downtrend_HighLine", OBJ_TREND, 0, highStartTime, highStartPrice, futureTime, highEndPrice))
            Print("Failed to create High Trendline");
         else
           {
            ObjectSetInteger(0, "Downtrend_HighLine", OBJPROP_COLOR, clrRed);
            ObjectSetInteger(0, "Downtrend_HighLine", OBJPROP_RAY_LEFT, true);
            ObjectSetInteger(0, "Downtrend_HighLine", OBJPROP_RAY_RIGHT, true);
           }

         datetime lowStartTime = pastTime;
         double lowStartPrice  = mParallel * (double)lowStartTime + bLowParallel;
         double lowEndPrice    = mParallel * (double)futureTime + bLowParallel;
         if(!ObjectCreate(0, "Uptrend_LowLine", OBJ_TREND, 0, lowStartTime, lowStartPrice, futureTime, lowEndPrice))
            Print("Failed to create Low Trendline");
         else
           {
            ObjectSetInteger(0, "Uptrend_LowLine", OBJPROP_COLOR, clrGreen);
            ObjectSetInteger(0, "Uptrend_LowLine", OBJPROP_RAY_LEFT, true);
            ObjectSetInteger(0, "Uptrend_LowLine", OBJPROP_RAY_RIGHT, true);
           }
        }
      else
        {
         datetime highStartTime = pastTime;
         double highStartPrice  = mHigh * (double)highStartTime + bHigh;
         double highEndPrice    = mHigh * (double)futureTime + bHigh;
         if(!ObjectCreate(0, "Downtrend_HighLine", OBJ_TREND, 0, highStartTime, highStartPrice, futureTime, highEndPrice))
            Print("Failed to create High Trendline");
         else
           {
            ObjectSetInteger(0, "Downtrend_HighLine", OBJPROP_COLOR, clrRed);
            ObjectSetInteger(0, "Downtrend_HighLine", OBJPROP_RAY_LEFT, true);
            ObjectSetInteger(0, "Downtrend_HighLine", OBJPROP_RAY_RIGHT, true);
           }

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

//+------------------------------------------------------------------+
//| Draw Support and Resistance Levels                               |
//+------------------------------------------------------------------+
void DrawSupportResistance()
  {
   double confirmedHighs[10], confirmedLows[10];
   int confHighCount = 0, confLowCount = 0;

   for(int i = 0; i < LookBackBars - 1; i++)
     {
      if(zzBuffer[i] != 0)
        {
         if(iHigh(_Symbol, InpTimeFrame, i) == zzBuffer[i] && confHighCount < 10)
           {
            confirmedHighs[confHighCount] = zzBuffer[i];
            confHighCount++;
           }
         else
            if(iLow(_Symbol, InpTimeFrame, i) == zzBuffer[i] && confLowCount < 10)
              {
               confirmedLows[confLowCount] = zzBuffer[i];
               confLowCount++;
              }
        }
     }

   int usedHighCount = (confHighCount >= 4) ? confHighCount - 1 : confHighCount;
   int usedLowCount  = (confLowCount  >= 4) ? confLowCount - 1  : confLowCount;

   double majorResistance = -1e9, majorSupport = 1e9;
   double minorResistance = -1e9, minorSupport = 1e9;
   double tempHigh = -1e9, tempLow = -1e9;
   for(int i = 0; i < usedHighCount; i++)
     {
      if(confirmedHighs[i] > majorResistance)
        {
         tempHigh = majorResistance;
         majorResistance = confirmedHighs[i];
        }
      else
         if(confirmedHighs[i] > tempHigh)
           {
            tempHigh = confirmedHighs[i];
           }
     }
   if(tempHigh > -1e9)
      minorResistance = tempHigh;
   for(int i = 0; i < usedLowCount; i++)
     {
      if(confirmedLows[i] < majorSupport)
        {
         tempLow = majorSupport;
         majorSupport = confirmedLows[i];
        }
      else
         if(confirmedLows[i] < tempLow)
           {
            tempLow = confirmedLows[i];
           }
     }
   if(tempLow < 1e9)
      minorSupport = tempLow;

   if(usedHighCount > 0)
     {
      if(!ObjectCreate(0, "Major_Resistance", OBJ_HLINE, 0, 0, majorResistance))
         Print("Failed to create Major Resistance");
      else
         ObjectSetInteger(0, "Major_Resistance", OBJPROP_COLOR, clrMagenta);

      if(minorResistance > -1e9 && minorResistance < majorResistance)
        {
         if(!ObjectCreate(0, "Minor_Resistance", OBJ_HLINE, 0, 0, minorResistance))
            Print("Failed to create Minor Resistance");
         else
            ObjectSetInteger(0, "Minor_Resistance", OBJPROP_COLOR, clrFuchsia);
        }
     }
   if(usedLowCount > 0)
     {
      if(!ObjectCreate(0, "Major_Support", OBJ_HLINE, 0, 0, majorSupport))
         Print("Failed to create Major Support");
      else
         ObjectSetInteger(0, "Major_Support", OBJPROP_COLOR, clrAqua);

      if(minorSupport < 1e9 && minorSupport > majorSupport)
        {
         if(!ObjectCreate(0, "Minor_Support", OBJ_HLINE, 0, 0, minorSupport))
            Print("Failed to create Minor Support");
         else
            ObjectSetInteger(0, "Minor_Support", OBJPROP_COLOR, clrBlue);
        }
     }
  }
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+



结果

在分析结果之前,让我们先将 ZigZag 指标添加到图表上。为了更清晰地理解这个过程,请参考下面的GIF图。这个视觉辅助工具将有助于说明所涉及的每个步骤。通过该图,您将看到指标是如何被集成和配置的。一旦这一点明确了,我们就可以继续进行结果分析。

插入指标

图 5. 初始化 ZigZag 指标

这一系列图表展示了结果,每个图表都附有详细的解释。首先,下面的图表显示了在阶梯指数上运行ZigZag分析器工具后获得的结果。正如观察到的,当EA应用于图表时,趋势线被准确地绘制出来。低线和高线都向下倾斜,表明这是一个下降趋势。此外,图表清晰地显示了主要和次要的支撑与阻力水平。

趋势线

图 6. 阶梯指数上的结果

这张截图为分析提供了额外的信息。在图中,您可以看到用于构建趋势线的关键波段顶点,如更低的低点和更低的高点。主要和次要的支撑与阻力水平被清晰地标记出来。值得注意的是,趋势线与主要水平线之间的交点凸显了强烈的反转点。最高的更低高点触及主要阻力,最低的更低低点触及主要支撑,在这些位置发生了反转。

趋势线

图 7. 阶梯指数

这张USDCHF(美元/瑞士法郎)图表显示了已绘制的趋势线。它们存在于市场中,但并非过于主导。市场尊重这些支撑和阻力水平,这验证了系统的有效性。我已高亮标出趋势线触及市场波动顶点的位置。

图 8. USDCHF

最后,我们可以查看其在波动率25指数上的表现。趋势线被清晰地绘制出来,连接了各个波段顶点。这些线条精确地突显了市场的运动。这种清晰度加强了该系统在各种市场条件下的可靠性。这些结果支持了我们方法的有效性。

图 9. 波动率 25 指数


结论

ZigZag分析器工具是使用MQL5自动化价格行为分析的一种强大方式。我们的测试表明,在趋势市场中,趋势线能够被有效地绘制出来。我相信这种方法可以引导开发出能够检测旗形和三角形等形态的额外工具。当运行时,ZigZag分析器可作为外汇交易初学者观察趋势和发现潜在支撑或阻力区域的起点。它对于经验丰富的交易者也很有价值,因为趋势线是价格行为分析的核心。这个工具非常适合学习,并且可以进行定制以适应不同的交易策略,同时结合其他方法进行确认。

日期 工具名称  说明 版本  更新  提示
01/10/24 图表投影仪 用于叠加前一天价格走势(带“幽灵”效果)的脚本。 1.0 首次发布 工具1
18/11/24 分析评论 它以表格形式提供前一日的市场信息,并预测市场的未来走向。 1.0 首次发布 工具2
27/11/24 分析大师 市场指标每两小时定期更新  1.01 第二版 工具3
02/12/24 分析预测器  每两小时定期更新市场指标,并集成Telegram推送功能。 1.1 第三版 工具4
09/12/24 波动性导航工具 该EA使用布林带、RSI和ATR指标分析市场状况。 1.0 首次发布 工具5
19/12/24 均值回归信号收割器  使用均值回归策略分析市场并提供信号  1.0  首次发布  工具6 
9/01/25  信号脉冲  多时间框架分析器 1.0  首次发布  工具7 
17/01/25  指标看板  带按钮的分析面板  1.0  首次发布 工具8 
21/01/25 外部资金流 通过外部库进行分析 1.0  首次发布 工具9 
27/01/25 VWAP 成交量加权平均价格   1.3  首次发布  工具10 
02/02/25  Heikin Ashi  势平滑与反转信号识别  1.0  首次发布  工具11
04/02/25  FibVWAP  通过 Python 分析生成信号  1.0  首次发布  工具12
14/02/25  RSI 背离  价格走势与 RSI 背离的对比  1.0  首次发布  工具13 
17/02/25  抛物线转向与反转 (PSAR)  自动化PSAR策略 1.0 首次发布  工具14
20/02/25  四分位绘制脚本  在图表上绘制四分位水平线  1.0  首次发布  工具15 
27/02/25  侵入检测器 当价格触及四分位水平时进行检测和警报 1.0   首次发布 工具16 
27/02/25  TrendLoom工具  多时间周期分析面板 1.0 首次发布 工具17
11/03/25  四分位看板  带有可激活或禁用四分位的面板  1.0  首次发布 工具18
26/03/25  ZigZag分析器  使用ZigZag指标绘制趋势线  1.0  首次发布  工具19 

本文由MetaQuotes Ltd译自英文
原文地址: https://www.mql5.com/en/articles/17625

附加的文件 |
ZigZag_Analyzer.mq5 (13.44 KB)
最近评论 | 前往讨论 (5)
diego herrera
diego herrera | 9 4月 2025 在 21:28
>克里斯蒂娜,你好。

我觉得这个指标非常有趣。它会对我的技术分析有很大帮助,因为我花了最多的时间来寻找正确的趋势、支撑位和阻力位。我下载了代码,编译也很正确,但当我把它添加到图表中时,却没有反映出任何信息。我是不是做错了什么?我又附上了一段视频。谢谢。
Christian Benjamin
Christian Benjamin | 12 4月 2025 在 08:18
diego herrera #:
>克里斯蒂娜,你好。

我觉得这个指标非常有趣。它会对我的技术分析有很大帮助,因为我花了最多的时间来寻找正确的趋势、支撑位和阻力位。我下载了代码,编译也很正确,但当我把它添加到图表中时,却没有反映出任何信息。我是不是做错了什么?我又附上了一段视频。谢谢。

迪戈- 埃雷拉

Christian Benjamin
Christian Benjamin | 12 4月 2025 在 08:26
diego herrera #:
>克里斯蒂娜,你好。

我觉得这个指标非常有趣。它会对我的技术分析有很大帮助,因为我花了最多的时间来寻找正确的趋势、支撑位和阻力位。我下载了代码,编译也很正确,但当我把它添加到图表中时,却没有反映出任何信息。我是不是做错了什么?我又附上了一段视频。谢谢。
我认为问题可能出在 ZigZag 指标的位置上。请尝试将它移到主指标文件夹中。如果问题仍然存在,请分享运行程序后专家选项卡中的 MQL5 日志。对延迟回复深表歉意。
linfo2
linfo2 | 13 4月 2025 在 21:48
我认为问题在于下载文件中不包含之字形指标,我不确定克里斯坦说的是哪个指标,但如果您需要自定义指标,可以使用示例中的之字形指标
Conor Mcnamara
Conor Mcnamara | 6 5月 2025 在 22:42
diego herrera #:
>克里斯蒂娜,你好。

我觉得这个指标非常有趣。它会对我的技术分析有很大帮助,因为我花了最多的时间来寻找正确的趋势、支撑位和阻力位。我下载了代码,编译也很正确,但当我把它添加到图表中时,却没有反映出任何信息。我是不是做错了什么?我又附上了一段视频。谢谢。

试试这个

int OnInit()
  {
   zzHandle = iCustom(_Symbol, InpTimeFrame, "Examples\\ZigZag", ZZ_Depth, ZZ_Deviation, ZZ_Backstep);
   if(zzHandle == INVALID_HANDLE)
     {
      Print("Error creating ZigZag handle");
      return(INIT_FAILED);
     }
     
   ChartIndicatorAdd(0, 0, zzHandle);
   return(INIT_SUCCEEDED);
  }
通过配对交易中的均值回归进行统计套利:用数学战胜市场 通过配对交易中的均值回归进行统计套利:用数学战胜市场
本文描述了投资组合层面的统计套利基础知识。其目标是帮助没有深厚数学知识的读者理解统计套利的原则,并提出一个概念性的起点框架。文章包含一个可运行的智能交易系统(EA)、一些关于其一年回测的笔记,以及用于复现实验的相应回测配置设置(.ini 文件)。
MQL5 简介(第 11 部分):MQL5 中使用内置指标的初学者指南(二) MQL5 简介(第 11 部分):MQL5 中使用内置指标的初学者指南(二)
了解如何使用 RSI、MA 和随机震荡指标等多种指标在 MQL5 中开发 EA 交易来检测隐藏的看涨和看跌背离。学习实施有效的风险管理并通过详细的示例和完整注释的源代码实现交易自动化,以达到教育目的!
从基础到中级:模板和类型名称(三) 从基础到中级:模板和类型名称(三)
在本文中,我们将讨论该主题的第一部分,这对初学者来说并不容易理解。为了避免更加困惑并正确解释这个话题,我们将把解释分为几个阶段。我们将把这篇文章用于第一阶段。然而,尽管在本文末尾,我们似乎已经陷入僵局,但事实上,我们将朝着另一种情况迈出一步,这将在下一篇文章中得到更好的理解。
智能系统健壮性测试 智能系统健壮性测试
在策略开发中,有许多错综复杂的细节需要考虑,对于初学交易者其中许多都未予重视。如是结果,众多交易者,包括我自己,都不得不历经苦难来学习这些教训。本文基于我观察到的大多数初学交易者在 MQL5 上开发策略时常见的陷阱。它将提供一系列提示、技巧、和示例,帮助辨别不合格的 EA,并以一种易于实现的方式来测试我们自己 EA 的稳健性。目标是教导读者,帮助他们未来购买 EA 时避免遭遇骗局,以及预防他们自己开发策略时的错误。