Zig Zag

Haiyue
13min

I. What is Zig Zag

Zig Zag is a technical analysis tool that filters market noise by connecting significant highs and lows on a price chart. It uses a percentage threshold (deviation) to ignore minor fluctuations, retaining only sufficiently large price swings, thereby drawing a clear zigzag line on the chart.

Historical Background

The exact inventor of the Zig Zag indicator is difficult to trace; it is one of the earliest “filter-type” indicators in technical analysis. The indicator was referenced in Arthur Merrill’s works and gained widespread use during the 1980s and 1990s as computerized technical analysis became prevalent. It has been integrated into virtually all major technical analysis platforms (such as MetaTrader, TradingView, Bloomberg, etc.).

The widespread popularity of Zig Zag is largely attributable to its application in Elliott Wave analysis — wave theory practitioners need an objective way to identify the major swing points in price, and Zig Zag provides exactly this functionality.

Indicator Classification

  • Type: Overlay indicator, plotted on the price chart
  • Category: Other Overlay / Filter
  • Default Parameters: deviation = 5 (a price change exceeding 5% is considered a valid swing)
  • Data Requirements: Requires High and Low data (or Close data)
Important Property: Zig Zag Repaints!

Zig Zag is a repainting indicator — its last line segment may change direction or position as new data arrives. Therefore, Zig Zag cannot be directly used for generating real-time trading signals. Its value lies in retrospective analysis and as an auxiliary tool, not in prediction.


II. Mathematical Principles and Calculation

Core Concept

The core of the Zig Zag algorithm is: Track the direction of price movement; when the magnitude of a reversal exceeds a preset threshold, record a pivot point and reverse the tracking direction.

Algorithm Parameters

  • dd: Deviation threshold, default is 5%
  • Data source: Typically uses High (when tracking upward) and Low (when tracking downward)

Algorithm Logic

Core loop logic: Track the current trend direction; if price continues in the same direction, update the extreme point; if the reversal magnitude exceeds the threshold d%d\%, confirm the previous extreme as a pivot and reverse direction.

Mathematical Conditions

Given the current trend direction is upward and the most recent high is PhighP_{high}, the reversal condition is:

PhighLtPhigh×100d\frac{P_{high} - L_t}{P_{high}} \times 100 \geq d

That is, the decline from the most recent high to the current low reaches or exceeds d%d\%.

Conversely, if the current trend direction is downward and the most recent low is PlowP_{low}, the reversal condition is:

HtPlowPlow×100d\frac{H_t - P_{low}}{P_{low}} \times 100 \geq d

The Repainting Issue

The last segment of Zig Zag is “provisional” — as the market continues moving in the current direction, the endpoint keeps updating. Only after the reversal condition is triggered does the previous segment become “confirmed.” When running in real-time, the last line segment may change at any moment.

Choosing the Deviation Parameter
  • d=1%d = 1\%: Very sensitive, marks nearly all minor swings, lots of noise
  • d=5%d = 5\%: Moderate sensitivity, commonly used default value
  • d=10%d = 10\%: Marks only large swings, suitable for long-term trend analysis
  • d=20%d = 20\%: Captures only bull/bear market level turning points

Parameter selection depends on the analytical purpose and timeframe.


III. Python Implementation

import numpy as np
import pandas as pd

def zigzag(high: pd.Series, low: pd.Series,
           deviation: float = 5.0) -> pd.DataFrame:
    """
    Calculate the Zig Zag indicator

    Parameters
    ----------
    high : pd.Series
        High price series
    low : pd.Series
        Low price series
    deviation : float
        Reversal threshold (percentage), default is 5.0

    Returns
    -------
    pd.DataFrame
        DataFrame with the following columns:
        - zigzag: Price value at pivot points, NaN elsewhere
        - pivot_type: 1 for high pivot, -1 for low pivot, 0 for non-pivot
        - zigzag_line: Linearly interpolated complete Zig Zag line
    """
    h = high.values.astype(float)
    l = low.values.astype(float)
    n = len(h)

    pivots = np.zeros(n)       # 0: non-pivot, 1: high, -1: low
    pivot_prices = np.full(n, np.nan)

    # Initialization: use the first two bars to determine initial direction
    if h[0] - l[1] > h[1] - l[0]:
        # First bar is high, second is low -> initial direction is down
        direction = -1
        last_high_idx = 0
        last_high_price = h[0]
        last_low_idx = 1
        last_low_price = l[1]
    else:
        direction = 1
        last_low_idx = 0
        last_low_price = l[0]
        last_high_idx = 1
        last_high_price = h[1]

    threshold = deviation / 100.0

    for i in range(2, n):
        if direction == 1:  # Currently tracking uptrend
            if h[i] > last_high_price:
                # Continue upward, update the high
                last_high_idx = i
                last_high_price = h[i]
            elif l[i] < last_high_price * (1 - threshold):
                # Reversal: decline from high exceeds threshold
                pivots[last_high_idx] = 1
                pivot_prices[last_high_idx] = last_high_price
                direction = -1
                last_low_idx = i
                last_low_price = l[i]

        else:  # direction == -1, currently tracking downtrend
            if l[i] < last_low_price:
                # Continue downward, update the low
                last_low_idx = i
                last_low_price = l[i]
            elif h[i] > last_low_price * (1 + threshold):
                # Reversal: rally from low exceeds threshold
                pivots[last_low_idx] = -1
                pivot_prices[last_low_idx] = last_low_price
                direction = 1
                last_high_idx = i
                last_high_price = h[i]

    # Mark the last unconfirmed pivot
    if direction == 1:
        pivots[last_high_idx] = 1
        pivot_prices[last_high_idx] = last_high_price
    else:
        pivots[last_low_idx] = -1
        pivot_prices[last_low_idx] = last_low_price

    # Build linearly interpolated Zig Zag line
    zigzag_line = pd.Series(pivot_prices, index=high.index).interpolate()

    return pd.DataFrame({
        "zigzag": pd.Series(pivot_prices, index=high.index),
        "pivot_type": pd.Series(pivots, index=high.index, dtype=int),
        "zigzag_line": zigzag_line,
    })


# ========== Usage Example ==========
if __name__ == "__main__":
    np.random.seed(42)
    dates = pd.date_range("2024-01-01", periods=200, freq="D")

    # Generate simulated data with trend and oscillation
    trend = np.linspace(0, 10, 200)
    cycle = 8 * np.sin(np.linspace(0, 6 * np.pi, 200))
    noise = np.cumsum(np.random.randn(200) * 0.5)
    price = 100 + trend + cycle + noise

    df = pd.DataFrame({
        "date": dates,
        "open":  price + np.random.randn(200) * 0.3,
        "high":  price + np.abs(np.random.randn(200) * 1.5),
        "low":   price - np.abs(np.random.randn(200) * 1.5),
        "close": price + np.random.randn(200) * 0.2,
    })
    df.set_index("date", inplace=True)

    # Calculate Zig Zag (5% deviation)
    zz = zigzag(df["high"], df["low"], deviation=5.0)
    df = pd.concat([df, zz], axis=1)

    # Print pivot points
    pivots = df[df["pivot_type"] != 0]
    print("=== Zig Zag Pivot Points ===")
    print(pivots[["high", "low", "close", "zigzag", "pivot_type"]])

    # Compare different deviation parameters
    for dev in [3, 5, 10]:
        zz_tmp = zigzag(df["high"], df["low"], deviation=dev)
        n_pivots = (zz_tmp["pivot_type"] != 0).sum()
        print(f"deviation={dev}%: {n_pivots} pivot points")

IV. Problems the Indicator Solves

1. Filtering Market Noise

The core value of Zig Zag lies in eliminating minor fluctuations, allowing analysts to clearly see the main direction and turning points of price movement:

  • A 5% deviation filters out routine minor gains and losses
  • Adjusting the deviation controls the intensity of filtering

2. Identifying Swing Highs and Lows (Swing Points)

The pivot points automatically annotated by Zig Zag correspond to swing highs and swing lows:

Pivot TypeInterpretation
High PivotThe endpoint of an upswing, potentially a resistance level
Low PivotThe endpoint of a downswing, potentially a support level

3. Elliott Wave Analysis Assistance

Wave theory requires identifying the five-wave impulse and three-wave corrective structures in price. Zig Zag assists wave counting in the following ways:

  • Automatically marks major turning points as candidates for wave start/end points
  • Controls the wave degree (large waves vs. small waves) by adjusting the deviation parameter
  • Can calculate the magnitude ratio of each swing to assist Fibonacci analysis

4. Calculating Retracement Ratios

The pivot points annotated by Zig Zag can be directly used to calculate Fibonacci retracement ratios:

Retracement=SwingendCurrentSwingendSwingstartRetracement = \frac{|Swing_{end} - Current|}{|Swing_{end} - Swing_{start}|}

Common Fibonacci retracement levels (38.2%, 50%, 61.8%) can be calculated automatically based on Zig Zag swings.

5. Measuring Swing Magnitudes

By analyzing Zig Zag swing data, you can compute market structure characteristics such as average up/down magnitude, swing duration, and bull-bear asymmetry.

Key Limitation

V. Advantages, Disadvantages, and Use Cases

Advantages

AdvantageDescription
Strong noise filteringEffectively eliminates minor fluctuations, revealing a clear price skeleton
Adjustable sensitivityFlexibly controls the degree of filtering through the deviation parameter
Automatic annotationAutomatically identifies swing highs and lows without manual marking
Essential for wave analysisAn indispensable tool for Elliott Wave practitioners
Foundation for statistical analysisProvides valuable market statistics on swing magnitude and frequency

Disadvantages

DisadvantageDescription
RepaintingThe last line segment changes with new data and cannot be used for real-time signals
Look-ahead biasImproper use in backtesting introduces severe look-ahead bias
Non-predictiveCan only describe past movements, not predict future direction
Parameter sensitiveThe choice of deviation significantly affects the number and position of identified pivots
Ignores time dimensionDoes not distinguish between fast and slow reversals

Use Cases

  • Elliott Wave analysis: Annotating wave structures is the most classic use of Zig Zag
  • Market structure analysis: Identifying Higher Highs / Lower Lows and other structural patterns
  • Strategy backtesting assistance: Analyzing historical swing magnitude and frequency to optimize strategy parameters
  • Fibonacci analysis: Calculating retracement and extension levels based on swing highs and lows
ComparisonZig ZagWilliams FractalDonchian Channel
Core mechanismPercentage deviationFixed-window extremesRolling-window extremes
RepaintingYesNo (confirmed once set)No
Parametersdeviation%periodperiod
Real-time usabilityHistorical segments onlyHas lag but usableReal-time usable
Primary useWave analysis / Structure descriptionSupport/resistance annotationChannel breakout

tip Practical Tips

  1. Never use Zig Zag as a real-time trading signal — it repaints, and the last line segment is unreliable.
  2. In Elliott Wave analysis, multiple Zig Zag overlays with different deviation values are typically used to identify waves of different degrees.
  3. Using confirmed pivot points to calculate Fibonacci retracement levels is an efficient method.
  4. If you need a non-repainting alternative, consider Williams Fractal — once confirmed, it does not change.
  5. In backtesting, ensure that signals depend only on pivot points that were “confirmed at the time,” to avoid look-ahead bias.