Williams Fractal

Haiyue
14min

I. What is Williams Fractal

Williams Fractal is a technical analysis tool introduced by renowned trading master Bill Williams in his classic book Trading Chaos (published 1995). It identifies local extremes on price charts (fractal highs and fractal lows) to mark potential support levels, resistance levels, and trend reversal points.

Historical Background

Bill Williams was a technical analysis pioneer who integrated chaos theory, fractal geometry, and nonlinear dynamics into trading. He believed that markets are fundamentally chaotic systems, but within the chaos lies an inherent order — fractal structures are the manifestation of this order. In mathematics, a “fractal” refers to a geometric structure that exhibits self-similarity at different scales. Williams applied this concept to financial markets, arguing that price movements display similar reversal patterns across different timeframes.

Williams Fractal is an important component of the Bill Williams trading system (which includes the Alligator indicator, AO Oscillator, AC Accelerator, etc.). In his system, fractals are used to confirm trend breakout entry signals.

Indicator Classification

  • Type: Overlay indicator, drawn as arrows or markers on the price chart
  • Category: Other Overlay / Pattern Recognition
  • Default Parameters: period = 2 (2 bars on each side of the center bar, forming a 5-bar fractal pattern)
  • Data Requirements: Requires High and Low data
The Essence of Fractals

Williams Fractal is essentially a local extremum detection algorithm. It identifies the location of the highest and lowest points within a group of 5 (or more) bars — these locations are often nodes where short-term bull-bear forces shift.


II. Mathematical Principles and Calculation

Core Definition

For the default parameter n=2n = 2 (2 bars on each side), fractals are defined as follows:

Fractal Up (Bearish Fractal):

Bar tt forms a fractal up when:

Ht>Ht2,Ht>Ht1,Ht>Ht+1,Ht>Ht+2H_t > H_{t-2},\quad H_t > H_{t-1},\quad H_t > H_{t+1},\quad H_t > H_{t+2}

The center bar’s high is strictly greater than the highs of the two bars on each side.

Fractal Down (Bullish Fractal):

Bar tt forms a fractal down when:

Lt<Lt2,Lt<Lt1,Lt<Lt+1,Lt<Lt+2L_t < L_{t-2},\quad L_t < L_{t-1},\quad L_t < L_{t+1},\quad L_t < L_{t+2}

The center bar’s low is strictly less than the lows of the two bars on each side.

Generalized Formula

When period = nn, the center bar requires nn bars on each side, for a total of 2n+12n + 1 bars:

Fractal Up at t:Ht=max(Htn,Htn+1,,Ht+n)\text{Fractal Up at } t: \quad H_t = \max(H_{t-n}, H_{t-n+1}, \ldots, H_{t+n}) Fractal Down at t:Lt=min(Ltn,Ltn+1,,Lt+n)\text{Fractal Down at } t: \quad L_t = \min(L_{t-n}, L_{t-n+1}, \ldots, L_{t+n})

Step-by-Step Calculation

  1. Select window size: Default n=2n = 2, total window length = 2n+1=52n + 1 = 5.
  2. Iterate through each bar: From bar nn to bar NnN - n (where NN is the total number of bars).
  3. Check fractal up condition: Is the current bar’s High the maximum of all High values within the window?
  4. Check fractal down condition: Is the current bar’s Low the minimum of all Low values within the window?
  5. Mark fractals: Annotate the bar position where conditions are met as a fractal up or fractal down.

Signal Delay Characteristics

Fractals have an inherent delay issue: because nn bars on the right side of the center bar must appear before a fractal can be confirmed:

  • With default parameters, fractal signals are delayed by at least 2 bars
  • In live trading, when you see a fractal being marked, it is actually an event from 2 bars ago
About Simultaneous Fractals

A single bar can be both a fractal up and a fractal down — this occurs when the bar has both the highest High and the lowest Low (i.e., a long doji or extreme volatility bar).


III. Python Implementation

import numpy as np
import pandas as pd

def williams_fractal(high: pd.Series, low: pd.Series,
                     period: int = 2) -> pd.DataFrame:
    """
    Calculate Williams Fractal

    Parameters
    ----------
    high : pd.Series
        High price series
    low : pd.Series
        Low price series
    period : int
        Number of bars on each side of the center bar, default is 2 (5-bar pattern)

    Returns
    -------
    pd.DataFrame
        DataFrame with fractal_up and fractal_down columns.
        True indicates a fractal exists at that position.
    """
    n = len(high)
    fractal_up = pd.Series(False, index=high.index, name="fractal_up")
    fractal_down = pd.Series(False, index=low.index, name="fractal_down")

    high_arr = high.values
    low_arr = low.values

    for i in range(period, n - period):
        # Check fractal up: is the center bar's High the highest in the window?
        is_up = True
        for j in range(1, period + 1):
            if high_arr[i] <= high_arr[i - j] or high_arr[i] <= high_arr[i + j]:
                is_up = False
                break
        fractal_up.iloc[i] = is_up

        # Check fractal down: is the center bar's Low the lowest in the window?
        is_down = True
        for j in range(1, period + 1):
            if low_arr[i] >= low_arr[i - j] or low_arr[i] >= low_arr[i + j]:
                is_down = False
                break
        fractal_down.iloc[i] = is_down

    return pd.DataFrame({"fractal_up": fractal_up, "fractal_down": fractal_down})


def williams_fractal_vectorized(high: pd.Series, low: pd.Series,
                                period: int = 2) -> pd.DataFrame:
    """
    Vectorized version of Williams Fractal (better performance)
    """
    fractal_up = pd.Series(True, index=high.index)
    fractal_down = pd.Series(True, index=low.index)

    for i in range(1, period + 1):
        fractal_up &= (high > high.shift(i)) & (high > high.shift(-i))
        fractal_down &= (low < low.shift(i)) & (low < low.shift(-i))

    # Set boundaries to False
    fractal_up.iloc[:period] = False
    fractal_up.iloc[-period:] = False
    fractal_down.iloc[:period] = False
    fractal_down.iloc[-period:] = False

    return pd.DataFrame({
        "fractal_up": fractal_up,
        "fractal_down": fractal_down,
    })


# ========== Usage Example ==========
if __name__ == "__main__":
    np.random.seed(42)
    dates = pd.date_range("2024-01-01", periods=100, freq="D")
    price = 100 + np.cumsum(np.random.randn(100) * 0.8)

    df = pd.DataFrame({
        "date": dates,
        "open":  price + np.random.randn(100) * 0.2,
        "high":  price + np.abs(np.random.randn(100) * 1.0),
        "low":   price - np.abs(np.random.randn(100) * 1.0),
        "close": price + np.random.randn(100) * 0.15,
        "volume": np.random.randint(1000, 10000, size=100),
    })
    df.set_index("date", inplace=True)

    # Calculate fractals
    fractals = williams_fractal_vectorized(df["high"], df["low"], period=2)
    df = pd.concat([df, fractals], axis=1)

    # Print fractal up points
    up_fractals = df[df["fractal_up"]]
    print("=== Fractal Up (Potential Resistance Levels) ===")
    print(up_fractals[["high", "low", "close"]].head(10))

    # Print fractal down points
    down_fractals = df[df["fractal_down"]]
    print("\n=== Fractal Down (Potential Support Levels) ===")
    print(down_fractals[["high", "low", "close"]].head(10))

    # Statistics
    print(f"\nTotal bars: {len(df)}")
    print(f"Fractal up count: {df['fractal_up'].sum()}")
    print(f"Fractal down count: {df['fractal_down'].sum()}")

    # Fractal breakout signals
    # Price breaks above the most recent fractal up High -> bullish
    # Price breaks below the most recent fractal down Low -> bearish
    df["last_up_fractal"] = np.where(df["fractal_up"], df["high"], np.nan)
    df["last_up_fractal"] = df["last_up_fractal"].ffill()

    df["last_down_fractal"] = np.where(df["fractal_down"], df["low"], np.nan)
    df["last_down_fractal"] = df["last_down_fractal"].ffill()

    df["breakout_up"] = df["close"] > df["last_up_fractal"].shift(1)
    df["breakout_down"] = df["close"] < df["last_down_fractal"].shift(1)

    print("\n=== Fractal Breakout Signals ===")
    breakouts = df[df["breakout_up"] | df["breakout_down"]]
    print(breakouts[["close", "last_up_fractal", "last_down_fractal",
                     "breakout_up", "breakout_down"]].tail(10))

IV. Problems the Indicator Solves

1. Identifying Local Support/Resistance Levels

Fractal points mark local extremes in price, which naturally form support and resistance levels:

Fractal TypeCorresponding PriceInterpretation
Fractal UpHigh valuePotential resistance — bulls reached their limit here and then retreated
Fractal DownLow valuePotential support — bears reached their limit here and then bounced

2. Confirming Trend Breakouts

The most important signal in the Bill Williams system is the fractal breakout:

  • Buy signal: Price breaks above the most recent fractal up’s High — bulls breach the prior high, indicating a potential trend initiation or continuation
  • Sell signal: Price breaks below the most recent fractal down’s Low — bears breach the prior low, indicating a potential downtrend initiation

3. Combined Use with the Alligator Indicator

In Bill Williams’ complete trading system, fractal signals are filtered through the Alligator indicator:

  • Only when a fractal up is above the Alligator’s Teeth line is the long signal valid
  • Only when a fractal down is below the Alligator’s Teeth line is the short signal valid
  • If a fractal is inside the Alligator’s “mouth,” the signal is ignored

4. Wave Counting Assistance

Fractal points can assist with Elliott Wave analysis:

  • Consecutive fractal ups and downs form the highs and lows of wave structures
  • Connecting fractal points outlines the market’s swing structure
Note

Fractal signals are inherently delayed by 2 bars (with default parameters). In fast-moving markets, by the time a fractal is confirmed, the price may have already moved a significant distance. Do not use fractals as your sole entry signal.


V. Advantages, Disadvantages, and Use Cases

Advantages

AdvantageDescription
Intuitive conceptIdentifying local highs and lows aligns with intuition and is easy to understand
Minimal parametersOnly one parameter (period), simple to adjust
Strong objectivityFractal determination is entirely based on mathematical conditions, with no subjective element
Cross-market applicabilityWorks with any market and timeframe that has OHLC data
Systematic support/resistanceAutomatically generates support/resistance levels, avoiding subjective bias from manual trendlines

Disadvantages

DisadvantageDescription
Signal lagRequires right-side bars for confirmation; delayed by at least nn bars
Excessive fractalsIn ranging markets, dense clusters of fractal points make filtering difficult
No strength informationAll fractals carry equal weight; cannot distinguish important from minor reversal points
No volume componentDoes not consider volume for validation, potentially marking low-quality reversal points
False breakout riskFractal breakout strategies are prone to consecutive false breakouts in ranging markets

Use Cases

  • Trending markets: Fractal breakout strategies perform best in clearly trending markets
  • Daily and higher timeframes: Short-period fractals contain too much noise; daily fractals are more meaningful
  • Combined with Alligator: Best results within the complete Bill Williams system
  • Support/resistance annotation: Automated marking of key price levels to assist discretionary analysis
ComparisonWilliams FractalPivot PointsZig Zag
Detection methodLocal extremes (fixed window)Formula-basedPercentage deviation
ParametersperiodNonedeviation%
Lagnn barsNone (prior day data)Uncertain (repaints)
Signal densityRelatively highFixed (one set per day)Adjustable
Practical Tips
  1. Increasing the period parameter (e.g., to 3 or 5) reduces the number of fractals and filters out more significant extreme points.
  2. Combining Alligator indicator filtering with fractal signals is the standard practice in the Bill Williams system and is strongly recommended.
  3. You can rank fractals by the absolute height of their High/Low values and prioritize those at the most extreme price levels.
  4. In multi-timeframe analysis, fractals on higher timeframes carry more significance than lower timeframes — daily fractals outweigh hourly fractals.
  5. Connecting fractal points can quickly outline the swing structure of price, assisting Elliott Wave analysis.