Term Structure of Interest Rates: Yield Curve Construction
Covers yield curve construction through zero rates, forward rates, and bootstrapping. Interpret curve shapes and build production-quality curves.
Quantitative FinanceAll chapters Term Structure of Interest RatesFull chapter · Sections in this chapter1 / 8 ↓
Reading tools Definitions, links & notes
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
Term Structure of Interest Rates
How much is a payment in five years worth today? The answer need not use the same rate as a payment next year. The term structure of interest rates describes how rates vary across maturities at a single observation date. A bank's 1-year and 5-year certificates of deposit, for example, may offer different rates; a flat curve is also possible.
The graphical representation of this relationship is called the yield curve. Market participants and policymakers use curves to compare valuations and study the economic outlook. Their shape reflects expectations about future rates together with term premia and market supply and demand. A slope alone cannot separate these influences or identify a unique growth or inflation forecast.
For fixed-income valuation, we need discount rates at the dates when payments occur. One rate generally cannot summarize every maturity, although a flat curve is a useful special case. This chapter connects the curve's rate and discount-factor representations, then constructs them from instrument prices.
Fundamental Rate Concepts
Begin with spot rates, also called zero rates, and forward rates. A spot rate runs from today's settlement date to one maturity; a forward rate describes a future interval. Discount factors express the same curve as present-value multipliers. Throughout the examples, we use continuously compounded annual rates, a common currency and a single default-free reference curve unless stated otherwise.
Zero rates describe the promised nominal return on a single-payment investment held to maturity, provided the payment is made. Forward rates follow from current discounts for future intervals; they are not automatically forecasts of the rates that will prevail then. These representations let us value known payments on a consistent curve. Uncertain or option-dependent payments require additional modeling.
Zero-Coupon Rates
A zero-coupon rate (or zero rate) is the maturity-specific yield implied by an investment that starts today and makes a single promised payment at maturity, with no intermediate cash flows. Its interpretation depends on the currency, credit class and compounding convention of the curve.
The single-payment structure makes zero rates a useful starting point for discounting: no coupon payments need separate reinvestment assumptions. It does not remove inflation compensation, term premia or any credit and liquidity effects present in the chosen market curve.
Consider a zero-coupon bond that pays $100 in years and trades today at price . The fundamental question is: what annualized rate of return does this investment offer? The zero rate satisfies:
where:
- : current market price of the zero-coupon bond
- : time to maturity in years
- : continuously compounded zero rate for maturity
- : the discount factor, representing how much $1 received at time is worth today
This equation shows that the price you pay today equals the future payment discounted back at the appropriate rate. The exponential form arises from continuous compounding, where interest accrues at every instant rather than at discrete intervals. Using continuous compounding simplifies many formulas and is standard in quantitative finance, even though actual market quotes often use different conventions.
Solving for the zero rate reveals how to extract the implied rate from observable prices:
There are no coupons to reinvest. If the bond pays in full and you hold it to maturity, its nominal return before taxes and fees is known. A sale before maturity exposes you to price changes, and the purchasing power of the final payment remains uncertain for a nominal bond.
Spot Rates and Their Relationship to Zero Rates
In yield-curve analysis, spot rate and zero rate normally mean the same maturity-specific single-payment yield. "Spot" identifies the present starting date, while "zero" identifies the absence of intermediate coupons. An advertised coupon or deposit rate may use different compounding or payment conventions and needs conversion before comparison.
Check settlement, currency and credit basis as well as compounding when comparing quoted rates. The Bank of England's yield-curve terminology uses spot and zero-coupon yields in this single-payment sense.
Discount Factors
While zero rates express the time value of money as an interest rate, discount factors express the same concept as a multiplier. The discount factor represents the present value today of receiving $1 at time :
where:
- : positive discount factor for finite real and finite ; for , it is below 1 when , equals 1 when , and exceeds 1 when
- : continuously compounded zero rate for maturity
- : time to maturity in years
Intuitively, the discount factor answers a simple question: how much would I pay today for a guaranteed $1 in years? At a fixed positive maturity, a higher zero rate produces a smaller discount factor. At , the factor is 1. With a constant positive zero rate, a longer wait also reduces the factor; with a constant negative rate, the factor instead increases with maturity. Across a general curve, the ordering depends on the rates at both maturities. A discount factor of 0.95 for one year means that $1 next year is worth $0.95 today. You would need to invest $0.95 now at the prevailing rate to have exactly $1 in one year.
Discount factors directly value known payments on a consistent curve. For a default-free bond paying fixed cash flows at times , its price is the sum of each payment multiplied by its corresponding discount factor:
where:
- : present value (price) of the bond
- : cash flow received at time
- : discount factor for maturity
- : total number of cash flows
The formula separates a known payment schedule from its discounts. A mortgage-backed security or an exotic rate derivative also needs a model for uncertain or optional payments. For example, under a suitable risk-neutral pricing measure with the money-market account as numeraire, a single payoff has price , assuming the relevant expectation exists. Here is the model's short rate and can depend on its path. This pricing expectation is different from a real-world forecast, and a random discount factor generally cannot be moved outside the expectation.
VisualizationOut [2]Expand
Matplotlib is building the font cache; this may take a moment.

Forward Rates
A forward rate is the interest rate implied by current spot rates for a loan that begins at a future date and matures at a later date . It represents the rate that can be locked in today for future borrowing or lending.
If we know today's discount rates, what future-interval rate is consistent with them? An FRA can hedge a future reference rate, normally through cash settlement rather than by providing loan principal. The curve determines a fair contractual rate under matching conventions, not the contract's monetary price. Our continuously compounded forward converts to a simple rate over a year fraction ; actual FRA day-count, settlement and curve conventions must also match.
Suppose you know the zero rates for 1 year and 2 years. What rate can you lock in today for borrowing during the second year? This is the 1-year forward rate starting in 1 year, denoted .
The logic relies on a no-arbitrage argument. Suppose the same default-free borrowing and lending curve is available, contracts are enforceable, and we ignore transaction costs and collateral or funding differences. Two strategies with identical dated cash flows must then have the same cost. Consider two ways to invest $1 for two years:
- Direct investment: Invest $1 for 2 years at rate , receiving
- Sequential investment: Invest $1 for 1 year at rate , then reinvest the proceeds for another year at the forward rate , receiving
Both strategies start with $1 today and end with cash in two years. In this model, the direct investment fixes its nominal terminal payoff. The sequential strategy matches it only if the second-year rate is contracted today on the same basis, rather than left to the unknown future spot rate. For no arbitrage, the two terminal amounts must be equal:
Solving step by step to extract the forward rate:
This derivation reveals a fundamental relationship: the forward rate is determined entirely by the two endpoint zero rates. No additional market information is needed. The forward rate is fully implied by the spot curve.
More generally, the forward rate between times and is:
where:
- : forward rate for the period from to
- : zero rates for maturities and
- : length of the forward period in years
The numerator is the difference in logarithmic accumulation, , between the two maturities. It is not a cash amount of interest. Dividing by the forward period's length gives its annualized continuously compounded rate.
This can also be expressed using discount factors:
The ratio represents the growth factor from to : it tells you how much $1 at time becomes by time at the implied forward rate. For positive discount factors and , a declining factor gives a ratio above 1 and a positive forward rate. Equal factors give a zero forward rate; an increasing factor gives a negative forward rate. The formula remains valid in all three cases.
Instantaneous Forward Rates
The forward rate formula leads to a limiting concept as the forward period shrinks. For a positive discount function that is differentiable at , the right-hand limit gives the instantaneous forward rate:
where:
- : today's instantaneous forward for future maturity , not the future short rate realized then
- : discount factor for maturity
- : an infinitesimally small time interval
- : the rate of change of the log discount factor with respect to time
This is today's curve-implied marginal rate for an interval starting at , not a guarantee about the rate a future deposit will earn. The limit concerns an infinitesimal tenor; "duration" has a separate meaning as a measure of price sensitivity. At a kink in , a two-sided derivative need not exist, so use one-sided rates where appropriate.
If is absolutely continuous on , and , integrating the forward rates recovers the zero rate:
where:
- : zero rate for maturity
- : today's instantaneous forward for future maturity
- : accumulates all the instantaneous rates from today to maturity
This relationship shows that the zero rate is simply the average of all instantaneous forward rates from today to maturity. Just as your average speed on a trip is total distance divided by total time, the zero rate is the average of all the "speeds," or instantaneous rates, along the way. This interpretation provides insight into the relationship between spot and forward curves: the spot rate to any maturity represents the cumulative effect of all the marginal rates encountered along the path from today to that maturity.
Numerical Example: Computing Forward Rates
Let's work through a concrete example to solidify these concepts. Suppose you observe the following zero rates:
| Maturity (years) | Zero Rate (%) |
|---|---|
| 1 | 4.0 |
| 2 | 4.5 |
| 3 | 5.0 |
What is the forward rate for year 2 (i.e., borrowing from year 1 to year 2)?
We apply the forward rate formula, substituting the known zero rates:
And the forward rate for year 3:
Notice that forward rates can be quite different from spot rates. The 1-year forward rate starting in year 2 is 6%, even though the 3-year zero rate is only 5%. This happens because forward rates must be higher to pull up the average when the curve is upward sloping. If you want your cumulative average to keep rising, each new contribution must exceed the current average. The 6% forward rate for year 3 exceeds the 5% zero rate because it needs to pull the three-year average up from where the two-year average (4.5%) left off.
PythonIn [3]Expand
import numpy as np
# Define zero rates (as decimals)
maturities = np.array([1, 2, 3])
zero_rates = np.array([0.04, 0.045, 0.05])
# Calculate forward rates for each year
forward_rates = []
for i in range(len(maturities)):
if i == 0:
# First forward rate equals the first spot rate
f = zero_rates[0]
else:
t1 = maturities[i - 1]
t2 = maturities[i]
z1 = zero_rates[i - 1]
z2 = zero_rates[i]
f = (z2 * t2 - z1 * t1) / (t2 - t1)
forward_rates.append(f)
forward_rates = np.array(forward_rates)
# Verify the calculation
print(f"Forward rate for year 2: {forward_rates[1] * 100:.1f}%")
print(f"Forward rate for year 3: {forward_rates[2] * 100:.1f}%")ConsoleOut [3]Expand
Forward rate for year 2: 5.0% Forward rate for year 3: 6.0%
ConsoleOut [4]Expand
Maturity Zero Rate Forward Rate ----------------------------------- 1 yr 4.0% 4.0% 2 yr 4.5% 5.0% 3 yr 5.0% 6.0%
In this example, the period forwards rise from 4.0% to 5.0% to 6.0%, faster than the endpoint zero rates. More generally, for , . Increasing endpoint zero rates therefore put the later interval forward above the endpoint zero rate; the initial interval forward equals its zero rate. This comparison does not imply that every increasing zero curve has an increasing or more steeply sloped forward curve.
VisualizationOut [5]Expand

Bootstrapping the Zero Curve
Direct single-payment prices do not cover every maturity needed for a curve. Long-dated zeros do exist, including Treasury STRIPS, but coupon-bearing instruments often supply additional pricing information. Bootstrapping is one way to extract maturity-specific discount factors from those prices.
A coupon bond's yield to maturity is the single internal rate of return that discounts its payment schedule to its price under a stated convention. It summarizes several cash-flow dates through a nonlinear pricing equation; it is not a simple average of their zero rates. We can work backward from coupon bond prices sequentially, or use a fitted curve model.
The Bootstrapping Algorithm
Bootstrapping extracts zero-coupon rates or discount factors from instrument prices in maturity order. Each step uses previously determined nodes and a stated interpolation rule for any missing payment dates, then solves the new maturity's pricing equation.
With suitable instruments and a local interpolation rule, each step can leave one new scalar unknown. Earlier coupon-date discounts must already be known or supplied by that rule. When interpolation depends on the candidate maturity rate, solve the resulting scalar equation rather than treating those intermediate discounts as fixed.
The algorithm works as follows:
- Start with an instrument that isolates one discount factor, such as a zero-coupon bond. The shortest coupon bond alone may not identify discounts for all its payment dates.
- For each successive maturity, discount earlier cash flows using solved nodes and the chosen interpolation rule.
- Solve for the new rate that reproduces the instrument's price, checking that the implied discount factor is positive.
- Repeat for the remaining instruments, with consistent currency, credit and quoting conventions.
Consider a 2-year annual coupon bond with face value $100, annual coupon payment dollars, and price . Here is a cash amount: a 5% annual coupon on $100 face gives . If we know the 1-year zero rate , we can solve for :
This equation says that the bond's price equals the present value of all its cash flows. The first coupon payment at year 1 is discounted at the known 1-year rate. The final payment (coupon plus principal) at year 2 is discounted at the unknown 2-year rate.
Rearranging step by step to isolate the unknown rate:
where:
- : market price of the 2-year bond
- : annual coupon payment
- : previously bootstrapped 1-year zero rate
- : unknown 2-year zero rate we're solving for
The numerator is the residual price after subtracting the first coupon's present value. It must be positive for a finite real zero rate when the final cash flow is positive. Dividing by that final payment gives its discount factor; the logarithm then recovers the rate. If the residual is nonpositive, check the data, assumptions and instrument suitability rather than taking its logarithm.
Worked Example: Bootstrapping from Bond Prices
Let's bootstrap a zero curve from the following bonds, all with $100 face value and annual coupon payments:
| Maturity | Coupon Rate | Price |
|---|---|---|
| 1 year | 0% | $96.15 |
| 2 years | 5% | $101.50 |
| 3 years | 6% | $103.00 |
Step 1: 1-year zero rate
The 1-year bond is a zero-coupon bond, so we can extract its rate directly using the fundamental zero-rate formula:
Step 2: 2-year zero rate
The 2-year bond pays $5 at year 1 and $105 at year 2. We know the 1-year rate, so we can discount the first coupon and solve for the 2-year rate:
Step 3: 3-year zero rate
The 3-year bond pays $6 at years 1 and 2, and $106 at year 3. Using the rates already determined, we can discount the intermediate coupons and solve for the remaining unknown:
We retain unrounded discount factors throughout the calculation and round only the displayed results. The bootstrapped zero curve is:
| Maturity | Zero Rate |
|---|---|
| 1 year | 3.93% |
| 2 years | 4.12% |
| 3 years | 4.83% |
VisualizationsOut [6]Expand



Python Implementation of Bootstrapping
PythonIn [7]Expand
import numpy as np
def bootstrap_zero_curve(maturities, coupon_rates, prices, face_value=100):
"""
Bootstrap zero rates from coupon bond prices.
Parameters:
-----------
maturities : array-like
Bond maturities in years (assumed to be 1, 2, 3, ...)
coupon_rates : array-like
Annual coupon rates as decimals
prices : array-like
Bond prices
face_value : float
Face value of bonds
Returns:
--------
zero_rates : ndarray
Continuously compounded zero rates
discount_factors : ndarray
Discount factors for each maturity
Raises:
-------
ValueError
If a final payment, residual price after known cash flows, or implied
discount factor is nonfinite or nonpositive.
"""
n = len(maturities)
zero_rates = np.zeros(n)
discount_factors = np.zeros(n)
for i in range(n):
maturity = maturities[i]
coupon = coupon_rates[i] * face_value
price = prices[i]
# Calculate PV of known cash flows
pv_known = 0
for j in range(i):
pv_known += coupon * discount_factors[j]
# Final cash flow
final_cf = face_value + coupon
if not np.isfinite(final_cf) or final_cf <= 0:
raise ValueError("Final cash flow must be finite and positive.")
# Solve for discount factor
residual = price - pv_known
if not np.isfinite(residual) or residual <= 0:
raise ValueError(
"Residual price must be finite and positive; "
"check quotes and valuation assumptions."
)
df = residual / final_cf
if not np.isfinite(df) or df <= 0:
raise ValueError(
"Implied discount factor must be finite and positive; "
"check quotes and valuation assumptions."
)
discount_factors[i] = df
# Convert to zero rate
zero_rates[i] = -np.log(df) / maturity
return zero_rates, discount_factorsPythonIn [8]Expand
# Bond data from our example
maturities = np.array([1, 2, 3])
coupon_rates = np.array([0.00, 0.05, 0.06])
prices = np.array([96.15, 101.50, 103.00])
# Bootstrap the zero curve
zero_rates, discount_factors = bootstrap_zero_curve(maturities, coupon_rates, prices)ConsoleOut [9]Expand
Bootstrapped Zero Curve ---------------------------------------- Maturity Discount Factor Zero Rate ---------------------------------------- 1 yr 0.9615 3.93% 2 yr 0.9209 4.12% 3 yr 0.8651 4.83%
The bootstrapped rates (3.93%, 4.12%, 4.83%) match our manual calculations exactly. The discount factors (0.9615, 0.9209, 0.8651) show how $1 received in the future is worth progressively less as the maturity increases. A dollar in 3 years is worth only about 87 cents today.
Handling Intermediate Maturities
Real-world bootstrapping must handle bonds that don't mature at convenient annual intervals. When you need a discount factor for a maturity that falls between bootstrapped points, you interpolate. Common interpolation methods include:
- Linear interpolation on zero rates: Simple but can produce non-smooth forward rates
- Log-linear interpolation on discount factors: Preserves positive factors between positive endpoints; the implied interval forward rate is positive, zero, or negative according to whether the factors decline, stay constant, or increase
- Cubic spline interpolation: Produces smooth curves but may oscillate
PythonIn [10]Expand
from scipy.interpolate import CubicSpline
def interpolate_zero_curve(
known_maturities, known_rates, target_maturities, method="cubic"
):
"""
Interpolate within the known maturity range.
Outside it, linear interpolation holds endpoint rates constant,
while the cubic branch extrapolates its end polynomial.
"""
if method == "linear":
return np.interp(target_maturities, known_maturities, known_rates)
elif method == "cubic":
cs = CubicSpline(known_maturities, known_rates)
return cs(target_maturities)
else:
raise ValueError(f"Unknown method: {method}")
# Create a fine grid of maturities
fine_maturities = np.linspace(0.5, 3.5, 100)
# Add an illustrative zero-year anchor equal to the one-year rate.
# This is a boundary assumption, not another observed instrument.
extended_maturities = np.concatenate([[0], maturities])
extended_rates = np.concatenate(
[[zero_rates[0]], zero_rates]
) # Equal endpoint anchors do not force a flat cubic between them.
# Interpolate
interpolated_rates = interpolate_zero_curve(
extended_maturities, extended_rates, fine_maturities, method="cubic"
)VisualizationOut [11]Expand

Within its supported range, interpolation supplies discounts for intermediate payment dates. Outside that range, choose and justify an extrapolation policy. Pricing derivatives with uncertain payments can also require volatility, credit or other model inputs beyond this curve.
Yield Curve Shapes and Their Interpretation
Yield-curve shapes contain economic information, but they combine expected rate paths with premia and other market effects. The examples below illustrate possible interpretations, not a unique explanation for each shape.
Normal (Upward Sloping) Curve
An upward-sloping curve has higher rates at longer maturities. Possible contributors include:
- An expected rise in future short-term rates, which can be associated with growth or inflation pressures.
- A positive term premium for bearing longer-duration interest-rate risk.
- Compensation for expected inflation and a separate premium for inflation uncertainty in nominal yields.
Upward curves are often steep near the end of recessions or early in expansions, when short rates are relatively low. Later tightening can flatten them. The slope alone does not identify which contributor dominates.
Inverted Curve
An inverted curve has short-term rates above long-term rates. Specified US Treasury spreads have been useful historical recession indicators, with false positives and varying lead times. Possible contributors to inversion include:
- Expectations of falling short-term rates, possibly because of anticipated weakness and policy easing.
- Demand for long-term government bonds that raises prices and lowers yields.
- Low or negative term premia, which can also lower long yields relative to short yields.
A Federal Reserve Bank of San Francisco study examined the 10-year minus 1-year Treasury spread from January 1955 through February 2018. All nine recessions in that sample were preceded by inversion, with leads of 6 to 24 months and one false positive. This is a dated statistical association, not a guarantee about any particular inversion.
Flat Curve
A flat curve has similar rates across the maturities being compared. It may appear during a transition in slope, but can also arise from stable expected rates or offsetting premia. Possible interpretations include:
- A central bank nearing the end of a tightening or easing cycle.
- Offsetting effects of expected growth, inflation and policy; flatness alone does not measure disagreement.
- Changes in term premia that offset the expected-rate component.
Humped Curve
Intermediate-term rates can exceed both ends of the curve, creating a "humped" shape. Several mechanisms can contribute:
- Short-term rates are expected to rise temporarily before declining.
- A concentration of bond supply affects particular maturities.
- Investor demand differs across maturity segments.
These mechanisms can overlap. A schematic hump cannot establish their relative contributions.
Visualizing Different Curve Shapes
VisualizationsOut [12]Expand




Economic Theories of the Term Structure
Several theories attempt to explain why yield curves take different shapes:
Pure expectations theory treats today's forward as the expected future spot rate after abstracting from term premia and the relevant convexity adjustment. We can write this simplifying economic hypothesis as:
Here is the forward observed today, is the spot rate observed at future time for tenor , and is today's physical, or real-world, expectation. The observation time and tenor are different quantities. Under these simplifying assumptions, rising forwards suggest rising expected future spot rates. The equality is not a general no-arbitrage identity, and risk-neutral pricing under a specified measure does not imply that real-world expected rates equal forwards. The Federal Reserve's term-structure model notes explain why premium definitions and convexity conventions matter.
Liquidity Preference Theory adds a positive maturity-related premium to the expectations component. In this hypothesis, a preference for less volatile short-maturity claims creates extra compensation for holding long bonds, so the curve can slope upward even with flat rate expectations. This is a proposed mechanism, not a guarantee that every estimated term premium is positive or increasing.
Strict Market Segmentation Theory treats supply and demand within maturity segments as independent, assuming little effective substitution across them. Liability matching can motivate different maturity preferences: an insurer and a money market fund need not seek the same cash-flow dates.
Preferred Habitat Theory combines elements of the previous theories. Investors have preferred maturities but will shift if the yield premium is sufficiently attractive. This allows expectations to influence the entire curve while acknowledging institutional preferences.
VisualizationOut [13]Expand

Practical Curve Construction
Building a production-quality yield curve involves several practical considerations beyond basic bootstrapping.
Instrument Selection
The choice of instruments for curve construction depends on the curve's intended use.
- Treasury curves use government bond prices as a nominal reference in their currency; sovereign credit and liquidity characteristics still matter.
- Swap curves depend on their currency, reference index and collateral conventions. Historical LIBOR curves differ from modern overnight-index curves. All LIBOR settings have ceased, with the final synthetic USD settings ending on 30 September 2024; SOFR is a USD replacement, not a universal replacement in every currency.
- Corporate yields reflect credit risk together with liquidity, risk premia and other features. A spread over a reference curve is not pure default compensation.
Short-end instruments must match the intended curve: Treasury bills, unsecured commercial paper and overnight-index swaps have different risk and contractual characteristics. They are not interchangeable risk-free inputs. Longer maturities may use suitable bonds or swaps, with consistent reference and collateral conventions.
Building a Multi-Instrument Curve
PythonIn [14]Expand
from scipy.optimize import brentq
def build_full_curve(instruments):
"""
Build a zero curve from multiple instrument types.
Parameters:
-----------
instruments : list of dicts
Each dict contains: type, maturity, rate or price, and other info.
Coupon bonds use a positive integer frequency (annual by default),
a decimal annual coupon rate, and a regular schedule with no stubs.
The coupon-bond solve needs a sign-changing rate bracket [-0.10, 1.00].
Use quotes with consistent currency, credit and compounding conventions.
Returns:
--------
maturities, zero_rates : arrays
"""
# Sort by maturity
instruments = sorted(instruments, key=lambda x: x["maturity"])
maturities = []
zero_rates = []
for inst in instruments:
t = inst["maturity"]
if inst["type"] == "deposit":
# Simple rate to continuous rate conversion
simple_rate = inst["rate"]
cont_rate = np.log(1 + simple_rate * t) / t
maturities.append(t)
zero_rates.append(cont_rate)
elif inst["type"] == "zero_bond":
price = inst["price"]
face = inst.get("face", 100)
cont_rate = -np.log(price / face) / t
maturities.append(t)
zero_rates.append(cont_rate)
elif inst["type"] == "coupon_bond":
price = inst["price"]
face = inst.get("face", 100)
frequency = inst.get("frequency", 1)
numeric_types = (int, float, np.integer, np.floating)
if (
isinstance(frequency, (bool, np.bool_))
or not isinstance(frequency, numeric_types)
or not np.isfinite(frequency)
or frequency <= 0
or frequency != int(frequency)
):
raise ValueError("Coupon frequency must be a positive integer.")
frequency = int(frequency)
if not np.isfinite(t) or t <= 0:
raise ValueError("Coupon maturity must be finite and positive.")
payment_count = int(round(t * frequency))
if payment_count < 1 or not np.isclose(
t * frequency, payment_count, rtol=0.0, atol=1e-10
):
raise ValueError(
"Only regular coupon schedules without stubs are supported."
)
coupon = inst["coupon"] * face / frequency
# Integer payment indices avoid floating steps including maturity.
# The final coupon is added exactly once in final_pv below.
coupon_times = np.arange(1, payment_count, dtype=float) / frequency
known_maturities = np.asarray(maturities)
known_rates = np.asarray(zero_rates)
def price_residual(candidate_rate):
# Include the candidate maturity so missing coupon-date rates,
# such as the 4-year rate for a 5-year bond, are interpolated.
curve_maturities = np.append(known_maturities, t)
curve_rates = np.append(known_rates, candidate_rate)
coupon_rates = np.interp(coupon_times, curve_maturities, curve_rates)
pv_coupons = np.sum(coupon * np.exp(-coupon_rates * coupon_times))
final_pv = (face + coupon) * np.exp(-candidate_rate * t)
return pv_coupons + final_pv - price
lower, upper = -0.10, 1.00
lower_residual = price_residual(lower)
upper_residual = price_residual(upper)
if (
not np.isfinite(lower_residual)
or not np.isfinite(upper_residual)
or (
lower_residual != 0
and upper_residual != 0
and np.signbit(lower_residual) == np.signbit(upper_residual)
)
):
raise ValueError(
f"Coupon bond at maturity {t} needs a finite sign-changing "
"rate bracket within [-0.10, 1.00]; review its quotes or model domain."
)
cont_rate = brentq(price_residual, lower, upper)
maturities.append(t)
zero_rates.append(cont_rate)
return np.array(maturities), np.array(zero_rates)PythonIn [15]Expand
# Example: Build a curve from mixed instruments
instruments = [
{"type": "deposit", "maturity": 0.25, "rate": 0.035}, # 3-month deposit
{"type": "deposit", "maturity": 0.5, "rate": 0.037}, # 6-month deposit
{"type": "zero_bond", "maturity": 1, "price": 96.15}, # 1-year T-bill
{"type": "coupon_bond", "maturity": 2, "coupon": 0.05, "price": 101.50},
{"type": "coupon_bond", "maturity": 3, "coupon": 0.06, "price": 103.00},
{"type": "coupon_bond", "maturity": 5, "coupon": 0.055, "price": 100.50},
]
full_maturities, full_rates = build_full_curve(instruments)ConsoleOut [16]Expand
Complete Zero Curve ------------------------------ Maturity Zero Rate ------------------------------ 0.25 yr 3.48% 0.50 yr 3.67% 1.00 yr 3.93% 2.00 yr 4.12% 3.00 yr 4.83% 5.00 yr 5.31%
The curve shows rates increasing from 3.48% at 3 months to 5.31% at 5 years. The short-end rates (deposits at 0.25 and 0.5 years) are lower than the bond-derived rates, which reflects the upward slope in these inputs. The increase from the 3-year rate (4.83%) to the 5-year rate (5.31%) gives higher maturity-specific discount rates. Interpreting it as expected future rate increases requires assumptions or additional evidence that separates expectations from term premia, such as a term-structure model or surveys; the slope alone does not establish that forecast. Because no 4-year instrument is supplied, the bootstrap linearly interpolates the 4-year zero rate between the solved 3- and 5-year nodes when valuing the 5-year bond's year-4 coupon.
Computing Forward Rates from the Curve
Once you have a zero curve, you can derive forward rates for any period:
PythonIn [17]Expand
def compute_forward_rates(maturities, zero_rates):
"""
Compute forward rates between consecutive maturities.
"""
forward_rates = np.zeros(len(maturities))
forward_rates[0] = zero_rates[0] # First forward = first spot
for i in range(1, len(maturities)):
t1 = maturities[i - 1]
t2 = maturities[i]
z1 = zero_rates[i - 1]
z2 = zero_rates[i]
forward_rates[i] = (z2 * t2 - z1 * t1) / (t2 - t1)
return forward_rates
forward_rates = compute_forward_rates(full_maturities, full_rates)ConsoleOut [18]Expand
Zero Rates vs Forward Rates --------------------------------------------- Period Zero Rate Forward Rate --------------------------------------------- 0 to 0.25yr 3.48% 3.48% 0.25 to 0.50yr 3.67% 3.85% 0.50 to 1.00yr 3.93% 4.19% 1.00 to 2.00yr 4.12% 4.32% 2.00 to 3.00yr 4.83% 6.24% 3.00 to 5.00yr 5.31% 6.03%
The forward rates range from 3.48% for the first quarter to 6.24% for the period from year 2 to year 3. The forward rate from year 3 to year 5 is 6.03%, which exceeds the 5-year zero rate of 5.31%. This divergence occurs because forward rates must compensate for the lower rates earlier in the curve. The higher marginal rates at the long end pull up the average to reach the 5-year zero rate.
VisualizationOut [19]Expand

The later interval forwards in the table exceed their endpoint zero rates because the zero nodes increase. For a differentiable continuous zero curve, the related instantaneous statement follows from the averaging relationship:
Where is differentiable, , so a positive zero-curve slope puts its instantaneous forward above the zero rate at . The next figure instead interpolates the finite-period forward values and zero nodes separately for display. Its dashed line is not the instantaneous forward derived from the blue interpolation; the shaded gap is only a comparison of those two displayed series.
VisualizationOut [20]Expand

Limitations and Practical Considerations
Building and using yield curves in practice involves several challenges that the idealized treatment above does not capture.
Real markets have bid-ask spreads and transaction costs. Choosing bid, ask or mid-prices changes the calibrated curve and the interpretation of its marks. Multiple curves may distinguish discounting from reference-rate projection, or different currencies, indices and collateral terms. The distinction is not generally a separate curve for existing positions versus new trades.
Liquidity varies across the curve. On-the-run Treasury issues are generally more actively traded than comparable off-the-run issues, with exceptions such as cheapest-to-deliver bonds. Instrument selection depends on the intended benchmark: the Federal Reserve's fitted Treasury curve, for example, excludes on-the-run and first-off-the-run issues to reduce special liquidity effects. Simply selecting the most liquid quotes does not remove every curve-construction bias.
Different coupons can give same-maturity bonds different yields to maturity because their payments weight a nonflat spot curve differently. They need not do so: on a flat curve with matched credit and conventions, different coupons can have identical yields to maturity. A high coupon alone does not establish a price premium to par; compare the coupon with the relevant discount rates. Tax effects are jurisdiction- and instrument-specific, and original-issue or market discount can be treated as interest rather than preferentially taxed capital gains.
Interpolation changes between-node rates and sensitivities even when the fitted nodes agree. Piecewise-linear zero rates generally create jumps in instantaneous forwards where interval slopes change; these features alone do not establish arbitrage or discontinuous bond prices. Cubic splines can oscillate or overshoot when nodes are sparse. The choice depends on the required smoothness, locality and instrument fit.
A curve needs an explicit observation time and purpose. Live pricing may require intraday updates as relevant quotes move, while a daily snapshot is suitable for other workflows. Yesterday's curve remains a historical as-of mark, not a current executable quote. The ECB's euro-area curves, for example, use business-daily production. Data quality, outlier checks and missing or stale quotes matter at either frequency.
VisualizationsOut [21]Expand


Summary
This chapter covered the fundamental concepts underlying fixed-income analysis and yield curve construction:
Zero rates and discount factors are the basis for valuing any fixed-income security. The zero rate for maturity tells you the rate of return for an investment from today until with no intermediate cash flows. The corresponding discount factor gives the present value of receiving $1 at time .
Forward rates represent the rates implied by the spot curve for future borrowing or lending. They can be computed from zero rates using the no-arbitrage relationship, but interpreting them as forecasts requires assumptions about term premia. For strictly increasing continuously compounded zero rates, a later interval forward rate exceeds the zero rate at its endpoint; the initial interval forward equals its corresponding zero rate.
Bootstrapping is a standard technique to extract zero rates from coupon bond prices. Proceeding from short to long maturities uses solved nodes and an explicit interpolation rule for missing coupon dates. If those intermediate rates depend on the new node, solve the coupled pricing equation rather than treating every coupon discount as already fixed.
Yield-curve shapes convey economic information without uniquely identifying its cause. An upward curve can reflect rising expected rates or a positive term premium. Inversions of selected US Treasury spreads have historically preceded recessions, with sample-specific lead times and false positives. Flat or humped shapes can arise from different expected-rate paths, premia or supply and demand.
These tools support bond pricing, interest-rate derivative valuation and rate-risk measurement. A useful next step is to reprice each calibration instrument with the resulting discounts and check that the residuals are small under the stated conventions.
Quiz
Ready to test your understanding? Take this quick quiz to reinforce what you've learned about the term structure of interest rates.
Join the discussionAsk a question or share what you learned.
Keep exploring
Reference
Citation details
Cite or share this article.


Comments
Add to the conversation
A question, a correction, or an example from your own work.
No comments yet. Be the first to share your thoughts!