SigmaField
Mathematical Foundations for Quant Finance
0 of 256 lessons
      1. 5.11.1 Random walksConcept · 30 min
      2. 5.11.2 Markov chainsConcept · 35 min
      3. 5.11.3 Practice set: discrete-time processesPractice set · 25 min
      4. 5.11.4 Case study: pricing on a multi-step binomial treeCase study · 35 min

Level 5, Probability and statistics 5.11 Discrete-time processes Lesson 4 of 4

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

Case study: pricing on a multi-step binomial tree

From one step to nn steps

The inputs are those of case study 2.4.6: stock price S0=100S_0 = 100, strike K=100K = 100, T=0.5T = 0.5 years, rate r=0.03r = 0.03 (continuous) and volatility σ=0.2\sigma = 0.2, both yearly decimals. A European option can be exercised only at TT; that is the kind priced so far.

Split TT into nn steps of length Δt=Tn\Delta t = \frac{T}{n} years. Each step is case study 5.2.5's one-period model with Δt\Delta t in place of TT: the stock is multiplied by uu or by dd.

Rule: One step of the tree

u=eσΔt,d=1uq=erΔt−du−d\begin{gathered} u = e^{\sigma\sqrt{\Delta t}}, \quad d = \frac{1}{u} \\[4pt] q = \frac{e^{r\Delta t} - d}{u - d} \end{gathered}

qq is the risk-neutral probability of an up move, the same at every step, and each step discounts by e−rΔte^{-r\Delta t}. For qq to lie between 0 and 1, d<erΔt<ud < e^{r\Delta t} < u, as in case study 5.2.5.

An up move followed by a down move ends where a down move followed by an up move does, because ud=1ud = 1. So the tree recombines: after ii steps there are only i+1i + 1 nodes, not 2i2^i. The node with jj up moves has the price S0ujdi−j=S0u2j−iS_0u^jd^{i - j} = S_0u^{2j - i}.

The tree as a random walk

The log of the price, ln⁡(Si/S0)=σΔt (2U−i)\ln(S_i/S_0) = \sigma\sqrt{\Delta t}\,(2U - i), is a random walk with steps of ±σΔt\pm\sigma\sqrt{\Delta t} (lesson 5.11.1). Under qq the number of up moves is U∼Bin⁡(i,q)U \sim \operatorname{Bin}(i, q), and the price moves from node to node as a Markov chain (lesson 5.11.2).

With n=100n = 100, q=0.501768q = 0.501768, and lesson 5.11.1's rules give the log price at TT a mean of 0.0050 and a variance of 0.0200. Those are case study 5.6.5's m=(r−σ22)Tm = (r - \frac{\sigma^2}{2})T and v2=σ2Tv^2 = \sigma^2T: the tree has the lognormal model's mean and spread.

Working backwards

At expiry every final node has a known payoff. One step earlier, each node has two successors, and case study 5.2.5's rule prices it from them. Repeat back to today: this is backward induction.

Rule: Backward induction

Vexpiry=payoff(S)V=e−rΔt(q Vup+(1−q) Vdown)\begin{gathered} V_{\text{expiry}} = \text{payoff}(S) \\[4pt] V = e^{-r\Delta t}\left(q\,V_{\text{up}} + (1 - q)\,V_{\text{down}}\right) \end{gathered}

VV is the option's value at a node, and VupV_{\text{up}} and VdownV_{\text{down}} its values at the two nodes one step later. The value today is the one at the first node.

Two steps by hand

With n=2n = 2, Δt=0.25\Delta t = 0.25: u=1.105171u = 1.105171, d=0.904837d = 0.904837, q=0.512599q = 0.512599 and one step discounts by e−0.0075=0.992528e^{-0.0075} = 0.992528.

nodestockcall
expiry, two up122.140322.1403
expiry, one up1000
expiry, none up81.87310
after one step, up110.51710.992528×0.512599×22.1403=11.26430.992528 \times 0.512599 \times 22.1403 = 11.2643
after one step, down90.48370
today1000.992528×0.512599×11.2643=5.73090.992528 \times 0.512599 \times 11.2643 = 5.7309

Two steps give 5.7309, below Black–Scholes 6.3710, where one step gave 7.7512 above it. The middle final node sits exactly at the strike, so only one of the three final prices pays anything.

Try it

The parameters, then the three final prices and payoffs, then one step back to the two middle nodes and one more to today.

⌘/Ctrl + Enter runs the cell

Replication at every node

Case study 3.3.4's three-state market needed the call itself: the bond and the stock alone could not copy it. On a tree each step has only two states, up and down. Their 2×22 \times 2 payoff matrix has rank 2, so the bond and the stock complete each step (lesson 3.3.2).

At each node, solve case study 5.2.5's 2×22 \times 2 system for the shares Δ\Delta and the bonds that pay 1 one step later:

nodeshares Δ\Deltabondsvalue
today0.5623−50.8769-50.87695.7309
after one step, up1−100-10011.2643
after one step, down000

The hedge changes from node to node. After an up move the call is sure to pay and behaves like one share less 100 bonds; after a down move it is worthless.

No new money is ever needed. Today's portfolio is worth exactly 11.2643 after an up move and 0 after a down move, the cost of the next node's portfolio. So the tree's price is the cost of copying the call, and by the law of one price (case study 3.3.4) its value.

Try it

replicate solves the payoff matrix of one step, as in case studies 3.3.4 and 5.2.5. It returns the bonds, the shares and their cost at that node.

The last line checks that today's portfolio is worth each next node's value.

⌘/Ctrl + Enter runs the cell

Closing in on Black–Scholes

tree_price below runs backward induction for any nn, a whole column of nodes at a time. For the call:

steps nntree priceminus 6.3710error × n\times\, n
17.7512+1.3801+1.38011.38
25.7309−0.6401-0.6401−1.28-1.28
36.8385+0.4675+0.46751.40
46.0334−0.3377-0.3377−1.35-1.35
56.6501+0.2790+0.27901.40
106.2323−0.1387-0.1387−1.39-1.39
506.3430−0.0281-0.0281−1.40-1.40
1006.3570−0.0140-0.0140−1.40-1.40
1,0006.3696−0.0014-0.0014−1.41-1.41

The price closes in on 6.3710 as nn grows, from above for odd nn and from below for even nn. The error times nn settles near ±1.4\pm 1.4, so the error shrinks like 1.4n\frac{1.4}{n}: ten times the steps, a tenth of the error. It stays under a cent from n=141n = 141 on.

Since the number of up moves is Bin⁡(n,q)\operatorname{Bin}(n, q) (case study 5.2.5), a European price is also one sum:

price=e−rT∑j=0n(nj)qj(1−q)n−j×payoff ⁣(S0u2j−n)\begin{aligned} \text{price} &= e^{-rT}\textstyle\sum_{j = 0}^{n} \binom{n}{j} q^j (1 - q)^{n - j} \\ &\qquad \times \text{payoff}\!\left(S_0u^{2j - n}\right) \end{aligned}

For n=100n = 100 it gives 6.3570, as backward induction did. An American option, below, has no such sum.

The tree price of the call against the number of steps. It zigzags around Black–Scholes, and on log scales the distance falls on a line of slope −1-1: the error is about 1.4n\frac{1.4}{n}.

Try it

tree_price holds the values of one column of nodes in an array V. Each pass of the loop replaces it by the column one step earlier: V[1:] are the up successors and V[:-1] the down ones.

Then the 100-step call by the binomial sum, with binom.pmf, and qq; the mean and variance of the log price after 100 steps; and, for 100 and 101 steps, the final nodes nearest the strike with the price.

⌘/Ctrl + Enter runs the cell

Why the price zigzags

The payoff max⁡(S−100,0)\max(S - 100, 0) has a kink at the strike, and the tree sees it only through its final nodes. With an even nn one final node sits exactly at 100 (for n=100n = 100, the nodes nearest the strike are 97.21 and 100). With an odd nn two nodes straddle it (for n=101n = 101, 98.60 and 101.42). So odd and even trees err on opposite sides.

A common fix averages two neighbours: 12(6.3570+6.3846)=6.3708\frac{1}{2}(6.3570 + 6.3846) = 6.3708 for n=100n = 100 and 101, within 0.0002 of Black–Scholes.

Try it below. Drag the number of steps from 1 to 100 and watch the price swing around 6.3710. Up to 4 steps, each node shows its stock price and the call's value.

Explore

Each step the stock goes up by u or down by d. At expiry the option pays its payoff; each earlier node is the discounted risk-neutral average of the two after it. Add steps and watch the price close in on Black–Scholes.

1006.8492.162.19108.5111.3684.9301004.32117.7418.2478.27092.160108.518.51127.7627.76

Above each node the stock price, below it the option's value.

02040608010066.26.46.66.8

Tree price for 1 to 100 steps; the dashed line is Black–Scholes.

One step
Δt = 0.16667 years: u = 1.08508, d = 0.92159, q = 0.51026
Tree price, 3 steps
6.8385
Black–Scholes
6.371: the tree is off by 0.4675
Check yourself
A colleague prices the call with 100 steps (6.3570) and with 101 steps (6.3846). Which single number is the best estimate of the price?

The American put

An American option may be exercised at any time up to TT. At every node its holder compares the payoff from exercising now with the value of holding on, the backward-induction average. The option is worth the larger.

Rule: American option on a tree

V=max⁡(payoff(S), hold)hold=e−rΔt(q Vup+(1−q) Vdown)\begin{gathered} V = \max\big(\text{payoff}(S),\ \text{hold}\big) \\[4pt] \text{hold} = e^{-r\Delta t}\left(q\,V_{\text{up}} + (1 - q)\,V_{\text{down}}\right) \end{gathered}

Applied at every node from expiry back to today; where the payoff is larger, the holder exercises early. The American put has no formula like Black–Scholes, and a tree is a standard way to price it.

Take the put with strike 100 on the two-step tree. At expiry it pays 18.1269, 0 and 0. After one down move the stock is 90.4837: holding is worth 0.992528×0.487401×18.1269=8.76910.992528 \times 0.487401 \times 18.1269 = 8.7691, but exercising pays 100−90.4837=9.5163100 - 90.4837 = 9.5163. The holder exercises.

So the two-step American put is worth 4.6036, against 4.2421 for the European. With more steps:

steps nnEuropean putAmerican putdifference
16.26246.26240
24.24214.60360.3615
35.34975.46820.1184
104.74354.92800.1845
1004.86825.00200.1338
1,0004.88085.00910.1282
2,0004.88155.00940.1279

The European column closes in on the Black–Scholes put, 6.3710−1.4888=4.88226.3710 - 1.4888 = 4.8822, by put–call parity (case study 3.3.4). The American price creeps up more slowly; 10,000 steps give 5.0098. The right to exercise early is worth about 13 cents, 2.6% of the European price.

For a call on a stock without dividends, the check never chooses early exercise: the American call costs what the European one does. With time τ\tau left, put–call parity (case study 3.3.4) gives C=S−Ke−rτ+P≥S−Ke−rτC = S - Ke^{-r\tau} + P \ge S - Ke^{-r\tau} for the European call. For r>0r > 0 that is more than the payoff S−KS - K, so holding wins.

Try it

tree_price now takes american=True: after each step back it replaces every value by the larger of holding and exercising at that node's price.

The last lines are the two-step check at the down node and the two-step American and European prices. The final line is the American call minus the European call with 100 steps.

⌘/Ctrl + Enter runs the cell

In the explorer above, choose the put and tick the American box. Nodes where exercising beats holding turn red, all of them low in the tree.

Where to exercise

At each time there is a critical price: below it, exercising beats holding. A low price means a large payoff now and little time for the stock to climb back.

On the 1,000-step tree the boundary lies between the highest node where exercise pays and the next node up. Three months in, that is between 84.37 and 85.13; with under two days left, between 96.49 and 97.35. Today it is about 81 (by the finer tree of the figure): the stock must fall about 19% first.

Below the curve, exercising the put now beats holding it. The critical price rises from about 81 today to the strike at expiry, as the time for the stock to recover runs out.

Longer expiries

The longer the put runs, the more early exercise is worth. A European put gains value with its expiry at first, then loses it: KK received far in the future is worth little today. An American holder can take K−SK - S whenever it suits.

expiry TT (years)0.515103050
European put (Black–Scholes)4.886.4610.4010.937.404.22
American put (2,000 steps)5.016.7412.2014.7417.6618.28

The American price keeps rising with TT, ever more slowly. Case study 6.2.4 values a put that never expires and compares it with this tree.

Try it

american_put also records, at each step i, the highest node where exercising beats holding. The cell prints that node and the next one up, at four times: t=0.05t = 0.05, 0.25, 0.45 and 0.495 years.

Then Black–Scholes against the American tree for longer expiries. 2,000 steps give two correct decimals here.

⌘/Ctrl + Enter runs the cell
Check yourself
On a tree, the American put's holding value at a node is 8.77 and exercising pays 9.52. What value does the node get, and what does the holder do?

Try it

The bad-qq example with one step and with twenty, then the node and path counts.

⌘/Ctrl + Enter runs the cell
Exercise

A binomial tree pricer

Write binomial_price(S, K, T, r, sigma, n, kind="call", american=False). It returns the price of a call or a put with strike K and expiry T on a recombining tree with n steps. Use u=eσΔtu = e^{\sigma\sqrt{\Delta t}}, d=1ud = \frac{1}{u} and q=erΔt−du−dq = \frac{e^{r\Delta t} - d}{u - d}.

  • Work back from the payoff at expiry by backward induction. With american=True, take the larger of holding and exercising at every node.
  • Raise ValueError if n is not a whole number of at least 1, or if S, K, T or sigma is not positive. Raise it too if kind is not "call" or "put", or if qq is not strictly between 0 and 1.

The tests use this case study's call at 1, 2 and 100 steps, put–call parity, and the American put at 2 and 100 steps. They check that an American call costs what the European one does, and that a deep in-the-money American put is worth exactly its payoff. They also try a bad qq, zero volatility and a fractional n.

See the tests
import math

# The call and put of case study 2.4.6: S = 100, K = 100, T = 0.5, r = 0.03, sigma = 0.2
ARGS = (100, 100, 0.5, 0.03, 0.2)


def expect_value_error(call, why):
    try:
        call()
    except ValueError:
        return
    raise AssertionError(f"expected ValueError: {why}")


def test_one_step():
    """One step prices the call at 7.7512, as in case study 5.2.5"""
    expect_close(binomial_price(*ARGS, 1), 7.751159552620581, what="binomial_price(..., n=1)", rel=1e-9)


def test_two_steps():
    """Two steps price the call at 5.7309"""
    expect_close(binomial_price(*ARGS, 2), 5.730919862093777, what="binomial_price(..., n=2)", rel=1e-9)


def test_hundred_steps():
    """100 steps price the call at 6.3570, about 1.4 cents below Black–Scholes 6.3710"""
    expect_close(binomial_price(*ARGS, 100), 6.356980314193379, what="binomial_price(..., n=100)", rel=1e-9)


def test_put_call_parity():
    """With 50 steps, the European call minus the European put is S − Ke^(−rT) = 1.4888"""
    c = binomial_price(*ARGS, 50, kind="call")
    p = binomial_price(*ARGS, 50, kind="put")
    expect_close(p, 4.85416311717389, what="European put, n=50", rel=1e-9)
    expect_close(c - p, 100 - 100 * math.exp(-0.015), what="call minus put", rel=1e-9)


def test_american_put():
    """The American put is worth 4.6036 with 2 steps and 5.0020 with 100"""
    expect_close(binomial_price(*ARGS, 2, kind="put", american=True), 4.603575924794417, what="American put, n=2", rel=1e-9)
    expect_close(binomial_price(*ARGS, 100, kind="put", american=True), 5.00195775821218, what="American put, n=100", rel=1e-9)


def test_american_call():
    """An American call on a stock without dividends costs the same as the European call"""
    expect_close(binomial_price(*ARGS, 100, kind="call", american=True), 6.356980314193379, what="American call, n=100", rel=1e-9)


def test_deep_in_the_money():
    """An American put with the stock at 70 is worth exactly its payoff, 30"""
    expect_close(binomial_price(70, 100, 0.5, 0.03, 0.2, 100, kind="put", american=True), 30.0, what="American put at S=70", rel=1e-12)


def test_other_inputs():
    """A one-year put with S = 50, K = 55, r = 5% and σ = 30%, 200 steps: 7.3305 European, 7.8127 American"""
    expect_close(binomial_price(50, 55, 1.0, 0.05, 0.3, 200, kind="put"), 7.330473104051195, what="European put", rel=1e-9)
    expect_close(binomial_price(50, 55, 1.0, 0.05, 0.3, 200, kind="put", american=True), 7.812666020148489, what="American put", rel=1e-9)


def test_bad_q():
    """σ = 1% and r = 5% with one step of half a year make q = 2.29, so ValueError"""
    expect_value_error(lambda: binomial_price(100, 100, 0.5, 0.05, 0.01, 1), "q would be 2.2883, not a probability")


def test_zero_volatility():
    """Zero volatility raises ValueError instead of dividing by zero"""
    expect_value_error(lambda: binomial_price(100, 100, 0.5, 0.03, 0.0, 10), "with sigma = 0, u = d = 1")


def test_bad_inputs():
    """n = 0, n = 2.5, a negative stock price or kind="straddle" raise ValueError"""
    expect_value_error(lambda: binomial_price(*ARGS, 0), "n = 0 is not a tree")
    expect_value_error(lambda: binomial_price(*ARGS, 2.5), "n = 2.5 is not a whole number of steps")
    expect_value_error(lambda: binomial_price(-100, 100, 0.5, 0.03, 0.2, 10), "a stock price must be positive")
    expect_value_error(lambda: binomial_price(*ARGS, 10, kind="straddle"), "kind must be call or put")

Summary card

  • A tree of nn steps uses Δt=Tn\Delta t = \frac{T}{n}, u=eσΔtu = e^{\sigma\sqrt{\Delta t}}, d=1ud = \frac{1}{u} and q=erΔt−du−dq = \frac{e^{r\Delta t} - d}{u - d}. It recombines: i+1i + 1 nodes after ii steps, at S0u2j−iS_0u^{2j - i}. The log price is a random walk with steps ±σΔt\pm\sigma\sqrt{\Delta t}.
  • Backward induction: the payoff at expiry, then V=e−rΔt(qVup+(1−q)Vdown)V = e^{-r\Delta t}(qV_{\text{up}} + (1 - q)V_{\text{down}}) at each node. Each step is complete with the bond and the stock. So the price is the cost of a portfolio rebalanced at each node, with no new money.
  • For the call of case study 2.4.6: 7.7512 with one step, 5.7309 with two and 6.3570 with 100. The tree closes in on Black–Scholes 6.3710, with an error of about 1.4n\frac{1.4}{n} whose sign alternates between odd and even nn.
  • American option: V=max⁡(payoff,hold)V = \max(\text{payoff}, \text{hold}) at every node. The American put is worth 5.0098 (10,000 steps) against 4.8822 for the European. It is exercised below a critical price that rises from about 81 today to 100 at expiry. An American call on a stock without dividends is never exercised early.

Free further reading

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