SigmaField
Mathematical Foundations for Quant Finance
0 of 256 lessons
      1. 2.2.1 Rate of change and the slope of a tangentConcept · 25 min
      2. 2.2.2 The derivative as a limit, and finite differencesConcept · 35 min
      3. 2.2.3 Practice set: the derivativePractice set · 20 min
      4. 2.2.4 Case study: bond price sensitivity and durationCase study · 30 min

Level 2, Single-variable calculus 2.2 The derivative Lesson 4 of 4

Case studyLesson 2.2.4 in Level 2, Single-variable calculus. About 30 minutes.

Case study: bond price sensitivity and duration

Price as a function of yield

A bond pays a coupon cc at the end of each year and its face value FF after TT years. At the yield yy (a decimal: 5% is 0.05), its price is the present value of those payments (case study 1.5.5):

P(y)=∑t=1Tc(1+y)t+F(1+y)TP(y) = \sum_{t=1}^{T} \frac{c}{(1 + y)^t} + \frac{F}{(1 + y)^T}

A higher yield divides every payment by more, so the price falls as the yield rises. Its derivative P′(y)P'(y), written dPdy\frac{dP}{dy}, is negative: the graph slopes down (lesson 2.2.2).

The price of the 5-year 4% bond against its yield, with the tangent at 5%. The tangent's error is tiny for small moves, grows about four times when the move doubles, and is positive both ways.

The tangent at 5% is the duration picture: its slope is P′(0.05)P'(0.05). The rest of this case study measures that slope, puts it in traders' units, and tests how far the tangent holds.

Bumping the yield

The formula is a sum of powers of 1+y1 + y. Differentiating it exactly needs the rules of module 2.3. Instead, do what lesson 2.2.2 did with a function you can only evaluate: move the yield a little each way and reprice.

Rule: Price sensitivity by bumping

P′(y)≈P(y+h)−P(y−h)2hP'(y) \approx \frac{P(y + h) - P(y - h)}{2h}

The central difference of lesson 2.2.2. Reprice at a slightly higher and a slightly lower yield, and divide by the total move, 2h2h. A bump of one basis point is h=0.0001h = 0.0001.

Try it

A 1 bp rise moves the price by −0.042086-0.042086; a 1 bp fall moves it by +0.04211+0.04211. The two are not mirror images, because the curve bends.

So the forward difference gives −420.86-420.86, the backward one −421.10-421.10 and the central one −420.98-420.98. SymPy's exact derivative is −420.97967-420.97967: the central difference is off by 0.00003, the one-sided ones by about 0.12.

⌘/Ctrl + Enter runs the cell

How big a bump?

Lesson 2.2.2 showed that a finite difference fails if hh is too big or too small. The same holds here.

Try it

A 100 bp bump (h=0.01h = 0.01) is off by 0.26: the curve bends too much over that range. One basis point is off by 0.00003, and h=10−6h = 10^{-6} by about 10−810^{-8}.

At h=10−12h = 10^{-12} rounding takes over and the error grows to 0.0044. One basis point is accurate enough, and it is a move markets really make.

⌘/Ctrl + Enter runs the cell

Units: per unit, per 1%, per basis point

The slope −420.98-420.98 is in price per unit of yield. One unit is a move of 1 in yy: 100 percentage points. No market moves that far, so the slope is rescaled to a realistic move.

yield move1 (100%)0.01 (1%)0.0001 (1 bp)
price change−420.98-420.98−4.21-4.21−0.0421-0.0421

The last column has a name. The DV01 (dollar value of an 01, a basis point) is the price fall for a 1 bp rise in yield. It is quoted as a positive number.

Rule: DV01

DV01=−P′(y)×0.0001\text{DV01} = -P'(y) \times 0.0001

For the bond, 0.0421 per $100 of face value. A position of $10 million face loses about $4,209.80 for each basis point that yields rise.

A slope of −420.98-420.98, a change of −4.21-4.21 per 1% and a DV01 of 0.0421 describe the same bond. Always check which unit a number is in before comparing two of them.

Try it

The central-difference slope in the three units, then the DV01 per $100 and on $10 million of face value.

⌘/Ctrl + Enter runs the cell

Duration

A slope in dollars grows with the size of the position. Dividing by the price turns it into a percentage, which compares bonds of any size.

Rule: Modified duration

D=−1P dPdyD = -\frac{1}{P}\,\frac{dP}{dy}

The fall in price, as a fraction of the price, per unit rise in yield. With D=4.40D = 4.40, a 1% rise in yield (Δy=0.01\Delta y = 0.01) cuts the price by about 4.40×0.01=4.40%4.40 \times 0.01 = 4.40\%.

For the 5-year 4% bond, D=420.98/95.67≈4.4003D = 420.98 / 95.67 \approx 4.4003. Other bonds at the same 5% yield:

bondpriceDD
5-year, 8% coupon112.994.1496
5-year, 4% coupon95.674.4003
5-year, zero coupon78.354.7619
10-year, 4% coupon92.287.9615
10-year, zero coupon61.399.5238
30-year, 4% coupon84.6316.0942

Longer bonds are more sensitive. For the same maturity, a lower coupon means a higher duration, because more of the money arrives at the end. Duration also moves with the yield: the 5-year 4% bond has 4.5042 at 3% and 4.2997 at 7%.

Try it

duration divides the bumped slope by the price. The loop reproduces the table.

⌘/Ctrl + Enter runs the cell

The zero-coupon bond: the clean case

A zero-coupon bond pays only its face value, at time TT: P(y)=F(1+y)TP(y) = \frac{F}{(1 + y)^T}. Its duration is exactly T1+y\frac{T}{1 + y}: for 5 years at 5%, 51.05=4.7619\frac{5}{1.05} = 4.7619, as the bump found.

So for a zero, duration is its maturity divided by 1+y1 + y. A coupon bond is a bundle of payments at different times. Weight each payment's time by its share of the price and average: that is the Macaulay duration, DMacD_{\text{Mac}}.

For the 5-year 4% bond, 85% of the value arrives at year 5 and the rest earlier, so the average time is 4.62 years. Dividing by 1+y1 + y gives 4.40, the bumped duration. For yearly payments, D=DMac1+yD = \frac{D_{\text{Mac}}}{1 + y}; case study 2.3.7 shows why, with the differentiation rules.

Try it

The present value of each payment, its share of the price, and the weighted average time.

The last line compares the Macaulay duration over 1.051.05 with the zero's 51.05\frac{5}{1.05}.

⌘/Ctrl + Enter runs the cell

How far does the tangent go?

The derivative gives the tangent line of lesson 2.2.1: P(y+Δy)≈P(y)+P′(y) ΔyP(y + \Delta y) \approx P(y) + P'(y)\,\Delta y, where Δy\Delta y (delta y) is the change in yield. Here it is against the true price, both ways.

yield movetrue pricetangenttrue − tangent
−2%-2\%104.5797104.09010.48959
−1%-1\%100.000099.88030.11968
−1-1 bp95.712695.71260.00001
+1+1 bp95.628495.62840.00001
+1%+1\%91.575391.46070.11455
+2%+2\%87.699487.25090.44848

For a 1 bp move the tangent is off by only 0.00001. For 1% it is off by about 0.12, and for 2% by about 0.45 to 0.49. Doubling the move roughly quadruples the error, like the (x−3)2(x - 3)^2 gap in lesson 2.2.1.

The true price is above the tangent in every row. So duration overstates the loss when yields rise, and understates the gain when they fall. That bend is convexity, the subject of case study 2.4.5.

The −1%-1\% row is a yield of 4%, equal to the coupon rate. There the price is exactly 100: a bond whose yield equals its coupon rate is worth its face value.

Try it

The table above, from the central-difference slope. The gap prints with five decimals, so 1 bp shows as 0.00001.

⌘/Ctrl + Enter runs the cell
Check yourself
Bond A has modified duration 4.4 and bond B has 8.0. Yields rise by 0.5%. Roughly how do their prices change?
Check yourself
A bond position has a DV01 of $4,209.80. Yields fall by 3 basis points. Roughly what happens to its value?
Exercise

Price sensitivity, DV01 and duration

Write three functions for a bond with yearly coupons. Yields are decimals.

  1. price_slope(coupon, face, years, y, h=1e-4) returns the central-difference estimate of dPdy\frac{dP}{dy}.
  2. dv01(coupon, face, years, y) returns the price fall for a 1 bp rise, −P′(y)×0.0001-P'(y) \times 0.0001, as a positive number.
  3. modified_duration(coupon, face, years, y) returns D=−1PdPdyD = -\frac{1}{P}\frac{dP}{dy}.

Write your own price function inside, or copy the one from the cells above.

See the tests
def test_slope():
    """The 5-year 4% bond at 5% has dP/dy of about −420.98"""
    expect_close(price_slope(4, 100, 5, 0.05), -420.97969897625376, what="price_slope(4, 100, 5, 0.05)", rel=1e-6)


def test_slope_is_negative():
    """Prices fall as yields rise, so the slope is negative"""
    s = price_slope(6, 100, 10, 0.03)
    assert s < 0, f"expected a negative slope, got {s}"


def test_dv01():
    """Its DV01 is about 0.0421 per 100 of face value, a positive number"""
    expect_close(dv01(4, 100, 5, 0.05), 0.042097969897625376, what="dv01(4, 100, 5, 0.05)", rel=1e-6)


def test_dv01_scales_with_face():
    """On 10 million of face value (coupon 400,000 a year) the DV01 is about 4,209.80"""
    expect_close(dv01(400_000, 10_000_000, 5, 0.05), 4209.796989762537, what="dv01(400000, 10000000, 5, 0.05)", rel=1e-6)


def test_duration():
    """Its modified duration is about 4.40"""
    expect_close(modified_duration(4, 100, 5, 0.05), 4.4003072662927565, what="modified_duration(4, 100, 5, 0.05)", rel=1e-6)


def test_zero_coupon():
    """A 5-year zero-coupon bond at 5% has duration 5 / 1.05"""
    expect_close(modified_duration(0, 100, 5, 0.05), 5 / 1.05, what="modified_duration(0, 100, 5, 0.05)", rel=1e-6)


def test_zero_yield():
    """At a yield of exactly 0 the 5-year 4% bond has duration about 4.6667 (no division by zero)"""
    expect_close(modified_duration(4, 100, 5, 0.0), 4.666666981666623, what="modified_duration(4, 100, 5, 0.0)", rel=1e-6)


def test_longer_is_riskier():
    """A 10-year bond has a higher duration than a 5-year one"""
    d5, d10 = modified_duration(4, 100, 5, 0.05), modified_duration(4, 100, 10, 0.05)
    assert d10 > d5, f"expected the 10-year duration ({d10:.3f}) above the 5-year ({d5:.3f})"

Summary card

  • A bond's price falls as its yield rises; the derivative dPdy\frac{dP}{dy} measures how fast. It is the slope of the tangent to the price curve.
  • Estimate it by bumping: P(y+h)−P(y−h)2h\frac{P(y + h) - P(y - h)}{2h} with hh of one basis point. A much bigger bump suffers from the bend, a much smaller one from rounding.
  • Units: per unit of yield, per 1% (× 0.01) or per basis point (× 0.0001). DV01 =−P′(y)×0.0001= -P'(y) \times 0.0001, quoted positive.
  • Modified duration D=−1PdPdyD = -\frac{1}{P}\frac{dP}{dy}: the percentage price change per unit change in yield. Longer and lower-coupon bonds have higher duration; a zero has D=T1+yD = \frac{T}{1 + y}.
  • The tangent P+P′ΔyP + P'\Delta y is excellent for small moves and always below the true price; the gap for large moves is convexity.

Free further reading

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