SigmaField
Mathematical Foundations for Quant Finance
0 of 256 lessons
      1. 2.7.1 Taylor polynomialsConcept · 30 min
      2. 2.7.2 Standard series and the size of the errorConcept · 35 min
      3. 2.7.3 Practice set: Taylor seriesPractice set · 20 min
      4. 2.7.4 Case study: duration–convexity and delta–gammaCase study · 35 min

Level 2, Single-variable calculus 2.7 Taylor series Lesson 4 of 4

Case studyLesson 2.7.4 in Level 2, Single-variable calculus. About 35 minutes.

Case study: duration–convexity and delta–gamma

The bond estimate is a Taylor polynomial

Case study 2.4.5 estimated the price change of the 5-year 4% bond at a 5% yield with duration DD and convexity CC. Here yy is the yield and Δy\Delta y its change, both decimals. Write the quadratic copy of the price P(y)P(y) in step form (lesson 2.7.1), with step Δy\Delta y:

P(y+Δy)≈P(y)+P′(y) Δy+P′′(y)2 (Δy)2\begin{gathered} P(y + \Delta y) \approx P(y) + P'(y)\,\Delta y \\ + \frac{P''(y)}{2}\,(\Delta y)^2 \end{gathered}

Subtract P(y)P(y) and divide by it. Since D=−P′PD = -\frac{P'}{P} (case study 2.2.4) and C=P′′PC = \frac{P''}{P} (case study 2.4.5), the right side becomes case study 2.4.5's estimate.

Rule: Duration–convexity is a Taylor polynomial

ΔPP≈−D Δy+12 C (Δy)2the degree-2 copy of P about y\begin{gathered} \frac{\Delta P}{P} \approx -D\,\Delta y + \frac{1}{2}\,C\,(\Delta y)^2 \\[4pt] \text{the degree-2 copy of } P \text{ about } y \end{gathered}

The 12\frac{1}{2} is the 12!\frac{1}{2!} of lesson 2.7.1: it makes the copy's second derivative equal P′′(y)P''(y). This is where case study 2.4.5's 12\frac{1}{2} comes from.

Case study 2.4.5's numbers follow. At 5%, P=95.6705P = 95.6705, P′=−420.9797P' = -420.9797 and P′′=2341.2878P'' = 2341.2878. For a 2% rise the copy gives 95.6705−8.4196+0.4683=87.719295.6705 - 8.4196 + 0.4683 = 87.7192: in percent, −8.8006%+0.4894%-8.8006\% + 0.4894\%, as there.

Try it

SymPy gives the exact derivatives of the price formula at 5%. The second line builds the quadratic copy at Δy=0.02\Delta y = 0.02, term by term.

The last line is the same number from DD and CC: the two forms agree.

⌘/Ctrl + Enter runs the cell

The third-order term

The next term of the copy is P′′′(y)3!(Δy)3\frac{P'''(y)}{3!}(\Delta y)^3. Differentiating one payment ct(1+y)−tc_t(1 + y)^{-t} a third time, by the power and chain rules (lessons 2.3.1 and 2.3.3), gives −t(t+1)(t+2) ct(1+y)−t−3-t(t + 1)(t + 2)\,c_t(1 + y)^{-t-3}.

That is negative for every payment, so P′′′<0P''' < 0 for any bond with positive payments. Here P′′′=−15397.35P''' = -15397.35, so the cubic term is −0.0205-0.0205 for a 2% rise and +0.0205+0.0205 for a 2% fall.

Case study 2.4.5 saw the duration–convexity error grow about eight times when the move doubled, and change sign. Lesson 2.7.1 explains both: a quadratic copy misses by about a constant times (Δy)3(\Delta y)^3, an odd power. The cubic term is most of that miss; each error below is the true price minus the estimate.

yield movetrue pricedegree 2its errorcubic termerror with it
−3%-3\%109.4269109.35350.07340.06930.0041
−2%-2\%104.5797104.55840.02130.02050.0008
+2%+2\%87.699487.7192−0.0198-0.0198−0.0205-0.02050.0008
+3%+3\%84.029284.0947−0.0656-0.0656−0.0693-0.06930.0037

With the cubic term the error falls about 25 times at ±2%\pm 2\%. Because P′′′<0P''' < 0, for moves like these the quadratic copy overshoots when yields rise and undershoots when they fall.

When it matters

For the 5-year bond the cubic term is about 2 cents on a 2% move. It grows fast with maturity, like convexity in case study 2.4.5. For a 2% rise, per $100 of face value:

bondpricedegree-2 errorafter the cubic term
5-year, 4% coupon95.67−0.0198-0.01980.0008
10-year, 4% coupon92.28−0.1005-0.10050.0062
30-year, 4% coupon84.63−1.0026-1.00260.1499
30-year, zero coupon23.14−0.6830-0.68300.1101

For the 30-year zero, the duration–convexity estimate is off by about 3% of the price. A stress test with moves this large on long bonds needs the third term, or simply a full repricing, which is cheap for a bond.

Even the cubic term leaves 0.15 on the 30-year coupon bond: the fourth and later terms still count. Each payment's series in Δy\Delta y converges for ∣Δy∣<1.05|\Delta y| < 1.05, the distance from 5% to the break at y=−1y = -1 (lesson 2.7.2), so more terms do close the gap.

Try it

taylor_errors computes the exact derivatives up to the third and prints the price and P′′′P'''. Then, for each move, it prints the true price, the error of the quadratic copy, the cubic term and the error with it.

The first call is the 5-year bond of the first table; the other three are the rest of the second table.

⌘/Ctrl + Enter runs the cell
Check yourself
A risk report estimates every bond's price change from duration and convexity. Yields jump 2%. For which bond is the estimate worst, as a share of its price?

Delta and gamma

Now the call of case studies 2.4.6 and 2.6.5: strike K=100K = 100, T=0.5T = 0.5 years, rate r=0.03r = 0.03 and volatility σ=0.2\sigma = 0.2, with the stock at S0=100S_0 = 100. Hold everything but the stock price SS fixed, and the price c(S)c(S) is a function of one variable.

Its first two derivatives have names. Delta is the slope dcdS\frac{dc}{dS}, and gamma is the second derivative d2cdS2\frac{d^2c}{dS^2}. The Derivatives Modelling course derives both formulas; here, take them as given.

Rule: Delta and gamma of a call

Δ=dcdS=N(d1)Γ=d2cdS2=φ(d1)S σT\begin{gathered} \Delta = \frac{dc}{dS} = N(d_1) \\[4pt] \Gamma = \frac{d^2c}{dS^2} = \frac{\varphi(d_1)}{S\,\sigma\sqrt{T}} \end{gathered}

d1d_1 is the one in case study 2.4.6's formula, NN is the standard normal cumulative distribution and φ\varphi its density (lesson 2.6.3). Delta is in dollars of option per dollar of stock; gamma is the change in delta per dollar.

At S0=100S_0 = 100, d1=0.176777d_1 = 0.176777, N(d1)=0.570158N(d_1) = 0.570158 and φ(d1)=0.392757\varphi(d_1) = 0.392757. So Δ=0.5702\Delta = 0.5702: the option gains about 57 cents per $1 rise in the stock. And Γ=0.392757100×0.2×0.5=0.0278\Gamma = \frac{0.392757}{100 \times 0.2 \times \sqrt{0.5}} = 0.0278: each $1 rise adds about 0.0278 to delta.

For a call, N(d1)N(d_1) lies between 0 and 1. Gamma is positive, so c(S)c(S) is concave up (lesson 2.4.2), as a bond price is in yy.

The letter Δ\Delta has two jobs on a desk: alone it is delta, and in ΔS\Delta S it means "change in SS". So Δ⋅ΔS\Delta \cdot \Delta S reads "delta times the change in the stock price".

Checking the formulas

A central difference (lesson 2.2.2) with a 1-cent bump, h=0.01h = 0.01, confirms delta: c(100.01)−c(99.99)0.02=0.570158\frac{c(100.01) - c(99.99)}{0.02} = 0.570158. The central difference of the delta formula gives 0.027772, and so does the second central difference of the price (lesson 2.4.2).

How to choose the bump is the subject of case study 2.8.4, which computes Greeks by repricing alone.

Try it

call is case study 2.4.6's formula and greeks returns delta and gamma. The first line prints d1d_1, N(d1)N(d_1) and φ(d1)\varphi(d_1).

The last line holds the three central differences with h=0.01h = 0.01.

⌘/Ctrl + Enter runs the cell

The delta–gamma estimate

The quadratic copy of c(S)c(S) about S0S_0, in step form with step ΔS\Delta S, is the option version of duration–convexity. Write ΔV\Delta V for the change in the option's value.

Rule: Delta–gamma estimate

ΔV≈Δ⋅ΔS+12 Γ (ΔS)2\Delta V \approx \Delta \cdot \Delta S + \frac{1}{2}\,\Gamma\,(\Delta S)^2

The degree-2 Taylor polynomial of the price in the stock price, minus the price today. As for convexity, the 12\frac{1}{2} is 12!\frac{1}{2!}. With Γ>0\Gamma > 0 the second term adds value whichever way the stock moves.

stock move ΔS\Delta Srepriceddelta onlydelta–gammaerror of delta–gamma
−10-10−4.2667-4.2667−5.7016-5.7016−4.3130-4.31300.0462
−5-5−2.4940-2.4940−2.8508-2.8508−2.5036-2.50360.0096
−2-2−1.0840-1.0840−1.1403-1.1403−1.0848-1.08480.0008
+2+21.19501.14031.1959−0.0009-0.0009
+5+53.18222.85083.1979−0.0158-0.0158
+10+106.94795.70167.0902−0.1422-0.1422

Each error is the repriced change minus the estimate. Delta alone always comes out too low, because the curve bends above its tangent.

The gamma term fixes most of that. What remains grows about like the cube of the move (lesson 2.7.1): 0.0009 for a $2 rise, 0.0158 for $5 and 0.1422 for $10.

Try it

The loop reprices the call after each move, including $20 each way. It prints the true change, the two estimates and the error of the second.

The last line is the delta-hedged position of the note below. It prints the gamma term for a $5 move, then the repriced change minus the delta term, up and down.

⌘/Ctrl + Enter runs the cell

Where it fails

A quadratic copy works when the step is small next to the scale on which the curve bends. For an option, that scale is about SσTS\sigma\sqrt{T}, the stock's typical move over the option's remaining life: $14.14 for six months. A $5 move is about a third of that.

Large moves. A $20 move is more than one typical move: the error reaches −1.27-1.27 on the way up.

Short expiry at the money. As TT shrinks, the price curve closes in on the payoff max⁡(S−K,0)\max(S - K, 0), with its corner at the strike (lesson 2.2.2). At the money, gamma grows like 1T\frac{1}{\sqrt{T}}, and the parabola cannot follow a corner.

time to expirySσTS\sigma\sqrt{T}Γ\Gammaerror, $5 riseerror, $5 fall
6 months14.140.0278−0.0158-0.01580.0096
1 month5.770.0689−0.0767-0.0767−0.0214-0.0214
1 week2.770.1438−0.4009-0.4009−0.3271-0.3271
1 day1.050.3811−2.7028-2.7028−2.6588-2.6588

With a day left the call is worth $0.42 and Γ=0.3811\Gamma = 0.3811. The estimate says a $5 fall makes the call $2.24 more valuable; in fact it loses its whole value, $0.42.

The call's price against the stock price, with the tangent and the delta–gamma parabola at $100. With six months left the parabola follows the curve over the whole range. With one day left the curve is nearly the payoff's corner, and the parabola leaves it within a few dollars.

Away from the strike the short-dated curve is almost straight, and the bend crowds around the strike. A week from expiry, gamma is 0.00009 at strike 90 and 0.00044 at strike 110. For short-dated options near the money, desks reprice rather than trust the estimate.

Try it

The loop takes the same at-the-money call with four expiries. It prints SσTS\sigma\sqrt{T}, gamma, and the delta–gamma error for a $5 rise and a $5 fall.

The next line is the one-day call: its price, the estimate for a $5 fall, and the true change. The last is the one-week gamma at strikes 90 and 110.

⌘/Ctrl + Enter runs the cell
Check yourself
A trader holds a delta-hedged short call: short the option, long Δ\Delta shares. The stock moves sharply. Ignoring time decay, what happens?
Exercise

Delta, gamma and the delta–gamma estimate

Write two functions for a Black–Scholes call, using norm.cdf and norm.pdf from scipy.stats.

  1. call_delta_gamma(S, K, T, r, sigma) returns the pair (delta, gamma): N(d1)N(d_1) and φ(d1)SσT\frac{\varphi(d_1)}{S\sigma\sqrt{T}}. If T≤0T \le 0 or σ≤0\sigma \le 0, raise ValueError: the formulas divide by zero.
  2. delta_gamma_change(S, K, T, r, sigma, dS) returns Δ⋅dS+12Γ (dS)2\Delta \cdot dS + \frac{1}{2}\Gamma\,(dS)^2, the estimated change in the call's value when the stock moves by dS.

The tests check the six-month call above and an in-the-money strike, and compare delta and gamma with central differences for another option. They also try $5 moves both ways, and pass T=0T = 0 and σ=0\sigma = 0.

See the tests
import math

from scipy.stats import norm


def _call(S, K, T, r, sigma):
    d1 = (math.log(S / K) + (r + sigma**2 / 2) * T) / (sigma * math.sqrt(T))
    return S * norm.cdf(d1) - K * math.exp(-r * T) * norm.cdf(d1 - sigma * math.sqrt(T))


def _pair(result):
    assert isinstance(result, tuple) and len(result) == 2, f"expected a pair (delta, gamma), got {result!r}"
    return result


def test_six_month_call():
    """The 6-month at-the-money call has delta 0.570158 and gamma 0.027772"""
    delta, gamma = _pair(call_delta_gamma(100, 100, 0.5, 0.03, 0.2))
    expect_close(delta, 0.5701581024006669, what="delta", rel=1e-9)
    expect_close(gamma, 0.027772131739916564, what="gamma", rel=1e-9)


def test_delta_by_central_difference():
    """Delta matches a central difference of the price for S = 50, K = 55, T = 0.25, σ = 0.35"""
    h = 1e-4
    bumped = (_call(50 + h, 55, 0.25, 0.01, 0.35) - _call(50 - h, 55, 0.25, 0.01, 0.35)) / (2 * h)
    delta, _ = _pair(call_delta_gamma(50, 55, 0.25, 0.01, 0.35))
    expect_close(delta, bumped, what="delta against the central difference", rel=1e-6)


def test_gamma_by_second_difference():
    """Gamma matches the second central difference of the price for the same option"""
    h = 1e-3
    bumped = (_call(50 + h, 55, 0.25, 0.01, 0.35) - 2 * _call(50, 55, 0.25, 0.01, 0.35) + _call(50 - h, 55, 0.25, 0.01, 0.35)) / h**2
    _, gamma = _pair(call_delta_gamma(50, 55, 0.25, 0.01, 0.35))
    expect_close(gamma, bumped, what="gamma against the second central difference", rel=1e-4)


def test_five_dollar_fall():
    """A $5 fall: the estimate is −2.5036, within a cent of repricing (−2.4940)"""
    est = delta_gamma_change(100, 100, 0.5, 0.03, 0.2, -5)
    expect_close(est, -2.503638865254377, what="delta_gamma_change(..., dS=-5)", rel=1e-8)
    true = _call(95, 100, 0.5, 0.03, 0.2) - _call(100, 100, 0.5, 0.03, 0.2)
    assert abs(est - true) < 0.01, f"expected within 0.01 of the repriced change {true:.4f}, got {est:.4f}"


def test_both_directions():
    """The gamma term is added both ways: +$5 gives 3.1979, more than delta alone (2.8508)"""
    expect_close(delta_gamma_change(100, 100, 0.5, 0.03, 0.2, 5), 3.1979421587522916, what="delta_gamma_change(..., dS=5)", rel=1e-8)


def test_in_the_money():
    """In the money (strike 90), delta is 0.8217 and gamma 0.0184"""
    delta, gamma = _pair(call_delta_gamma(100, 90, 0.5, 0.03, 0.2))
    expect_close(delta, 0.8216804280613134, what="delta at strike 90", rel=1e-9)
    expect_close(gamma, 0.01844533441808336, what="gamma at strike 90", rel=1e-9)


def test_at_expiry():
    """T = 0 raises ValueError: the formulas divide by zero"""
    try:
        call_delta_gamma(100, 100, 0, 0.03, 0.2)
    except ValueError:
        return
    raise AssertionError("expected ValueError for T = 0")


def test_zero_volatility():
    """σ = 0 raises ValueError too"""
    try:
        delta_gamma_change(100, 100, 0.5, 0.03, 0, 5)
    except ValueError:
        return
    raise AssertionError("expected ValueError for sigma = 0")

Summary card

  • Duration–convexity, ΔPP≈−DΔy+12C(Δy)2\frac{\Delta P}{P} \approx -D\Delta y + \frac{1}{2}C(\Delta y)^2, is the degree-2 Taylor polynomial of the price; the 12\frac{1}{2} is 12!\frac{1}{2!}.
  • The next term, P′′′6(Δy)3\frac{P'''}{6}(\Delta y)^3 with P′′′<0P''' < 0, explains why the estimate overshoots when yields rise. It matters for long bonds and large moves.
  • A call's delta is N(d1)N(d_1) and its gamma φ(d1)SσT\frac{\varphi(d_1)}{S\sigma\sqrt{T}}; central differences confirm both.
  • ΔV≈Δ⋅ΔS+12Γ(ΔS)2\Delta V \approx \Delta \cdot \Delta S + \frac{1}{2}\Gamma(\Delta S)^2 is the option's quadratic copy. Its error grows like (ΔS)3(\Delta S)^3.
  • It fails when the move is large next to SσTS\sigma\sqrt{T}, above all for short-dated options at the money, where gamma is large and the price curve nears the payoff's corner. Then reprice.

Free further reading

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