价格行为分析工具包开发(第19部分):ZigZag分析器
引言
趋势线构成了技术分析和现代价格行为的基石。它们是价格形态的“骨架”,外汇、加密货币、大宗商品、股票和衍生品的交易者都依赖于此。当绘制正确时,趋势线能有效地揭示市场趋势,并帮助交易者发现潜在的机会。
无论您是日内交易者还是短线交易者,趋势线在许多交易系统和策略中都扮演着关键角色。在我们的价格行为分析工具包开发系列中,我们介绍一款名为ZigZag分析器的工具。该工具专注于使用ZigZag指标绘制趋势线,通过识别构成趋势线基础的波段顶点来实现。MQL5是一种强大的自动化交易系统语言,它允许我们构建像ZigZag分析器这样的高级工具,这些工具能够适应市场条件并支持实时决策。
我们将首先探讨趋势线,然后研究Zigzag指标,概述系统算法,展示MQL5代码,审查结果,最后以我们的最终见解作结。让我们看看下面的目录:
趋势线
趋势线是一条连接重要价格点(通常是更高的低点或更低的高点)的倾斜线,并作为支撑或阻力水平向未来延伸。它指明了价格变动的可能方向和动量。要绘制一条趋势线,首先需要确定市场的整体方向。上升趋势的特征是一系列更高的高点和更高的低点,而下降趋势则以更低的高点和更低的低点为标志。为了便于说明,考虑一个显示下降形态的图表,您需要连接下降的波段高点来建立趋势线。在价格盘整期间,多条趋势线的形成可以揭示出能够强化交易信号的形态。下面是一个图示。

图 1. 趋势线
该图表说明了两个关键概念:支撑与阻力。支撑位是价格下行运动常常停滞的区域,因为买方变得足够活跃以吸收卖压。当应用趋势线时,支撑通常通过连接重要低点的向上倾斜线来显示。相反,阻力位是价格上行运动因卖压加剧而频繁暂停的区域。对于趋势线,阻力由连接重要高点的向下倾斜线表示。交易者经常跨越多个时间周期绘制趋势线,以识别市场在短期、中期或长期内是向上还是向下移动。趋势线是技术分析中的一个宝贵工具,因为它们帮助市场参与者衡量资产价格的整体方向。通过在选定的时间框架内连接重要的高点或低点,这些线条直观地展示了市场是在向上、向下还是横盘。这一信息对于依赖价格趋势来指导决策的交易者和短期投资者尤其有用。
ZigZag指标
尽管Zig Zag指标的确切起源没有明确记载,但一些资料将其发现归功于比尔·沃尔夫,一位标普500(S&P 500)交易员,他以开发沃尔夫波浪而闻名,这是一种与艾略特波浪有些相似但图表技术独特的方法。沃尔夫波浪由五个波浪组成,描绘了供需向均衡价格移动的过程,请参阅。Zig Zag指标突显了超过指定阈值(通常以百分比表示)的重大价格反转。当价格波动超过此阈值时,该指标会绘制一个新点,并从上一个点画一条直线,从而有效地过滤掉较小的波动。通过这样做,它使得潜在趋势在不同时间框架上更容易被发现。
交易者可以调整百分比阈值(例如4%或5%)来捕捉或多或少的波动,具体取决于资产的波动性或他们的个人交易风格。这种灵活性有助于完善Zig Zag指标识别潜在转折点的方式。在基于波浪的分析中,如艾略特波浪理论,Zig Zag可以帮助阐明每个波浪的结构。最终,通常需要通过试验不同的设置,以在过滤噪音和检测有意义的价格变动之间找到最佳平衡。

图 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)

图 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); } } } }
趋势线绘制(如果两种趋势都有效)
斜率同号:使用平均斜率来绘制平行线。 斜率异号:绘制各自独立的趋势线。使用 ObjectCreate 和 ObjectSetInteger 来放置和设置线条样式。
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
注意: MetaQuotes Ltd.将保留所有关于这些材料的权利。全部或部分复制或者转载这些材料将被禁止。
本文由网站的一位用户撰写,反映了他们的个人观点。MetaQuotes Ltd 不对所提供信息的准确性负责,也不对因使用所述解决方案、策略或建议而产生的任何后果负责。
通过配对交易中的均值回归进行统计套利:用数学战胜市场
MQL5 简介(第 11 部分):MQL5 中使用内置指标的初学者指南(二)
从基础到中级:模板和类型名称(三)
智能系统健壮性测试
我觉得这个指标非常有趣。它会对我的技术分析有很大帮助,因为我花了最多的时间来寻找正确的趋势、支撑位和阻力位。我下载了代码,编译也很正确,但当我把它添加到图表中时,却没有反映出任何信息。我是不是做错了什么?我又附上了一段视频。谢谢。
>克里斯蒂娜,你好。
我觉得这个指标非常有趣。它会对我的技术分析有很大帮助,因为我花了最多的时间来寻找正确的趋势、支撑位和阻力位。我下载了代码,编译也很正确,但当我把它添加到图表中时,却没有反映出任何信息。我是不是做错了什么?我又附上了一段视频。谢谢。
迪戈- 埃雷拉
>克里斯蒂娜,你好。
我觉得这个指标非常有趣。它会对我的技术分析有很大帮助,因为我花了最多的时间来寻找正确的趋势、支撑位和阻力位。我下载了代码,编译也很正确,但当我把它添加到图表中时,却没有反映出任何信息。我是不是做错了什么?我又附上了一段视频。谢谢。
>克里斯蒂娜,你好。
我觉得这个指标非常有趣。它会对我的技术分析有很大帮助,因为我花了最多的时间来寻找正确的趋势、支撑位和阻力位。我下载了代码,编译也很正确,但当我把它添加到图表中时,却没有反映出任何信息。我是不是做错了什么?我又附上了一段视频。谢谢。
试试这个