← Back to Home
Is The Super Smoother Filter Better than Moving Averages

Is The Super Smoother Filter Better than Moving Averages

The Super Smoother filter, developed by John Ehlers, is a powerful tool in a technical analyst’s arsenal, designed to provide significant smoothing of price data (or other series) with substantially less lag than traditional moving averages like the Simple Moving Average (SMA). This article delves into what the Super Smoother is, its mathematical underpinnings, how to implement it in Python, and its applications in trading.

Introduction: The Quest for Smoother, Faster Indicators

Traders and analysts constantly seek indicators that can clearly define market trends and turning points without the common issue of excessive lag. While Simple Moving Averages (SMAs) provide smoothing, they do so at the cost of significant delay, meaning signals often come too late. Exponential Moving Averages (EMAs) reduce some lag but can be overly sensitive to short-term fluctuations or “whipsaws.”

John Ehlers, a prominent engineer and technical analyst, introduced a range of advanced digital signal processing techniques to trading, including the Super Smoother filter. It’s designed to be a 2-pole Infinite Impulse Response (IIR) filter that offers a good compromise between smoothness and responsiveness.

The Mechanics: How the Super Smoother Works

The Super Smoother is a type of digital filter. Specifically, it’s often implemented as a 2-pole Butterworth-like filter, which provides a flat response in the passband (preserving the trend) and a sharp rolloff in the stopband (attenuating noise).

The core idea is to calculate the current filtered value based on a weighted combination of recent input values and previous filtered values.

The Formula

The general formula for the 2-pole Super Smoother filter as often presented is:

Filt[i] = c1 * (Input[i] + Input[i-1]) / 2 + c2 * Filt[i-1] + c3 * Filt[i-2]

Where:

The Coefficients

The coefficients are determined by a length parameter, which is analogous to the period in a moving average. It dictates the “cutoff frequency” of the filter – essentially, how much smoothing is applied.

The coefficients are calculated as follows:

  1. a1 = exp(-sqrt(2) * pi / length)
  2. b1 = 2 * a1 * cos(sqrt(2) * pi / length)
  3. c2 = b1
  4. c3 = -a1 * a1
  5. c1 = 1 - c2 - c3

Here:

The term (Input[i] + Input[i-1]) / 2 represents a simple pre-smoothing of the input data by averaging the current and previous values.

Python Implementation

Here’s a Python function to calculate the Super Smoother filter for a pandas.Series:

import pandas as pd
import numpy as np

def super_smoother(s: pd.Series, length: int) -> pd.Series:
    """
    Calculates the Super Smoother filter for a given pandas Series.

    Args:
        s (pd.Series): The input series (e.g., closing prices).
        length (int): The lookback period or cutoff length for the filter.
                      Must be greater than 0.

    Returns:
        pd.Series: The Super Smoother filtered series.
    """
    if not isinstance(s, pd.Series):
        raise TypeError("Input 's' must be a pandas Series.")
    if length <= 0:
        raise ValueError("length must be positive.")
        
    filt = pd.Series(np.nan, index=s.index, dtype=float)
    
    # Coefficients
    a1 = np.exp(-np.sqrt(2) * np.pi / length)
    b1 = 2 * a1 * np.cos(np.sqrt(2) * np.pi / length)
    
    coef2 = b1
    coef3 = -a1 * a1
    coef1 = 1 - coef2 - coef3

    # Iterate using .iloc for positional access to handle all index types
    for i in range(len(s)):
        if pd.isna(s.iloc[i]): # Skip if current input is NaN
            # Optionally, carry forward the last valid filtered value if available
            if i > 0 and not pd.isna(filt.iloc[i-1]):
                filt.iloc[i] = filt.iloc[i-1]
            # else, it remains NaN
            continue

        if i == 0: # First point: Initialize with the first data point
            filt.iloc[i] = s.iloc[i]
        elif i == 1: # Second point: Initialize with the second data point
                     # Full formula cannot be applied yet as filt.iloc[i-2] (filt[-1]) doesn't exist.
                     # Priming with the input value is a common approach.
            filt.iloc[i] = s.iloc[i] 
        else: # From third point onwards, use the full recursive formula
            # Required previous values for the calculation:
            s_current = s.iloc[i]
            s_prev = s.iloc[i-1]
            filt_prev1 = filt.iloc[i-1]
            filt_prev2 = filt.iloc[i-2]

            # Check if all required previous values are valid numbers
            if pd.isna(s_prev) or pd.isna(filt_prev1) or pd.isna(filt_prev2):
                # Fallback if some necessary previous value is missing/NaN.
                # This might occur if `s` has intermittent NaNs not at the beginning,
                # or if the series is too short after initial NaNs.
                # A simple fallback is to use the current input value if valid,
                # or carry forward the previous filtered value.
                if not pd.isna(s_current):
                    filt.iloc[i] = s_current
                elif not pd.isna(filt_prev1):
                    filt.iloc[i] = filt_prev1
                # else, it remains NaN
                continue
            
            # Average of current and previous input value
            input_avg = (s_current + s_prev) / 2.0
            
            filt.iloc[i] = coef1 * input_avg + \
                           coef2 * filt_prev1 + \
                           coef3 * filt_prev2
    return filt

Explanation of the code:

  1. Input Validation: Checks if s is a pandas Series and length is positive.
  2. Initialization: An empty pandas.Series called filt is created with NaNs to store the filtered values.
  3. Coefficient Calculation: a1, b1, coef1, coef2, coef3 are calculated based on the length.
  4. Iteration and Filtering:
    • The code iterates through the input series s.
    • NaN Handling: If the current input s.iloc[i] is NaN, the corresponding filt.iloc[i] might be carried forward from the previous filtered value or left as NaN.
    • Priming the Filter:
      • The first point of filt (filt.iloc[0]) is set to the first point of s (s.iloc[0]).
      • The second point of filt (filt.iloc[1]) is set to the second point of s (s.iloc[1]). This is a common simplification because the full formula requires two previous filtered values, which aren’t available at the very beginning. The filter’s output becomes more accurate after these initial priming steps.
    • Recursive Calculation: For i >= 2, the full filter formula is applied using the current and previous input values, and the two preceding filtered values.
    • NaN Check for Inputs: Before applying the full formula, it checks if s.iloc[i-1], filt.iloc[i-1], or filt.iloc[i-2] are NaN. If so, it applies a fallback logic.

Applying and Interpreting the Super Smoother

The Super Smoother can be applied in several ways:

  1. Smoothing Price Data:

    Its primary use is to create a smoothed version of the price series. This smoothed line can make it easier to identify the underlying trend by filtering out market noise.

  2. As a Trend Indicator:

    • Direction: The direction of the Super Smoother line indicates the trend. An upward sloping line suggests an uptrend, while a downward sloping line suggests a downtrend.
    • Crossovers with Price: While not its primary signal mechanism, price crossing over the Super Smoother line can sometimes be interpreted as a potential change in trend, similar to how moving average crossovers are used. However, these should be used with caution and confirmation.
    • Crossovers of Two Super Smoothers: Using a faster and a slower Super Smoother and looking for their crossovers is another common approach, similar to dual moving average strategies.
  3. Use in More Complex Strategies:

    The Super Smoother is excellent for pre-processing data that will be fed into other indicators or models. For example:

    • Smoothing Momentum: Instead of applying it directly to price, you can calculate a momentum series (e.g., price.diff(n)) and then apply the Super Smoother to this momentum series. This can provide clearer momentum signals.
    • Smoothing Other Indicators: It can be used to smooth noisy outputs from other oscillators or indicators.

Advantages of the Super Smoother

Limitations and Considerations

Example: Smoothing BTC-USD Close Prices

Let’s apply the Super Smoother to Bitcoin (BTC-USD) closing prices. I will use yfinance to download the data, applying your preference for auto_adjust=False and droplevel for multi-level columns.

import yfinance as yf
import matplotlib.pyplot as plt

# 1) Download BTC-USD data
# Using your preferred settings for yfinance download
try:
    df = yf.download(
        "BTC-USD",
        start="2023-01-01", 
        end="2025-05-22", # Current date or as needed
        auto_adjust=False # As per your saved preference
    )
    if df.columns.nlevels > 1: # As per your saved preference
        df.columns = df.columns.droplevel(level=1)
    
    # Ensure 'Close' column exists and has data
    if 'Close' not in df.columns or df['Close'].isnull().all():
        print("Close price data is not available or all NaNs.")
        exit()
        
    # Drop rows where essential data like 'Close' is NaN, especially at the beginning
    df.dropna(subset=['Close'], inplace=True)
    if df.empty:
        print("DataFrame is empty after dropping NaNs from Close price.")
        exit()

except Exception as e:
    print(f"Error downloading data: {e}")
    exit()

# 2) Compute Super Smoother
# Let's try two different lengths to see the effect
df['SuperSmoother_10'] = super_smoother(df['Close'], length=10)
df['SuperSmoother_30'] = super_smoother(df['Close'], length=30)

# 3) Plot the results
plt.figure(figsize=(14, 7))
plt.plot(df.index, df['Close'], label='BTC-USD Close Price', color='lightgray', alpha=0.8)
plt.plot(df.index, df['SuperSmoother_10'], label='Super Smoother (length=10)', color='blue')
plt.plot(df.index, df['SuperSmoother_30'], label='Super Smoother (length=30)', color='red')

plt.title('BTC-USD Close Price and Super Smoother Filters')
plt.xlabel('Date')
plt.ylabel('Price (USD)')
plt.legend()
plt.grid(True, alpha=0.3)
plt.show()
Pasted image 20250522204146.png

Interpreting the Chart:

Conclusion

The Super Smoother filter is a valuable advancement over traditional moving averages for traders seeking a balance between smooth trend representation and responsive signal generation. Its ability to significantly reduce lag while effectively filtering noise makes it a versatile tool for direct trend analysis, pre-processing data for other indicators, or as a component in sophisticated trading strategies. However, like all tools, its effectiveness is enhanced when used thoughtfully, with an understanding of its parameters and in conjunction with other analytical techniques.