Bond Fundamentals and Pricing › Bond Pricing by Discounting Cash Flows
Quantitative FinanceAll chapters Bond Fundamentals and Pricing › Bond Pricing by Discounting Cash FlowsOne section · Sections in this chapter4 / 14 ↓
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
The price of a bond equals the present value of all its future cash flows, discounted at an appropriate rate. This principle connects bond valuation to the time value of money. Bond pricing answers this simple question. What would a rational investor pay today in exchange for a known stream of future payments? The answer requires us to account for the opportunity cost of capital, the return an investor could earn by deploying funds elsewhere.
Present Value Framework
Discounting compares money available at different dates under a stated rate convention. If $100 can earn 5% for one year, it grows to $105, so $105 due next year has a present value of $100 at that rate. A positive rate makes present value smaller than the future amount; the statement is not unconditional when rates can be zero or negative.
For known cash flows at times in years, and a single effective annual discount rate , the price in this simplified model is:
where:
- : current price of the bond
- : cash flow received at time (coupon or principal)
- : time in years until the -th cash flow
- : annual discount rate (yield)
- : total number of cash flows
The factor is the growth of one dollar over years under the effective annual rate convention. Dividing a future payment by it reverses that growth. At , a later otherwise identical payment has a lower present value; at the values are equal, while a negative rate reverses the ordering.

For the next examples, value a fixed-rate, noncallable bond immediately after a coupon date, with equally spaced payments, nonnegative coupons, positive face value, and a positive integer number of remaining periods. Let be the positive integer payment frequency and the nominal annual yield compounded times per year. Its periodic yield is , and the price is:
where:
- : bond price
- : cash flow at period
- : annual yield to maturity
- : number of payment periods per year
- : total number of payment periods
- : period counter (1, 2, ..., )
Here, counts payment periods rather than years, and we divide the annual yield by to get the per-period rate. This adjustment accounts for the compounding frequency. A bond paying semiannually compounds twice per year, so we use half the annual rate for each six-month period. The distinction matters. A 6% annual yield compounded semiannually is not the same as a 6% yield compounded annually. With semiannual compounding, you earn 3% in the first half of the year, then 3% on that larger balance in the second half, resulting in an effective annual yield slightly above 6%.
Pricing a Coupon Bond
Now that we understand how to discount individual cash flows, we can develop a simpler formula for pricing standard coupon bonds. Rather than discounting each payment individually, we can exploit the regular structure of coupon payments to derive a closed-form expression that separates the bond's value into two intuitive components.
For a standard coupon bond, we can separate the cash flows into an annuity (the coupons) and a lump sum (the face value). The coupon payments form what mathematicians call an ordinary annuity, a sequence of equal payments occurring at regular intervals. A compact formula exists for the present value of such an annuity, which we can combine with the discounted principal to get the total bond price.
Let be the coupon rate, the face value, the yield, the payment frequency, and the total number of periods. The price is:
where:
- : bond price
- : annual coupon rate (as a decimal)
- : face value (par value)
- : number of coupon payments per year
- : annual yield to maturity
- : total number of coupon periods ()
- : the periodic coupon payment amount
- : the present value annuity factor for ; at its continuous limit is .
The formula separates bond value into two components:
-
Coupon annuity (first term): The stream of periodic coupon payments forms an ordinary annuity. The annuity factor represents how much a dollar paid each period for periods is worth today. It's the sum of all the individual discount factors. To understand this factor, imagine receiving $1 at the end of each period for periods. The present value of the first dollar is , the second is , and so on. The annuity factor is simply the sum of this geometric series, expressed in a single formula. As the number of periods increases or the yield decreases, this factor grows. You're receiving more dollars, or each dollar is worth more in present value terms.
-
Principal repayment (second term): The face value received at maturity, discounted back to today by the factor . This term captures the return of the investor's principal. For long-maturity bonds, this discount factor can be quite small. At a 6% yield compounded semiannually over 30 years, the discount factor is approximately 0.17, meaning the present value of the $1,000 face value is only about $170.
For this fixed positive-cash-flow schedule, increasing the admissible yield decreases each discounted payment and therefore the total price. At the same maturity, yield, and compounding convention, reducing the coupon shifts more of the value toward the final principal payment and increases percentage price sensitivity. A zero-coupon bond is the limiting case. This comparison concerns percentage sensitivity, not necessarily the dollar price change of bonds with the same face value.
def _finite_number(value, name):
"""Convert one finite scalar; reject booleans and nonscalar inputs."""
if isinstance(value, (bool, np.bool_)) or np.ndim(value) != 0:
raise ValueError(f"{name} must be one finite number")
try:
result = float(value)
except (TypeError, ValueError, OverflowError) as exc:
raise ValueError(f"{name} must be one finite number") from exc
if not np.isfinite(result):
raise ValueError(f"{name} must be finite")
return result
def _regular_bond_inputs(face_value, coupon_rate, years_to_maturity, payments_per_year):
"""Validate the regular coupon-date model, not a dated settlement schedule."""
face = _finite_number(face_value, "face_value")
coupon = _finite_number(coupon_rate, "coupon_rate")
years = _finite_number(years_to_maturity, "years_to_maturity")
frequency = _finite_number(payments_per_year, "payments_per_year")
if face <= 0 or coupon < 0 or years < 0:
raise ValueError("Require positive face value, nonnegative coupon and maturity")
if frequency <= 0 or not frequency.is_integer():
raise ValueError("payments_per_year must be a positive integer")
periods = years * frequency
if not np.isfinite(periods) or abs(periods - round(periods)) > 1e-10:
raise ValueError(
"Maturity times frequency must be a finite integer period count"
)
return face, coupon, years, int(frequency), int(round(periods))
def price_bond(
face_value, coupon_rate, years_to_maturity, yield_rate, payments_per_year=2
):
"""Price regular, nonnegative fixed coupons on a coupon-date boundary.
Yield is a nominal annual quote compounded payments_per_year times.
Require yield > -payments_per_year and a whole remaining period count.
At zero remaining maturity, face value is due immediately. Irregular
schedules and between-coupon settlement need a dated cash-flow model.
"""
face, coupon, _, frequency, periods = _regular_bond_inputs(
face_value, coupon_rate, years_to_maturity, payments_per_year
)
annual_yield = _finite_number(yield_rate, "yield_rate")
periodic_yield = annual_yield / frequency
if periodic_yield <= -1:
raise ValueError("yield_rate must exceed -payments_per_year")
periodic_coupon = coupon * face / frequency
with np.errstate(over="ignore", invalid="ignore", under="ignore"):
log_growth = np.log1p(periodic_yield)
principal_factor = np.exp(-periods * log_growth)
annuity_factor = (
float(periods)
if periodic_yield == 0
else -np.expm1(-periods * log_growth) / periodic_yield
)
value = periodic_coupon * annuity_factor + face * principal_factor
if not np.isfinite(value) or value <= 0:
raise ValueError("Price is outside the supported floating-point range")
return float(value)# Price a 10-year bond with 6% coupon at different yield levels
face_value = 1000
coupon_rate = 0.06
years = 10
yields = [0.04, 0.05, 0.06, 0.07, 0.08]
prices = [price_bond(face_value, coupon_rate, years, y) for y in yields]Bond Prices at Different Yield Levels --------------------------------------------- Yield Price ($) Price/Par --------------------------------------------- 4.0% 1163.51 116.35% 5.0% 1077.95 107.79% 6.0% 1000.00 100.00% 7.0% 928.94 92.89% 8.0% 864.10 86.41%
This demonstrates the inverse relationship between yields and prices. When the yield equals the coupon rate (6%), the bond trades at par. When yields fall below the coupon rate, the bond trades at a premium because its fixed coupons are more attractive than market rates. When yields rise above the coupon rate, the bond trades at a discount.
Suppose new otherwise comparable bonds offer 7% coupons. A 6% coupon bond pays less interest per year for the same face value, so a lower purchase price can make its promised cash flows yield the required return. Comparisons need matching currency, credit, options, liquidity, and discounting assumptions. Equal promised-payment yields do not guarantee equal realized returns.

Pricing a Zero-Coupon Bond
Zero-coupon bonds strip away the complexity of multiple cash flows, leaving us with the purest expression of present value. With only a single payment at maturity, the pricing formula reduces to its most elemental form, making zero-coupon bonds ideal pedagogical tools for understanding how time and yield interact to determine value.
Zero-coupon bonds have a simpler pricing formula since there's only one cash flow:
where:
- : current price of the zero-coupon bond
- : face value paid at maturity
- : annual yield (discount rate)
- : time to maturity in years
The simplicity of this formula makes zero-coupon bonds ideal building blocks for understanding fixed income. The price represents exactly how much an investor must pay today to receive dollars in years, given the market's required return of . Notice that the price is always less than the face value (assuming a positive yield), and the discount increases with both yield and time to maturity. A 30-year zero-coupon bond at a 5% yield trades at roughly 23% of its face value, which reflects the substantial time value of money over such a long horizon.
def price_zero_coupon(face_value, years_to_maturity, yield_rate):
"""Price one future principal payment at an effective annual yield > -1."""
face = _finite_number(face_value, "face_value")
years = _finite_number(years_to_maturity, "years_to_maturity")
annual_yield = _finite_number(yield_rate, "yield_rate")
if face <= 0 or years < 0 or annual_yield <= -1:
raise ValueError("Require positive face, nonnegative maturity and yield > -1")
with np.errstate(over="ignore", under="ignore", invalid="ignore"):
value = face * np.exp(-years * np.log1p(annual_yield))
if not np.isfinite(value) or value <= 0:
raise ValueError("Price is outside the supported floating-point range")
return float(value)
# Price 5-year zero-coupon bonds at different yields
zcb_prices = [price_zero_coupon(1000, 5, y) for y in yields]Zero-Coupon Bond Prices (5-year maturity) ----------------------------------- Yield Price ($) ----------------------------------- 4.0% 821.93 5.0% 783.53 6.0% 747.26 7.0% 712.99 8.0% 680.58
At the same maturity and yield convention, a zero-coupon bond has greater percentage yield sensitivity than an otherwise comparable positive-coupon bond. Its value is concentrated in the terminal payment. Dollar price sensitivity also depends on the amount invested or face value, so it need not follow the same ranking.