Normal log returns make lognormal prices
Case study 1.4.7 showed that log returns add across time. Over years, is the sum of the daily log returns, with the price at time . If the daily log returns are independent and normal, their sum is normal (lesson 5.4.3), so is normal and is lognormal (lesson 5.4.4).
Case study 2.6.5 took this as its model: , with standard normal. Here is the continuous interest rate and the annual volatility, both decimals, and is in years.
Rule: The lognormal price
is the mean of and its standard deviation: the lognormal of lesson 5.4.4. The last line standardises and uses (lesson 5.4.3).
The variance grows in proportion to time. With 252 trading days a year, a day's log return has standard deviation . Then 126 days add up to , 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: , , and . Then has mean and standard deviation .
- The median is : half the futures end above it.
- The mean is , as case study 2.6.5 found. The in the exponent is what puts the median below it.
- . This is of case study 2.6.5, the chance that the call ends in the money.
| level | 80 | 90 | 100 | 110 | 120 |
|---|---|---|---|---|---|
| 0.946651 | 0.782412 | 0.514102 | 0.261545 | 0.104948 |
The 5% and 95% quantiles, , 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 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.
Which drift? Pricing against forecasting
The drift is the one the pricing model of case study 2.6.5 uses, so is a pricing probability, not a forecast. A forecast puts in the stock's own expected return instead. With , an assumption for illustration, and .
Always say which drift a probability uses. The volatility can be estimated from data far better than can; case study 5.8.5 shows how uncertain a mean return is.
Longer horizons: the mean runs away from the median
With and , the mean grows at the rate but the median only at . The gap widens with time, and fewer and fewer futures end above the mean.
| years | 0.5 | 1 | 5 | 10 | 30 |
|---|---|---|---|---|---|
| mean | 104.08 | 108.33 | 149.18 | 222.55 | 1102.32 |
| median | 103.05 | 106.18 | 134.99 | 182.21 | 604.96 |
| 0.4718 | 0.4602 | 0.4115 | 0.3759 | 0.2919 | |
| 0.416 | 0.3821 | 0.2512 | 0.1714 | 0.0502 | |
| normal model: | 0.017 | 0.077 | 0.26 |
, from lesson 5.4.4's with in place of . 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, , and the two forecasts with over six months.
The lognormal's standard deviation is the square root of lesson 5.4.4's variance, .
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 a day and the volatility, the standard deviation dividing by as in case study 5.3.5, is 1.0073%;
- the skewness is and the excess kurtosis 5.29 (lesson 5.3.3), where a normal has 0 for both;
- the worst day is and the best .
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: , with the sample mean and the volatility.
- Student t, 4 degrees of freedom: with . Lesson 5.4.4 gives the variance , so dividing by scales it to variance 1.
The choice is in the range usually fitted to daily stock returns. Case study 5.9.4 estimates 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.
| 0.001 | 0.005 | 0.01 | 0.05 | 0.25 | 0.5 | 0.75 | 0.95 | 0.99 | 0.995 | 0.999 | |
|---|---|---|---|---|---|---|---|---|---|---|---|
| sample (%) | 0.033 | 0.56 | 1.498 | 2.342 | 2.766 | 4.164 | |||||
| normal (%) | 0.673 | 1.651 | 2.337 | 2.589 | 3.107 | ||||||
| Student t (%) | 0.522 | 1.512 | 2.663 | 3.273 | 5.103 |
In the far left tail the normal is much too mild: its 0.1% quantile is against the sample's . The t gives . At the 1% level, where case study 5.3.5 read its value at risk, the normal says , the sample and the t .
In the middle the normal is too wide: its quartiles, and , sit outside the sample's, and . 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 and , with from t(4).ppf. The last lines find where the two models' quantiles cross, with brentq (lesson 1.3.2).
A 4-sigma day
Count the days more than standard deviations from the mean, and set each count against what each model expects in 1,000 days. For the scaled t, is .
| 2 | 3 | 4 | 5 | |
|---|---|---|---|---|
| days in the sample | 41 | 13 | 6 | 4 |
| normal: expected in 1,000 days | 45.5 | 2.7 | 0.063 | 0.001 |
| Student t: expected in 1,000 days | 47.4 | 13.2 | 4.8 | 2.1 |
| normal: expected per year | 11.5 | 0.68 | 0.016 | 0.000144 |
| Student t: expected per year | 12 | 3.34 | 1.21 | 0.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 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.
Try it
The annual volatility in the note: the daily standard deviation times .
Lognormal prices and tail probabilities
Write two functions.
prob_above(S0, level, T, mu, sigma)returns for , by the rule above.- Raise
ValueErrorifS0orlevelis not positive, or ifTorsigmais negative. - If
Torsigmais 0 there is no randomness: , so return 1.0 if that is abovelevel, and 0.0 otherwise.
- Raise
k_sigma_prob(k, nu=None)returns the probability that a return lands more thankstandard deviations from its mean, on either side. Withnu=Nonethe model is the normal; otherwise it is a Student t withnudegrees of freedom scaled to variance 1.- Raise
ValueErrorifkis negative, or ifnuis given and not above 2: such a t has no variance to scale by. - Keep far tails accurate: at the normal's answer is about , not 0 (case study 2.6.5).
- Raise
The tests use this case study's numbers, , , 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 , so is lognormal: , with for pricing.
- ; with and this is .
- The median is below the mean , and 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.