Mean Reversion Strategies
We are flooded with news of investors flocking to the stock market, driven by headlines touting the latest market trends and movements. Amidst the noise, we tend to miss out on a phenomenon which explains the bigger picture. That is the concept of mean reversion. While it’s easy to get caught up in the excitement of stock price fluctuations, there exists a tendency for stock prices to revert back to their historical averages over time. This suggests that even amid the chaos of market dynamics, there exists a certain equilibrium — a ‘mean return’ — that governs the broader equity universe.
This article aims to offer an introductory overview of mean-reversion strategies by delving into key concepts that underpin such approaches. These foundational concepts include stationarity, cointegration, and other pertinent principles essential to understanding the mechanics and rationale behind mean-reversion strategies.
A Quantitative Introduction
Let us first understand the relationship between individual stock returns and the overall market return.
Theoretically, if we introduce a daily statistical weight, w, for stock s, on day d, the market return M is defined through stock returns R. Each stock’s return time series is correlated with, or partially explained by the market. By running a rolling-window or ordinary least-squares regression, we are able to determine the market betas. The residual return term, r, is a stock-specific daily return that is a non-linear function of R.
By calculating market betas through regression analysis, traders can identify deviations where a stock’s return diverges from what would be expected based on its relationship with the market. These deviations may signal opportunities for mean reversion strategies, as stocks that exhibit unusually high or low residual returns compared to historical norms may be poised to revert back towards their expected levels relative to the market. Traders can leverage this information to formulate trading strategies, such as shorting stocks with high residual returns and buying those with low residual returns, in anticipation of these deviations correcting over time.
Quants explains this phenomenon as stocks moving along the mean flow, experiencing Brownian residual motion in the frame of reference of that flow. Thus, residual deviations revert to keep stocks with the mean. This mean-reversion behavior can be observed in cross-sectional regression analyses comparing future and past residuals. This principle underpins the development of more sophisticated multi-factor residual models, such as the Ornstein-Uhlenbeck process, which states that stock prices gravitate towards a central point of attraction, akin to the origin, over time.
Cointegration
In Econometrics and Time-Series Analysis, we introduce the concept of cointegration. Cointegration is one of the key components in mean-reversion strategies, particularly in pairs trading. Two time series are said to be cointegrated if a linear combination has constant mean and standard deviation. Cointegration is a useful technique for studying relationships in a multivariate time series. It is useful to model both long-run and short-run dynamics in a financial system. It is a more robust measure than correlation when measuring the linkage between two financial quantities. Moreover, it is important to note that cointegration differs from correlation. Two series can be highly correlated but may not be cointegrated. A time-series can be greater than another with a factor of 2, indicating high correlation. However, any linear combination will also grow rather than revert to a stable mean.
The idea of mean-reversion ties in to the concept of stationarity. A stationary time series is one whose properties do not depend on the time at which the series is observed. Thus, time series with trends, or with seasonality, are not stationary since the trend and seasonality will affect the value of the time series at different times. The statistical properties, such as the mean, variance and autocorrelation, of a stationary time series are independent of the period.
To meet the requirement of stationarity, we often have to manipulate the time-series data in several steps. One way is to divide a time series by another series that causes its trending behavior. However, a more common approach is to use season-to-season differences where we use the result of subtracting neighboring data points or values at seasonal lags from each other. If a univariate series becomes stationary after differential d times, it is said to be integrated of the order d. This helps to eliminate any form of unit roots. The defining characteristic of a non-stationary series with a unit root is its long memory. Since the current values are the sum of past disturbances, large innovations persist for much longer than for a mean-reverting stationary series.
Thankfully, it is really easy to implement in Python. Taking S&P 500 for example:
# Calculate the first difference
sp500_data['Close_diff'] = sp500_data['Close'].diff()
# Apply a log transformation to the differenced data
sp500_data['Log_diff'] = np.log(1 + sp500_data['Close_diff'])
# Calculate the seasonal difference (e.g., for monthly seasonality, you can use a lag of 12)
sp500_data['Seasonal_diff'] = sp500_data['Log_diff'].diff(12)
# Plot the seasonally differenced data
plt.figure(figsize=(12, 6))
plt.plot(sp500_data.index, sp500_data['Seasonal_diff'], label='Seasonal Difference')
plt.title('Seasonally Differenced S&P 500 Stock Prices')
plt.xlabel('Date')
plt.ylabel('Value')
plt.legend()
plt.grid(True)
plt.show()
Cointegration taps on the concept of stationarity. Short-term movements are alike in a statistical sense. It requires the joint distribution of any subset of time-series observations to be independent of time with respect to all moments, higher moments such as skew and kurtosis need to be constant, irrespective of the lag between different observations.
Usually, the residuals of the regression of one integrated time series on one or more such series yields non-stationary residuals that are also integrated and thus, behave like a random walk as mentioned above. However, in a cointegrated time-series, the regression produces coefficients that yield a linear combination of the time series in the form of the residuals that are stationary, even though the individual series are not. More importantly, the key takeaway is that its residuals are stationary with the two time-series having a mean reverting property. The most common example of cointegration is of a drunk man leashing his dog. Both trajectories are non-stationary, yet it is cointegrated as the dog and the drunk man will revert back to each other.
One way to test for stationarity is to use the Augmented Dickey-Fuller test (ADF test) which evaluates the null hypothesis that a time-series has a unit root against the alternative hypothesis that the time-series data is indeed stationary. The ADF test regresses the time series differences on a time trend on all lagged differences and computes a test statistic from the value of the coefficient on the lagged time-series value.
ADF Test is also really easy to implement in Python. Akaike Information Criteria (AIC) or Bayesian Information Criterion (BIC) will determine the lagged order of differencing.
# Perform the ADF test on the cleaned data
result = adfuller(sp500_data_cleaned['Seasonal_diff'], autolag='AIC')
# Extract and print the test results
adf_statistic = result[0]
p_value = result[1]
critical_values = result[4]
print(f'ADF Statistic: {adf_statistic}')
print(f'p-value: {p_value}')
print('Critical Values:')
for key, value in critical_values.items():
print(f' {key}: {value}')
# Check if the data is stationary based on the p-value
if p_value <= 0.05:
print('The data is likely stationary (reject null hypothesis)')
else:
print('The data is likely non-stationary (fail to reject null hypothesis)')
Output:
ADF Statistic: -19.563018891831202
p-value: 0.0
Critical Values:
1%: -3.4321522387754775
5%: -2.862336328589075
10%: -2.567193897993964
The data is likely stationary (reject null hypothesis)
There are two ways to test for cointegration — Engle-Granger Two-Step method and Johansson Likelihood-Ratio test.
The Engle-Granger test enables us to find the optimal hedge ratio by ruining a linear regression fit between two price series and using that hedge ratio to form a portfolio. Afterwards, we will run a stationarity test on this portfolio price series.
Testing for Cointegration
Engle-Granger Two-Step Test:
Step 1 — Cointegration Test: In the first step, the Engle-Granger method involves running a regression analysis on the two time-series variables to test for cointegration. This involves regressing one variable on the other and testing whether the residuals from this regression are stationary using Augmented Dickey-Fuller (ADF) test.
Step 2 — Error Correction: If cointegration is found in the first step, the second step involves estimating an error correction model (ECM). This model captures the short-term dynamics between the variables and their long-term equilibrium relationship. The error correction term captures the speed at which the variables adjust back to their equilibrium relationship after a shock.
When applied to more than two time-series, we need to use the Johansson Likelihood test. We will use a price variable that are vectors representing multiple price series with coefficients lambda and alpha as matrices. It helps to find the number of independent portfolios that can be formed by various linear combinations of the cointegrating price series based on both the eigenvector decomposition.
# Run the cointegration test
result = coint(data['variable1'], data['variable2'])
# Check if the p-value is less than your chosen significance level (e.g., 0.05)
if result[1] < 0.05:
print("Cointegration detected")
else:
print("No cointegration detected")
# If cointegration is detected, proceed to estimate the error correction model (ECM) in the second step
Johansen Likelihood-Ratio Test:
The test involves estimating a Vector Autoregression (VAR) model with the variables of interest and then testing for the rank of cointegration using likelihood ratio tests. The rank of cointegration refers to the number of cointegrating vectors or long-run relationships among the variables.
The Johansen test provides critical statistics such as trace statistics and eigenvalue statistics to determine the presence of cointegration and the number of cointegrating relationships among the variables.
If all of the hypotheses are rejected, then clearly, we have no independent portfolios that can form by linear combinations of the cointegrating price series. This indicates we have no cointegrating relationships. As a useful by-product, the eigenvectors found can be used as our hedge ratios for the individual price series to form a stationary portfolio.
Run the Johansen test
result = coint_johansen(data, det_order=0, k_ar_diff=1)
# Extract critical values and test statistics
trace_stat = result.lr1
eigen_stat = result.lr2
# Compare test statistics to critical values to determine the presence of cointegration
An Overview of Mean Reversion Strategies
The idea of mean-reversion strategies is simple. We calculate a common mean for both assets. If one asset hits an upper boundary above its mean, we sell that asset. On the other hand, if the other asset hits a lower boundary below its mean, we buy that asset. Based on its historical past-prices, the prices will revert back to the common mean. From the perspective of a trader, we can profit from this convergence.
Linear Mean-Reverting Strategy
Let us consider a short half-life linear mean-reverting strategy using the Johansen Test calculated eigenvector. The idea is that we own a number of units of one security to another proportional to their negative normalized deviation from its moving average (Z-Score), hoping that the two assets revert back to its mean. Obviously, this linear mean-reverting strategy is not practical due to its simplicity. We assume that we can enter an infinitesimal number of shares whenever the price moves by an infinitesimal amount. Furthermore, this strategy has virtually no parameters and therefore subjected to data-snooping bias (overfitting).
Bollinger Bands
Another mean-reverting strategy we can consider is the use of Bollinger Bands where we enter into a position only when the price deviates by more than an entry Z-score standard deviations from the mean (vice versa for Z-score exit). The Z-score entry is a free parameter which can be optimized during training and both standard deviations and mean can be computed within a look-back period. We can customize our Z-score entry and exit magnitude. This might have to be a fan-favorite for those with a technical analysis background.
window = 20
stock_data['SMA'] = stock_data['Adj Close'].rolling(window=window).mean()
stock_data['STD'] = stock_data['Adj Close'].rolling(window=window).std()
# Calculate Bollinger Bands
stock_data['Upper Band'] = stock_data['SMA'] + 2 * stock_data['STD']
stock_data['Lower Band'] = stock_data['SMA'] - 2 * stock_data['STD']
# Create signals
stock_data['Signal'] = np.where(stock_data['Adj Close'] < stock_data['Lower Band'], 1.0, 0.0)
stock_data['Signal'] = np.where(stock_data['Adj Close'] > stock_data['Upper Band'], -1.0, stock_data['Signal'])
# Calculate returns
stock_data['Returns'] = stock_data['Adj Close'].pct_change()
# Calculate strategy returns
stock_data['Strategy Returns'] = stock_data['Signal'].shift(1) * stock_data['Returns']
# Plotting
plt.figure(figsize=(14,7))
plt.plot(stock_data['Adj Close'], label='Close Price')
plt.plot(stock_data['SMA'], label='20-Day SMA')
plt.plot(stock_data['Upper Band'], label='Upper Bollinger Band')
plt.plot(stock_data['Lower Band'], label='Lower Bollinger Band')
plt.plot(stock_data[stock_data['Signal'] == 1.0].index,
stock_data['Adj Close'][stock_data['Signal'] == 1.0],
'^', markersize=10, color='g', lw=0, label='Buy Signal')
plt.plot(stock_data[stock_data['Signal'] == -1.0].index,
stock_data['Adj Close'][stock_data['Signal'] == -1.0],
'v', markersize=10, color='r', lw=0, label='Sell Signal')
plt.title('Mean Reversion Strategy with Bollinger Bands')
plt.legend()
plt.show()
Arbitrage Strategies
In financial markets, arbitrage occurs when traders exploit price differences between different financial instruments, such as stocks, bonds, currencies, or commodities, to make a risk-free profit. Arbitrage opportunities typically arise due to inefficiencies in the market or delays in information dissemination, allowing traders to buy low in one market and sell high in another. For instance, arbitrage opportunities arise from the relationship between index futures and the underlying stocks within an index. The prices of stocks in an index tend to closely track the value of the index futures, creating potential for profit through arbitrage. High-frequency trading firms exploit this relationship by leveraging intraday computations of both the futures contracts and the component stocks. They capitalize on discrepancies between the true market value of the index’s component stocks and the value reflected in the index futures. While the prices of the stocks in the index and the index futures generally move in tandem, there are instances where the futures prices lag behind changes in the stock prices, presenting opportunities for arbitrageurs to capitalize on market inefficiencies and profit from the convergence of prices over time.
Mean-Reversion Sizing Approach
One common approach in a mean-reverting strategy is to scale into a position. As the price of an asset moves further away from its mean, the potential profit from an eventual reversal also increases, prompting traders to add to their positions. This approach can be likened to cost-averaging in both directions. Additionally, traders employing this strategy don’t necessarily have to wait until the price fully reverts to its mean before taking profits; they can exit their positions as soon as the price reverts by a small increment. This flexibility is particularly valuable in cases where traders misjudge the stationarity of the time-series and the price fails to revert to its mean. Despite this, traders can still realize small profits by exiting their positions early.
Others may even posit a more dynamic method like the Kalman filter which can be an effective alternative to scaling into a position. Unlike scaling into a position, which involves gradually increasing the size of a trade as the price moves further away from its mean, the Kalman filter can provide more precise and timely signals for adjusting positions. By continuously updating its estimates based on new price data, the Kalman filter can help traders adapt to changing market conditions more effectively (this will be covered in a future article, stay tuned!).
Advantages of Mean Reversion Strategies
A significant advantage lies in the plethora of options available for such trades. All we need to do is to identify a pair of cointegrating assets that enables us to construct our own stationary, mean-reverting portfolio. Moreover, while many perceive this as a quantitative or statistical strategy, mean-reversion strategies are strongly supported by fundamental principles. This fundamental reasoning sets them apart from many momentum strategies, which rely solely on the presence of slower-reacting investors. Particularly advantageous for short-term traders, the shorter time scales associated with mean-reversion strategies lead to a higher frequency of trades over the long run, resulting in greater statistical confidence during back-testing and potentially higher returns.
Disadvantages of Mean Reversion Strategies
However, mean-reversion strategies are also vulnerable to fundamental shifts in market regime. Additionally, prolonged success with such strategies often leads to higher leverage, amplifying losses during prolonged periods of drawdowns.
Final Thoughts
In reality, it is increasingly difficult to profit from mean-reversion strategies. In the past, normal profits from pairs that do mean-revert are large enough to cover those losses from pairs that do not if we have a sufficiently large number of trades happening (the property of Law of Large Numbers). I believe the main reason behind the success of mean reversion is due to markets being much more inefficient back then. This could be due to the increasingly rapid flow of information and processing speeds today, leading to more crowded and noisy trades.
Furthermore, daily time-series of stocks almost never meet the definition of stationarity as many assume the geometric random walk of stocks — “once they walk away, they hardly return to their starting points”.
Lastly, assets seldom cointegrate out-of-sample. It is easy to find cointegrating pairs in any chosen period in time but when tested out-of-sample, it can lose its cointegration property just as easily.
However, I believe it is still possible to profit from mean-reversion strategies as long as we have a thorough fundamental understanding of the assets we are trading.
Finally, in the book - Algorithmic Trading: Winning Strategies and Their Rationale by Dr. Ernest Chan, he talks about a scientific process when approaching trading.
“When a trading strategy stops working, we should form a hypothesis of the reason, and then test empirically whether that hypothesis is supported by data. The outcome of this process is often a modified strategy that regains profits.”
By following this process, I believe that traders can adapt and refine their strategies, potentially recovering lost profits or even better, make profits.
References
[1] Algorithmic Trading: Winning Strategies and Their Rationale — Dr. Ernest Chan
[2] Machine Learning for Algorithmic Trading — Stefan Jansen
[3] Quantitative Finance V2 — Jonathan Ho
[4] Quantitative Portfolio Management — Micheal Isichenko