Bond Risk Measures and Immunization › Interest Rate Risk
Quantitative FinanceAll chapters Bond Risk Measures and Immunization › Interest Rate RiskOne section · Sections in this chapter2 / 13 ↓
Reading tools Definitions & inline links
Choose your expertise level to adjust how many terms are explained. Beginners see more tooltips, experts see fewer to maintain reading flow. Hover over underlined terms for instant definitions.
Article links
Make inline references clickable
Before developing formal risk measures, we establish the fundamental relationship between interest rates and bond prices. Understanding this relationship provides the intuition that underlies all duration and convexity calculations.
A bond represents a contractual promise to pay a stream of future cash flows. These cash flows consist of periodic coupon payments plus the return of principal at maturity. The question every bond investor faces is: what is this stream of future payments worth today? The answer depends critically on the discount rate applied to those payments.
For the fixed-cash-flow bonds considered here, price is the present value of promised payments. The first formula uses one yield per payment period, with positive discount base and nonnegative cash flows:
where:
- : the bond price
- : the cash flow at time (coupon payments and principal)
- : the yield to maturity (expressed as a decimal)
- : the total number of periods until maturity
For these fixed positive payments, a higher yield lowers price. A lower-coupon bond generally trades below an otherwise comparable higher-coupon bond when both are priced at the same yield. The coupon itself is contractual: an issuer does not automatically change an existing bond's payments when market yields move.
The magnitude of this price sensitivity depends on several bond characteristics, and understanding these relationships helps investors anticipate how their holdings will respond to rate changes:
- Maturity: A fixed payment due later has greater percentage yield sensitivity at the same yield convention. Longer coupon-bond maturity also increases duration in the examples below, but maturity alone is not a universal ordering rule across all coupon and yield combinations.
- Coupon rate: Holding maturity, yield and payment frequency fixed, lower coupons increase duration when there is a coupon date strictly before maturity. A zero-coupon bond has all its present-value weight at maturity, giving the greatest duration for those terms. Higher coupons shift present-value weight to earlier receipts when those dates exist. If all remaining payments arrive at maturity, changing the coupon does not change duration or create an earlier receipt.
- Current yield level: Price sensitivity is higher at lower yield levels due to the convex nature of the price-yield curve. Moving from a 2% yield to a 3% yield produces a larger percentage price change than moving from 8% to 9%, even though both represent a 100 basis point increase.
def _bond_terms(face_value, coupon_rate, years_to_maturity, yield_rate, frequency):
"""Validate the fixed, nonnegative-cash-flow, regular-payment model."""
from math import isclose, isfinite
from numbers import Real
values = (face_value, coupon_rate, years_to_maturity, yield_rate, frequency)
if any(
isinstance(x, bool) or not isinstance(x, Real) or not isfinite(x)
for x in values
):
raise ValueError("Bond inputs must be finite real numbers")
if face_value <= 0 or coupon_rate < 0 or years_to_maturity <= 0:
raise ValueError("Require positive face value and maturity, nonnegative coupon")
if frequency <= 0 or frequency != int(frequency):
raise ValueError("frequency must be a positive integer")
frequency = int(frequency)
raw_periods = years_to_maturity * frequency
periods = round(raw_periods)
if periods < 1 or not isclose(periods, raw_periods, rel_tol=0, abs_tol=1e-10):
raise ValueError("Maturity must lie on the regular payment grid")
periodic_yield = yield_rate / frequency
if 1 + periodic_yield <= 0:
raise ValueError("The periodic discount base must be positive")
return periods, face_value * coupon_rate / frequency, periodic_yield
def bond_price(face_value, coupon_rate, years_to_maturity, yield_rate, frequency=2):
"""
Calculate the price of a bond with periodic coupon payments.
Parameters:
- face_value: Par value of the bond
- coupon_rate: Annual coupon rate (decimal)
- years_to_maturity: Time to maturity in years
- yield_rate: Yield to maturity (decimal, annualized)
- frequency: Number of coupon payments per year
"""
from math import expm1, log1p
periods, coupon_payment, periodic_yield = _bond_terms(
face_value, coupon_rate, years_to_maturity, yield_rate, frequency
)
# Present value of coupon payments
if periodic_yield == 0:
pv_coupons = coupon_payment * periods
else:
pv_coupons = (
coupon_payment * -expm1(-periods * log1p(periodic_yield)) / periodic_yield
)
# Present value of face value
pv_face = face_value / (1 + periodic_yield) ** periods
return pv_coupons + pv_faceLet's examine how a bond's price changes across different yield levels:
import numpy as np
# 10-year bond with 5% coupon, $1000 face value
face_value = 1000
coupon_rate = 0.05
maturity = 10
# Calculate prices across yield range
yields = np.linspace(0.01, 0.10, 50)
prices = [bond_price(face_value, coupon_rate, maturity, y) for y in yields]
For the fixed nonnegative cash flows used here, the price-yield curve bends upward. From the same starting yield, a nonzero yield decrease gives a larger price gain than the loss caused by an equal increase, provided the discount base remains positive. Embedded options can change this convexity pattern.