← Back to Algorithmic Glossary

Volatility Clustering in Python

The empirical phenomenon where large market moves tend to be followed by large moves, and calm periods follow calm periods.

Definition

Volatility Clustering is one of the most robust stylized facts in financial econometrics, first formally described by Benoit Mandelbrot in 1963 and later formalized by Robert Engle's ARCH model (1982) and Bollerslev's GARCH model (1986). It describes the empirical observation that return volatility is serially correlated — high-volatility regimes cluster together, as do low-volatility regimes. This violates the classical assumption of i.i.d. (independent and identically distributed) returns that underlies the Black-Scholes model and many naive backtesting frameworks.

Quantitative Formula

σt2=ω+αϵt12+βσt12\sigma_t^2 = \omega + \alpha \epsilon_{t-1}^2 + \beta \sigma_{t-1}^2

This is the GARCH(1,1) model where σt2\sigma_t^2 is the conditional variance at time tt, ω\omega is a long-run variance constant, α\alpha captures the impact of last period's squared shock ϵt12\epsilon_{t-1}^2 (ARCH term), and β\beta captures the persistence of last period's variance σt12\sigma_{t-1}^2 (GARCH term). The constraint α+β<1\alpha + \beta < 1 ensures mean-reversion. High β\beta (typically 0.85–0.95 for equity markets) confirms strong volatility persistence.

Why It Matters in Backtesting

A backtest using constant volatility assumptions (e.g., fixed position sizing based on historical vol) will systematically over-leverage during the transition into high-vol regimes, dramatically amplifying drawdowns. Strategies that adapt position size to current realized volatility — vol-targeting — are far more stable in live markets. Rolling 20-day versus 252-day volatility divergence is a practical, real-time signal for detecting regime transitions in both backtesting and live deployment.

Python Implementation

import numpy as np
    import pandas as pd

    def analyze_volatility_clustering(returns: pd.Series, short_window: int = 20,
                                      long_window: int = 252) -> dict:
        """
        Detects volatility clustering via autocorrelation of squared returns
        and computes vol-targeted position sizes.
        """
        squared_returns = returns ** 2
        # Ljung-Box style: autocorrelation of squared returns (clustering signature)
        autocorr_lags = {f"lag_{lag}": squared_returns.autocorr(lag=lag) for lag in [1, 5, 10, 21]}
        rolling_short_vol = returns.rolling(short_window).std() * np.sqrt(252)
        rolling_long_vol = returns.rolling(long_window).std() * np.sqrt(252)
        # Vol-targeting: scale position size to maintain constant risk
        target_vol = 0.15  # 15% annualized target
        vol_scalar = target_vol / rolling_short_vol.replace(0, np.nan)
        vol_regime = pd.cut(
            rolling_short_vol,
            bins=[0, 0.10, 0.20, 0.35, np.inf],
            labels=["Low", "Normal", "Elevated", "Crisis"]
        )
        return {
            "squared_return_autocorrelations": autocorr_lags,
            "clustering_detected": autocorr_lags["lag_1"] > 0.1,
            "rolling_short_vol": rolling_short_vol,
            "rolling_long_vol": rolling_long_vol,
            "vol_targeting_scalar": vol_scalar,
            "current_regime": str(vol_regime.iloc[-1])
        }

Test this in a live environment

Stop running Jupyter notebooks locally. Paste this Volatility Clustering code directly into Valetha's Strategy Lab and run a full historical backtest in seconds.

Open the Python Strategy Lab

Ready to find your edge ?

Start for Free