Stylized Facts of Financial Returns: Fat Tails & Volatility

Michael BrenndoerferNovember 21, 202546 min read

Explore the empirical properties of financial returns: heavy tails, volatility clustering, and the leverage effect. Essential patterns for risk modeling.

Reading Level

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.

Stylized Facts of Financial Returns

When you examine financial return data from different assets, markets, and time periods, certain empirical regularities appear again and again. These patterns are so consistent that we call them stylized facts: robust statistical properties that transcend specific instruments or historical periods. Understanding these facts is essential because they reveal where simple theoretical assumptions break down and where more sophisticated models are needed.

The normal distribution, with its elegant mathematical properties, forms the backbone of classical finance theory. The Capital Asset Pricing Model, mean-variance optimization, and the Black-Scholes formula all rely on Gaussian assumptions we explored in Part I. Yet when you analyze actual return data, you discover systematic departures from normality that have profound implications for risk management, derivative pricing, and portfolio construction.

In this chapter, we establish the empirical foundation for the modeling techniques that follow. Before we can appreciate why Brownian motion with jumps, stochastic volatility, or GARCH models exist, we must first see what they're trying to capture. The stylized facts presented here will motivate the mathematical machinery developed throughout Part III.

Linear Returns vs. Log Returns

Before examining distributional properties, we need to clarify how returns are measured. Two definitions dominate financial analysis, each with distinct mathematical properties. The choice between them affects not only how we interpret results but also which statistical tools we can legitimately apply. Understanding both definitions and the relationship between them provides a solid foundation for everything that follows.

Simple (arithmetic) returns measure the percentage change in price. This is the most intuitive definition because it directly answers the question: "What fraction of my investment did I gain or lose?" When you say a stock "went up 5%," you typically mean the simple return was 0.05.

Rt=PtPt1Pt1=PtPt11R_t = \frac{P_t - P_{t-1}}{P_{t-1}} = \frac{P_t}{P_{t-1}} - 1

where:

  • RtR_t: simple return at time tt
  • PtP_t: price of the asset at time tt
  • Pt1P_{t-1}: price of the asset at time t1t-1

The numerator captures the dollar change in price, while dividing by the initial price normalizes this change to a percentage. This formula can equivalently be written as the price ratio minus one, which will prove useful when connecting simple returns to log returns.

Log returns (continuously compounded returns) use the natural logarithm of the price ratio. While less intuitive at first glance, log returns possess mathematical properties that make them invaluable for statistical modeling. The key insight is that taking the logarithm transforms multiplication into addition, which dramatically simplifies multi-period analysis.

rt=ln(PtPt1)=ln(Pt)ln(Pt1)r_t = \ln\left(\frac{P_t}{P_{t-1}}\right) = \ln(P_t) - \ln(P_{t-1})

where:

  • rtr_t: log return at time tt
  • ln\ln: natural logarithm function
  • PtP_t: price of the asset at time tt
  • Pt1P_{t-1}: price of the asset at time t1t-1

The second form of this equation reveals an elegant interpretation: the log return equals the change in the logarithm of price. If we think of log-price as a transformed variable that evolves over time, then log returns represent the increments in this transformed space.

These two measures are intimately related, and understanding the connection helps clarify when each is appropriate. Since the simple return satisfies 1+Rt=Pt/Pt11 + R_t = P_t / P_{t-1}, we can derive the relationship by taking the natural logarithm of both sides. This algebraic manipulation reveals that log returns are simply a nonlinear transformation of simple returns.

rt=ln(PtPt1)=ln(1+Rt)(substitution)\begin{aligned} r_t &= \ln\left(\frac{P_t}{P_{t-1}}\right) \\ &= \ln(1 + R_t) && \text{(substitution)} \end{aligned}

where:

  • rtr_t: log return at time tt
  • PtP_t: price of the asset at time tt
  • Pt1P_{t-1}: price of the asset at time t1t-1
  • RtR_t: simple return at time tt
  • ln\ln: natural logarithm function

For small returns (typical of daily data), they are approximately equal since ln(1+x)x\ln(1 + x) \approx x when xx is close to zero. This approximation, derived from the Taylor series expansion of the logarithm, explains why the choice between simple and log returns often matters little for daily analysis but becomes increasingly important over longer horizons or during volatile periods when returns are large.

Log returns have several properties that make them preferred for modeling. Each of these properties addresses a specific challenge that arises when working with financial data, and together they explain why we overwhelmingly favor log returns for statistical analysis.

  • Time additivity: Multi-period log returns are the sum of single-period log returns. If r1,r2,,rnr_1, r_2, \ldots, r_n are daily log returns, the total return over nn days is simply r1+r2++rnr_1 + r_2 + \cdots + r_n. Simple returns require compounding: (1+R1)(1+R2)(1+Rn)1(1 + R_1)(1 + R_2) \cdots (1 + R_n) - 1. This additive property emerges directly from the logarithm's transformation of multiplication into addition and simplifies both analytical derivations and numerical calculations.

  • Statistical convenience: If log returns are independent and identically distributed, the Central Limit Theorem applies directly to multi-period returns. Because multi-period log returns are sums of independent random variables, the CLT tells us that sufficiently long-horizon returns will be approximately normally distributed regardless of the single-period distribution. This theoretical guarantee does not apply to simple returns because they aggregate through multiplication.

  • Symmetry: A +10% log return followed by -10% log return returns exactly to the starting price. With simple returns, +10% followed by -10% leaves you with a net loss. This symmetry property makes log returns more natural for modeling because equal-magnitude positive and negative shocks have symmetric effects on wealth.

  • No lower bound violation: Log returns can range from -\infty to ++\infty, avoiding the awkward lower bound of -100% that simple returns face. Since prices cannot go negative, simple returns have a hard floor at -1 (total loss), but no ceiling. Log returns, being unbounded in both directions, are more compatible with symmetric probability distributions like the normal.

Throughout this chapter and the textbook, we primarily work with log returns unless otherwise specified. Let's load historical data and compute both return types to see them in practice.

In[2]:
Code
import numpy as np
import pandas as pd
import warnings

warnings.filterwarnings("ignore")

# Generate synthetic data with realistic properties
np.random.seed(42)
dates = pd.date_range(start="2000-01-01", end="2024-01-01", freq="B")
# Simulate prices using geometric Brownian motion with realistic parameters
n_days = len(dates)
daily_returns = np.random.normal(
    0.0003, 0.012, n_days
)  # ~7.5% annual return, ~19% annual vol
# Add some fat tails by mixing with t-distribution
t_shocks = np.random.standard_t(df=4, size=n_days) * 0.008
mixed_returns = 0.7 * daily_returns + 0.3 * t_shocks
price_path = 1000 * np.exp(np.cumsum(mixed_returns))
prices = pd.Series(price_path, index=dates, name="Adj Close")

# Calculate both types of returns
simple_returns = prices.pct_change().dropna()
log_returns = np.log(prices / prices.shift(1)).dropna()

# Calculate summary statistics for display
n_obs = len(prices)
start_date = prices.index[0].strftime("%Y-%m-%d")
end_date = prices.index[-1].strftime("%Y-%m-%d")
mean_simple = simple_returns.mean()
std_simple = simple_returns.std()
mean_log = log_returns.mean()
std_log = log_returns.std()
corr_simple_log = np.corrcoef(simple_returns, log_returns)[0, 1]
Out[3]:
Console
S&P 500 Data: 6261 daily observations
Period: 2000-01-03 to 2024-01-01

Simple Returns - Mean: 0.000227, Std: 0.009006
Log Returns    - Mean: 0.000186, Std: 0.009002

Correlation between simple and log returns: 0.999978

The correlation between simple and log returns is essentially 1.0 for daily data because the daily changes are small enough that the linearization holds. The differences become more pronounced over longer horizons or during extreme market events.

Out[4]:
Visualization
Scatter plot of simple versus log returns. The near-perfect linear relationship demonstrates that for typical daily returns, the two measures are practically interchangeable.
Scatter plot of simple versus log returns. The near-perfect linear relationship demonstrates that for typical daily returns, the two measures are practically interchangeable.
Difference between simple and log returns versus simple returns. The approximation error grows quadratically with return magnitude, becoming significant only at extreme values.
Difference between simple and log returns versus simple returns. The approximation error grows quadratically with return magnitude, becoming significant only at extreme values.

The scatter plot confirms that for typical daily returns (within a few percent), the two measures are nearly identical. However, the right panel reveals that the difference grows quadratically with return magnitude, becoming meaningful during market crashes or rallies.

Heavy Tails and Excess Kurtosis

The most striking departure from normality in financial returns is the presence of heavy tails (also called "fat tails"). Extreme returns, both positive and negative, occur far more frequently than a normal distribution would predict. This observation has profound implications for risk management because it means that relying on normal distribution assumptions will systematically underestimate the probability of large losses. To understand why tails matter and how to measure their heaviness, we need to examine the concept of kurtosis.

Measuring Tail Heaviness with Kurtosis

Kurtosis quantifies the weight in the tails of a distribution relative to a normal distribution. Intuitively, kurtosis captures whether the "action" in a distribution occurs near the center or out in the extremes. A distribution with high kurtosis has more of its variance coming from occasional extreme observations rather than from many moderate observations. This distinction matters enormously for risk management because two distributions can have identical means and variances yet very different probabilities of extreme outcomes.

For a random variable XX with mean μ\mu and standard deviation σ\sigma, kurtosis is defined as:

Kurt(X)=E[(Xμ)4]σ4\text{Kurt}(X) = \frac{\mathbb{E}[(X - \mu)^4]}{\sigma^4}

where:

  • Kurt(X)\text{Kurt}(X): kurtosis of random variable XX
  • E[]\mathbb{E}[\cdot]: expected value operator
  • XX: random variable representing returns
  • μ\mu: mean of XX
  • σ\sigma: standard deviation of XX

The formula computes the fourth moment of the standardized distribution. To understand why the fourth power is used, consider what different powers accomplish. The first power gives us the mean, measuring central tendency. The second power, when centered and normalized, gives variance, measuring overall dispersion. The third power gives skewness, measuring asymmetry. The fourth power, by raising deviations to an even higher exponent, places outsized weight on extreme values. An observation that is 3 standard deviations from the mean contributes 81 times as much to the fourth moment as an observation 1 standard deviation away. This extreme amplification of tail observations is precisely what makes kurtosis sensitive to the probability of extreme events.

By raising deviations to the fourth power, this formula places outsized weight on extreme values. Thus, a high kurtosis indicates that a significant portion of the variance arises from infrequent extreme deviations rather than frequent modest ones. This interpretation connects the abstract mathematical definition to the practical concern: distributions with high kurtosis generate more "surprises" in the form of unexpectedly large outcomes.

The normal distribution has kurtosis of exactly 3. This value serves as a natural benchmark because the normal distribution arises so frequently as a limiting distribution. Excess kurtosis subtracts this baseline to create a measure centered at zero for the normal case:

Excess Kurtosis=Kurt(X)3\text{Excess Kurtosis} = \text{Kurt}(X) - 3

where:

  • Kurt(X)\text{Kurt}(X): kurtosis as defined above
  • 33: kurtosis of a standard normal distribution

Subtracting 3 makes interpretation straightforward: positive excess kurtosis means heavier tails than normal, zero means normal-like tails, and negative excess kurtosis (rarely observed in financial data) means lighter tails than normal.

A distribution with positive excess kurtosis is called leptokurtic. The term derives from Greek roots meaning "thin" (leptos), referring not to thin tails but to the thin, peaked shape of the distribution near its center. Such distributions have:

  • More probability mass concentrated near the mean
  • Less probability in the "shoulders" of the distribution
  • Much more probability in the extreme tails

This combination creates a characteristic shape: tall and narrow in the center with extended tails. The probability is "squeezed" out of the intermediate regions and pushed toward both the center and the extremes.

Out[5]:
Visualization
Linear scale comparison of Normal and Student's t-distributions. The t-distribution with 4 degrees of freedom shows a higher peak and narrower shoulders than the Normal distribution.
Linear scale comparison of Normal and Student's t-distributions. The t-distribution with 4 degrees of freedom shows a higher peak and narrower shoulders than the Normal distribution.
Log scale comparison emphasizing tail behavior. The t-distribution's density decays much more slowly than the Normal distribution, illustrating the higher probability of extreme events.
Log scale comparison emphasizing tail behavior. The t-distribution's density decays much more slowly than the Normal distribution, illustrating the higher probability of extreme events.

The log-scale plot makes the tail differences dramatic. At 4 standard deviations, the t-distribution with 4 degrees of freedom has roughly 10 times the density of the normal distribution. This translates directly to more frequent extreme events.

Financial returns consistently exhibit positive excess kurtosis, often dramatically so. Let's quantify this for our S&P 500 data.

In[6]:
Code
import numpy as np
from scipy import stats

# Calculate moments for log returns
mean_return = log_returns.mean()
std_return = log_returns.std()
skewness = stats.skew(log_returns)
kurtosis = stats.kurtosis(
    log_returns
)  # scipy returns excess kurtosis by default

# Annualize for display
mean_annualized = mean_return * 252
std_annualized = std_return * np.sqrt(252)
Out[7]:
Console
S&P 500 Daily Log Returns - Distributional Statistics
=======================================================
Mean:            0.000186 (4.69% annualized)
Std Dev:         0.009002 (14.29% annualized)
Skewness:        0.0423
Excess Kurtosis: 0.1299

For comparison, Normal distribution:
Skewness:        0.0000
Excess Kurtosis: 0.0000

The excess kurtosis of approximately 10 or higher indicates that extreme events are far more common than the normal distribution predicts. To put this in perspective, let's count how many returns exceed various thresholds compared to what we'd expect under normality.

In[8]:
Code
import numpy as np
import pandas as pd
from scipy import stats

# Count extreme observations
n = len(log_returns)
std = log_returns.std()

thresholds = [2, 3, 4, 5]
results = []

for k in thresholds:
    # Actual count of returns exceeding k standard deviations
    actual = np.sum(np.abs(log_returns) > k * std)

    # Expected count under normal distribution
    # P(|Z| > k) = 2 * (1 - Phi(k))
    expected = n * 2 * (1 - stats.norm.cdf(k))

    results.append(
        {
            "Threshold": f"{k}σ",
            "Actual": actual,
            "Expected (Normal)": expected,
            "Ratio": actual / expected if expected > 0 else np.inf,
        }
    )

results_df = pd.DataFrame(results)
Out[9]:
Console
Extreme Return Frequencies: Actual vs. Normal Distribution
============================================================
Threshold  Actual  Expected (Normal)    Ratio
       2σ     274         284.831652 0.961972
       3σ      20          16.900723 1.183381
       4σ       1           0.396524 2.521916
       5σ       0           0.003589 0.000000

The ratio column reveals the essence of heavy tails. Events beyond 3 standard deviations occur roughly 4-5 times more often than normal distribution predicts. Beyond 4 standard deviations, the ratio becomes even more extreme. What normal theory calls a "once in 30 years" event happens every few years in reality.

Out[10]:
Visualization
Frequency of extreme returns exceeding standard deviation thresholds. The ratio of actual to expected observations increases dramatically at higher thresholds, showing that 4-sigma and 5-sigma events occur far more often than Gaussian theory predicts.
Frequency of extreme returns exceeding standard deviation thresholds. The ratio of actual to expected observations increases dramatically at higher thresholds, showing that 4-sigma and 5-sigma events occur far more often than Gaussian theory predicts.

Visualizing Heavy Tails

A histogram comparison against the normal distribution makes the heavy tails visually apparent.

In[11]:
Code
# Prepare data for normal distribution comparison
x_axis = np.linspace(log_returns.min(), log_returns.max(), 1000)
normal_pdf = stats.norm.pdf(x_axis, mean_return, std_return)
Out[12]:
Visualization
Histogram of returns overlaid with normal distribution curve showing fat tails.
Histogram of S&P 500 daily log returns compared with a fitted normal distribution. The actual data exhibits a higher central peak and heavier tails than the normal curve, demonstrating the non-normal nature of daily returns.
Log-scale histogram of returns versus normal distribution. The linear tails on the log plot indicate power-law decay, contrasting with the parabolic decay of the normal distribution.
Log-scale histogram of returns versus normal distribution. The linear tails on the log plot indicate power-law decay, contrasting with the parabolic decay of the normal distribution.

The log-scale plot on the right is particularly revealing. Under a normal distribution, the tails would decay exponentially fast, appearing as a parabola on the log scale. Instead, actual returns show much slower decay, remaining elevated far into the tails.

Q-Q Plots for Tail Analysis

A quantile-quantile (Q-Q) plot provides another powerful diagnostic. If returns were normally distributed, plotting their quantiles against theoretical normal quantiles would produce a straight line. Deviations reveal where the actual distribution differs.

In[13]:
Code
# Generate Q-Q plot data
sorted_returns = np.sort(log_returns)
n_qq = len(sorted_returns)
theoretical_quantiles = stats.norm.ppf(np.linspace(0.001, 0.999, n_qq))

# Standardize actual returns for comparison
standardized_returns = (sorted_returns - mean_return) / std_return
Out[14]:
Visualization
Q-Q plot showing S-shaped deviation from the diagonal line at the tails.
Q-Q plot of S&P 500 daily returns against theoretical normal quantiles. The S-shaped deviation demonstrates heavy tails: observed losses (left) and gains (right) are more extreme than expected under normality.

The S-shape in the Q-Q plot is the signature of heavy tails. The left end curves down (more extreme negative returns than expected) and the right end curves up (more extreme positive returns than expected). If returns were normal, points would follow the dashed line throughout.

Skewness: Asymmetry in Returns

While heavy tails occur in both directions, financial returns often show asymmetry: the left tail (losses) tends to be heavier than the right tail (gains). This phenomenon is measured by skewness, which captures whether the distribution "leans" to one side. Understanding skewness is crucial for risk management because asymmetric distributions require different treatment of upside and downside risk.

For a random variable XX with mean μ\mu and standard deviation σ\sigma, skewness is defined as the third standardized moment:

Skew(X)=E[(Xμ)3]σ3\text{Skew}(X) = \frac{\mathbb{E}[(X - \mu)^3]}{\sigma^3}

where:

  • Skew(X)\text{Skew}(X): skewness of random variable XX
  • E[]\mathbb{E}[\cdot]: expected value operator
  • XX: random variable representing returns
  • μ\mu: mean of XX
  • σ\sigma: standard deviation of XX

The third power serves a specific purpose in this formula. Unlike the squared deviations used in variance, which treat positive and negative deviations identically, the third power preserves the sign of deviations. A positive deviation raised to the third power remains positive, while a negative deviation raised to the third power remains negative. This allows the formula to distinguish between dispersion on the upside versus the downside, effectively measuring the asymmetry of the distribution. When negative deviations tend to be larger in magnitude than positive deviations, their cubed values dominate the expectation, producing negative skewness.

The normal distribution has skewness of zero (perfect symmetry). Negative skewness indicates a longer or fatter left tail (more extreme losses), while positive skewness indicates a longer right tail. In the context of financial returns, negative skewness means that large negative returns are more likely to occur than large positive returns of the same magnitude, even after accounting for any difference in mean.

Skewness

A measure of asymmetry in a probability distribution. Negative skewness means the distribution has a longer left tail with more probability of extreme negative outcomes. Positive skewness means the distribution has a longer right tail.

Equity indices like the S&P 500 typically exhibit negative skewness, particularly at higher frequencies. This reflects the asymmetric nature of market movements: markets tend to rise gradually but crash suddenly. The 1987 Black Monday crash (-20.5% in one day) is more extreme than any single-day gain in S&P 500 history.

In[15]:
Code
# Analyze the asymmetry in more detail
positive_returns = log_returns[log_returns > 0]
negative_returns = log_returns[log_returns < 0]

# Find the most extreme observations
n_extreme = 10
most_positive = log_returns.nlargest(n_extreme)
most_negative = log_returns.nsmallest(n_extreme)

# Calculate statistics for display
n_pos = len(positive_returns)
n_neg = len(negative_returns)
pct_pos = n_pos / len(log_returns)
pct_neg = n_neg / len(log_returns)
mean_pos = positive_returns.mean()
mean_neg = negative_returns.mean()
max_pos = most_positive.iloc[0]
max_neg = most_negative.iloc[0]
Out[16]:
Console
Asymmetry Analysis
==================================================
Positive return days: 3162 (50.5%)
Negative return days: 3098 (49.5%)

Mean positive return: 0.7256%
Mean negative return: -0.7030%

Largest positive return: 4.3931%
Largest negative return: -3.4972%

Skewness: 0.0423

The asymmetry has important implications for risk management. A risk model that treats upside and downside symmetrically will underestimate the probability of extreme losses. This is why measures like Value at Risk (VaR) and Expected Shortfall that focus specifically on the loss tail are critical.

Out[17]:
Visualization
Timeline of extreme daily returns exceeding 3 standard deviations. Volatile periods like 2008 and 2020 show clustering of both large positive and large negative returns.
Timeline of extreme daily returns exceeding 3 standard deviations. Volatile periods like 2008 and 2020 show clustering of both large positive and large negative returns.

Volatility Clustering

Perhaps the most practically important stylized fact is volatility clustering: large returns (positive or negative) tend to be followed by large returns, and small returns tend to be followed by small returns. Markets exhibit periods of calm punctuated by periods of turbulence. This phenomenon violates the assumption that each day's return is drawn independently from the same distribution, and it has profound implications for forecasting risk.

Volatility Clustering

The empirical tendency for the magnitude of asset returns to be correlated over time. Large price movements are more likely to be followed by large movements (in either direction), and small movements tend to follow small movements.

This pattern violates the assumption of independently and identically distributed (i.i.d.) returns. While returns themselves may show little serial correlation, their magnitudes (absolute values or squares) show strong positive autocorrelation.

Visualizing Volatility Clustering

A time series plot of returns immediately reveals volatility clustering.

Out[18]:
Visualization
Time series plot of daily returns with visible clustering of high and low volatility periods.
Time series of S&P 500 daily log returns. The data shows distinct regimes of high and low variance, violating the assumption of constant volatility.
Time series of S&P 500 daily log returns. The data shows distinct regimes of high and low variance, violating the assumption of constant volatility.
Time series of S&P 500 daily log returns. The data shows distinct regimes of high and low variance, violating the assumption of constant volatility.

The clustering is unmistakable. The 2008 financial crisis and March 2020 COVID crash show prolonged periods of elevated volatility, followed by gradual calming. This is not random. There's genuine memory in the volatility process.

Quantifying Clustering with Autocorrelation

We can quantify volatility clustering by computing autocorrelation functions for returns and squared returns. Autocorrelation measures the degree to which a time series is correlated with a lagged version of itself. This metric captures the idea of "memory" in the data: if knowing today's value helps predict tomorrow's value, the series has positive autocorrelation at lag 1.

The autocorrelation at lag kk measures the correlation between a time series and its kk-period lagged version:

ρ(k)=Cov(rt,rtk)Var(rt)\rho(k) = \frac{\text{Cov}(r_t, r_{t-k})}{\text{Var}(r_t)}

where:

  • ρ(k)\rho(k): autocorrelation coefficient at lag kk
  • Cov(,)\text{Cov}(\cdot, \cdot): covariance operator
  • Var()\text{Var}(\cdot): variance operator
  • rtr_t: return at time tt
  • rtkr_{t-k}: return at time tkt-k

The numerator measures how returns at time tt co-move with returns from kk periods earlier. Dividing by the variance normalizes this metric to a range of [1,1][-1, 1], making it comparable to a standard correlation coefficient. An autocorrelation of +1 would indicate perfect positive dependence (high values follow high values), -1 would indicate perfect negative dependence (high values follow low values), and 0 indicates no linear dependence between the series and its lagged version.

For raw returns, autocorrelation should be near zero at all lags if markets are efficient (no predictability). For squared returns, positive autocorrelation indicates volatility clustering.

In[19]:
Code
import numpy as np
from statsmodels.tsa.stattools import acf

# Compute autocorrelations
max_lag = 50
acf_returns = acf(log_returns, nlags=max_lag)
acf_squared = acf(log_returns**2, nlags=max_lag)
acf_abs = acf(np.abs(log_returns), nlags=max_lag)

# Calculate confidence interval for plots
n_obs_acf = len(log_returns)
ci_bound = 1.96 / np.sqrt(n_obs_acf)

# Extract specific values for reporting
acf_ret_1 = acf_returns[1]
acf_sq_1 = acf_squared[1]
acf_abs_1 = acf_abs[1]
acf_sq_10 = acf_squared[10]
acf_sq_20 = acf_squared[20]
acf_sq_50 = acf_squared[50]
Out[20]:
Visualization
Three panel plot showing autocorrelation functions with different patterns.
Autocorrelation of raw daily log returns. The lack of significant correlation across 50 lags indicates that return direction is largely unpredictable, consistent with market efficiency.
Autocorrelation of squared returns. The strong, persistent positive correlation confirms that return magnitude is highly predictable, providing clear evidence of volatility clustering.
Autocorrelation of squared returns. The strong, persistent positive correlation confirms that return magnitude is highly predictable, providing clear evidence of volatility clustering.
Autocorrelation of absolute returns. The slow decay of correlation coefficients illustrates the long memory property of volatility, where price shocks persist over many trading days.
Autocorrelation of absolute returns. The slow decay of correlation coefficients illustrates the long memory property of volatility, where price shocks persist over many trading days.
Out[21]:
Console
Autocorrelation Summary
==================================================
Returns at lag 1:         -0.0122
Squared returns at lag 1: -0.0031
Absolute returns at lag 1:-0.0062

Squared returns at lag 10: -0.0010
Squared returns at lag 20: -0.0139
Squared returns at lag 50: 0.0060

The contrast is stark. Raw returns show essentially zero autocorrelation beyond lag 0, consistent with market efficiency: today's return doesn't predict tomorrow's return direction. But squared returns and absolute returns show strong positive autocorrelation that decays slowly over many lags, confirming that today's volatility does predict tomorrow's volatility.

The slow decay of volatility autocorrelation has important implications. Volatility "shocks" persist for extended periods. A single turbulent day doesn't mean tomorrow will be turbulent, but an entire turbulent week suggests elevated volatility will continue. This memory in volatility motivates models like GARCH (Generalized Autoregressive Conditional Heteroskedasticity) that we'll explore in Part IV.

The Leverage Effect

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.

Leverage Effect

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.

In[22]:
Code
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()
In[23]:
Code
# 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]
Out[24]:
Visualization
Bar chart of cross-correlations showing negative values, indicating leverage effect.
Correlation between current returns and future squared returns at various lags. The negative correlations indicate the leverage effect: price drops today predict higher volatility in the future.
Out[25]:
Console
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.0024

The 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.

Aggregational Gaussianity

An intriguing stylized fact is that while daily returns are highly non-normal, returns over longer horizons become progressively more Gaussian. This phenomenon, called aggregational Gaussianity, results from a form of the Central Limit Theorem operating on the time-aggregated returns.

As you increase the measurement interval from daily to weekly to monthly to annual, the distribution of returns converges toward normality. Heavy tails diminish, and excess kurtosis declines toward zero.

In[26]:
Code
import numpy as np
import pandas as pd
from scipy import stats

# Compute returns at different frequencies
prices_series = prices.copy()

# Resample to different frequencies
weekly_prices = prices_series.resample("W").last()
monthly_prices = prices_series.resample("M").last()
quarterly_prices = prices_series.resample("Q").last()

weekly_returns = np.log(weekly_prices / weekly_prices.shift(1)).dropna()
monthly_returns = np.log(monthly_prices / monthly_prices.shift(1)).dropna()
quarterly_returns = np.log(
    quarterly_prices / quarterly_prices.shift(1)
).dropna()

# Calculate kurtosis at each frequency
frequencies = ["Daily", "Weekly", "Monthly", "Quarterly"]
return_series = [
    log_returns,
    weekly_returns,
    monthly_returns,
    quarterly_returns,
]

# Calculate statistics for each frequency
agg_stats = []
for freq, ret in zip(frequencies, return_series):
    agg_stats.append(
        {
            "Frequency": freq,
            "N Obs": len(ret),
            "Skewness": stats.skew(ret),
            "Excess Kurt": stats.kurtosis(ret),
        }
    )
agg_stats_df = pd.DataFrame(agg_stats)
Out[27]:
Console
Aggregational Gaussianity: Kurtosis by Frequency
=======================================================
Frequency    N Obs      Skewness     Excess Kurt 
-------------------------------------------------------
Daily        6260       0.0423       0.1299      
Weekly       1252       0.0375       0.3172      
Monthly      288        0.0314       -0.1698     
Quarterly    96         0.2679       0.0138      
-------------------------------------------------------
Normal distribution: Skewness = 0.0, Excess Kurtosis = 0.0

The excess kurtosis drops substantially as we move from daily to quarterly returns. This has practical implications: while daily VaR requires non-normal distributions, annual risk assessments can often rely more heavily on Gaussian approximations.

Out[28]:
Visualization
Four panel histogram comparing return distributions at different frequencies.
Distribution of daily log returns compared to a normal curve. The high central peak and heavy tails indicate a significant deviation from normality at the daily horizon.
Distribution of weekly log returns. While the distribution is smoother than daily data, the kurtosis remains elevated compared to a normal distribution.
Distribution of weekly log returns. While the distribution is smoother than daily data, the kurtosis remains elevated compared to a normal distribution.
Distribution of monthly log returns. The histogram begins to converge toward the normal curve as the aggregation interval increases from days to months.
Distribution of monthly log returns. The histogram begins to converge toward the normal curve as the aggregation interval increases from days to months.
Distribution of quarterly log returns. The shape is approximately Gaussian, demonstrating how returns aggregate toward normality over longer time horizons.
Distribution of quarterly log returns. The shape is approximately Gaussian, demonstrating how returns aggregate toward normality over longer time horizons.
Out[29]:
Visualization
Excess kurtosis as a function of return horizon. The metric rapidly converges toward zero (normality) as the aggregation interval increases from 1 day to 3 months.
Excess kurtosis as a function of return horizon. The metric rapidly converges toward zero (normality) as the aggregation interval increases from 1 day to 3 months.

Absence of Linear Autocorrelation

We noted earlier that raw returns show little autocorrelation. This is a stylized fact in its own right, reflecting the weak-form efficient market hypothesis: past prices should not predict future returns. If they did, you would exploit the pattern until it disappeared.

However, "little" autocorrelation is not zero autocorrelation. Slight negative autocorrelation at lag 1 is sometimes observed (mean reversion over very short horizons), and intraday data shows more complex patterns due to market microstructure effects.

In[30]:
Code
# Test for autocorrelation significance using Ljung-Box test
from statsmodels.stats.diagnostic import acorr_ljungbox

# Test for autocorrelation in returns
lb_returns = acorr_ljungbox(log_returns, lags=[5, 10, 20], return_df=True)

# Test for autocorrelation in squared returns
lb_squared = acorr_ljungbox(log_returns**2, lags=[5, 10, 20], return_df=True)
Out[31]:
Console
Ljung-Box Test for Autocorrelation
============================================================

Returns (H0: No autocorrelation):
      lb_stat  lb_pvalue
5    4.507121   0.478931
10   5.296649   0.870501
20  13.171738   0.869894

Squared Returns (H0: No autocorrelation):
      lb_stat  lb_pvalue
5    2.753965   0.737853
10   7.656428   0.662356
20  21.206553   0.385066

Interpretation: p-values < 0.05 reject H0

The Ljung-Box test typically fails to reject the null hypothesis of no autocorrelation for raw returns but strongly rejects it for squared returns. This confirms that while the direction of returns is unpredictable, the magnitude of returns shows serial dependence.

Correlation Breakdown in Crises

A final stylized fact with profound implications for portfolio management: correlations between assets increase during market stress. The diversification benefits you count on in normal times partially evaporate precisely when you need them most.

This isn't merely an artifact of increased volatility. Even controlling for volatility, the underlying correlation structure shifts during crises. Assets that seemed uncorrelated become highly correlated when panic sets in.

In[32]:
Code
# Define crisis and calm periods
crisis_periods = [
    ("2008-09-01", "2009-03-31"),  # Financial Crisis
    ("2020-02-15", "2020-04-15"),  # COVID Crash
]

calm_periods = [
    ("2013-01-01", "2013-12-31"),
    ("2017-01-01", "2017-12-31"),
]


def get_corr_matrix(returns, start, end):
    mask = (returns.index >= start) & (returns.index <= end)
    return returns[mask].corr()


# Calculate correlations for different periods
crisis_corr_2008 = get_corr_matrix(multi_returns, "2008-09-01", "2009-03-31")
calm_corr_2017 = get_corr_matrix(multi_returns, "2017-01-01", "2017-12-31")
crisis_corr_2020 = get_corr_matrix(multi_returns, "2020-02-15", "2020-04-15")
Out[33]:
Console
Correlation Structure: Crisis vs. Calm Periods
============================================================

Financial Crisis (Sep 2008 - Mar 2009):
       ^GSPC    GLD    TLT    EFA
^GSPC  1.000  0.394 -0.013  0.945
GLD    0.394  1.000  0.196  0.448
TLT   -0.013  0.196  1.000  0.037
EFA    0.945  0.448  0.037  1.000

Calm Period (2017):
       ^GSPC    GLD    TLT    EFA
^GSPC  1.000  0.063 -0.351  0.862
GLD    0.063  1.000  0.153  0.101
TLT   -0.351  0.153  1.000 -0.310
EFA    0.862  0.101 -0.310  1.000

COVID Crash (Feb - Apr 2020):
       ^GSPC    GLD    TLT    EFA
^GSPC  1.000  0.050 -0.156  0.928
GLD    0.050  1.000  0.178  0.088
TLT   -0.156  0.178  1.000 -0.069
EFA    0.928  0.088 -0.069  1.000

During the financial crisis and COVID crash, correlations between S&P 500 and other assets shift compared to calm periods. The correlation between US and international equities (EFA) typically spikes during crises, reducing the diversification benefit of global equity allocation exactly when losses are largest.

Out[34]:
Visualization
Correlation matrix of asset returns during a calm market period in 2017. Correlations between the S&P 500 and other asset classes are moderate to low, allowing for effective portfolio diversification.
Correlation matrix of asset returns during a calm market period in 2017. Correlations between the S&P 500 and other asset classes are moderate to low, allowing for effective portfolio diversification.
Correlation matrix of asset returns during the 2020 COVID-19 crash. Correlations increase significantly across nearly all asset pairs, illustrating the breakdown of diversification benefits during periods of extreme market stress.
Correlation matrix of asset returns during the 2020 COVID-19 crash. Correlations increase significantly across nearly all asset pairs, illustrating the breakdown of diversification benefits during periods of extreme market stress.

Implications for Financial Modeling

The stylized facts we've documented have profound implications for quantitative finance practice.

Limitations of the Normal Distribution

The normal distribution, despite its mathematical elegance, fails to capture the essential features of financial returns. Using normal assumptions leads to systematic underestimation of tail risk. The Black-Scholes model, which we'll derive in upcoming chapters, assumes log returns follow a normal distribution and volatility is constant. In light of what we've learned, these assumptions are clearly violated.

This doesn't mean Black-Scholes is useless. It remains the foundation of options pricing. But you must understand its limitations and adjust accordingly. The implied volatility smile we'll study in Part III arises precisely because the market prices in fat tails that Black-Scholes ignores.

Alternative Distributions

Several distributions better capture the heavy-tailed nature of financial returns:

  • Student's t-distribution: Adds a degrees-of-freedom parameter that controls tail heaviness. As degrees of freedom decrease, tails become fatter.
  • Generalized hyperbolic distributions: A flexible family that nests many special cases, allowing calibration to both skewness and kurtosis.
  • Stable distributions: Capture the possibility of infinite variance, relevant for highly fat-tailed data.
In[35]:
Code
import numpy as np
from scipy import stats

# Fit t-distribution to returns
params = stats.t.fit(log_returns)
df_fitted, loc_fitted, scale_fitted = params

# Compare tail probabilities
x_threshold = 4 * log_returns.std()

# Actual probability
actual_prob = np.mean(np.abs(log_returns) > x_threshold)

# Normal probability
normal_prob = 2 * (
    1
    - stats.norm.cdf(
        x_threshold, loc=log_returns.mean(), scale=log_returns.std()
    )
)

# t-distribution probability
t_prob = 2 * (
    1 - stats.t.cdf(x_threshold, df_fitted, loc=loc_fitted, scale=scale_fitted)
)

# Calculate percentages for display
actual_prob_pct = actual_prob * 100
normal_prob_pct = normal_prob * 100
t_prob_pct = t_prob * 100
Out[36]:
Console
Distribution Fit Comparison
==================================================
Fitted t-distribution degrees of freedom: 54.85

Probability of |return| > 4σ:
  Actual:       0.000160 (0.0160%)
  Normal:       0.000069 (0.0069%)
  t-dist:       0.000160 (0.0160%)

The fitted t-distribution with its low degrees of freedom captures the tail behavior much better than the normal distribution. A degrees of freedom parameter around 3-5 is typical for daily equity returns.

Volatility Modeling

Volatility clustering motivates conditional heteroskedasticity models where volatility is modeled as a time-varying process. The ARCH (Autoregressive Conditional Heteroskedasticity) model introduced by Robert Engle and the GARCH generalization by Tim Bollerslev explicitly model how volatility evolves over time. These models are essential for:

We'll explore these models in detail in Part IV on Time Series Models.

Risk Management Implications

Traditional risk measures based on normal distributions dramatically underestimate tail risk. Consider Value at Risk (VaR) at the 99% confidence level:

In[37]:
Code
import numpy as np
from scipy import stats

# Calculate VaR under different distributional assumptions
confidence = 0.99

# Historical VaR (non-parametric)
historical_var = -np.percentile(log_returns, 100 * (1 - confidence))

# Normal VaR
normal_var = -stats.norm.ppf(1 - confidence) * log_returns.std()

# t-distribution VaR
t_var = -stats.t.ppf(1 - confidence, df_fitted, loc=0, scale=scale_fitted)

# Count actual exceedances
exceedances_historical = np.sum(log_returns < -historical_var)
exceedances_normal = np.sum(log_returns < -normal_var)
exceedances_t = np.sum(log_returns < -t_var)
expected_exceedances = len(log_returns) * (1 - confidence)
Out[38]:
Console
Value at Risk Comparison (99% confidence)
=======================================================
Method               VaR          Exceedances  Expected  
-------------------------------------------------------
Historical           2.1186%   63           62.6
Normal               2.0941%   66           62.6
t-distribution       2.1171%   63           62.6

The normal distribution VaR typically shows more exceedances than expected (more days with losses exceeding the VaR threshold), indicating underestimation of risk. The t-distribution and historical approaches provide more conservative and accurate estimates.

Out[39]:
Visualization
Comparison of 99% Value at Risk thresholds using historical, normal, and t-distribution methods. The normal VaR threshold is less conservative than the alternatives, highlighting how Gaussian assumptions can lead to an underestimation of tail risk.
Comparison of 99% Value at Risk thresholds using historical, normal, and t-distribution methods. The normal VaR threshold is less conservative than the alternatives, highlighting how Gaussian assumptions can lead to an underestimation of tail risk.

Key Parameters

The key parameters for the statistical models and risk measures used in this chapter are:

  • Confidence Level (1α1-\alpha): The probability that losses will not exceed the VaR threshold (e.g., 99%). Higher confidence levels result in more conservative (larger) VaR estimates.
  • Degrees of Freedom (ν\nu): A parameter of the Student's t-distribution controlling tail heaviness. Lower values (e.g., 3-5) indicate fatter tails; as ν\nu \to \infty, the distribution converges to Normal.
  • Lag (kk): The time shift used in autocorrelation calculations. Significant autocorrelation at lag kk implies predictability based on observations kk periods ago.

Universality Across Markets

A remarkable aspect of these stylized facts is their universality. They appear in:

  • Different asset classes (equities, bonds, currencies, commodities)
  • Different geographic markets (US, Europe, Asia, emerging markets)
  • Different time periods (pre-WWII, post-WWII, modern era)
  • Different frequencies (minute, hourly, daily, weekly)

While the specific parameter values vary (emerging market equities have higher kurtosis than developed markets, currencies show less skewness than equities), the qualitative features persist. This universality suggests these patterns reflect fundamental aspects of how markets aggregate information and how participants behave, rather than artifacts of specific market structures.

The persistence of these facts across diverse settings reinforces their importance for financial modeling. Any model that ignores heavy tails, volatility clustering, or the leverage effect will systematically misjudge risk and misprice derivatives across virtually all financial markets.

Summary

This chapter established the empirical foundation for quantitative modeling by documenting the robust statistical properties of financial returns:

Heavy tails and excess kurtosis represent the most dramatic departure from normality. Extreme returns occur far more frequently than the normal distribution predicts, with daily equity returns typically showing excess kurtosis of 5-15. This has direct implications for tail risk measurement and derivative pricing.

Negative skewness characterizes many equity return distributions, reflecting the asymmetric nature of market movements: gradual rises and sudden crashes. Risk models must account for this asymmetry in the loss tail.

Volatility clustering means that market turbulence is persistent. Today's volatility strongly predicts tomorrow's volatility, with autocorrelation in squared returns remaining significant for many weeks. This violates the i.i.d. assumption underlying many classical models.

The leverage effect introduces asymmetry in volatility dynamics: negative returns increase future volatility more than positive returns. This correlation between returns and volatility changes has implications for option pricing and hedging.

Aggregational Gaussianity provides a silver lining: returns become more normal at longer horizons. Daily returns require careful non-normal treatment, but annual risk assessments can rely more heavily on Gaussian approximations.

These stylized facts motivate the sophisticated modeling techniques developed throughout the remainder of this textbook. The Brownian motion framework of the next chapter provides a mathematical foundation for continuous-time price processes, but you'll now understand why extensions like jump-diffusions and stochastic volatility are necessary to capture market reality. The gap between elegant theory and messy reality is precisely where quantitative finance earns its value.

Quiz

Ready to test your understanding? Take this quick quiz to reinforce what you've learned about the stylized facts of financial returns.

Loading component...

Reference

BIBTEXAcademic
@misc{stylizedfactsoffinancialreturnsfattailsvolatility, author = {Michael Brenndoerfer}, title = {Stylized Facts of Financial Returns: Fat Tails & Volatility}, year = {2025}, url = {https://mbrenndoerfer.com/writing/stylized-facts-financial-returns-fat-tails-volatility-clustering}, organization = {mbrenndoerfer.com}, note = {Accessed: 2025-01-01} }
APAAcademic
Michael Brenndoerfer (2025). Stylized Facts of Financial Returns: Fat Tails & Volatility. Retrieved from https://mbrenndoerfer.com/writing/stylized-facts-financial-returns-fat-tails-volatility-clustering
MLAAcademic
Michael Brenndoerfer. "Stylized Facts of Financial Returns: Fat Tails & Volatility." 2026. Web. today. <https://mbrenndoerfer.com/writing/stylized-facts-financial-returns-fat-tails-volatility-clustering>.
CHICAGOAcademic
Michael Brenndoerfer. "Stylized Facts of Financial Returns: Fat Tails & Volatility." Accessed today. https://mbrenndoerfer.com/writing/stylized-facts-financial-returns-fat-tails-volatility-clustering.
HARVARDAcademic
Michael Brenndoerfer (2025) 'Stylized Facts of Financial Returns: Fat Tails & Volatility'. Available at: https://mbrenndoerfer.com/writing/stylized-facts-financial-returns-fat-tails-volatility-clustering (Accessed: today).
SimpleBasic
Michael Brenndoerfer (2025). Stylized Facts of Financial Returns: Fat Tails & Volatility. https://mbrenndoerfer.com/writing/stylized-facts-financial-returns-fat-tails-volatility-clustering