← Back to Algorithmic Glossary

Mean Reversion in Python

A strategy paradigm based on the statistical tendency of asset prices to return to their long-run historical average after deviating from it.

Definition

Mean Reversion is one of the two foundational paradigms of quantitative trading (alongside Trend Following), rooted in the statistical concept of stationarity. It posits that asset prices, spreads, volatility, or other financial time series that are stationary will inevitably revert toward their long-run mean after experiencing transient deviations. The theoretical foundation draws from Ornstein-Uhlenbeck processes in stochastic calculus and pairs trading in statistical arbitrage. Mean reversion strategies profit from identifying when a series has deviated 'too far' from equilibrium and positioning for the return journey, typically using z-score thresholds as entry and exit signals.

Quantitative Formula

dXt=θ(μXt)dt+σdWtdX_t = \theta(\mu - X_t)dt + \sigma dW_t

This is the Ornstein-Uhlenbeck (OU) stochastic differential equation. XtX_t is the asset price or spread at time tt, μ\mu is the long-run mean, θ>0\theta > 0 is the mean-reversion speed (how quickly the series reverts), σ\sigma is the diffusion coefficient (volatility), and dWtdW_t is a Wiener process increment. The half-life of mean reversion is t1/2=ln(2)/θt_{1/2} = \ln(2) / \theta — the expected time for a deviation to decay by 50%.

Why It Matters in Backtesting

The half-life of mean reversion is the single most important parameter for a mean-reversion backtest. A half-life of 2 days demands intraday execution infrastructure; a half-life of 30 days is compatible with daily bar strategies. Critically, mean-reversion strategies are highly sensitive to transaction costs — they generate many trades with small individual edges that slippage can easily erase. A rigorous backtest must verify stationarity via ADF or KPSS tests before assuming mean reversion exists, and must apply realistic slippage to avoid overstating net returns.

Python Implementation

import numpy as np
    import pandas as pd
    from statsmodels.tsa.stattools import adfuller

    def mean_reversion_strategy(prices: pd.Series, entry_z: float = 2.0,
                                exit_z: float = 0.5, lookback: int = 60) -> dict:
        """
        Implements a z-score based mean reversion strategy with stationarity testing.
        Generates long/short signals when price deviates beyond entry_z standard deviations.
        """
        # Test for stationarity (prerequisite for mean reversion)
        adf_result = adfuller(prices.dropna(), autolag="AIC")
        rolling_mean = prices.rolling(lookback).mean()
        rolling_std = prices.rolling(lookback).std()
        z_score = (prices - rolling_mean) / rolling_std
        # Half-life estimation via AR(1) regression
        lag_prices = prices.shift(1).dropna()
        delta_prices = prices.diff().dropna()
        aligned = pd.concat([delta_prices, lag_prices], axis=1).dropna()
        beta = np.polyfit(aligned.iloc[:, 1], aligned.iloc[:, 0], 1)[0]
        half_life = -np.log(2) / beta if beta < 0 else np.inf
        signals = pd.Series(0, index=prices.index)
        signals[z_score < -entry_z] = 1    # Buy when oversold
        signals[z_score > entry_z] = -1   # Sell when overbought
        signals[(z_score > -exit_z) & (z_score < exit_z)] = 0  # Exit at mean
        return {
            "signals": signals,
            "z_score": z_score,
            "half_life_days": half_life,
            "is_stationary": adf_result[1] < 0.05,  # p-value < 0.05 rejects unit root
            "adf_p_value": adf_result[1]
        }

Test this in a live environment

Stop running Jupyter notebooks locally. Paste this Mean Reversion 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