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 and convexity . Here is the yield and its change, both decimals. Write the quadratic copy of the price in step form (lesson 2.7.1), with step :
Subtract and divide by it. Since (case study 2.2.4) and (case study 2.4.5), the right side becomes case study 2.4.5's estimate.
Rule: Duration–convexity is a Taylor polynomial
The is the of lesson 2.7.1: it makes the copy's second derivative equal . This is where case study 2.4.5's comes from.
Case study 2.4.5's numbers follow. At 5%, , and . For a 2% rise the copy gives : in percent, , as there.
Try it
SymPy gives the exact derivatives of the price formula at 5%. The second line builds the quadratic copy at , term by term.
The last line is the same number from and : the two forms agree.
The third-order term
The next term of the copy is . Differentiating one payment a third time, by the power and chain rules (lessons 2.3.1 and 2.3.3), gives .
That is negative for every payment, so for any bond with positive payments. Here , so the cubic term is for a 2% rise and 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 , an odd power. The cubic term is most of that miss; each error below is the true price minus the estimate.
| yield move | true price | degree 2 | its error | cubic term | error with it |
|---|---|---|---|---|---|
| 109.4269 | 109.3535 | 0.0734 | 0.0693 | 0.0041 | |
| 104.5797 | 104.5584 | 0.0213 | 0.0205 | 0.0008 | |
| 87.6994 | 87.7192 | 0.0008 | |||
| 84.0292 | 84.0947 | 0.0037 |
With the cubic term the error falls about 25 times at . Because , 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:
| bond | price | degree-2 error | after the cubic term |
|---|---|---|---|
| 5-year, 4% coupon | 95.67 | 0.0008 | |
| 10-year, 4% coupon | 92.28 | 0.0062 | |
| 30-year, 4% coupon | 84.63 | 0.1499 | |
| 30-year, zero coupon | 23.14 | 0.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 converges for , the distance from 5% to the break at (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 . 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.
Delta and gamma
Now the call of case studies 2.4.6 and 2.6.5: strike , years, rate and volatility , with the stock at . Hold everything but the stock price fixed, and the price is a function of one variable.
Its first two derivatives have names. Delta is the slope , and gamma is the second derivative . The Derivatives Modelling course derives both formulas; here, take them as given.
Rule: Delta and gamma of a call
is the one in case study 2.4.6's formula, is the standard normal cumulative distribution and 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 , , and . So : the option gains about 57 cents per $1 rise in the stock. And : each $1 rise adds about 0.0278 to delta.
For a call, lies between 0 and 1. Gamma is positive, so is concave up (lesson 2.4.2), as a bond price is in .
The letter has two jobs on a desk: alone it is delta, and in it means "change in ". So reads "delta times the change in the stock price".
Checking the formulas
A central difference (lesson 2.2.2) with a 1-cent bump, , confirms delta: . 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 , and .
The last line holds the three central differences with .
The delta–gamma estimate
The quadratic copy of about , in step form with step , is the option version of duration–convexity. Write for the change in the option's value.
Rule: Delta–gamma estimate
The degree-2 Taylor polynomial of the price in the stock price, minus the price today. As for convexity, the is . With the second term adds value whichever way the stock moves.
| stock move | repriced | delta only | delta–gamma | error of delta–gamma |
|---|---|---|---|---|
| 0.0462 | ||||
| 0.0096 | ||||
| 0.0008 | ||||
| 1.1950 | 1.1403 | 1.1959 | ||
| 3.1822 | 2.8508 | 3.1979 | ||
| 6.9479 | 5.7016 | 7.0902 |
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.
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 , 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 on the way up.
Short expiry at the money. As shrinks, the price curve closes in on the payoff , with its corner at the strike (lesson 2.2.2). At the money, gamma grows like , and the parabola cannot follow a corner.
| time to expiry | error, $5 rise | error, $5 fall | ||
|---|---|---|---|---|
| 6 months | 14.14 | 0.0278 | 0.0096 | |
| 1 month | 5.77 | 0.0689 | ||
| 1 week | 2.77 | 0.1438 | ||
| 1 day | 1.05 | 0.3811 |
With a day left the call is worth $0.42 and . 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 , 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.
Delta, gamma and the delta–gamma estimate
Write two functions for a Black–Scholes call, using norm.cdf and norm.pdf from scipy.stats.
call_delta_gamma(S, K, T, r, sigma)returns the pair(delta, gamma): and . If or , raiseValueError: the formulas divide by zero.delta_gamma_change(S, K, T, r, sigma, dS)returns , the estimated change in the call's value when the stock moves bydS.
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 and .
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, , is the degree-2 Taylor polynomial of the price; the is .
- The next term, with , explains why the estimate overshoots when yields rise. It matters for long bonds and large moves.
- A call's delta is and its gamma ; central differences confirm both.
- is the option's quadratic copy. Its error grows like .
- It fails when the move is large next to , above all for short-dated options at the money, where gamma is large and the price curve nears the payoff's corner. Then reprice.