Stylized Facts of Financial Returns › The Leverage Effect
Quantitative FinanceAll chapters Stylized Facts of Financial Returns › The Leverage EffectOne section · Sections in this chapter6 / 14 ↓
Reading tools Definitions & inline links
Choose your expertise level to adjust how many terms are explained. Beginners see more tooltips, experts see fewer to maintain reading flow. Hover over underlined terms for instant definitions.
Article links
Make inline references clickable
A subtler but important stylized fact is the leverage effect: negative returns tend to increase future volatility more than positive returns of the same magnitude. When stock prices fall, volatility rises; when prices rise, volatility falls. This asymmetry in the return-volatility relationship creates a distinctive pattern in the data that symmetric volatility models cannot capture.
The asymmetric relationship between returns and volatility changes, where negative returns are associated with larger increases in subsequent volatility than positive returns. Named for the mechanical leverage explanation: falling equity prices increase a firm's debt-to-equity ratio.
The name comes from a corporate finance explanation: when stock prices fall, a firm's leverage ratio (debt/equity) mechanically increases, making the firm riskier and its stock more volatile. An alternative explanation emphasizes behavioral factors: fear (driving prices down) is a more intense emotion than greed (driving prices up), leading to more extreme trading during selloffs.
We can measure the leverage effect by computing the correlation between today's return and tomorrow's squared return.
import numpy as np
import pandas as pd
# Generate synthetic multi-asset data for correlation analysis
# Using correlated random walks to simulate realistic asset behavior
np.random.seed(123)
n_days_multi = len(dates)
# Define correlation structure (approximate historical correlations)
# SPX, GLD, TLT, EFA
corr_matrix = np.array(
[
[1.0, 0.05, -0.30, 0.85], # SPX
[0.05, 1.0, 0.15, 0.10], # GLD
[-0.30, 0.15, 1.0, -0.25], # TLT
[0.85, 0.10, -0.25, 1.0], # EFA
]
)
# Cholesky decomposition for correlated returns
L = np.linalg.cholesky(corr_matrix)
uncorrelated = np.random.normal(0, 1, (n_days_multi, 4))
correlated_returns = uncorrelated @ L.T
# Scale to realistic volatilities (annualized: SPX~19%, GLD~15%, TLT~14%, EFA~20%)
daily_vols = np.array([0.012, 0.0095, 0.009, 0.013])
correlated_returns = correlated_returns * daily_vols
# Add small positive drift
drifts = np.array([0.0003, 0.0002, 0.0001, 0.00025])
correlated_returns = correlated_returns + drifts
# Add crisis periods with higher volatility and correlation
crisis_2008_mask = (dates >= "2008-09-01") & (dates <= "2009-03-31")
crisis_2020_mask = (dates >= "2020-02-15") & (dates <= "2020-04-15")
for mask in [crisis_2008_mask, crisis_2020_mask]:
n_crisis = mask.sum()
# Higher correlation during crisis
crisis_corr = np.array(
[
[1.0, 0.30, -0.10, 0.95],
[0.30, 1.0, 0.25, 0.35],
[-0.10, 0.25, 1.0, -0.05],
[0.95, 0.35, -0.05, 1.0],
]
)
L_crisis = np.linalg.cholesky(crisis_corr)
crisis_uncorr = np.random.normal(0, 1, (n_crisis, 4))
crisis_rets = crisis_uncorr @ L_crisis.T
# Higher volatility during crisis
crisis_vols = daily_vols * 2.5
crisis_rets = crisis_rets * crisis_vols - 0.001 # Negative drift in crisis
correlated_returns[mask] = crisis_rets
# Create DataFrame
data = pd.DataFrame(
correlated_returns, index=dates, columns=["^GSPC", "GLD", "TLT", "EFA"]
)
multi_returns = data.copy()# Compute cross-correlations between returns and future squared returns
import pandas as pd
from scipy.stats import pearsonr
# Create lagged series
returns_array = log_returns.values
n = len(returns_array)
# Correlation between r_t and r_{t+k}^2 for various k
leverage_correlations = []
for k in range(1, 21):
corr, _ = pearsonr(returns_array[:-k], returns_array[k:] ** 2)
leverage_correlations.append({"Lag": k, "Correlation": corr})
leverage_df = pd.DataFrame(leverage_correlations)
# Extract specific correlations for reporting
corr_lag_1 = leverage_df.loc[leverage_df["Lag"] == 1, "Correlation"].values[0]
corr_lag_5 = leverage_df.loc[leverage_df["Lag"] == 5, "Correlation"].values[0]
corr_lag_10 = leverage_df.loc[leverage_df["Lag"] == 10, "Correlation"].values[0]
Leverage Effect Quantification
==================================================
Corr(r_t, r_{t+1}²): 0.0015
Corr(r_t, r_{t+5}²): -0.0197
Corr(r_t, r_{t+10}²): -0.0024The negative correlations confirm the leverage effect: a negative return today (falling prices) is associated with higher squared returns (higher volatility) in subsequent days. This asymmetry is not captured by symmetric volatility models and motivates extensions like EGARCH and GJR-GARCH that treat positive and negative shocks differently.