SigmaField
Mathematical Foundations for Quant Finance
0 of 256 lessons
      1. 5.4.1 Densities, distribution functions and the uniform distributionConcept · 30 min
      2. 5.4.2 The exponential distributionConcept · 25 min
      3. 5.4.3 The normal distributionConcept · 30 min
      4. 5.4.4 The lognormal, Student t and chi-squared distributionsConcept · 30 min
      5. 5.4.5 Practice set: continuous distributionsPractice set · 25 min
      6. 5.4.6 Case study: lognormal prices and fat-tailed returnsCase study · 35 min

Level 5, Probability and statistics 5.4 Continuous distributions Lesson 6 of 6

Case studyLesson 5.4.6 in Level 5, Probability and statistics. About 35 minutes.

Case study: lognormal prices and fat-tailed returns

Normal log returns make lognormal prices

Case study 1.4.7 showed that log returns add across time. Over TT years, ln⁡(ST/S0)\ln(S_T / S_0) is the sum of the daily log returns, with StS_t the price at time tt. If the daily log returns are independent and normal, their sum is normal (lesson 5.4.3), so ln⁡ST\ln S_T is normal and STS_T is lognormal (lesson 5.4.4).

Case study 2.6.5 took this as its model: ST=S0 e(r−σ2/2)T+σT ZS_T = S_0\,e^{(r - \sigma^2/2)T + \sigma\sqrt{T}\,Z}, with ZZ standard normal. Here rr is the continuous interest rate and σ\sigma the annual volatility, both decimals, and TT is in years.

Rule: The lognormal price

ln⁡ST∼N(m, σ2T)m=ln⁡S0+(r−12σ2)TP(ST>L)=N(m−ln⁡LσT)\begin{gathered} \ln S_T \sim \mathcal{N}(m,\ \sigma^2 T) \\[4pt] m = \ln S_0 + (r - \tfrac{1}{2}\sigma^2)T \\[4pt] P(S_T > L) = N\left(\frac{m - \ln L}{\sigma\sqrt{T}}\right) \end{gathered}

mm is the mean of ln⁡ST\ln S_T and σT\sigma\sqrt{T} its standard deviation: the lognormal of lesson 5.4.4. The last line standardises ln⁡L\ln L and uses P(Z>z)=N(−z)P(Z > z) = N(-z) (lesson 5.4.3).

The variance σ2T\sigma^2 T grows in proportion to time. With 252 trading days a year, a day's log return has standard deviation 0.2252≈1.26%\frac{0.2}{\sqrt{252}} \approx 1.26\%. Then 126 days add up to 126×0.22252=0.22×0.5126 \times \frac{0.2^2}{252} = 0.2^2 \times 0.5, if the days are independent (module 5.5 makes this precise).

Median, mean and the chance of ending above a level

Take case study 2.6.5's stock: S0=100S_0 = 100, T=0.5T = 0.5, r=0.03r = 0.03 and σ=0.2\sigma = 0.2. Then ln⁡ST\ln S_T has mean ln⁡100+0.005≈4.610170\ln 100 + 0.005 \approx 4.610170 and standard deviation 0.20.5≈0.1414210.2\sqrt{0.5} \approx 0.141421.

  • The median is e4.610170≈100.5013e^{4.610170} \approx 100.5013: half the futures end above it.
  • The mean is 100 e0.03×0.5≈101.5113100\,e^{0.03 \times 0.5} \approx 101.5113, as case study 2.6.5 found. The −12σ2-\frac{1}{2}\sigma^2 in the exponent is what puts the median below it.
  • P(ST>100)=N(0.035355)≈0.514102P(S_T > 100) = N(0.035355) \approx 0.514102. This is N(d2)N(d_2) of case study 2.6.5, the chance that the call ends in the money.
level LL8090100110120
P(ST>L)P(S_T > L)0.9466510.7824120.5141020.2615450.104948

The 5% and 95% quantiles, e4.610170∓1.644854×0.141421e^{4.610170 \mp 1.644854 \times 0.141421}, are 79.64 and 126.82. The 90% range reaches 20.86 below the median and 26.32 above it: on the price scale the lognormal leans to the right.

Try it

The first lines put the model into SciPy's lognorm (lesson 5.4.4), with s the standard deviation of ln⁡ST\ln S_T and scale the median.

The loop prints the table by the rule, then by lognorm. The last lines simulate a million prices in one seeded run. The mean, the median and the share above 120 land near 101.51, 100.50 and 0.1049.

⌘/Ctrl + Enter runs the cell

Which drift? Pricing against forecasting

The drift rr is the one the pricing model of case study 2.6.5 uses, so N(d2)N(d_2) is a pricing probability, not a forecast. A forecast puts in the stock's own expected return μ\mu instead. With μ=8%\mu = 8\%, an assumption for illustration, P(ST>100)≈0.584P(S_T > 100) \approx 0.584 and P(ST>120)≈0.1407P(S_T > 120) \approx 0.1407.

Always say which drift a probability uses. The volatility can be estimated from data far better than μ\mu can; case study 5.8.5 shows how uncertain a mean return is.

Longer horizons: the mean runs away from the median

With μ=8%\mu = 8\% and σ=20%\sigma = 20\%, the mean grows at the rate μ\mu but the median only at μ−12σ2=6%\mu - \frac{1}{2}\sigma^2 = 6\%. The gap widens with time, and fewer and fewer futures end above the mean.

years TT0.5151030
mean104.08108.33149.18222.551102.32
median103.05106.18134.99182.21604.96
P(ST>mean)P(S_T > \text{mean})0.47180.46020.41150.37590.2919
P(ST<100)P(S_T < 100)0.4160.38210.25120.17140.0502
normal model: P(ST<0)P(S_T < 0)9.9×10−139.9 \times 10^{-13}3.7×10−73.7 \times 10^{-7}0.0170.0770.26

P(ST>mean)=N(−12σT)P(S_T > \text{mean}) = N(-\frac{1}{2}\sigma\sqrt{T}), from lesson 5.4.4's N(−σ/2)N(-\sigma/2) with σT\sigma\sqrt{T} in place of σ\sigma. Over 30 years the mean is 1,102 but fewer than three futures in ten reach it. A loss over the whole period becomes rarer, from 41.6% to 5.0%.

The last row explains why prices are not modelled as normal. Give a normal price the same mean and standard deviation as the lognormal. Over 10 years it is negative 7.7% of the time, and over 30 years 26%.

The 10-year price as a lognormal and as a normal with the same mean and standard deviation. The lognormal stays positive and leans right, with the mean above the median; the normal puts 7.7% of its area on negative prices.

Try it

The loop prints one row of the horizon table per line. Then come the 30-year ratio of mean to median, eσ2T/2e^{\sigma^2 T/2}, and the two forecasts with μ=8%\mu = 8\% over six months.

The lognormal's standard deviation is the square root of lesson 5.4.4's variance, mean×eσ2T−1\text{mean} \times \sqrt{e^{\sigma^2 T} - 1}.

⌘/Ctrl + Enter runs the cell
Check yourself
A stock is modelled as lognormal with 8% drift and 20% volatility. Over 30 years its mean price is 1,102 from a start of 100. What does that say about a typical investor's outcome?

Daily returns have fatter tails than the normal

Real daily stock returns usually have more extreme days than a normal distribution allows. Level 5 uses an invented series that behaves the same way, introduced in case study 5.3.5.

It holds 1,000 daily log returns, drawn with a fixed seed from a Student t with 4 degrees of freedom. Scaling sets their volatility near 1% a day. They are illustrative, not market data.

For these returns:

  • the mean is −0.0061%-0.0061\% a day and the volatility, the standard deviation dividing by nn as in case study 5.3.5, is 1.0073%;
  • the skewness is −0.31-0.31 and the excess kurtosis 5.29 (lesson 5.3.3), where a normal has 0 for both;
  • the worst day is −5.47%-5.47\% and the best +6.87%+6.87\%.

Case study 5.3.5 found these moments and read value at risk from the sample. Here the question is which distribution describes the returns, so compare two models with the sample's mean and volatility.

  • Normal: N(xˉ,s2)\mathcal{N}(\bar{x}, s^2), with xˉ\bar{x} the sample mean and ss the volatility.
  • Student t, 4 degrees of freedom: xˉ+s T2\bar{x} + s\,\frac{T}{\sqrt{2}} with T∼t4T \sim t_4. Lesson 5.4.4 gives t4t_4 the variance 42=2\frac{4}{2} = 2, so dividing by 2\sqrt{2} scales it to variance 1.

The choice ν=4\nu = 4 is in the range usually fitted to daily stock returns. Case study 5.9.4 estimates ν\nu from these data instead of choosing it.

Comparing quantiles

A quantile–quantile comparison lines up the sample's quantiles (lesson 5.4.1) against a model's at the same probabilities. If the model fits, the two agree.

pp0.0010.0050.010.050.250.50.750.950.990.9950.999
sample (%)−5.373-5.373−3.696-3.696−2.656-2.656−1.562-1.562−0.51-0.510.0330.561.4982.3422.7664.164
normal (%)−3.119-3.119−2.601-2.601−2.35-2.35−1.663-1.663−0.686-0.686−0.006-0.0060.6731.6512.3372.5893.107
Student t (%)−5.116-5.116−3.286-3.286−2.675-2.675−1.525-1.525−0.534-0.534−0.006-0.0060.5221.5122.6633.2735.103

In the far left tail the normal is much too mild: its 0.1% quantile is −3.12%-3.12\% against the sample's −5.37%-5.37\%. The t gives −5.12%-5.12\%. At the 1% level, where case study 5.3.5 read its value at risk, the normal says −2.35%-2.35\%, the sample −2.66%-2.66\% and the t −2.68%-2.68\%.

In the middle the normal is too wide: its quartiles, −0.686%-0.686\% and 0.673%0.673\%, sit outside the sample's, −0.51%-0.51\% and 0.56%0.56\%. The sample has 76.2% of its days within one standard deviation of the mean, against 68.27% for a normal and 76.98% for the scaled t. Case study 5.3.5 saw the same crowded middle in the data; the t has it too.

The sample's quantiles against each model's. The ends bend away from the line against the normal, and stay close to it against the scaled Student t.

The normal and the scaled t cross near 1.95 standard deviations. Inside that, the t's quantiles are closer to the mean than the normal's; beyond it they are further out.

So a fat-tailed model is not simply "wider": it moves probability from the shoulders to both the centre and the tails. That is why case study 5.3.5 found the normal value at risk too large at 95% and too small at 99%.

Try it

The cell regenerates the shared returns exactly: seed 50 and standard_t(4, size=1000). It prints the moments, then the table, in percent.

np.quantile(x, p) gives the sample quantiles. The model quantiles are xˉ+s zp\bar{x} + s\,z_p and xˉ+s tp/2\bar{x} + s\,t_p/\sqrt{2}, with tpt_p from t(4).ppf. The last lines find where the two models' quantiles cross, with brentq (lesson 1.3.2).

⌘/Ctrl + Enter runs the cell

A 4-sigma day

Count the days more than kk standard deviations from the mean, and set each count against what each model expects in 1,000 days. For the scaled t, ∣X−xˉ∣>ks|X - \bar{x}| > ks is ∣T∣>k2|T| > k\sqrt{2}.

kk2345
days in the sample411364
normal: expected in 1,000 days45.52.70.0630.001
Student t: expected in 1,000 days47.413.24.82.1
normal: expected per year11.50.680.0160.000144
Student t: expected per year123.341.210.532

At 2 standard deviations the two models agree with each other and with the sample. Beyond that they part fast.

A 4-sigma day has probability 6.33×10−56.33 \times 10^{-5} under the normal: 0.016 a year of 252 trading days, or once in 62.6 years. Under the t it has probability 0.0048, or 1.21 a year.

The sample's 6 such days in about four years fit the t, not the normal. A risk model that says "once in 63 years" for something that happens yearly will be surprised often. Case study 4.7.5 made the same warning for joint crashes.

Try it

norm.sf and t(4).sf give the one-sided tails; doubling gives both sides. A daily probability times 252 is the expected number a year; 1 divided by it is "once in so many years".

The counts come from the standardised returns z of the shared data.

⌘/Ctrl + Enter runs the cell
Check yourself
On the quantile comparison against the normal, the lowest points sit well below the line: the sample's 0.1% quantile is −5.37%-5.37\% where the normal says −3.12%-3.12\%. What does that mean?

Try it

The annual volatility in the note: the daily standard deviation times 252\sqrt{252}.

⌘/Ctrl + Enter runs the cell
Exercise

Lognormal prices and tail probabilities

Write two functions.

  1. prob_above(S0, level, T, mu, sigma) returns P(ST>level)P(S_T > \text{level}) for ST=S0e(μ−σ2/2)T+σTZS_T = S_0e^{(\mu - \sigma^2/2)T + \sigma\sqrt{T}Z}, by the rule above.
    • Raise ValueError if S0 or level is not positive, or if T or sigma is negative.
    • If T or sigma is 0 there is no randomness: ST=S0eμTS_T = S_0e^{\mu T}, so return 1.0 if that is above level, and 0.0 otherwise.
  2. k_sigma_prob(k, nu=None) returns the probability that a return lands more than k standard deviations from its mean, on either side. With nu=None the model is the normal; otherwise it is a Student t with nu degrees of freedom scaled to variance 1.
    • Raise ValueError if k is negative, or if nu is given and not above 2: such a t has no variance to scale by.
    • Keep far tails accurate: at k=9k = 9 the normal's answer is about 2.3×10−192.3 \times 10^{-19}, not 0 (case study 2.6.5).

The tests use this case study's numbers, T=0T = 0, σ=0\sigma = 0, and the bad inputs above.

See the tests
import math

from scipy.stats import norm


def test_in_the_money():
    """Case study 2.6.5's stock ends above 100 with probability N(d₂), about 0.514102"""
    expect_close(prob_above(100, 100, 0.5, 0.03, 0.2), 0.514101801652164, what="prob_above(100, 100, 0.5, 0.03, 0.2)", rel=1e-9)


def test_above_120():
    """The same stock ends above 120 with probability about 0.104948"""
    expect_close(prob_above(100, 120, 0.5, 0.03, 0.2), 0.10494776543969564, what="prob_above(100, 120, ...)", rel=1e-8)


def test_real_world_drift():
    """With an 8% drift instead, it ends above 100 with probability about 0.583998"""
    expect_close(prob_above(100, 100, 0.5, 0.08, 0.2), 0.5839979857136824, what="prob_above with mu = 0.08", rel=1e-9)


def test_long_horizon_mean():
    """Over 30 years only about 0.2919 of futures end above the mean, 100e^2.4"""
    expect_close(prob_above(100, 100 * math.exp(0.08 * 30), 30, 0.08, 0.2), norm.cdf(-0.2 * math.sqrt(30) / 2), what="P(S_T > mean) over 30 years", rel=1e-9)


def test_no_time_left():
    """At T = 0 the price is S0: above 90 for certain, above 110 never"""
    expect_equal(prob_above(100, 90, 0, 0.08, 0.2), 1.0, what="prob_above(100, 90, T=0)")
    expect_equal(prob_above(100, 110, 0, 0.08, 0.2), 0.0, what="prob_above(100, 110, T=0)")


def test_no_volatility():
    """With sigma = 0 the price grows to 100e^0.04 ≈ 104.08: above 103 for certain"""
    expect_equal(prob_above(100, 103, 0.5, 0.08, 0.0), 1.0, what="prob_above(100, 103, sigma=0)")
    expect_equal(prob_above(100, 105, 0.5, 0.08, 0.0), 0.0, what="prob_above(100, 105, sigma=0)")


def test_bad_prices():
    """A zero or negative price, a negative T or a negative volatility raises ValueError"""
    for args in [(0, 100, 0.5, 0.03, 0.2), (100, -5, 0.5, 0.03, 0.2), (100, 100, -1, 0.03, 0.2), (100, 100, 0.5, 0.03, -0.2)]:
        try:
            prob_above(*args)
        except ValueError:
            continue
        raise AssertionError(f"expected ValueError for prob_above{args}")


def test_normal_four_sigma():
    """Under the normal, a 4-sigma day has probability about 0.0000633"""
    expect_close(k_sigma_prob(4), 6.334248366623973e-05, what="k_sigma_prob(4)", rel=1e-9)


def test_t_four_sigma():
    """Under a t with 4 degrees of freedom scaled to variance 1, about 0.004813"""
    expect_close(k_sigma_prob(4, 4), 0.00481267833004422, what="k_sigma_prob(4, nu=4)", rel=1e-8)


def test_zero_sigma():
    """Any move at all, k = 0, has probability 1"""
    expect_close(k_sigma_prob(0), 1.0, what="k_sigma_prob(0)")
    expect_close(k_sigma_prob(0, 5), 1.0, what="k_sigma_prob(0, nu=5)")


def test_far_tail():
    """At 9 sigma the normal's answer is about 2.3e-19, not 0"""
    expect_close(k_sigma_prob(9), 2 * norm.sf(9), what="k_sigma_prob(9)", rel=1e-9)


def test_large_nu():
    """With a million degrees of freedom the t is almost the normal: about 0.0027 at 3 sigma"""
    expect_close(k_sigma_prob(3, 1_000_000), 2 * norm.sf(3), what="k_sigma_prob(3, nu=1e6)", rel=1e-3)


def test_bad_tail_inputs():
    """nu = 2 (no variance) or a negative k raises ValueError"""
    for args in [(3, 2), (3, 1.5), (-1, None)]:
        try:
            k_sigma_prob(*args)
        except ValueError:
            continue
        raise AssertionError(f"expected ValueError for k_sigma_prob{args}")

Summary card

  • Independent normal daily log returns add to a normal ln⁡ST\ln S_T, so STS_T is lognormal: ln⁡ST∼N(ln⁡S0+(μ−12σ2)T,σ2T)\ln S_T \sim \mathcal{N}(\ln S_0 + (\mu - \frac{1}{2}\sigma^2)T, \sigma^2T), with μ=r\mu = r for pricing.
  • P(ST>L)=N(ln⁡(S0/L)+(μ−12σ2)TσT)P(S_T > L) = N\left(\frac{\ln(S_0/L) + (\mu - \frac{1}{2}\sigma^2)T}{\sigma\sqrt{T}}\right); with μ=r\mu = r and L=KL = K this is N(d2)N(d_2).
  • The median S0e(μ−σ2/2)TS_0e^{(\mu - \sigma^2/2)T} is below the mean S0eμTS_0e^{\mu T}, and P(ST>mean)=N(−12σT)P(S_T > \text{mean}) = N(-\frac{1}{2}\sigma\sqrt{T}) falls with the horizon. A normal price model gives negative prices.
  • The shared daily returns have excess kurtosis 5.29. Their far quantiles lie well beyond the normal's and close to a Student t with 4 degrees of freedom scaled to the same volatility.
  • A 4-sigma day: once in 62.6 years under the normal, 1.21 a year under the t; the sample has 6 in 1,000 days.

Free further reading

Optional. This lesson covers what you need; these add more examples and practice. All are free to read.