Zig Zag
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)
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
- : 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 , 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 , the reversal condition is:
That is, the decline from the most recent high to the current low reaches or exceeds .
Conversely, if the current trend direction is downward and the most recent low is , the reversal condition is:
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.
- : Very sensitive, marks nearly all minor swings, lots of noise
- : Moderate sensitivity, commonly used default value
- : Marks only large swings, suitable for long-term trend analysis
- : 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 Type | Interpretation |
|---|---|
| High Pivot | The endpoint of an upswing, potentially a resistance level |
| Low Pivot | The 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:
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.
V. Advantages, Disadvantages, and Use Cases
Advantages
| Advantage | Description |
|---|---|
| Strong noise filtering | Effectively eliminates minor fluctuations, revealing a clear price skeleton |
| Adjustable sensitivity | Flexibly controls the degree of filtering through the deviation parameter |
| Automatic annotation | Automatically identifies swing highs and lows without manual marking |
| Essential for wave analysis | An indispensable tool for Elliott Wave practitioners |
| Foundation for statistical analysis | Provides valuable market statistics on swing magnitude and frequency |
Disadvantages
| Disadvantage | Description |
|---|---|
| Repainting | The last line segment changes with new data and cannot be used for real-time signals |
| Look-ahead bias | Improper use in backtesting introduces severe look-ahead bias |
| Non-predictive | Can only describe past movements, not predict future direction |
| Parameter sensitive | The choice of deviation significantly affects the number and position of identified pivots |
| Ignores time dimension | Does 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
Comparison with Related Indicators
| Comparison | Zig Zag | Williams Fractal | Donchian Channel |
|---|---|---|---|
| Core mechanism | Percentage deviation | Fixed-window extremes | Rolling-window extremes |
| Repainting | Yes | No (confirmed once set) | No |
| Parameters | deviation% | period | period |
| Real-time usability | Historical segments only | Has lag but usable | Real-time usable |
| Primary use | Wave analysis / Structure description | Support/resistance annotation | Channel breakout |
tip Practical Tips
- Never use Zig Zag as a real-time trading signal — it repaints, and the last line segment is unreliable.
- In Elliott Wave analysis, multiple Zig Zag overlays with different deviation values are typically used to identify waves of different degrees.
- Using confirmed pivot points to calculate Fibonacci retracement levels is an efficient method.
- If you need a non-repainting alternative, consider Williams Fractal — once confirmed, it does not change.
- In backtesting, ensure that signals depend only on pivot points that were “confirmed at the time,” to avoid look-ahead bias.