Return Distribution
In finance a return is the gain or loss on an investment over a period, as a fraction of what it started at:
\[ R_t = \frac{P_t - P_{t-1}}{P_{t-1}} \]
The return distribution is the probability distribution of those returns. Instead of “average return is 5%” it carries the whole range of outcomes and their probabilities, which is what any risk calculation actually needs.
Four features describe it. The mean is the expected return. Variance measures squared spread; volatility usually means its square root and the usual measure of risk. Skewness is asymmetry — negative skew means large losses are the long side. Kurtosis, or fat tails, is how much more likely extreme moves are than a normal distribution would say. Real returns generally show both fat tails and negative skew, which is the compact statement of why crashes are more common than classical models predict.
Two arithmetic facts that come first
Before any distributional modeling, the definition above has consequences that decide what the distribution is even a distribution of.
import numpy as np
from scipy.stats import t as tdist, norm, kurtosis, skew
prices = np.array([100.0, 110.0, 99.0, 108.9])
simple = np.diff(prices) / prices[:-1]
logr = np.diff(np.log(prices))
print(f" prices {np.array2string(prices, precision=2)}")
print(f" simple returns {np.array2string(simple, precision=6, floatmode='fixed')}")
print(f" log returns {np.array2string(logr, precision=6, floatmode='fixed')}")
print(f" sum of log returns {logr.sum():>10.6f} -> total growth"
f" {np.exp(logr.sum()):>10.6f}")
print(f" sum of simple {simple.sum():>10.6f} -> actual total"
f" {prices[-1] / prices[0]:>10.6f}")
r = np.array([0.10, -0.10, 0.10, -0.10])
grow = np.prod(1 + r)
print(f" arithmetic mean of simple returns {r.mean():>12.6f}")
print(f" money actually left per unit {grow:>12.6f}")
print(f" geometric mean per period {grow ** (1 / len(r)) - 1:>12.6f}")
nu = 4
draws = 2_000_000
rng = np.random.default_rng(0)
daily = tdist(nu).ppf(rng.random(draws)) / np.sqrt(nu / (nu - 2))
"""Daily returns with the fat tails real markets show, standardized so the
comparison across horizons is about shape and not scale."""
print(f"{'horizon':>12}{'excess kurtosis':>18}{'P(beyond 4 sd)':>17}{'normal would be':>18}")
for h in (1, 5, 21, 63, 252):
a = daily[: (draws // h) * h].reshape(-1, h).sum(axis=1)
a = (a - a.mean()) / a.std()
print(f"{h:>12}{kurtosis(a):>18.4f}{np.mean(np.abs(a) > 4):>17.6f}"
f"{2 * norm.sf(4):>18.6f}")
from scipy.signal import lfilter
phi, eta_sd = 0.98, 0.15
eta = norm.ppf(rng.random(draws))
lv = lfilter([eta_sd], [1.0, -phi], eta)
shock = norm.ppf(rng.random(draws))
clustered = np.exp(lv / 2) * shock
clustered = (clustered - clustered.mean()) / clustered.std()
"""Now the daily shocks are ordinary normals and only the volatility
persists: a violent day is followed by violent days. This is what
GARCH-type models describe, and it is the only fat tail here."""
print(f"{'horizon':>12}{'independent draws':>20}{'clustered volatility':>23}"
f"{'P(beyond 4 sd)':>17}")
for h in (1, 5, 21, 63, 252):
a = daily[: (draws // h) * h].reshape(-1, h).sum(axis=1)
b = clustered[: (draws // h) * h].reshape(-1, h).sum(axis=1)
b = (b - b.mean()) / b.std()
print(f"{h:>12}{kurtosis((a - a.mean()) / a.std()):>20.4f}{kurtosis(b):>23.4f}"
f"{np.mean(np.abs(b) > 4):>17.6f}")
# prices [100. 110. 99. 108.9]
# simple returns [ 0.100000 -0.100000 0.100000]
# log returns [ 0.095310 -0.105361 0.095310]
# sum of log returns 0.085260 -> total growth 1.089000
# sum of simple 0.100000 -> actual total 1.089000
# arithmetic mean of simple returns 0.000000
# money actually left per unit 0.980100
# geometric mean per period -0.005013
# horizon excess kurtosis P(beyond 4 sd) normal would be
# 1 19.6380 0.004794 0.000063
# 5 4.0797 0.002037 0.000063
# 21 1.0089 0.000693 0.000063
# 63 0.3644 0.000347 0.000063
# 252 0.1725 0.000126 0.000063
# horizon independent draws clustered volatility P(beyond 4 sd)
# 1 19.6380 2.3761 0.002642
# 5 4.0797 2.1759 0.002530
# 21 1.0089 2.0177 0.002310
# 63 0.3644 1.4923 0.001670
# 252 0.1725 0.6831 0.000882
What the four blocks establish
Simple returns do not add across time and log returns do. That is why almost all empirical work on return distributions is done in log returns: a multi-period return becomes a sum, and a sum is what the central limit theorem, variance scaling and most estimators are built for. Converting back at the end is one exponential.
The second block is the same fact in money. Alternating gains and losses of 10% leave an arithmetic mean of exactly zero and an investor with 98.01% of their capital. The arithmetic mean of simple returns is not what compounds; the geometric mean is, and the gap between them grows with volatility. A fund advertised on its average annual return and one advertised on its compound return are not quoting the same number.
For independent identically distributed returns with finite variance, a central limit theorem describes standardized sums near their center. It does not make finite-horizon extreme tails Gaussian. The Student-t(4) example has an infinite fourth moment, so its finite-sample kurtosis cannot show that population excess kurtosis disappeared at an annual horizon. Report the horizon and moment assumptions with the measurement.
The fourth is why the effect survives in real markets anyway. In the right-hand column every daily shock is an ordinary normal draw and the only departure is that volatility persists. That alone manufactures fat tails, and it keeps them through aggregation, because the extreme days arrive in clusters rather than spread evenly. This is the mechanism GARCH-type models are built to describe, and it is why volatility clustering and fat tails are usually two descriptions of one phenomenon rather than two separate facts.
Modeling choices, and what they are for
The normal distribution is the simplest assumption and the one early theory, including Black-Scholes, was built on. It is convenient and it underestimates extremes. Student’s \(t\) is the standard heavy-tailed replacement. Empirical distributions use the historical returns directly and assume nothing, at the price of saying nothing past the worst day observed. Mixtures of normals approximate the effect of regime changes. GARCH models make volatility time-varying, so the conditional distribution changes with recent history instead of staying fixed.
A typical liquid equity has an average daily return near 0.05% and a daily volatility around 1%, so the distribution sits close to zero and its interesting behavior is entirely in the tails. Portfolio returns are weighted sums of asset returns under their joint distribution; marginal distributions alone do not specify dependence, and diversification works on the shape of the result, not only on its variance.
The uses follow from the shape. Value-at-risk and expected shortfall are quantities of the tail, so they need the distribution and not the average. Option payoffs are nonlinear, so their prices depend on the whole distribution. Portfolio optimization trades expected return against variance and, when it is done carefully, against skewness and kurtosis too. Stress testing asks how likely a given extreme loss is, which is a risk forecast read off the tail. In every one of those, the two blocks above matter more than the choice of family: horizon, marginal tail behavior and dependence jointly determine aggregate risk.
References
- SciPy documentation,
scipy.stats.t,scipy.stats.kurtosisandscipy.signal.lfilter. docs.scipy.org
Discover more from Insightful Data Lab
Subscribe to get the latest posts sent to your email.
