SigmaField
Mathematical Foundations for Quant Finance
0 of 256 lessons
      1. 6.1.1 Differential equations, and separable equationsConcept · 35 min
      2. 6.1.2 Linear first-order equations and integrating factorsConcept · 35 min
      3. 6.1.3 Practice set: first-order equationsPractice set · 25 min
      4. 6.1.4 Case study: bond prices under a deterministic short rateCase study · 35 min

Level 6, Differential equations 6.1 First-order ODEs Lesson 4 of 4

Case studyLesson 6.1.4 in Level 6, Differential equations. About 35 minutes.

Case study: bond prices under a deterministic short rate

A bank account that follows the short rate

The short rate r(t)r(t) is the interest rate a bank pays at time tt on money left with it for the next instant. It is a rate per year, compounded continuously (case study 1.4.6). This case study takes it as known in advance, a deterministic short rate: r(t)=0.03+0.01tr(t) = 0.03 + 0.01t, with tt in years.

Let B(t)B(t) be the balance of an account that starts with B(0)=1B(0) = 1. Over a short time Δt\Delta t it earns about r(t) B(t) Δtr(t)\,B(t)\,\Delta t. Dividing by Δt\Delta t and letting it shrink gives a differential equation.

Rule: A bank account

dBdt=r(t) B,B(0)=1B(t)=exp⁡(∫0tr(s) ds)\begin{gathered} \frac{dB}{dt} = r(t)\,B, \quad B(0) = 1 \\[4pt] B(t) = \exp\Big(\int_0^t r(s)\,ds\Big) \end{gathered}

The equation is separable (lesson 6.1.1): dBB=r(t) dt\frac{dB}{B} = r(t)\,dt, so ln⁡B=∫0tr(s) ds\ln B = \int_0^t r(s)\,ds, with the constant fixed by B(0)=1B(0) = 1. With a constant rate it is the familiar erte^{rt}.

The letter ss inside the integral is the time of each instant, since tt is already the upper limit. The integral is the area under the short rate (lesson 2.5.3), a pure number: a rate per year times years. For the rising rate,

∫0t(0.03+0.01s) ds=0.03t+0.005t2\int_0^t (0.03 + 0.01s)\,ds = 0.03t + 0.005t^2

After 10 years that area is 0.8, so the account holds B(10)=e0.8≈2.225541B(10) = e^{0.8} \approx 2.225541.

The price of a zero-coupon bond

A zero-coupon bond pays 1 at its maturity TT and nothing before (case study 2.2.4). Write P(0,T)P(0, T) for its price today. The rates are known, so the bond and the account are both riskless, and they must grow alike.

Put P(0,T)P(0, T) in the account today: at TT it has grown to P(0,T) B(T)P(0, T)\,B(T), which must equal the bond's 1. Any other price allows an arbitrage (case study 3.3.4). If the bond were cheaper, you could borrow from the account, buy bonds, and keep a profit with nothing to pay later.

Rule: Bond price under a deterministic short rate

P(0,T)=1B(T)=exp⁡(−∫0Tr(s) ds)\begin{gathered} P(0, T) = \frac{1}{B(T)} \\[4pt] = \exp\Big(-\int_0^T r(s)\,ds\Big) \end{gathered}

Discount by the area under the short rate up to maturity. For the rising rate, P(0,T)=e−(0.03T+0.005T2)P(0, T) = e^{-(0.03T + 0.005T^2)}.

maturity TT1251020
rising rate0.9656050.9231160.7595720.4493290.074274
flat 3%0.9704460.9417650.8607080.7408180.548812

At one year the two are close. The gap widens with maturity, because each extra year is discounted at a higher rate than the last.

The price solves an equation in the maturity

Seen as a function of its maturity, the price solves a differential equation of its own. The fundamental theorem (lesson 2.5.3) gives ddT∫0Tr(s) ds=r(T)\frac{d}{dT}\int_0^T r(s)\,ds = r(T), and the chain rule then gives

dPdT=−r(T) PP(0,0)=1\begin{gathered} \frac{dP}{dT} = -r(T)\,P \\[4pt] P(0, 0) = 1 \end{gathered}

A bond maturing now is worth its payment. Each extra instant of maturity is discounted at the short rate of that instant.

The explorer below draws this equation, P′=−(0.03+0.01T) PP' = -(0.03 + 0.01T)\,P, with yy for PP and tt for TT; the exact curve ends at 0.44933. Euler's method with h=1h = 1 lands on 0.45637, too high by 0.00705. Try h=0.5h = 0.5 and h=0.1h = 0.1: the error falls to 0.00331, then 0.00063.

Explore

Each short line has the slope the equation gives at that point. A solution follows the lines. Move the starting value, then step along the field with Euler's method and shrink the step to follow the exact curve.

024681000.51

The dark curve is the exact solution; the coloured one is Euler's method.

Exact solution at t = 10
0.44933
Euler, 20 steps of 0.5
0.45264, off by 0.00331

Try it

P is the bond price from the rule; flat discounts at a constant 3%. The loop prints the table.

The last line is the account after 10 years, and its reciprocal, which is the 10-year price.

⌘/Ctrl + Enter runs the cell

Yields and forward rates

The continuous yield y(T)y(T) is the one constant rate that gives the same price: P(0,T)=e−y(T) TP(0, T) = e^{-y(T)\,T}. It is case study 1.6.4's continuous zero rate. Taking logs gives it from the price.

Rule: Yield

y(T)=−ln⁡P(0,T)T=1T∫0Tr(s) ds\begin{gathered} y(T) = -\frac{\ln P(0, T)}{T} \\[4pt] = \frac{1}{T}\int_0^T r(s)\,ds \end{gathered}

The area under the short rate divided by the time: the average short rate up to maturity.

For the rising rate, y(T)=0.03+0.005Ty(T) = 0.03 + 0.005T: 3.5% at one year, 8% at ten and 13% at twenty. The yield curve, the yield against maturity, rises, but half as steeply as the short rate.

The instantaneous forward rate

Case study 1.6.4 read one-year forward rates off a curve, 1+f=Dn−1Dn1 + f = \frac{D_{n-1}}{D_n}: the growth the curve implies for one year alone. With continuous rates, the forward rate from T1T_1 to T2T_2 is 1T2−T1ln⁡P(0,T1)P(0,T2)\frac{1}{T_2 - T_1}\ln\frac{P(0, T_1)}{P(0, T_2)}. Shrink the gap to an instant and it becomes a derivative.

Rule: Instantaneous forward rate

f(T)=−ddTln⁡P(0,T)=r(T)\begin{gathered} f(T) = -\frac{d}{dT}\ln P(0, T) \\[4pt] = r(T) \end{gathered}

The rate the curve fixes today for lending over an instant at time TT. With a known short rate, the fundamental theorem makes it the short rate itself.

At T=10T = 10 the forward rate is 13%, and a central difference of −ln⁡P-\ln P (lesson 2.8.1) agrees. The one-year forward rate from 9 to 10 is 12.5%, the short rate at mid-year, 9.5. The yield is the average of the forward rates up to TT, so on a rising curve it trails them: 8% against 13% at ten years.

The rising short rate drags the price far below a flat 3%. The forward rate is the short rate itself, 13% at ten years; the yield, its average so far, is 8%.

Try it

Each line prints the yield and the short rate at a maturity. Then a central difference of −ln⁡P-\ln P at 10 with h=10−4h = 10^{-4}, rounded.

The last line is the one-year forward rate from 9 to 10, against the short rate at 9.5.

⌘/Ctrl + Enter runs the cell
Check yourself
Under the rising short rate, the 10-year yield is 8% and the short rate at 10 years is 13%. Which gives the price today of 1 paid in 10 years?

Checking with solve_ivp

The exact price needs ∫r\int r in closed form; solve_ivp (lesson 6.1.2) needs only rr itself. On dPdT=−r(T) P\frac{dP}{dT} = -r(T)\,P from P=1P = 1, with rtol=10−10\text{rtol} = 10^{-10} and atol=10−12\text{atol} = 10^{-12}, it matches the table at every maturity to about 10−1010^{-10}. With the default tolerances the 10-year price comes out 0.449235, off by about 10−410^{-4}: too rough for a price.

Backwards from maturity

A bond also has a value at each date tt before maturity: V(t)=exp⁡(−∫tTr(s) ds)V(t) = \exp\big(-\int_t^T r(s)\,ds\big). It solves dVdt=r(t) V\frac{dV}{dt} = r(t)\,V, and what is known is its end: V(T)=1V(T) = 1. A condition at the final time is a terminal condition, and the equation is solved backwards from it.

Given a t_span that runs from 10 down to 0, solve_ivp steps backwards and lands on 0.449329. Option values are found the same way, backwards from their payoff (lesson 6.3.2 and case study 6.3.4).

A short rate from a table

A desk's short-rate curve comes from market quotes at a few dates, joined by straight lines with np.interp (lesson 1.6.1). Take rates of 3.0%, 4.2%, 5.5%, 6.0% and 6.2% at 0, 1, 3, 5 and 10 years: there is no formula to integrate.

The area under straight-line pieces is exactly the trapezoid rule on the knots (lesson 2.8.2), so e−areae^{-\text{area}} is the exact price: 0.575222 at 10 years, a yield of 5.53%. solve_ivp agrees to within 10−810^{-8}; the corners in the rate cost it some of the accuracy that rtol asks for.

Try it

The first loop solves for the price at five maturities, with the default and with tight tolerances. Each line prints the prices and the largest error, rounded to one figure.

Then the backward solve from V(10)=1V(10) = 1, and the price under the rate from a table, against e−areae^{-\text{area}} from np.trapezoid.

⌘/Ctrl + Enter runs the cell

A stock that pays dividends

The same equations price a forward contract on a stock: an agreement today to buy the stock at time TT for a price FF, fixed now and paid at TT. Nothing changes hands today, so the fair FF is the one that makes the deal worth nothing now.

Copy the deal with the bank: borrow to buy the stock today, hold it to TT, and repay the loan then. Whatever you owe at TT is the fair forward price, because at TT you hold one share either way. Dividends paid along the way change both sides.

A dividend yield

Say the stock pays dividends continuously at a dividend yield qq: per year, qq times its price, reinvested at once in more shares. The number of shares N(t)N(t) then grows by dNdt=qN\frac{dN}{dt} = qN, a separable equation with N(T)=N(0) eqTN(T) = N(0)\,e^{qT}.

To hold one share at TT, buy e−qTe^{-qT} shares today: 0.818731 of a share for q=2%q = 2\% and T=10T = 10. Borrow their cost, S0e−qTS_0 e^{-qT}, from the account; by the account equation the debt grows to S0e−qTB(T)S_0e^{-qT}B(T) at TT.

Rule: Forward price with a dividend yield

F=S0 e−qTP(0,T)F = \frac{S_0\,e^{-qT}}{P(0, T)}

Without dividends, F=S0P(0,T)F = \frac{S_0}{P(0, T)}: today's price grown at the short rate. A yield qq lowers it by the factor e−qTe^{-qT}, the shares the dividends will add.

With S0=S_0 = $100 and T=10T = 10, the forward price is $222.5541 without dividends and $182.2119 with q=2%q = 2\%. That is 100e0.6100e^{0.6}: the yield takes 0.2 off the area of 0.8 under the short rate.

Dividends in dollars

Now say the stock pays a fixed $2 a year in cash, continuously, instead of a yield. Buy one share today with a loan of S0=S_0 = $100, and use the dividends to pay the loan down. The loan L(t)L(t) grows with interest and shrinks with the dividends:

dLdt=r(t) L−cL(0)=S0,c=2\begin{gathered} \frac{dL}{dt} = r(t)\,L - c \\[4pt] L(0) = S_0, \quad c = 2 \end{gathered}

This is linear (lesson 6.1.2), with p=−r(t)p = -r(t). Its integrating factor e−∫0tr(s) dse^{-\int_0^t r(s)\,ds} is the discount factor P(0,t)P(0, t) itself. So (P(0,t) L)′=−c P(0,t)\big(P(0, t)\,L\big)' = -c\,P(0, t), and integrating from 0 to TT gives the forward price, the loan left at TT:

Rule: Forward price with cash dividends

F=L(T)=S0−c∫0TP(0,t) dtP(0,T)\begin{gathered} F = L(T) \\[4pt] = \frac{S_0 - c\int_0^T P(0, t)\,dt}{P(0, T)} \end{gathered}

c∫0TP(0,t) dtc\int_0^T P(0, t)\,dt is the present value of the dividends: case study 2.5.5's integral, with P(0,t)P(0, t) in place of e−rte^{-rt}. Take it off the stock price, then grow what is left to TT.

Completing the square in −(0.03t+0.005t2)-(0.03t + 0.005t^2) turns P(0,t)P(0, t) into a bell curve, which has no antiderivative formula (lesson 2.5.2), so quad finds the area: ∫010P(0,t) dt≈7.480262\int_0^{10} P(0, t)\,dt \approx 7.480262. The dividends pay $20 in all, worth $14.9605 today, and F≈F \approx $189.2588. Solving the loan equation with solve_ivp gives the same to about 10−910^{-9}.

Try it

The first lines are the dividend yield: the shares to buy today, then the forward price without and with q=2%q = 2\%.

Then the cash dividends: quad gives the present value of the stream, and solve_ivp follows the loan to year 10. The last line prints the two forward prices and their gap, rounded.

⌘/Ctrl + Enter runs the cell
Check yourself
A stock pays a continuous dividend yield of 2%, reinvested in more shares. How many shares must you buy today to hold exactly one share in 10 years?

Try it

The desk notes in numbers: the price with the rates typed in percent, the annual rate for an 8% continuous yield, a negative rate, and the straight-line rate pushed to 30 years.

⌘/Ctrl + Enter runs the cell
Exercise

A curve from a short rate

Write three functions. Each takes rate, a function of time giving the short rate per year as a decimal, and maturities in years.

  1. zero_price(rate, T) returns P(0,T)P(0, T) by solving dPdT=−r(T) P\frac{dP}{dT} = -r(T)\,P from P(0,0)=1P(0, 0) = 1 with solve_ivp, using rtol=1e-10 and atol=1e-12. Return 1.0 when T is 0, and raise ValueError when T is negative.
  2. zero_yield(rate, T) returns the continuous yield −ln⁡P(0,T)T-\frac{\ln P(0, T)}{T}. Raise ValueError unless T is positive.
  3. forward_rate(rate, T1, T2) returns the forward rate 1T2−T1ln⁡P(0,T1)P(0,T2)\frac{1}{T_2 - T_1}\ln\frac{P(0, T_1)}{P(0, T_2)}. Raise ValueError unless 0≤T1<T20 \le T_1 < T_2.

The tests use the rising rate, a flat rate, a negative rate and a rate read from a table with np.interp.

See the tests
import math

import numpy as np


def rising(t):
    return 0.03 + 0.01 * t


def test_rising_rate_price():
    """Under the short rate 0.03 + 0.01t, 1 paid in 10 years costs 0.449329 today"""
    expect_close(zero_price(rising, 10), math.exp(-0.8), what="zero_price(rising, 10)", rel=1e-8)


def test_flat_rate_price():
    """A flat 3% for 5 years gives the price e^(-0.15)"""
    expect_close(zero_price(lambda t: 0.03, 5), math.exp(-0.15), what="zero_price(flat 3%, 5)", rel=1e-8)


def test_maturity_now():
    """A bond maturing now is worth 1"""
    expect_close(zero_price(rising, 0), 1.0, what="zero_price(rising, 0)")


def test_negative_maturity():
    """A negative maturity raises ValueError"""
    try:
        zero_price(rising, -1)
    except ValueError:
        return
    raise AssertionError("expected ValueError for T = -1")


def test_negative_rate():
    """A flat -0.5% for 5 years makes the bond cost more than 1: e^0.025"""
    expect_close(zero_price(lambda t: -0.005, 5), math.exp(0.025), what="zero_price(flat -0.5%, 5)", rel=1e-8)


def test_yield_is_average_rate():
    """The 10-year yield under the rising rate is its average, 8%"""
    expect_close(zero_yield(rising, 10), 0.08, what="zero_yield(rising, 10)", rel=1e-7)


def test_yield_needs_positive_maturity():
    """A yield at maturity 0 raises ValueError"""
    try:
        zero_yield(rising, 0)
    except ValueError:
        return
    raise AssertionError("expected ValueError for T = 0: the yield divides by T")


def test_forward_rate():
    """The forward rate from 9 to 10 years is 12.5%, the short rate at 9.5"""
    expect_close(forward_rate(rising, 9, 10), 0.125, what="forward_rate(rising, 9, 10)", rel=1e-6)


def test_forward_rate_order():
    """A forward rate from 5 back to 2 raises ValueError"""
    try:
        forward_rate(rising, 5, 2)
    except ValueError:
        return
    raise AssertionError("expected ValueError when T2 is not after T1")


def test_rate_from_a_table():
    """A rate joined by straight lines between quotes gives e^(-area) by the trapezoid rule: 0.575222"""
    knots, rates = [0, 1, 3, 5, 10], [0.030, 0.042, 0.055, 0.060, 0.062]
    price = zero_price(lambda t: np.interp(t, knots, rates), 10)
    expect_close(price, math.exp(-np.trapezoid(rates, knots)), what="zero_price(table rate, 10)", rel=1e-7)

Summary card

  • A bank account under a short rate r(t)r(t) grows by dBdt=r(t) B\frac{dB}{dt} = r(t)\,B, so B(T)=e∫0Tr(s) dsB(T) = e^{\int_0^T r(s)\,ds}.
  • With known rates, a zero-coupon bond costs P(0,T)=e−∫0Tr(s) dsP(0, T) = e^{-\int_0^T r(s)\,ds}; as a function of maturity it solves dPdT=−r(T) P\frac{dP}{dT} = -r(T)\,P with P(0,0)=1P(0, 0) = 1. For r=0.03+0.01tr = 0.03 + 0.01t, P(0,10)=e−0.8≈0.449329P(0, 10) = e^{-0.8} \approx 0.449329.
  • The continuous yield is the average short rate, y(T)=1T∫0Try(T) = \frac{1}{T}\int_0^T r; the instantaneous forward rate f(T)=−ddTln⁡Pf(T) = -\frac{d}{dT}\ln P is the short rate r(T)r(T) itself.
  • solve_ivp prices from rr alone, forwards from P=1P = 1 or backwards from the terminal condition V(T)=1V(T) = 1; set tight tolerances.
  • Forward prices: F=S0e−qTP(0,T)F = \frac{S_0e^{-qT}}{P(0, T)} with a dividend yield qq; F=S0−c∫0TP(0,t) dtP(0,T)F = \frac{S_0 - c\int_0^T P(0, t)\,dt}{P(0, T)} with cash dividends at cc a year, whose integrating factor is the discount factor.

Free further reading

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