preview
Price Action Analysis Toolkit Development (Part 19): ZigZag Analyzer

Price Action Analysis Toolkit Development (Part 19): ZigZag Analyzer

MetaTrader 5Examples | 9 April 2025, 07:57
3 409 4
Christian Benjamin
Christian Benjamin

Introduction

Trend lines form the backbone of technical analysis and modern price action. They act as the skeleton of price patterns that traders in forex, cryptocurrencies, commodities, stocks, and derivatives rely on. When drawn correctly, trend lines efficiently reveal market trends and help traders spot potential opportunities.

Whether you are a day trader or a short-term trader, trend lines play a key role in many trading systems and strategies. In our Price Action Analysis Toolkit development series, we introduce a tool called the ZigZag Analyzer. This tool focuses on drawing trend lines using the ZigZag indicator to identify swing points that form the basis for constructing trend lines. MQL5 is a powerful language for automating trading systems, and it allows us to build advanced tools like the ZigZag Analyzer that adapt to market conditions and support real-time decision-making.

We will begin by exploring trendlines, then examine the Zigzag indicator, outline the system algorithm, present the MQL5 code, review the outcomes, and conclude with our final insights. Let's see the table of contents below:



Trendlines

A trend line is a sloping line that connects significant price points, typically higher lows or lower highs, and projects into the future as a support or resistance level. It indicates the likely direction and momentum of price movements. To draw a trendline, begin by identifying the overall market direction. An uptrend is characterized by a sequence of higher highs and higher lows, whereas a downtrend is marked by lower highs and lower lows. For illustrative purposes, consider a chart showing a downward pattern, where you would connect the descending swing highs to establish the trendline. During price consolidation, the formation of multiple trend lines can reveal patterns that strengthen trading signals. Below is a pictorial representation.

Uptrend

Fig 1. Trendlines

The chart illustrates two key concepts: support and resistance. A support level is an area where downward price movement often stalls because buyers become active enough to absorb selling pressure. When trendlines are applied, support is typically shown by upward-sloping lines that connect important low points. In contrast, a resistance level is an area where upward price movement frequently pauses due to intensified selling pressure. With trendlines, resistance is represented by downward-sloping lines that link significant high points. Traders frequently draw trend lines across multiple time horizons to identify whether a market is moving upward or downward over short, medium, or long durations.
Trend lines are a valuable tool in technical analysis because they help market participants gauge the overall direction of an asset’s price. By connecting significant highs or lows over a chosen timeframe, these lines visually represent whether the market is moving upward, downward, or sideways. This insight is especially useful for traders and short-term investors who rely on price trends to guide their decisions.



ZigZag Indicator

While the exact origin of the Zig Zag indicator is not definitively documented, some sources attribute its discovery to Bill Wolfe, an S&P 500 trader known for developing Wolfe Waves, a method somewhat similar to Elliott Waves but with distinct charting techniques. Wolfe Waves consist of five waves that illustrate supply and demand moving toward an equilibrium price, refer to. The Zig Zag indicator highlights significant price reversals that exceed a specified threshold, typically expressed as a percentage. When the price swings beyond this threshold, the indicator plots a new point and draws a straight line from the previous point, effectively filtering out smaller fluctuations. By doing so, it makes underlying trends easier to spot across various time frames.

Traders can adjust the percentage threshold (e.g., 4% or 5%) to capture more or fewer price swings, depending on the volatility of the asset or their personal trading style. This flexibility helps refine how the Zig Zag indicator identifies potential turning points. In wave-based analysis, such as Elliot wave theory, the Zig Zag can help clarify the structure of each wave. Ultimately, experimenting with different settings is often necessary to find the optimal balance between filtering out noise and detecting meaningful price movements.

Zigzag Indicator

Fig 2. Zig Zag Indicator

Figure 2 above shows a chart with the Zig Zag indicator. It demonstrates how trendlines can be constructed from its swings. The Zig Zag indicator simplifies trend analysis by filtering out minor price fluctuations and highlighting key turning points. It marks significant highs and lows, which serve as anchor points for drawing trendlines. In an uptrend, connecting these swing lows creates a support line, while in a downtrend, linking swing highs forms a resistance line. This method emphasizes the overall direction of the market by eliminating noise. Adjusting the indicator's sensitivity can further refine the accuracy of these trendlines for improved trading insights.



System Algorithm

1. Global Declarations and Input Parameters

In this initial section, we set up key inputs and global variables that let you customize the analyzer without digging into the code. We define parameters like the chart timeframe and specific properties for the ZigZag indicator such as depth, deviation, and backstep. These settings determine how the analyzer picks out swing points. We also declare arrays to store the ZigZag data and to record the time and price of key pivots.

We value this setup for its simplicity and modular design. For instance, by changing the timeframe parameter, you can easily switch between short-term and long-term trends. The ZigZag properties let you adjust the sensitivity of the indicator, so you can adapt the analysis to different market conditions or assets. We use global variables and arrays as a central hub for all dynamic data. This approach improves performance and makes the code easier to maintain. When new data arrives, it is efficiently stored and ready for analysis, making customization and debugging much simpler.

Overall, by centralizing these critical inputs and declarations, we create a flexible and clear tool that empowers you to tailor the analyzer to your needs while keeping the underlying processes robust and reliable.

// 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: The timeframe for analysis.
  • ZZ_Depth, ZZ_Deviation, ZZ_Backstep: Parameters that influence the sensitivity and behavior of the ZigZag indicator.

Arrays like zzBuffer store the ZigZag indicator’s output. Additional arrays (pivotTimes, pivotPrices, pivotIsPeak) are reserved for holding details about each detected pivot. A variable (lastBarTime) helps to determine whether a new bar has started, ensuring that the analysis is only updated when necessary.

2. Initialization Function (OnInit)

In the Initialization Function, we execute the code once when the indicator is loaded. Our main goal here is to create and configure the ZigZag indicator using the user inputs. We call the iCustom function to try to instantiate the ZigZag indicator and store its handle. If the indicator fails to initialize and returns an invalid handle, we log an error and halt further processing.

We use this function to ensure that everything is set up correctly before any analysis takes place. It acts as a gatekeeper, preventing the rest of the code from running if there is an issue with the indicator setup. This early check saves time and avoids unnecessary errors later in the execution. By centralizing the initialization here, we make it easier to manage and debug the system if something goes wrong. The function also makes sure that all user-defined settings are applied right from the start, setting a solid foundation for the analysis that follows.

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);
  }

  • Indicator Creation: Uses iCustom to load the ZigZag indicator with specific parameters.
  • Error Handling: Checks if the returned handle is valid. Logs an error message and aborts if initialization fails.

3. Deinitialization Function (OnDeinit)

When the indicator is removed or the platform is closed, it’s essential to clean up any objects and free allocated resources. The OnDeinit function takes care of this by deleting any graphical objects (like trendlines or horizontal lines) that were drawn on the chart and releasing the ZigZag indicator handle. This ensures that no unwanted elements remain on the chart and that the system’s resources are properly managed.

Resource Cleanup: Deletes all created chart objects to avoid clutter.

Handle Release: Frees the ZigZag indicator handle using IndicatorRelease.

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. Main Execution Function (OnTick)

This is the heart of the algorithm that runs with every new tick. The function first checks if a new bar has formed by comparing the current bar’s timestamp with the last stored time. If no new bar is detected, the function exits early to save processing power. Once a new bar is confirmed, it removes old chart objects, retrieves the most recent ZigZag data using CopyBuffer, and then calls two helper functions: one for drawing trendlines and another for drawing support and resistance levels.

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();
  }

  • New Bar Check: Uses the iTime function to detect a change in the bar.
  • Buffer Update: Refreshes the zzBuffer with the latest ZigZag data.
  • Chart Updates: Clears previous graphical objects to prepare for new ones. Calls functions to draw trendlines and support/resistance lines.

5. Drawing ZigZag-Based Trendlines (DrawZigZagTrendlines)

This function is responsible for identifying the significant swing points from the ZigZag data and using them to calculate trendlines. It goes through the buffer to collect highs and lows based on whether the current ZigZag value matches the high or low of the bar. Once the swing points are gathered, the code uses linear regression to determine the trendline for each set of points. The regression calculation employs the following formulas:

1. Slope (m)

Fig 3. Regression Formula

2. Intercept (b)

Intercept (b)

Fig 4. Intercept

  • (t) represents the time values (or the independent variable).
  • (p) represents the price values (or the dependent variable).
  • (N) is the number of data points used in the regression.

Swing Point Extraction: Loops through the zzBuffer to gather up to 10 highs and lows.                                                        
Exclusion of Latest Swing: Discards the most recent swing to reduce noise.                                                                      
Regression Analysis: Calculates slope (m) and intercept (b) using the formulas above.

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);
           }
        }
     }
  }

Trendline Drawing (If both trends are valid)

Same Sign Slopes: Use an averaged slope for parallel lines.                                                                                                                  Different Signs: Draw individual trendlines. Uses ObjectCreate and ObjectSetInteger to place and style the lines.

6. Drawing Support and Resistance Levels (DrawSupportResistance)

The support and resistance part of the code is designed to dynamically identify key horizontal price levels based on the confirmed swing points provided by the ZigZag indicator. Beyond simply designating the highest confirmed high as the major resistance and the lowest confirmed low as the major support, the algorithm delves deeper by filtering out significant highs and lows to determine both major and minor levels. This process is crucial because support and resistance are fundamental concepts in technical analysis; support represents a level where the price tends to stop falling due to increased demand, while resistance indicates a level where the price typically struggles to move higher due to supply pressure.

In this implementation, the code examines all confirmed swing points, meticulously sorting and comparing them to isolate the most extreme values. This ensures that the levels drawn on the chart are not merely transient fluctuations but represent meaningful zones that have historically influenced price reversals. By identifying the second-highest and second-lowest values as minor resistance and support, the algorithm provides additional layers of insight. These minor levels can serve as early signals or secondary targets, offering traders a nuanced view of potential price reactions before reaching the more critical major levels.

This approach is incredibly useful in a constantly changing market. As new price data rolls in, the ZigZag indicator picks up fresh swing points, automatically updating the support and resistance levels to reflect the latest market structure. This real-time adjustment is crucial for traders, helping them stay aligned with shifting price barriers that could influence their entry or exit decisions. These levels are visually mapped onto the chart as horizontal lines in distinct colors, making them easy to spot at a glance. This added clarity allows traders to quickly identify key areas where price might react, helping them place stop-loss orders or set profit targets with more confidence. Ultimately, this section of the code doesn’t just calculate support and resistance, it transforms raw market data into clear, actionable insights, making it an essential tool for any trader using technical

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);
        }
     }
  }

Data collection iterates through the buffer to extract confirmed highs and lows. Level determination uses comparisons to isolate the extreme values. Major resistance/support are set as the most extreme values, while the next most extreme values become minor levels. Graphical representation creates horizontal lines (OBJ_HLINE) for each identified level. Colors are applied to each line for easy differentiation.


MQL5 Code

//+------------------------------------------------------------------+
//|                                               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);
        }
     }
  }
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+



Outcomes

Before analyzing the outcomes, let's first add the ZigZag indicator to the chart. To gain a clearer understanding of the process, please refer to the GIF diagram below. This visual aid will help illustrate each step involved. By following the diagram, you'll see how the indicator is integrated and configured. Once that's clear, we can move on to the analysis of the results.

Inserting Indicator

Fig 5. Initializing ZigZag Indicator

This series of diagrams illustrates the outcomes, with each diagram accompanied by detailed explanations. First, the diagram below shows the result obtained after running the Zig Zag Analyzer tool on the Step Index. As observed, the trendlines are accurately drawn when the EA is applied to the chart. Both the lowline and the topline slope downwards, indicating a downtrend. Additionally, the diagrams clearly display both major and minor support and resistance levels.

Trendlines

Fig 6. Outcome on Step Index

This screenshot provides additional insights into the analysis. In the image, you can see key swing points such as lower highs and lower lows used for constructing trendlines. Major and minor support and resistance levels are clearly marked. Notably, the intersections between the trendlines and major levels highlight strong reversal points. The highest lower high meets major resistance and the lowest lower low meets major support, where a reversal occurs.

Trendlines

Fig 7. Step Index

This USDCHF chart shows drawn trendlines. They are present in the market, but not overly dominant. The market respects the support and resistance levels, which validates the system's effectiveness. I have highlighted the points where the trendlines touch the market swings.

Fig 8. USDCHF

Lastly, we can view its performance on the Volatility 25 index. Trendlines are drawn clearly connecting the swing points. The lines highlight the market's movement with precision. This clarity reinforces the system's reliability in various market conditions. The results support the effectiveness of our approach.

Fig 9. Volatility 25 Index


Conclusion

The Zig Zag Analyzer tool is a powerful way to automate price action analysis using MQL5. Our tests indicate that trendlines are drawn effectively in trending markets. I believe this approach can lead to the development of additional tools that detect patterns such as flag and triangle formations. When run, the Zig Zag Analyzer serves as a starting point for beginners in forex trading to observe trends and spot potential support or resistance zones. It is also valuable for experienced traders because trendlines are central to price action analysis. This tool is excellent for learning and can be customized to suit different trading strategies while incorporating other methods for confirmation.

Date Tool Name  Description Version  Updates  Notes
01/10/24 Chart Projector Script to overlay the previous day's price action with a ghost effect. 1.0 Initial Release Tool number 1
18/11/24 Analytical Comment It provides previous day's information in a tabular format, as well as anticipates the future direction of the market. 1.0 Initial Release Tool number 2
27/11/24 Analytics Master Regular Update of market metrics after every two hours  1.01 Second Release Tool number 3
02/12/24 Analytics Forecaster  Regular Update of market metrics after every two hours with telegram integration 1.1 Third Edition Tool number 4
09/12/24 Volatility Navigator The EA analyzes market conditions using the Bollinger Bands, RSI and ATR indicators 1.0 Initial Release Tool Number 5
19/12/24 Mean Reversion Signal Reaper  Analyzes market using mean reversion strategy and provides signal  1.0  Initial Release  Tool number 6 
9/01/25  Signal Pulse  Multiple timeframe analyzer 1.0  Initial Release  Tool number 7 
17/01/25  Metrics Board  Panel with button for analysis  1.0  Initial Release Tool number 8 
21/01/25 External Flow Analytics through external libraries 1.0  Initial Release Tool number 9 
27/01/25 VWAP Volume Weighted Average Price   1.3  Initial Release  Tool number 10 
02/02/25  Heikin Ashi  Trend Smoothening and reversal signal identification  1.0  Initial Release  Tool number 11
04/02/25  FibVWAP  Signal generation through python analysis  1.0  Initial Release  Tool number  12
14/02/25  RSI DIVERGENCE  Price action versus RSI divergences  1.0  Initial Release  Tool number 13 
17/02/25  Parabolic Stop and Reverse (PSAR)  Automating PSAR strategy 1.0 Initial Release  Tool number 14
20/02/25  Quarters Drawer Script  Drawing quarters levels on chart  1.0  Initial Release  Tool number 15 
27/02/25  Intrusion Detector Detect and alert when price reaches quarters levels 1.0   Initial Release Tool number 16 
27/02/25  TrendLoom Tool  Multi timeframe analytics panel 1.0 Initial Release Tool number 17
11/03/25  Quarters Board  Panel with buttons to activate or disable quarters levels  1.0  Initial Release Tool number 18
26/03/25  ZigZag Analyzer  Drawing trendlines using ZigZag Indicator  1.0  Initial Release  Tool number 19 

Attached files |
ZigZag_Analyzer.mq5 (13.44 KB)
Last comments | Go to discussion (4)
diego herrera
diego herrera | 9 Apr 2025 at 21:28
>Hi Cristina, greetings.

I find this indicator very interesting. It would help me a lot in my technical analysis, since I spend the most time on it to find the right trend, support, and resistance. I downloaded the code, and it compiled correctly, but when I added it to the chart, no information is reflected. Am I doing something wrong? I've attached a video again. Regards.
Christian Benjamin
Christian Benjamin | 12 Apr 2025 at 08:18
diego herrera #:
>Hi Cristina, greetings.

I find this indicator very interesting. It would help me a lot in my technical analysis, since I spend the most time on it to find the right trend, support, and resistance. I downloaded the code, and it compiled correctly, but when I added it to the chart, no information is reflected. Am I doing something wrong? I've attached a video again. Regards.

Hie diego herrera 

Christian Benjamin
Christian Benjamin | 12 Apr 2025 at 08:26
diego herrera #:
>Hi Cristina, greetings.

I find this indicator very interesting. It would help me a lot in my technical analysis, since I spend the most time on it to find the right trend, support, and resistance. I downloaded the code, and it compiled correctly, but when I added it to the chart, no information is reflected. Am I doing something wrong? I've attached a video again. Regards.
I believe the problem may stem from the location of the ZigZag indicator. Please try moving it into the main indicators folder. If the issue persists, kindly share the MQL5 logs from the Experts tab after running the program. Apologies for the delayed response.
linfo2
linfo2 | 13 Apr 2025 at 21:48
I think the problem is the zigazag indicator is not included in the down load , I am not sure which one Christain refers to but you can use the zigzag indicator from examples if you need it as the custom indicator 
Quantitative approach to risk management: Applying VaR model to optimize multi-currency portfolio using Python and MetaTrader 5 Quantitative approach to risk management: Applying VaR model to optimize multi-currency portfolio using Python and MetaTrader 5
This article explores the potential of the Value at Risk (VaR) model for multi-currency portfolio optimization. Using the power of Python and the functionality of MetaTrader 5, we demonstrate how to implement VaR analysis for efficient capital allocation and position management. From theoretical foundations to practical implementation, the article covers all aspects of applying one of the most robust risk calculation systems – VaR – in algorithmic trading.
Creating a Trading Administrator Panel in MQL5 (Part IX): Code Organization (IV): Trade Management Panel class Creating a Trading Administrator Panel in MQL5 (Part IX): Code Organization (IV): Trade Management Panel class
This discussion covers the updated TradeManagementPanel in our New_Admin_Panel EA. The update enhances the panel by using built-in classes to offer a user-friendly trade management interface. It includes trading buttons for opening positions and controls for managing existing trades and pending orders. A key feature is the integrated risk management that allows setting stop loss and take profit values directly in the interface. This update improves code organization for large programs and simplifies access to order management tools, which are often complex in the terminal.
Neural Networks in Trading: Point Cloud Analysis (PointNet) Neural Networks in Trading: Point Cloud Analysis (PointNet)
Direct point cloud analysis avoids unnecessary data growth and improves the performance of models in classification and segmentation tasks. Such approaches demonstrate high performance and robustness to perturbations in the original data.
Neural Networks in Trading: Hierarchical Vector Transformer (Final Part) Neural Networks in Trading: Hierarchical Vector Transformer (Final Part)
We continue studying the Hierarchical Vector Transformer method. In this article, we will complete the construction of the model. We will also train and test it on real historical data.