Edges
Mean Reversion Explained
Stretch a rubber band, release it, and it snaps back. That is the intuition behind mean reversion — and like most intuitions in trading, it captures something real while hiding the parts that can hurt you.
Mean Reversion — Price Oscillating Around Moving Average
Markets exhibit mean reversion across asset classes, time horizons, and instruments. Prices that deviate sharply from a historical average tend to return toward it. The tendency is real, measurable, and exploitable — under the right conditions. Understanding those conditions is what separates a mean reversion strategy from a strategy that loses money buying falling assets and calling it value.
This article covers the mechanics, the behavioral basis, the failure modes, and a working Python implementation. It also connects to the broader question of what makes any of this an edge — which is worth reading in context of what a trading edge is before going further.
What Mean Reversion Is
Mean reversion is the statistical tendency for a variable that has moved away from its long-run average to move back toward it over time. In price terms: a stock that has fallen 15% below its 20-day moving average has an elevated probability of recovering some of that gap relative to a stock trading at its moving average.
The statistical underpinning comes from the Ornstein-Uhlenbeck process — a continuous-time stochastic process with a drift term that pulls the variable back toward its long-run mean. In plain language: the further the variable strays from its mean, the stronger the pull back toward it. Unlike a random walk, which has no memory of where it has been, an OU process is stationary — it does not drift indefinitely in one direction.
Whether an asset's price series behaves like an OU process — whether it is stationary — is an empirical question, not an assumption. The augmented Dickey-Fuller test is the standard check. If the price series fails the stationarity test, mean reversion strategies applied to it are misspecified. The process is not mean-reverting; you are buying falling knives on a linear trend.
A cleaner framework: work with z-scores instead of raw prices. A z-score measures how many standard deviations the current price sits above or below its rolling mean, normalized for the rolling standard deviation of that period. At z = +2.0, price is two standard deviations above the mean. Historical distribution says that is unusual. A mean reversion signal goes short at that level, expecting a return toward z = 0.
The z-score approach has two structural advantages. It normalizes for volatility — a 5-point move in a high-volatility stock carries a different signal than a 5-point move in a low-volatility one. And it gives you a parameter-independent way to compare signals across instruments. The entry and exit thresholds are in the same unit regardless of the underlying price level.
Why It Works
Mean reversion works because it has a mechanism. The rubber-band metaphor is not decoration — it describes real forces that operate in markets.
Behavioral anchoring. Market participants anchor to recent price levels. After a sharp decline, sellers who missed the exit start to hold, and marginal buyers who were priced out enter. After a sharp rally, profit-taking pressure increases and marginal buyers become more reluctant. These forces are asymmetric — they accelerate the move on the way out and resist it on the way back — but they create a systematic bias toward reversion.
Market maker inventory management. Market makers absorb order flow by taking the other side of trades. When a surge of sell orders pushes a price down, the market maker accumulates long inventory. To unwind that inventory without taking a loss, they shade their quotes to attract buyers at the depressed price. This creates mechanical buying pressure after a one-directional move.
Statistical arbitrage between correlated assets. When two historically correlated stocks diverge — one rises while the other lags — statistical arbitrage funds short the winner and long the laggard, betting on convergence. This is pairs trading, the most explicit implementation of mean reversion logic. The force is real because the capital deployed to exploit it is real.
Which market structures support reversion. Mean reversion tends to be stronger in: liquid stocks with high short-interest that limits extended rallies; mean-reverting interest rate environments where rate moves are bounded; commodity markets with known production cost floors and demand ceilings; pairs of instruments with a structural economic link (e.g., two oil refiners in the same geography). It tends to be weaker in: illiquid markets where prices can gap without triggering the counter-pressure; assets in strong fundamental trends where the "mean" is itself trending; markets dominated by passive index-tracking flows that have no reversion incentive.
When It Fails
Mean reversion fails in ways that are well-defined enough to anticipate.
Trending regimes. The most common failure mode: you apply a mean reversion framework to an asset in a structural trend and interpret every move below the rolling mean as an entry opportunity. In a trending market, the rolling mean is itself moving in one direction. The z-score entry fires, price continues lower, and the rolling mean eventually catches down to meet it — at a significant loss. The fix is regime detection: a trend filter that suspends mean reversion entries when a longer-period trend indicator (e.g., 200-day slope, ADX above threshold) signals a trending environment.
Structural breaks. A mean reversion strategy calibrated on the 2019 price behavior of a retail stock fails when that retailer loses a major distribution contract in 2021 and the stock reprices to a new structural level. The old mean is no longer relevant. The position is not mean reversion — it is a fundamental bet on recovery, made with a statistical tool that was not designed for that decision. Structural breaks are detectable after the fact via stationarity testing on rolling windows; they are difficult to detect in real time.
Crowding. When too many participants are running the same mean reversion signal on the same instruments, the trade becomes crowded. Crowded mean reversion strategies exhibit a specific failure pattern: slow decay during normal periods as the crowd absorbs each other's exits, followed by violent, correlated unwinds when volatility spikes. The unwind is fast and simultaneous because every participant sees the same signal. The result looks like mean reversion "failing" but the mechanism is crowding pressure, not market structure change.
Transaction costs on narrow signals. Mean reversion signals on shorter time horizons (intraday or daily bars) often require frequent entries and exits. At realistic spread and slippage, these strategies go from profitable gross to roughly breakeven or negative net. Before running any mean reversion strategy live, model the transaction cost load at your expected trade frequency and position size.
How to Code It in Python
The following implements a z-score based mean reversion signal on a single price series. The function accepts a price series and three parameters: lookback window length, entry z-score threshold, and exit z-score threshold.
import pandas as pd
import numpy as np
def mean_reversion_signal(
prices: pd.Series,
lookback: int = 20,
entry_z: float = 2.0,
exit_z: float = 0.5
) -> pd.Series:
rolling_mean = prices.rolling(lookback).mean()
rolling_std = prices.rolling(lookback).std()
z_score = (prices - rolling_mean) / rolling_std
signal = pd.Series(0, index=prices.index)
signal[z_score < -entry_z] = 1 # long when price below mean by 2+ std
signal[z_score > entry_z] = -1 # short when price above mean by 2+ std
signal[(z_score > -exit_z) & (z_score < exit_z)] = 0 # exit near mean
return signal
lookback controls how many periods are used to compute the rolling mean and standard deviation. A shorter lookback (10–15) makes the signal more reactive — it detects reversion opportunities faster, but it is also noisier and more sensitive to short-term volatility spikes that are not real mean reversion. A longer lookback (30–60) produces a more stable mean estimate but misses shorter reversion cycles. The right value depends on the time horizon of the behavioral or structural mechanism you are exploiting. For daily bars on equities, 15–25 is a reasonable starting range for behavioral edge hypotheses; for intraday bars, the lookback should be shorter in absolute time, which usually means a higher bar count.
entry_z sets the z-score threshold at which a position is entered. At 2.0 standard deviations, you are entering on moves that historically occur roughly 5% of the time. At 1.5, entries are more frequent but the expected reversion distance to the mean is smaller. At 2.5, entries are rarer but signal higher confidence in reversion. The relationship between entry z and edge quality is not monotonic — very high z-scores can indicate structural breaks rather than reversion opportunities. Typical values range from 1.5 to 2.5 for daily signals.
exit_z sets the z-score band inside which a position is closed. At 0.5, the exit triggers when price is within half a standard deviation of the mean — close to flat. Setting exit_z = 0 exits exactly at the mean, which rarely happens cleanly on daily closes. Values between 0.25 and 0.75 are common. The exit should not be set too tight — exiting at exit_z = 0 on daily bars can result in premature exits that miss the full reversion move.
What to tune and what to be careful about. Optimize lookback first, holding entry and exit z fixed at conventional values (2.0/0.5). This tells you the time horizon of the reversion signal. Then test sensitivity: the edge should produce positive results across a reasonable range of parameter values around your chosen point — not only at exactly the optimized value. If performance drops sharply as soon as you move away from the best parameter, the strategy is overfit to the test period. Also note that this signal function does not handle position sizing, transaction costs, or the holding period between entry and exit — those are inputs to the surrounding backtest framework, not this function.
Running a quick sanity check.
import yfinance as yf
prices = yf.download("SPY", start="2020-01-01", end="2023-12-31")["Close"].squeeze()
sig = mean_reversion_signal(prices, lookback=20, entry_z=2.0, exit_z=0.5)
print(sig.value_counts())
print(f"Long entries: {(sig == 1).sum()}")
print(f"Short entries: {(sig == -1).sum()}")
print(f"Flat periods: {(sig == 0).sum()}")
On SPY daily data from 2020 to 2023, at entry_z=2.0, expect a low number of entries — SPY rarely moves more than 2 standard deviations from its 20-day mean on a closing basis without it being a regime event. If you see hundreds of entries, check that the rolling standard deviation is not collapsing to near zero during flat periods, which would inflate the z-score artificially.
The Oyamori Approach
Oyamori treats mean reversion as one of several persistent edge categories in the catalog, alongside momentum, structural flow, and sentiment-driven signals. The platform tracks each edge's live performance against its in-sample baseline, monitors for the regime transitions and crowding signals that indicate when the edge is stressed, and surfaces those conditions before they become losses.
The implementation above is a single-asset signal. Oyamori's mean reversion edges extend to pairs and small baskets of correlated assets, where the reversion mechanism is stronger because it is grounded in a structural economic relationship rather than statistical stationarity alone. A pair that reverts because of shared cost structures or regulatory exposure is a more durable edge than a single stock reverting to its own rolling mean.
For traders building their own mean reversion framework, the how-to-find-your-trading-edge article covers the hypothesis-first process that applies directly to mean reversion edge discovery — including how to document the mechanism and specify the conditions under which you would stop trading it.