SigmaField
Mathematical Foundations for Quant Finance
0 of 256 lessons
      1. 5.6.1 Functions of random variablesConcept · 35 min
      2. 5.6.2 Moment generating and characteristic functionsConcept · 30 min
      3. 5.6.3 Sums of independent random variablesConcept · 25 min
      4. 5.6.4 Practice set: transformationsPractice set · 20 min
      5. 5.6.5 Case study: the lognormal from the normalCase study · 30 min

Level 5, Probability and statistics 5.6 Transformations and generating functions Lesson 5 of 5

Case studyLesson 5.6.5 in Level 5, Probability and statistics. About 30 minutes.

Case study: the lognormal from the normal

The model: a sum of normal log returns

The inputs are those of case studies 2.4.6 and 2.6.5. Today's price is S0=100S_0 = 100 dollars, the horizon T=0.5T = 0.5 years, the interest rate r=0.03r = 0.03 and the volatility σ=0.2\sigma = 0.2, both annual decimals with rr continuously compounded. ZZ is a standard normal random variable, with the density φ\varphi that case study 2.6.5 used as a weight.

The log return Y=ln⁡(ST/S0)Y = \ln(S_T/S_0) (case study 1.4.7) is

Y=m+vZm=(r−σ22)T,v=σT\begin{gathered} Y = m + vZ \\[4pt] m = \left(r - \tfrac{\sigma^2}{2}\right)T, \qquad v = \sigma\sqrt{T} \end{gathered}

a linear function of ZZ, so it is normal (lesson 5.6.1), with mean m=0.005m = 0.005 and standard deviation v≈0.141421v \approx 0.141421.

Why the log return is normal

Case study 5.4.6 split the six months into 126 trading days and added up the variances of the daily log returns. Lesson 5.6.3 finishes the argument. Each day's log return is independent and N(m126,v2126)\mathcal{N}\left(\frac{m}{126}, \frac{v^2}{126}\right), and the MGF of their sum is the 126th power of one day's MGF:

(em126t+v2126⋅t22)126=emt+v2t2/2\left(e^{\frac{m}{126}t + \frac{v^2}{126}\cdot\frac{t^2}{2}}\right)^{126} = e^{mt + v^2t^2/2}

That is the MGF of N(m,v2)\mathcal{N}(m, v^2), so by uniqueness (lesson 5.6.2) the six-month log return is exactly normal. Its standard deviation is v=σTv = \sigma\sqrt{T}: four times the horizon gives twice the spread.

Try it

The first line prints mm, vv and the daily volatility σ252\frac{\sigma}{\sqrt{252}}. The second checks the MGF identity at t=0.7t = 0.7.

Then 20,000 seeded paths of 126 daily log returns, each path added up. In this run the sums have mean 0.0059 and standard deviation 0.1404, against 0.005 and 0.1414. The small mean is the harder number to pin down.

Skewness and excess kurtosis (lesson 5.3.3) come out near 0, as for a normal; adding 0.0 turns a -0.0 into 0.0.

⌘/Ctrl + Enter runs the cell

The density of the price

The price ST=S0eYS_T = S_0e^Y rises with YY. By the distribution-function method of lesson 5.6.1, ST≤sS_T \le s exactly when Y≤ln⁡(s/S0)Y \le \ln(s/S_0), so for s>0s > 0

FST(s)=N ⁣(ln⁡(s/S0)−mv)F_{S_T}(s) = N\!\left(\frac{\ln(s/S_0) - m}{v}\right)

with NN the standard normal distribution function. For the density, use the monotone rule with y=ln⁡(s/S0)y = \ln(s/S_0) and dyds=1s\frac{dy}{ds} = \frac{1}{s}.

Rule: Density of the future price

fST(s)=1v s φ ⁣(ln⁡(s/S0)−mv)s>0\begin{gathered} f_{S_T}(s) = \frac{1}{v\,s}\,\varphi\!\left(\frac{\ln(s/S_0) - m}{v}\right) \\[4pt] s > 0 \end{gathered}

The lognormal density of lesson 5.4.4, with the factor 1s\frac{1}{s} from the stretch of eye^y. It is 0 for s≤0s \le 0: a price S0eYS_0e^Y is always positive.

The density is 0.009598 at $80, 0.028192 at $100 and 0.010711 at $120, per dollar. It is not symmetric about $100: the price cannot fall below 0 but can rise without limit, so the right tail is longer.

From FF come the probabilities that case study 5.4.6 tabulated. P(ST≤90)≈0.217588P(S_T \le 90) \approx 0.217588, one minus its 0.782412, and P(ST>100)=N ⁣(mv)≈0.514102P(S_T > 100) = N\!\left(\frac{m}{v}\right) \approx 0.514102, the N(d2)N(d_2) of case study 2.6.5.

Try it

f is the rule; SciPy's lognorm gives the same values, with s set to vv and scale to the median S0emS_0e^m. quad checks that the area is 1.

The last line gives the two probabilities from norm.cdf.

⌘/Ctrl + Enter runs the cell

The mean, from the normal's MGF

The mean needs one line with lesson 5.6.2. Since ST=S0emevZS_T = S_0e^m e^{vZ}, its mean is S0em E[evZ]=S0emMZ(v)S_0e^m\,\mathbb{E}[e^{vZ}] = S_0e^m M_Z(v), and MZ(v)=ev2/2M_Z(v) = e^{v^2/2}:

E[ST]=S0 em+v2/2=S0 e(r−σ2/2)T+σ2T/2=S0 erT\begin{aligned} \mathbb{E}[S_T] &= S_0\,e^{m + v^2/2} \\ &= S_0\,e^{(r - \sigma^2/2)T + \sigma^2 T/2} = S_0\,e^{rT} \end{aligned}

That is $101.5113. Case study 2.6.5 found it by completing the square inside an integral. The normal's MGF is that step done once, for every tt.

Rule: Mean of a lognormal

Y∼N(m,v2)E[eY]=MY(1)=em+v2/2\begin{gathered} Y \sim \mathcal{N}(m, v^2) \\[4pt] \mathbb{E}[e^Y] = M_Y(1) = e^{m + v^2/2} \end{gathered}

The mean that lesson 5.4.4 stated: the normal's MGF at t=1t = 1. It is larger than eE[Y]=eme^{\mathbb{E}[Y]} = e^m by the factor ev2/2e^{v^2/2}.

The same function gives the spread. E[ST2]=S02 E[e2Y]=S02MY(2)=S02e2m+2v2≈10512.711\mathbb{E}[S_T^2] = S_0^2\,\mathbb{E}[e^{2Y}] = S_0^2 M_Y(2) = S_0^2e^{2m + 2v^2} \approx 10512.711. Subtracting the squared mean leaves 208.1656, the variance lesson 5.4.4 stated, so the standard deviation is $14.4279, or S0erTeσ2T−1S_0e^{rT}\sqrt{e^{\sigma^2 T} - 1}.

Try it

The first line is the MGF route: ev2/2e^{v^2/2}, eme^m and their product times S0S_0. quad then integrates s f(s)s\,f(s) for the mean directly.

The last line computes E[ST2]\mathbb{E}[S_T^2] from MY(2)M_Y(2), the variance, and the standard deviation two ways.

⌘/Ctrl + Enter runs the cell

Why the −σ22-\frac{\sigma^2}{2} is there

Prices in case study 2.6.5 are averages in a risk-neutral model: every asset grows on average like money in the bank. So the model must give E[ST]=S0erT\mathbb{E}[S_T] = S_0e^{rT}. Then the discounted average, e−rT E[ST]e^{-rT}\,\mathbb{E}[S_T], is today's price $100.

A log return of rT+vZrT + vZ, with no correction, would overshoot. The average of evZe^{vZ} is ev2/2e^{v^2/2}, above e0=1e^0 = 1, though vZvZ averages 0. The mean would be S0erT+σ2T/2≈102.5315S_0e^{rT + \sigma^2T/2} \approx 102.5315, which discounts to 101.005: more than the $100 the stock costs today.

Subtracting half the variance from the log drift cancels the factor ev2/2e^{v^2/2} exactly. Lesson 5.7.1 states the general fact behind it, Jensen's inequality: for a convex gg, E[g(Y)]≥g(E[Y])\mathbb{E}[g(Y)] \ge g(\mathbb{E}[Y]).

The cost of the correction: a median below the mean

Case study 5.4.6 watched the median fall behind the mean as the horizon grew. In the pricing model the mean is pinned at S0erTS_0e^{rT} whatever σ\sigma is, while the median S0emS_0e^m falls as σ\sigma grows. Six months out:

volatility σ\sigma0.10.20.40.8
median price101.26100.5097.5386.50
mean price101.51101.51101.51101.51
P(ST<mean)P(S_T < \text{mean})0.51410.52820.55620.6114

At 80% volatility the typical outcome is a loss, to $86.50, while the mean is still $101.51. A few very large outcomes hold the mean up, and 61% of outcomes fall below it.

The last row is N ⁣(v2)N\!\left(\frac{v}{2}\right), as in lesson 5.4.4. The reason: the log of the mean, ln⁡S0+m+v22\ln S_0 + m + \frac{v^2}{2}, is v22\frac{v^2}{2} above the median log price, which is v2\frac{v}{2} standard deviations.

Try it

The first line is the model without the correction: its mean and its discounted mean. The loop rebuilds the table.

⌘/Ctrl + Enter runs the cell

The median and the mode

STS_T is an increasing function of YY, so quantiles pass straight through (lesson 5.6.1). The median of YY is mm, so the median price is S0em≈100.5013S_0e^m \approx 100.5013. In the same way the 5% and 95% quantiles are S0em∓1.644854vS_0e^{m \mp 1.644854v}, the $79.64 and $126.82 of case study 5.4.6.

The mode, where the density peaks, is S0em−v2≈98.5112S_0e^{m - v^2} \approx 98.5112; it is stated here and checked on a grid below. So the mode, the median and the mean come in that order, all within $3.

Five years out they spread apart: the mode is $86.07, the median $105.13 and the mean $116.18. The price then ends below its mean with probability 0.5885.

The density of STS_T, six months and five years out. The price cannot fall below 0 but has a long right tail. So the mean sits above the median, the median above the mode, and the gaps grow with σT\sigma\sqrt{T}.

Try it

For each horizon the cell prints the mode, the median, the mean and P(ST<mean)P(S_T < \text{mean}). Then it finds the peak of the density on a grid with steps of 0.001, which lands on the mode.

⌘/Ctrl + Enter runs the cell

A simulation check

Draw ZZ, compute ST=S0em+vZS_T = S_0e^{m + vZ}, and every claim above can be checked by counting. In one seeded run of 200,000 draws, the mean is $101.53, the median $100.52 and the standard deviation $14.43.

The share at or below $90 is 0.2174, against 0.217588. The share below the mean is 0.5285, against 0.528186. The lowest of the 200,000 prices is $51.62: never negative.

Try it

The same seed draws the histogram in the figure above.

Case study 5.7.5 prices the call by averaging discounted payoffs over draws like these, and measures how far such an average can stray.

⌘/Ctrl + Enter runs the cell
Check yourself
For this stock six months out, which is largest?
Check yourself
A colleague simulates ST=S0erT+σTZS_T = S_0e^{rT + \sigma\sqrt{T}Z}, leaving out the −σ22-\frac{\sigma^2}{2}. What happens?

On the desk

Try it

The first two lines are the SciPy mistakes, each beside the right distribution.

The last line discounts the forecast mean with μ=8%\mu = 8\% at the rate rr.

⌘/Ctrl + Enter runs the cell
Exercise

The distribution of a future price

Write price_summary(S0, r, sigma, T) and prob_below(level, S0, r, sigma, T) for the model of this case study.

  1. price_summary returns a dict with keys "mean", "median", "mode" and "sd" of STS_T: S0erTS_0e^{rT}, S0emS_0e^m, S0em−v2S_0e^{m - v^2} and S0erTeσ2T−1S_0e^{rT}\sqrt{e^{\sigma^2T} - 1}.
  2. prob_below returns P(ST≤level)P(S_T \le \text{level}), which is N ⁣(ln⁡(level/S0)−mv)N\!\left(\frac{\ln(\text{level}/S_0) - m}{v}\right). A level of 0 or below gives 0.
  3. With σ=0\sigma = 0 or T=0T = 0, STS_T is certain to be S0erTS_0e^{rT}. Then all three centres equal it and sd is 0; prob_below is 1 at or above it and 0 below.
  4. Raise ValueError if S0≤0S_0 \le 0, σ<0\sigma < 0 or T<0T < 0.

The tests check the six-month and five-year numbers above, the probabilities 0.217588 and 0.528186, and the edge cases.

See the tests
import math


def test_six_months():
    """Six months out: mean 101.5113, median 100.5013, mode 98.5112, sd 14.4279"""
    got = price_summary(100, 0.03, 0.2, 0.5)
    expect_close(got["mean"], 101.51130646157189, what="mean", rel=1e-9)
    expect_close(got["median"], 100.5012520859401, what="median", rel=1e-9)
    expect_close(got["mode"], 98.51119396030626, what="mode", rel=1e-9)
    expect_close(got["sd"], 14.427945946151569, what="sd", rel=1e-7)


def test_five_years():
    """Five years out: mode 86.07, median 105.13, mean 116.18"""
    got = price_summary(100, 0.03, 0.2, 5)
    expect_close(got["mode"], 100 * math.exp(-0.15), what="mode", rel=1e-9)
    expect_close(got["median"], 100 * math.exp(0.05), what="median", rel=1e-9)
    expect_close(got["mean"], 100 * math.exp(0.15), what="mean", rel=1e-9)


def test_probabilities():
    """P(S_T <= 90) is 0.217588, and P(S_T <= mean) is N(v/2) = 0.528186"""
    expect_close(prob_below(90, 100, 0.03, 0.2, 0.5), 0.21758753431780564, what="prob_below(90)", rel=1e-6)
    expect_close(prob_below(100 * math.exp(0.015), 100, 0.03, 0.2, 0.5), 0.5281859888985083, what="prob_below(mean)", rel=1e-6)
    expect_close(prob_below(100, 100, 0.03, 0.2, 0.5), 1 - 0.514101801652164, what="prob_below(100) = 1 - N(d2)", rel=1e-6)


def test_order():
    """With volatility, mode < median < mean, and the median has probability 1/2 below it"""
    got = price_summary(50, 0.01, 0.35, 2)
    assert got["mode"] < got["median"] < got["mean"], f"expected mode < median < mean, got {got}"
    expect_close(prob_below(got["median"], 50, 0.01, 0.35, 2), 0.5, what="prob_below(median)", rel=1e-9)


def test_no_volatility():
    """With sigma = 0 the price is certain: S0 e^(rT) = 101.5113, sd 0, probabilities 0 or 1"""
    got = price_summary(100, 0.03, 0.0, 0.5)
    for key in ("mean", "median", "mode"):
        expect_close(got[key], 100 * math.exp(0.015), what=key, rel=1e-12)
    expect_close(got["sd"], 0.0, what="sd")
    expect_close(prob_below(101.6, 100, 0.03, 0.0, 0.5), 1.0, what="prob_below just above the certain price")
    expect_close(prob_below(101.4, 100, 0.03, 0.0, 0.5), 0.0, what="prob_below just below the certain price")


def test_at_expiry():
    """With T = 0 the price is today's, 100"""
    got = price_summary(100, 0.03, 0.2, 0)
    expect_close(got["median"], 100.0, what="median at T = 0", rel=1e-12)
    expect_close(got["sd"], 0.0, what="sd at T = 0")


def test_non_positive_level():
    """A level of 0 or below has probability 0"""
    expect_close(prob_below(0, 100, 0.03, 0.2, 0.5), 0.0, what="prob_below(0)")
    expect_close(prob_below(-5, 100, 0.03, 0.2, 0.5), 0.0, what="prob_below(-5)")


def test_bad_inputs():
    """S0 <= 0, a negative volatility or a negative horizon raises ValueError"""
    cases = [
        ("S0 = 0", lambda: price_summary(0, 0.03, 0.2, 0.5)),
        ("sigma = -0.2", lambda: price_summary(100, 0.03, -0.2, 0.5)),
        ("T = -1", lambda: prob_below(90, 100, 0.03, 0.2, -1)),
    ]
    for name, call in cases:
        try:
            call()
        except ValueError:
            continue
        raise AssertionError(f"expected ValueError for {name}")

Summary card

  • With ZZ standard normal, ST=S0em+vZS_T = S_0e^{m + vZ}, m=(r−σ22)Tm = (r - \frac{\sigma^2}{2})T, v=σTv = \sigma\sqrt{T}. The log return is a sum of independent daily normals, so it is normal with variance σ2T\sigma^2T (lesson 5.6.3).
  • Density (lesson 5.6.1): f(s)=1vsφ(ln⁡(s/S0)−mv)f(s) = \frac{1}{vs}\varphi\left(\frac{\ln(s/S_0) - m}{v}\right) for s>0s > 0, the lognormal; P(ST≤s)=N(ln⁡(s/S0)−mv)P(S_T \le s) = N\left(\frac{\ln(s/S_0) - m}{v}\right).
  • Mean from the normal's MGF (lesson 5.6.2): E[eY]=em+v2/2\mathbb{E}[e^Y] = e^{m + v^2/2}, so E[ST]=S0erT\mathbb{E}[S_T] = S_0e^{rT}. MY(2)M_Y(2) gives the standard deviation S0erTeσ2T−1S_0e^{rT}\sqrt{e^{\sigma^2T} - 1}.
  • The −σ22-\frac{\sigma^2}{2} cancels E[evZ]=ev2/2\mathbb{E}[e^{vZ}] = e^{v^2/2}, so the discounted mean price is S0S_0; without it the model values the stock at $101.005.
  • Mode S0em−v2S_0e^{m - v^2} below median S0emS_0e^m below mean S0erTS_0e^{rT}; P(ST<mean)=N(v2)P(S_T < \text{mean}) = N(\frac{v}{2}). The gaps grow with σT\sigma\sqrt{T}.
  • Use lognorm(s=v, scale=S0 * exp(m)), keep σ\sigma and TT annual, and treat σ=0\sigma = 0 or T=0T = 0 separately.

Free further reading

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