Interest Rate Swap Valuation: Bond Portfolio & FRA Methods
Covers interest rate swap valuation through bond portfolio and FRA methods. Topics include curve bootstrapping, DV01 risk measures, and hedging applications.
Quantitative FinanceAll chapters Interest Rate Swap Valuation and ApplicationsFull chapter · Sections in this chapter1 / 17 ↓
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
Interest Rate Swap Valuation and Applications
In the previous chapter, we introduced the mechanics of interest rate swaps: the exchange of fixed-rate payments for floating-rate payments between two counterparties. You learned how these instruments work, what drives their use, and the basic terminology that market participants employ. Now we turn to the critical question: how do we determine the value of a swap?
Swap valuation connects present-value calculations to quoting, hedging and synthetic cash-flow construction. This chapter develops the bond and FRA approaches in a stated single-curve model, calibrates a curve to hypothetical par quotes, and applies the results to matched borrowing hedges, synthetic coupons and asset-liability management.
The key insight is that a swap can be decomposed into more fundamental instruments we've already studied. We can view it either as a portfolio of bonds or as a portfolio of forward rate agreements. The bond view groups cash flows by leg; the FRA view groups them by payment period. Under the assumptions below, the two constructions give the same value.
The examples use one curve for both projecting floating payments and discounting them, with no credit adjustment or floating spread. The reset-date examples assume that the current floating fixing matches the forward rate from that curve. Under these assumptions, the bond and FRA valuations agree. Later, we discuss why market pricing may require separate projection and discount curves.
Swap Valuation as a Bond Portfolio
The most intuitive approach to swap valuation treats each leg of the swap as a bond. This perspective builds on our existing knowledge of bond pricing and allows us to value swaps using familiar present value techniques. Consider a plain vanilla interest rate swap from your perspective if you are receiving fixed and paying floating. This position is economically equivalent to:
- Being long a fixed-rate bond (receiving fixed coupon payments)
- Being short a floating-rate bond (making floating-rate payments)
The value of the swap is simply the difference between these two bond values. This decomposition works because the cash flows of the swap exactly replicate the combined cash flows of holding a fixed-rate bond while having borrowed at a floating rate. Understanding this equivalence turns swap valuation from an abstract exercise into a straightforward application of bond mathematics.
Fixed-Rate Bond Valuation
The fixed leg of a swap generates a stream of known cash flows, which makes it the more straightforward component to value. Unlike the floating leg, where future payments depend on interest rates that have not yet been determined, the fixed leg's payments are contractually specified at inception. This certainty allows us to apply standard present value techniques directly.
If the swap has a notional principal , a fixed rate , and payment dates at times , we can calculate the present value of the fixed leg by discounting each cash flow back to today. For the bond-portfolio representation, add a hypothetical principal repayment at maturity. The corresponding fixed-rate bond value is:
where:
- : corresponding fixed-rate bond value, including hypothetical principal
- : fixed interest rate
- : day count fraction for period
- : notional principal amount
- : discount factor from today to time
- : payment date for period
- : total number of payment periods
- : present value of the coupon payments
- : present value of the notional principal repayment
The final term represents the notional principal "repayment" at maturity. Even though no principal exchanges hands in a swap, we include it for the bond equivalence to hold. This might look like an accounting trick, but it's essential: by adding and subtracting the notional principal on both legs, we maintain the mathematical equivalence between the swap and the bond portfolio. The notional terms cancel when we compute the net swap value, but including them allows us to value each leg independently using standard bond pricing formulas.
Recall from our discussion of bond pricing in Part II, Chapter 2 that the discount factor represents the present value of receiving one dollar at time . These discount factors are derived from the term structure of interest rates and encode all the information we need about the time value of money across different horizons.
Floating-Rate Bond Valuation
The floating leg presents an interesting challenge: we don't know the future floating rates. Future floating coupons are uncertain, so direct cash-flow projection is less straightforward. Replication values them without predicting the realized rates. However, a useful property simplifies the calculation and clarifies how floating-rate instruments behave.
Just after a floating-rate reset, a floating-rate bond trades at par because each future payment will be set to the prevailing market rate. This property is key to understanding floating-rate instruments.
To understand why, consider what happens at a reset date. The floating rate for the upcoming period is set to the current market rate, which by definition represents fair compensation for lending money over that period. At the next reset, the same thing happens: the rate adjusts to whatever the market rate is at that time. Since each coupon payment reflects the market rate at the time it was set, the bond always offers a fair market return. Consequently, investors are willing to pay exactly face value for it, and the bond's value equals its face value at each reset date.
This logic extends to any future reset date as well. Looking forward from today, we know that at each future reset, the bond will be worth par. This recursive property dramatically simplifies our valuation task.
Between reset dates, the corresponding floating-rate bond is worth the present value of the known next coupon plus its par continuation value:
where:
- : corresponding floating-rate bond value between resets, including hypothetical principal
- : floating rate set for the current period
- : day count fraction for the current period
- : notional principal
- : discount factor to the next payment date
- : next payment date
- : the fixed coupon payment for the current period
- : known next coupon plus par continuation value immediately after payment (or principal repayment at final maturity)
The intuition here is straightforward: between reset dates, we know exactly what the next coupon payment will be because the rate has already been fixed. We also know that immediately after that payment, the bond will be worth par because a new rate will be set. Therefore, the current value is simply the present value of the known coupon plus the present value of receiving par at the next reset.
At inception or immediately after a reset, this simplifies to:
where:
- : corresponding floating-rate bond value immediately after coupon payment and a matching reset
- : notional principal amount
The floating-rate bond is worth exactly par. This result means that at reset dates, we do not need to forecast any future interest rates to value the floating leg. We simply know it equals the notional amount.
The Swap Value
Combining these results, the value of a receive-fixed, pay-floating swap is:
where:
- : total value of the swap
- : corresponding fixed-rate bond value including hypothetical principal
- : corresponding floating-rate bond value including hypothetical principal
At a matching reset in this model, K above the current par swap rate for the same remaining schedule makes the corresponding fixed-rate bond worth more than par and the receive-fixed swap positive. If K is below that matching par rate, the fixed bond is below par and the receive-fixed swap is negative. Do not compare K directly with an arbitrary zero yield.
For a par swap entered without an upfront value premium, choose the fixed rate so that . At that rate, the fixed and floating coupon present values are equal. An off-market coupon can instead have nonzero initial value and an offsetting upfront payment.
The par swap rate is the fixed rate that makes a swap have zero initial value. For matching fixed and floating payment schedules in the single-curve model, it is an accrual- and discount-factor-weighted average of simple forward rates. It is not an unqualified forecast of average future short-term rates.
Let's implement this valuation approach.
PythonIn [3]Expand
import numpy as np
# Define discount factors from a hypothetical yield curve
# These would come from the term structure in practice
maturities = np.array([0.5, 1.0, 1.5, 2.0, 2.5, 3.0]) # Years
zero_rates = np.array(
[0.035, 0.038, 0.040, 0.042, 0.043, 0.044]
) # Continuous compounding
# Calculate discount factors
discount_factors = np.exp(-zero_rates * maturities)
# Swap parameters
notional = 10_000_000 # $10 million
fixed_rate = 0.05 # 5% annual fixed rate
payment_frequency = 0.5 # Semiannual payments
n_payments = 6 # 3-year swap with semiannual paymentsPythonIn [4]Expand
def value_fixed_leg(notional, fixed_rate, discount_factors, payment_freq):
"""
Value the fixed leg of a swap as a fixed-rate bond.
"""
# Fixed payments each period
fixed_payment = notional * fixed_rate * payment_freq
# PV of coupon payments
pv_coupons = np.sum(fixed_payment * discount_factors)
# PV of notional at maturity
pv_notional = notional * discount_factors[-1]
return pv_coupons + pv_notional
def value_floating_leg_at_reset(notional):
"""
Value the floating leg at a reset date (equals par).
Between resets, discount the full known next coupon plus par;
that calculation is not implemented by this reset-date shortcut.
"""
# At reset, floating-rate bond is worth par
return notional
# Calculate values
fixed_leg_value = value_fixed_leg(
notional, fixed_rate, discount_factors, payment_frequency
)
floating_leg_value = value_floating_leg_at_reset(notional)
swap_value = fixed_leg_value - floating_leg_valueConsoleOut [5]Expand
Swap Valuation Using Bond Portfolio Approach ================================================== Notional Principal: $10,000,000 Fixed Rate: 5.00% Swap Tenor: 3.0 years Fixed Bond Value (incl. hypothetical principal): $10,158,654.57 Floating Bond Value (incl. hypothetical principal): $10,000,000.00 Swap Value (Rec Fix): $158,654.57
VisualizationOut [6]Expand

The positive swap value indicates that the fixed rate of 5% is favorable compared to current market rates. The receive-fixed party holds a valuable position because they are locked into receiving a higher rate than what the market currently offers for new swaps.
Key Parameters
The key parameters for swap valuation using the bond portfolio approach are fundamental inputs that determine the swap's cash flows and their present values:
- N: Notional principal amount. Used to scale the cash flows, though it is not exchanged in a standard swap. This amount determines the size of each interest payment and is the reference for calculating percentage-based coupon payments.
- K: Fixed interest rate. The rate received by you in the receive-fixed position, contractually determined at inception and remaining constant throughout the swap's life.
- r: Risk-free interest rate. Derived from the discount factors used to value the cash flows. In practice, this comes from the term structure observed in the market.
- : Accrual year fraction for payment period i; the regular semiannual example uses 0.5. Contractual day-count conventions determine coupon amounts, while payment dates determine discounting times. Payments per year is a separate frequency, equal to 2 for regular semiannual dates.
Swap Valuation as a Portfolio of FRAs
An alternative and equally valid approach views a swap as a series of forward rate agreements. Rather than thinking of the swap as two bonds, we can decompose it into individual exchanges of fixed for floating payments at each payment date. Each payment exchange in the swap can be treated as a separate FRA. This perspective connects swap pricing directly to forward rates, which we discussed in the context of the term structure in Part II, Chapter 3.
The FRA approach offers different insights than the bond approach. While the bond view emphasizes the swap's relationship to fixed income securities, the FRA view highlights how each payment period contributes to the swap's total value. This decomposition is especially useful for analyzing where swap value comes from along the yield curve.
Forward Rate Agreement Review
Before applying this to swaps, let's review forward rate agreements and their valuation. A forward rate agreement is a contract to exchange a fixed interest payment for a floating payment at a future date, based on a rate determined at an earlier fixing date. The contract specifies a notional amount, a forward period, and a contracted rate against which the realized floating rate will be compared.
The payoff of a receive-fixed FRA settling at time for a period of length starting at is:
where:
- : accrual period length
- : notional principal
- : contracted forward rate
- : realized floating rate at time
The payoff is positive when the contracted rate exceeds the realized floating rate, meaning you receive the difference. Conversely, the payoff is negative when the floating rate exceeds the contracted rate.
To determine the present value before settlement, we use the simple-accrual forward rate implied by today's curve. In this single-curve model, . Its expectation interpretation uses the payment-date forward measure, associated with the bond maturing at , rather than an unqualified physical forecast. For the stated end-of-period payoff, the present value is:
where:
- : present value of the FRA
- : simple-accrual forward rate observed today for the period starting at , with the same accrual fraction as the payoff
- : discount factor to the payment date
- : start date of the forward period
- : accrual period length
- : notional principal
- : contracted forward rate
This formula tells us that the FRA has positive value when the contracted rate exceeds the market forward rate , and negative value when the market forward rate exceeds the contracted rate.
Decomposing the Swap
With the FRA valuation framework established, we can now view the entire swap as a portfolio of these individual contracts. A swap with payment dates can be decomposed into forward rate agreements, each corresponding to one payment exchange. The value of the entire swap is the sum of these FRA values:
where:
- : total value of the swap
- : total number of payment dates
- : day count fraction for period
- : notional principal
- : fixed rate
- : forward rate for period ()
- : discount factor for payment at time
For a par swap entered without an upfront premium, choose K so that the initial swap value is zero; an off-market coupon can instead require an upfront value adjustment. By setting the swap value to zero and solving for the fixed rate, we can derive the par swap rate. The derivation proceeds as follows:
Solving for :
where:
- : par swap rate
- : number of payment periods
- : forward rate for period
- : day count fraction
- : discount factor
- : present value of the expected floating leg payments per unit of notional
- : annuity factor for payments of per unit notional, corresponding to a fixed annual rate of 1
The par swap rate is a weighted average of simple forward rates, with normalized weights proportional to . For equal accrual fractions and declining discount factors, later periods receive lower weights. Neither that ordering nor a forecast of physical expected short-term rates follows without additional assumptions. In this single-curve model, the floating payments telescope because , with .
The example below takes each accrual fraction to equal the difference between its maturity times in years, so the semiannual schedule uses . For a contractual day-count convention, use that convention's accrual fraction in the simple-forward conversion and payment calculation; it need not equal the zero curve's time difference.
PythonIn [7]Expand
def calculate_forward_rates(zero_rates, maturities):
"""
Convert continuous zero rates to simple interval forward rates.
Maturities are positive and strictly increasing, in years from today.
This example's accrual fractions equal the maturity differences.
"""
forward_rates = []
for i in range(len(maturities)):
t1 = 0.0 if i == 0 else maturities[i - 1]
log_growth_start = 0.0 if i == 0 else zero_rates[i - 1] * t1
t2 = maturities[i]
# D(t1)/D(t2) - 1, divided by the period's simple accrual.
f = np.expm1(zero_rates[i] * t2 - log_growth_start) / (t2 - t1)
forward_rates.append(f)
return np.array(forward_rates)
def value_swap_via_fra(
notional, fixed_rate, forward_rates, discount_factors, payment_freq
):
"""Value aligned FRA periods using a common accrual fraction in years.
payment_freq is the shared accrual fraction, not payments per year.
Forward and discount arrays must be finite, nonempty, aligned 1D vectors;
discount factors must be positive. Irregular accruals need a generalization.
"""
forward_rates = np.asarray(forward_rates, dtype=float)
discount_factors = np.asarray(discount_factors, dtype=float)
if (
forward_rates.ndim != 1
or discount_factors.ndim != 1
or forward_rates.size == 0
or forward_rates.size != discount_factors.size
or not np.all(np.isfinite(forward_rates))
or not np.all(np.isfinite(discount_factors))
or np.any(discount_factors <= 0)
):
raise ValueError("Use nonempty aligned finite 1D arrays and positive discounts")
scalars = np.asarray([notional, fixed_rate, payment_freq], dtype=float)
if scalars.shape != (3,) or not np.all(np.isfinite(scalars)) or scalars[2] <= 0:
raise ValueError(
"Use finite scalar inputs and a positive common accrual fraction"
)
notional, fixed_rate, payment_freq = scalars
total_value = 0
for i, (fwd, disc) in enumerate(zip(forward_rates, discount_factors)):
# Each FRA: delta * N * (K - F) * Z
fra_value = payment_freq * notional * (fixed_rate - fwd) * disc
total_value += fra_value
return total_value
# Calculate forward rates
forward_rates = calculate_forward_rates(zero_rates, maturities)
# Value swap via FRA approach
swap_value_fra = value_swap_via_fra(
notional, fixed_rate, forward_rates, discount_factors, payment_frequency
)
# Calculate individual FRA values for analysis
fra_values = []
for fwd, disc in zip(forward_rates, discount_factors):
fra_values.append(payment_frequency * notional * (fixed_rate - fwd) * disc)ConsoleOut [8]Expand
Forward Rates and FRA Decomposition ============================================================ Period Forward Rate Discount Factor FRA Value ------------------------------------------------------------ 0.0-0.5y 3.5308% 0.982652 $ 72,185.42 0.5-1.0y 4.1423% 0.962713 $ 41,285.29 1.0-1.5y 4.4488% 0.941765 $ 25,957.06 1.5-2.0y 4.8581% 0.919431 $ 6,525.04 2.0-2.5y 4.7557% 0.898077 $ 10,971.79 2.5-3.0y 4.9605% 0.876341 $ 1,729.98 ------------------------------------------------------------ Total Swap Value: $ 158,654.57 Verification - Bond approach value: $158,654.57
VisualizationsOut [9]Expand


Both approaches yield the same swap value, confirming their equivalence. The FRA approach provides additional insight by breaking down the swap's value across individual payment periods. This decomposition reveals which parts of the yield curve contribute most to the swap's value and helps you understand the term structure exposure embedded in your positions.
Key Parameters
The key parameters for the FRA valuation approach connect swap pricing directly to the forward rate curve and the term structure of interest rates:
- : Simple-accrual forward rate for period , implied by the ratio of the endpoint discount factors. Its expectation interpretation depends on the pricing measure; it is not automatically a physical forecast.
- : Fixed rate of the swap. Compared against the forward rate to determine the value of each period. The difference between and drives each FRA's contribution to the swap value.
- : Contractual accrual year fraction (day-count fraction). Use the same fraction when annualizing the simple forward and calculating its coupon amount.
- : Discount factor. Used to bring the future FRA payoff back to present value. These factors encode the time value of money embedded in the current term structure.
The Swap Curve
A par swap curve shows fixed rates for zero-initial-value swaps across maturities under specified contract conventions. Swap quotes are used to calibrate projection and discount curves for valuing interest-rate products. The resulting curves are valuation inputs; par quotes are not zero-coupon discount factors and do not by themselves specify every derivative's risks.
Why the Swap Curve Matters
Government bond yields can be affected by supply-demand imbalances, tax considerations, and liquidity preferences. Swap rates also contain more than expectations: their interpretation depends on the projection and discount curves, risk premia, and contract terms. For example, investor demand or central-bank purchases can make particular government bonds trade rich or cheap relative to comparable maturities; above-par or below-par price alone does not establish that effect. The swap curve is particularly important because:
- It provides swap-rate quotes across maturities, often out to 30 years or more; a fitted curve connects those quotes without uniquely identifying expected future short-term rates.
- Swap quotes are inputs to curve calibration. The resulting discount factors, together with appropriate floating-rate projection curves and risk-model inputs, support derivative valuation. A par swap quote is not itself a zero-coupon discount factor, and option valuation also needs assumptions about future rate variability.
- Historically, unsecured IBOR-based swap curves reflected an interbank funding benchmark, including bank credit and liquidity components. RFR-based overnight index swap (OIS) curves instead reference overnight near-risk-free rates and do not generally measure banks' unsecured term borrowing costs (BIS, Beyond LIBOR).
- Liquidity and quote freshness vary by market and tenor. A fitted curve may use interpolation or indicative inputs, so a displayed curve point is not necessarily a fresh executable quote.
Bootstrapping the Swap Curve
Bootstrapping is the process of extracting discount factors from market swap rates. The term "bootstrapping" refers to the iterative nature of the procedure: we use information derived from shorter maturities to solve for discount factors at longer maturities. The technique works iteratively, starting from the shortest maturity and working outward.
For a par swap with maturity and swap rate , the no-arbitrage condition requires that the value of the fixed leg equals the value of the floating leg. In the matching-reset single-curve model, the corresponding floating-rate bond including hypothetical principal is worth 1 per unit notional. A par swap therefore has a corresponding fixed-rate bond worth 1 as well:
where:
- : par swap rate for maturity
- : number of payment periods up to maturity
- : day count fraction for period
- : discount factor at coupon date . For the explicit last-factor solve, factors with must already be known or fixed by a stated interpolation rule; is the new endpoint.
- : discount factor for the final maturity (the unknown variable)
On a coupon-complete grid, earlier coupon-date discount factors are available from previous steps. Sparse quotes leave unquoted coupon dates: a 5-year annual-pay swap still pays at year 4 even if no 4-year swap is quoted. We therefore specify an interpolation rule and include those dates in the calibration. If their discounts depend on the new endpoint, we solve the full coupon equation for that endpoint.
We can rearrange the no-arbitrage condition to solve explicitly for the unknown discount factor by separating the final term and isolating :
where:
- : discount factor for maturity
- : par swap rate for maturity
- : number of payment periods
- : day count fraction for period
- : known discount factor for time
- : day count fraction for the final period
- : remaining value to be covered by the final payment
- : final coupon plus hypothetical principal per unit notional, the coefficient multiplying the final discount factor
The explicit quotient requires and a positive solved discount factor. It applies when the earlier coupon factors are fixed inputs. For this sparse-quote example, we instead interpolate continuously compounded zero rates linearly between quoted nodes, keep the first zero rate flat before the first node, and solve each full coupon equation with the trial endpoint included. Integer payment indices generate every regular coupon date. The resulting curve must reprice every input quote using those same conventions. This specifies a model curve; finite quotes do not uniquely determine its values at all other dates.
PythonIn [10]Expand
def bootstrap_swap_curve(swap_rates, maturities, payment_freq=0.5):
"""Calibrate a regular-coupon single curve to hypothetical par quotes.
payment_freq is the accrual interval in years, not payments per year.
Maturities must be positive increasing multiples of that interval.
Continuous zero rates are linear between quoted nodes and flat before
the first node. Each trial includes the current endpoint when valuing
every contractual coupon, including unquoted intermediate dates.
This teaching example does not support stubs or multiple curves.
"""
from scipy.optimize import brentq
swap_rates = np.asarray(swap_rates, dtype=float)
maturities = np.asarray(maturities, dtype=float)
delta = float(payment_freq)
if (
swap_rates.ndim != 1
or maturities.ndim != 1
or len(swap_rates) != len(maturities)
or not len(maturities)
or not np.all(np.isfinite(swap_rates))
or not np.all(np.isfinite(maturities))
or not np.isfinite(delta)
or delta <= 0
or np.any(maturities <= 0)
or np.any(np.diff(maturities) <= 0)
):
raise ValueError(
"Use finite aligned quotes and positive increasing maturities."
)
period_counts = np.rint(maturities / delta).astype(int)
if (
np.any(period_counts < 1)
or not np.allclose(period_counts * delta, maturities, rtol=0, atol=1e-10)
or np.any(1 + swap_rates * delta <= 0)
):
raise ValueError(
"Use regular whole-period maturities and admissible coupon rates."
)
zero_rates = np.empty(len(maturities))
for i, (rate, maturity, periods) in enumerate(
zip(swap_rates, maturities, period_counts)
):
times = delta * np.arange(1, periods + 1)
nodes = maturities[: i + 1]
def par_residual(trial_zero):
trial_rates = np.append(zero_rates[:i], trial_zero)
discounts = np.exp(-np.interp(times, nodes, trial_rates) * times)
return rate * delta * np.sum(discounts) + discounts[-1] - 1
# Explicit finite bracket for this educational rate range; reject
# inconsistent/unbracketed quotes rather than return a false curve.
lower, upper = -0.5, 1.0
if par_residual(lower) * par_residual(upper) > 0:
raise ValueError(
"Quote has no root in the stated illustrative zero-rate bracket."
)
zero_rates[i] = brentq(par_residual, lower, upper, xtol=1e-14)
discount_factors = np.exp(-zero_rates * maturities)
return discount_factors, zero_rates
# Hypothetical par swap quotes; single-curve regular-coupon exercise
market_maturities = np.array([1, 2, 3, 5, 7, 10])
market_swap_rates = np.array([0.047, 0.050, 0.052, 0.055, 0.057, 0.058])PythonIn [11]Expand
# Simplified bootstrap for illustration
def simple_bootstrap(swap_rates, maturities):
"""Annual-payment specialization of the full-coupon calibration above."""
return bootstrap_swap_curve(swap_rates, maturities, payment_freq=1.0)
# Bootstrap the curve
boot_disc_factors, boot_zero_rates = simple_bootstrap(
market_swap_rates, market_maturities
)ConsoleOut [12]Expand
Bootstrapped Swap Curve
=================================================================
Maturity Swap Rate Discount Factor Zero Rate
-----------------------------------------------------------------
1 yr 4.700% 0.955110 4.5929%
2 yr 5.000% 0.906900 4.8862%
3 yr 5.200% 0.858532 5.0844%
5 yr 5.500% 0.763759 5.3901%
7 yr 5.700% 0.675665 5.6008%
10 yr 5.800% 0.565404 5.7021%VisualizationOut [13]Expand

This figure compares annual-pay par quotes with continuously compounded zero rates. For the stated hypothetical data, the calibrated zeros increase from 4.5929% at one year to 5.7021% at ten years. They are not interchangeable with par coupons: the latter price a full coupon schedule, whereas each zero rate describes a single payment.
VisualizationOut [14]Expand

Key Parameters
The key parameters for bootstrapping the swap curve are the inputs and outputs of the iterative procedure that builds the discount function from market data:
- : Par swap rate. The market rate observed for a specific maturity, representing the fixed rate at which a new swap would have zero value.
- : Discount factor. The unknown variable being solved for iteratively at each maturity point. These factors encode the present value of future cash flows.
- Maturities: The tenor points of the input swap curve (e.g., 1y, 2y, 5y). Market data is typically available at standard tenors, and interpolation may be needed for intermediate points.
- Zero Rate: The continuously compounded spot rate implied by the discount factor. Calculated as , and gives an alternative representation of the same information contained in the discount factors.
Determining the Par Swap Rate
To calculate a par quote for a new swap, find the fixed coupon that makes its initial model value zero under the specified payment, floating-index and discounting conventions. A client can also request an off-market coupon, in which case a nonzero initial value may be offset by an upfront payment. The calculation below uses discount factors to obtain the par coupon in the chapter's single-curve model.
For a par swap at inception, fixed and floating coupon PVs are equal. Equivalently, after adding equal hypothetical principal repayments, the corresponding fixed- and floating-rate bonds both have value N under the matching single-curve reset assumptions.
PythonIn [15]Expand
def calculate_par_swap_rate(discount_factors, maturities, payment_freq=1.0):
"""Par rate for a complete regular coupon grid in a single curve.
payment_freq is the common accrual interval in years. Every coupon date
must appear, not only quoted curve pillars. Stubs/day-count variation
and separate projection curves require a more general implementation.
"""
discounts = np.asarray(discount_factors, dtype=float)
times = np.asarray(maturities, dtype=float)
delta = float(payment_freq)
if (
discounts.ndim != 1
or times.ndim != 1
or not len(times)
or len(discounts) != len(times)
or not np.isfinite(delta)
or delta <= 0
or not np.all(np.isfinite(discounts))
or np.any(discounts <= 0)
or not np.all(np.isfinite(times))
or not np.allclose(
times, delta * np.arange(1, len(times) + 1), rtol=0, atol=1e-10
)
):
raise ValueError("Provide positive factors at every regular coupon date.")
annuity = delta * np.sum(discounts)
return (1 - discounts[-1]) / annuity
# Calculate par rates for different tenors
tenors = [1, 2, 3, 5, 7, 10]
calculated_par_rates = []
for i, tenor in enumerate(tenors):
# Reprice every annual coupon date, not just quoted maturity nodes.
payment_times = np.arange(1, int(tenor) + 1, dtype=float)
payment_zeros = np.interp(payment_times, market_maturities, boot_zero_rates)
payment_discounts = np.exp(-payment_zeros * payment_times)
par_rate = calculate_par_swap_rate(payment_discounts, payment_times)
calculated_par_rates.append(par_rate)ConsoleOut [16]Expand
Par Swap Rate Calculation
==================================================
Tenor Market Rate Calculated
--------------------------------------------------
1 yr 4.7000% 4.7000%
2 yr 5.0000% 5.0000%
3 yr 5.2000% 5.2000%
5 yr 5.5000% 5.5000%
7 yr 5.7000% 5.7000%
10 yr 5.8000% 5.8000%
Full annual coupon repricing should recover each input quote to numerical tolerance.The calculated rates reprice the hypothetical input quotes using the full annual coupon schedule and the same interpolation policy as calibration. Remaining numerical differences reflect solver and floating-point tolerance. Different contractual schedules require their own payment dates and accrual fractions; changing frequency is a separate calculation, not an explanation for roundoff.
Key Parameters
The key parameters for calculating the par swap rate connect the discount function to the fair fixed rate for a new swap:
- : Payment-date discount factors used for the fixed coupon annuity and hypothetical principal in the bond representation; the plain interest-rate swap does not exchange that principal.
- : Contractual accrual year fraction for period i. The example uses a common annual accrual of 1.0, not a payments-per-year parameter.
- : Fixed coupon rate that equalizes fixed and floating coupon PVs. Equivalently, the corresponding bonds including hypothetical principal are at par, giving zero initial net swap value.
Mark-to-Market Swap Valuation
Once a swap is traded, its value can change as interest rates move. A par swap's initial zero value can become positive or negative, but some cash-flow states or curve moves leave it unchanged. Mark-to-market (MTM) valuation calculates the current model value of an existing position. Its applications include:
- Regulatory capital calculations for banks within the applicable framework, which distinguish current counterparty exposure from measures of potential future exposure
- Collateral management under Credit Support Annexes (CSAs), where parties exchange margin based on the current value of their positions
- Profit and loss attribution, where you need to understand how rate movements have affected portfolio value
- Risk management and hedging, where understanding current exposure is essential for making informed decisions about position adjustments
Valuation After Rate Changes
Consider a 5-year receive-fixed swap entered at a rate of 5.5%. After rates change, we need to revalue the swap using current market discount factors. The mechanics are identical to initial valuation: we calculate the present value of the fixed leg and subtract the floating leg value.
PythonIn [17]Expand
def value_existing_swap(notional, fixed_rate, remaining_payments, discount_factors):
"""Annual receive-fixed MTM at a matching base floating reset.
Factors cover all remaining annual coupon dates. The first fixing
agrees with this base curve; this function is a base valuation, not
a risk shock that is allowed to change an already observed fixing.
Equal hypothetical principal repayments cancel in the bond difference.
"""
discounts = np.asarray(discount_factors, dtype=float)
if (
discounts.ndim != 1
or not len(discounts)
or isinstance(remaining_payments, bool)
or not isinstance(remaining_payments, int)
or remaining_payments != len(discounts)
or not np.all(np.isfinite(discounts))
or np.any(discounts <= 0)
or not np.isfinite(notional)
or notional <= 0
or not np.isfinite(fixed_rate)
):
raise ValueError("Use finite inputs and a factor for every annual payment.")
fixed_bond = notional * (fixed_rate * np.sum(discounts) + discounts[-1])
floating_bond = notional
return fixed_bond - floating_bond
# Original swap parameters
original_swap = {
"notional": 50_000_000,
"fixed_rate": 0.055,
"original_tenor": 5,
"elapsed_years": 2,
}
# Remaining payments: 3 years
remaining_years = original_swap["original_tenor"] - original_swap["elapsed_years"]
# Hypothetical current curve; not an observed or dated market snapshot
new_maturities = np.array([1, 2, 3])
new_zero_rates = np.array([0.042, 0.044, 0.046]) # Rates are lower now
new_discount_factors = np.exp(-new_zero_rates * new_maturities)
# Value the swap
current_value = value_existing_swap(
original_swap["notional"],
original_swap["fixed_rate"],
remaining_years,
new_discount_factors,
)ConsoleOut [18]Expand
Mark-to-Market Swap Valuation ======================================================= Original Swap Terms: Notional: $50,000,000 Fixed Rate: 5.50% Original Tenor: 5 years Time Elapsed: 2 years Remaining: 3 years Hypothetical Current Zero Rates: 1-year: 4.20% 2-year: 4.40% 3-year: 4.60% Swap Valuation: Mark-to-Market Value: $1,105,690.30
In this annual receive-fixed example, a small parallel decline in the current continuous zero rates increases model MTM under the stated fixing convention. This is a model mark, not an executable takeover or unwind quote; actual amounts can differ because of bid-offer spreads, fees and contractual close-out terms.
Key Parameters
The key parameters for mark-to-market valuation distinguish between the original contract terms and current market conditions:
- : The original fixed rate on the swap. This rate was set at inception and remains constant throughout the swap's life, regardless of how market rates move.
- : Notional principal amount. The reference amount used to calculate payment sizes, unchanged from inception.
- : Current market discount factors. These reflect the new interest rate environment and are used to present-value the remaining cash flows.
- : Remaining time to maturity. Only remaining cash flows are valued; past payments have no effect on current mark-to-market value.
Swap Risk Measures
Understanding how swap values respond to rate changes matters for risk management. You need to know how much money could be gained or lost as interest rates fluctuate. The key measures are DV01 (dollar value of a basis point) and swap duration, both of which quantify interest rate sensitivity.
DV01: Dollar Value of a Basis Point
DV01 is the signed local, first-order value sensitivity per one basis point (0.01%) parallel shift in the specified yield curve. It approximates a small value change; finite moves are repriced and can differ because of curvature. Comparisons across positions require the same shock, fixing convention and normalization, or explicit conversion to a common basis. The mathematical definition is:
where:
- : dollar value of a basis point
- : value of the swap
- : yield curve level (interest rate)
- : one basis point scaling factor
The minus sign makes DV01 positive for a position that gains from a small decline in the specified curve. The five-year receive-fixed example below has positive DV01 under its stated curve and fixed-current-coupon convention. That is a property of its remaining cash-flow exposure, not of the receive-fixed label alone. Preserve any already observed floating fixing in rate scenarios.
For a counterexample, take one remaining annual payment with notional , fixed rate , known floating rate , and discount factor . The net cash flow is , so its value is . A lower continuous zero rate increases and makes this negative value more negative. With one year remaining, the signed local DV01 is per 100 notional.
PythonIn [19]Expand
def calculate_swap_dv01(notional, fixed_rate, maturities, zero_rates, bump=0.0001):
"""Signed one-basis-point sensitivity for this annual-coupon example.
Value immediately after the current annual floating fixing. Set that
fixing from the base curve once and preserve the known coupon in every
parallel continuous-zero-rate shock. Future floating coupons continue
to reset. The supplied dates are the complete annual grid 1,...,T.
"""
maturities = np.asarray(maturities, dtype=float)
zero_rates = np.asarray(zero_rates, dtype=float)
if (
maturities.ndim != 1
or zero_rates.ndim != 1
or len(maturities) != len(zero_rates)
or not len(maturities)
or not np.all(np.isfinite(maturities))
or not np.all(np.isfinite(zero_rates))
or not np.allclose(maturities, np.arange(1, len(maturities) + 1))
or not np.isfinite(notional)
or notional <= 0
or not np.isfinite(fixed_rate)
or not np.isfinite(bump)
or bump <= 0
):
raise ValueError(
"Use a full annual grid, finite inputs, positive notional and bump."
)
df_original = np.exp(-zero_rates * maturities)
df_up = np.exp(-(zero_rates + bump) * maturities)
df_down = np.exp(-(zero_rates - bump) * maturities)
known_first_fixing = np.expm1(zero_rates[0] * maturities[0])
def swap_value(discounts):
fixed_bond = notional * (fixed_rate * np.sum(discounts) + discounts[-1])
floating_bond = notional * (1 + known_first_fixing) * discounts[0]
return fixed_bond - floating_bond
v_original = swap_value(df_original)
v_up = swap_value(df_up)
v_down = swap_value(df_down)
dv01 = (v_down - v_up) / (2 * bump) * 0.0001
return dv01, v_original, v_up, v_down
# Calculate DV01 for a 5-year swap
maturities_5y = np.array([1, 2, 3, 4, 5])
rates_5y = np.array([0.045, 0.048, 0.050, 0.052, 0.053])
dv01, v_base, v_up, v_down = calculate_swap_dv01(
10_000_000, 0.052, maturities_5y, rates_5y
)ConsoleOut [20]Expand
Swap DV01 Calculation ================================================== 5-Year Receive-Fixed Swap Notional: $10,000,000 Fixed Rate: 5.20% Scenario Analysis: Base Value: $ -89,557.62 Rates +1bp: $ -93,039.50 Rates -1bp: $ -86,073.69 DV01: $ 3,482.91
Swap Duration
A par swap has zero net market value, so ordinary modified duration, , is undefined there. We use a different, explicit reference: notional-normalized rate sensitivity,
This has units of years and does not depend on the position's notional scale. Net dollar sensitivity is fixed-bond sensitivity minus floating-bond sensitivity. In this annual example, the current floating coupon is already fixed, so the corresponding floating bond retains exposure through its next payment. It is not held at par in every shocked scenario. A short time to the next reset can justify a small-floating-duration approximation, but that is a separate stated approximation.
PythonIn [21]Expand
# Calculate notional-normalized sensitivity D_N
def calculate_swap_duration(dv01, notional):
"""Return D_N = DV01 / (notional * 0.0001), not duration divided by net MTM."""
if not np.isfinite(notional) or notional <= 0 or not np.isfinite(dv01):
raise ValueError("Use finite DV01 and positive finite notional.")
return dv01 / (notional * 0.0001)
swap_duration = calculate_swap_duration(dv01, 10_000_000)ConsoleOut [22]Expand
Swap Duration Analysis ================================================== 5-Year Notional-Normalized Sensitivity: 3.48 years
VisualizationsOut [23]Expand


Practical Applications
The following examples show how swaps can change borrowing-rate exposure, construct synthetic fixed coupons and adjust a balance sheet's duration gap under stated matching and risk assumptions.
Hedging Interest Rate Exposure
If you have floating-rate debt, you face uncertainty about future interest payments. When rates rise, borrowing costs increase, potentially squeezing profit margins or even threatening solvency. A pay-fixed, receive-floating swap allows you to convert your exposure to fixed-rate debt synthetically. You continue to make floating payments on your original loan but enter a swap to receive floating and pay fixed. The floating index received on the swap offsets the matching index paid on the loan, leaving the fixed swap payment plus the loan's original spread and any fees. This requires matching notionals, index conventions, payment dates and accrual fractions.
PythonIn [24]Expand
import numpy as np
import pandas as pd
# Corporate hedging example
corporate_loan = {
"principal": 100_000_000,
"spread": 0.015, # SOFR + 150 bps
"reference_rate": "SOFR",
}
hedge_swap = {
"notional": 100_000_000,
"fixed_rate": 0.048, # Pay 4.80% fixed
"receive": "SOFR",
}
def analyze_corporate_hedge(loan, swap, sofr_scenarios):
"""
Compare annualized borrowing rates in hypothetical SOFR scenarios.
Assumes equal loan/swap notionals, matching SOFR observation, reset,
accrual and payment conventions, and an unchanged loan spread.
This is a rate illustration, not a discounted valuation or forecast.
"""
results = []
for sofr in sofr_scenarios:
# Without hedge
unhedged_cost = sofr + loan["spread"]
# With hedge
# Loan: Pay SOFR + spread
# Swap: Pay fixed, receive SOFR
# Net: Pay fixed + spread
hedged_cost = swap["fixed_rate"] + loan["spread"]
results.append(
{
"sofr": sofr,
"unhedged": unhedged_cost,
"hedged": hedged_cost,
"savings": unhedged_cost - hedged_cost,
}
)
return pd.DataFrame(results)
sofr_scenarios = np.arange(0.02, 0.10, 0.01)
hedge_analysis = analyze_corporate_hedge(corporate_loan, hedge_swap, sofr_scenarios)ConsoleOut [25]Expand
Corporate Interest Rate Hedge Analysis ================================================================= Loan Terms: $100M at SOFR + 1.50% Swap Terms: Pay 4.80% fixed, receive SOFR SOFR Unhedged Cost Hedged Cost Saving/(cost), pp ----------------------------------------------------------------- 2.00% 3.50% 6.30% -2.80 pp 3.00% 4.50% 6.30% -1.80 pp 4.00% 5.50% 6.30% -0.80 pp 5.00% 6.50% 6.30% +0.20 pp 6.00% 7.50% 6.30% +1.20 pp 7.00% 8.50% 6.30% +2.20 pp 8.00% 9.50% 6.30% +3.20 pp 9.00% 10.50% 6.30% +4.20 pp
The table shows that the hedge locks in a fixed borrowing cost of 6.30% (4.80% swap rate + 1.50% spread). When SOFR exceeds 4.80%, the hedge provides savings; when SOFR is below 4.80%, the unhedged position would have been cheaper, but the hedge provides certainty. A borrower who values predictable payments may accept a higher cost in low-rate scenarios in exchange for budgeting certainty.
VisualizationOut [26]Expand

Creating Synthetic Assets
Swaps enable the creation of synthetic instruments that may not be available directly in the market. If you own a floating-rate note but desire fixed-rate exposure, you can use a swap to transform the economic characteristics of the asset. Rather than selling the floating-rate note and purchasing a fixed-rate bond, which might involve transaction costs and tax consequences, you can overlay a swap to achieve the desired exposure.
PythonIn [27]Expand
def create_synthetic_fixed_bond(floating_asset, swap):
"""
Describe fixed-coupon exposure from a performing matched-notional package.
The asset and swap must match SOFR, accrual, reset and payment conventions.
This does not price the package or remove issuer/counterparty credit risk.
"""
# Floating asset: Receive SOFR + spread
# Swap: Pay SOFR, receive fixed
# Net: Receive fixed + spread (synthetic fixed bond)
synthetic_coupon_rate = swap["receive_fixed_rate"] + floating_asset["spread"]
return {
"type": "Synthetic Fixed Bond",
"coupon_rate": synthetic_coupon_rate,
"components": {
"Floating Asset": f"SOFR + {floating_asset['spread']:.2%}",
"Swap Receive": f"{swap['receive_fixed_rate']:.2%}",
"Swap Pay": "SOFR",
},
}
# Example: Convert floating-rate note to synthetic fixed
frn = {
"spread": 0.0080, # SOFR + 80 bps
"principal": 25_000_000,
}
conversion_swap = {"pay": "SOFR", "receive_fixed_rate": 0.045}
synthetic = create_synthetic_fixed_bond(frn, conversion_swap)ConsoleOut [28]Expand
Synthetic Fixed-Rate Bond Construction ======================================================= Starting Position: Floating Rate Note Coupon: SOFR + 0.80% Principal: $25,000,000 Swap Overlay: Pay: SOFR Receive: 4.50% fixed Result: Synthetic Fixed-Rate Bond Fixed coupon rate: 5.30% (4.50% + 0.80% spread)
With equal notionals, matching SOFR observation, accrual, reset and payment dates, and both contracts performing, the combined coupon rate is 5.30%: the swap's fixed rate plus the note's spread. This establishes coupon income, not a yield-to-maturity, which also requires the package price and dated redemption cash flows. The issuer exposure remains, and the swap adds counterparty exposure; contractual basis risk can arise if the two contracts' floating-rate terms diverge.
Asset-Liability Management
Banks and insurance companies use swaps to manage mismatches between asset and liability duration. For a common parallel-rate shock, let and be asset and liability values and and their durations for that same shock. The first-order equity change is . Equity falls for a small rate rise when , equivalently when the asset-normalized gap is positive (). Raw duration ordering alone does not state this condition. The following example has long-duration mortgages funded by short-duration deposits.
PythonIn [29]Expand
def calculate_duration_gap(assets, liabilities):
"""
Calculate duration gap for an institution.
"""
# Weighted average duration of assets
total_assets = sum(a["value"] for a in assets)
asset_duration = sum(a["value"] * a["duration"] for a in assets) / total_assets
# Weighted average duration of liabilities
total_liabilities = sum(l["value"] for l in liabilities)
liability_duration = (
sum(l["value"] * l["duration"] for l in liabilities) / total_liabilities
)
# Duration gap (simplified)
duration_gap = asset_duration - liability_duration * (
total_liabilities / total_assets
)
return {
"asset_duration": asset_duration,
"liability_duration": liability_duration,
"duration_gap": duration_gap,
"total_assets": total_assets,
"total_liabilities": total_liabilities,
}
# Bank balance sheet
bank_assets = [
{"type": "Mortgages", "value": 800_000_000, "duration": 7.5},
{"type": "Commercial Loans", "value": 400_000_000, "duration": 3.0},
{"type": "Securities", "value": 200_000_000, "duration": 5.0},
]
bank_liabilities = [
{"type": "Deposits", "value": 1_000_000_000, "duration": 0.5},
{"type": "CDs", "value": 250_000_000, "duration": 2.0},
{"type": "Subordinated Debt", "value": 50_000_000, "duration": 6.0},
]
gap_analysis = calculate_duration_gap(bank_assets, bank_liabilities)ConsoleOut [30]Expand
Bank Duration Gap Analysis ======================================================= Asset Duration: 5.86 years Liability Duration: 1.00 years Duration Gap: 4.93 years Total Assets: $1.40 billion Total Liabilities: $1.30 billion Equity: $0.10 billion
VisualizationsOut [31]Expand


To reduce the duration gap, choose a receive-floating, pay-fixed swap with negative signed dollar duration under the same parallel-rate shock as the balance sheet. For that selected exposure, the pay-fixed position gains value when rates rise and offsets some of the losses on long-duration assets. The calculation below assumes the stated positive magnitude of this negative sensitivity; the pay-fixed label alone does not establish its size or sign for every fixing and cash-flow state.
PythonIn [32]Expand
def hedge_duration_gap(gap_analysis, target_gap, payer_sensitivity_magnitude):
"""Positive payer notional for a reduction in an asset-normalized gap.
d=payer_sensitivity_magnitude>0 means dV_payer/dy=N*d, so signed
notional-normalized duration is -d. Balance-sheet and swap sensitivities
must use the same first-order parallel shock. This is not net-value
duration, which is undefined for a zero-MTM swap.
"""
current_gap = gap_analysis["duration_gap"]
total_assets = gap_analysis["total_assets"]
d = payer_sensitivity_magnitude
if (
not np.all(np.isfinite([current_gap, total_assets, target_gap, d]))
or total_assets <= 0
or d <= 0
or target_gap > current_gap
):
raise ValueError(
"Use positive assets/magnitude and a target no larger than the gap."
)
return (current_gap - target_gap) * total_assets / d
target_duration_gap = 1.0 # Target 1-year gap
payer_sensitivity_magnitude = 4.0 # Assumed |D_N|, years; signed payer D_N=-4
hedge_notional = hedge_duration_gap(
gap_analysis, target_duration_gap, payer_sensitivity_magnitude
)ConsoleOut [33]Expand
Duration Gap Hedging Strategy ======================================================= Current Duration Gap: 4.93 years Target Duration Gap: 1.00 years Gap Reduction Needed: 3.93 years Hedging Swap (Pay Fixed, Receive Float): Payer |D_N| magnitude: 4.0 years Required Notional: $1,375 million
A pay-fixed notional of $1.375 billion reduces the calculated duration gap from 4.93 years to the target of 1.00 year under the assumed 4-year sensitivity magnitude. The swap contributes negative dollar duration, reducing the balance sheet's first-order parallel-rate exposure. The nonzero target leaves residual rate risk; it is not complete immunization.
Worked Example: Existing Swap Value and Rate Risk
This worked example combines the bond-portfolio valuation, a matching current par comparator and signed rate-risk analysis for an existing swap. We then identify the additional inputs needed for a hold-or-unwind decision.
Problem Setup
Suppose we entered a 10-year receive-fixed swap three years ago at a rate of 5.00%. The current curve's matching seven-year par coupon is below this 5.00% contracted coupon. We need to:
- Value the existing swap position
- Calculate the DV01 exposure
- Discuss the position's conditional rate risk and the additional inputs needed for a hold-or-unwind decision
PythonIn [34]Expand
# Swap parameters
swap_details = {
"notional": 200_000_000,
"original_rate": 0.050,
"original_tenor": 10,
"years_elapsed": 3,
"remaining_tenor": 7,
}
# Hypothetical continuous-zero curve for the remaining annual schedule
current_curve = {
"maturities": np.array([1, 2, 3, 4, 5, 6, 7]),
"zero_rates": np.array([0.038, 0.040, 0.042, 0.044, 0.046, 0.047, 0.048]),
}
# Current annual-pay par rate, computed from the same current zero curve.
current_discounts = np.exp(-current_curve["zero_rates"] * current_curve["maturities"])
current_7y_swap_rate = calculate_par_swap_rate(
current_discounts, current_curve["maturities"], payment_freq=1.0
)PythonIn [35]Expand
# Step 1: Value the existing swap
def comprehensive_swap_valuation(swap, curve):
"""
Complete swap valuation with breakdown.
"""
N = swap["notional"]
K = swap["original_rate"]
remaining = swap["remaining_tenor"]
maturities = curve["maturities"][:remaining]
zero_rates = curve["zero_rates"][:remaining]
discount_factors = np.exp(-zero_rates * maturities)
# Fixed leg components
fixed_payments = N * K * np.ones(remaining) # Annual payments
pv_fixed_payments = fixed_payments * discount_factors
pv_notional = N * discount_factors[-1]
fixed_leg_pv = np.sum(pv_fixed_payments) + pv_notional
# Floating leg (at par since we're at a reset date)
floating_leg_pv = N
# Swap value
swap_value = fixed_leg_pv - floating_leg_pv
return {
"fixed_leg_pv": fixed_leg_pv,
"floating_leg_pv": floating_leg_pv,
"swap_value": swap_value,
"fixed_payments": fixed_payments,
"discount_factors": discount_factors,
"pv_fixed_payments": pv_fixed_payments,
}
valuation = comprehensive_swap_valuation(swap_details, current_curve)ConsoleOut [36]Expand
================================================================= PENSION FUND SWAP ANALYSIS REPORT ================================================================= 1. POSITION DETAILS ----------------------------------------------------------------- Type: Receive Fixed, Pay Floating Notional: $200,000,000 Fixed Rate: 5.00% Original Tenor: 10 years Time Elapsed: 3 years Remaining Tenor: 7 years 2. CURRENT MARKET CONDITIONS ----------------------------------------------------------------- Curve-implied annual 7Y par rate: 4.8620% Rate Advantage: 13.8 bps 3. SWAP VALUATION ----------------------------------------------------------------- Fixed Bond PV (incl. hypothetical principal): $ 201,619,546.27 Floating Bond PV (incl. hypothetical principal): $ 200,000,000.00 Net Swap Value: $ 1,619,546.27
PythonIn [37]Expand
# Step 2: Calculate DV01
def swap_dv01_analysis(swap, curve, bump_size=0.0001):
"""Annual existing-swap risk immediately after the current fixing.
Keep that observed fixing unchanged under every parallel zero shock.
bump_size controls the central derivative, normalized back to one bp;
the displayed +/-1bp and +/-10bp stress scenarios remain fixed.
"""
N = swap["notional"]
K = swap["original_rate"]
remaining = swap["remaining_tenor"]
if isinstance(remaining, bool) or not isinstance(remaining, int) or remaining <= 0:
raise ValueError("Use a positive integer number of annual payments.")
maturities = np.asarray(curve["maturities"], dtype=float)[:remaining]
zero_rates = np.asarray(curve["zero_rates"], dtype=float)[:remaining]
if len(maturities) != remaining or len(zero_rates) != remaining:
raise ValueError("The curve must cover every remaining annual payment.")
dv01, _, _, _ = calculate_swap_dv01(N, K, maturities, zero_rates, bump=bump_size)
known_first_fixing = np.expm1(zero_rates[0] * maturities[0])
scenarios = {}
for name, shift in [
("down_10bp", -0.001),
("down_1bp", -0.0001),
("base", 0.0),
("up_1bp", 0.0001),
("up_10bp", 0.001),
]:
discounts = np.exp(-(zero_rates + shift) * maturities)
fixed_bond = N * (K * np.sum(discounts) + discounts[-1])
floating_bond = N * (1 + known_first_fixing) * discounts[0]
scenarios[name] = fixed_bond - floating_bond
return dv01, scenarios
dv01, scenarios = swap_dv01_analysis(swap_details, current_curve)ConsoleOut [38]Expand
4. RISK ANALYSIS ----------------------------------------------------------------- DV01: $ 102,356.18 Notional sensitivity D_N: 5.12 years Scenario Analysis: Scenario Swap Value P&L vs Base -------------------------------------------------- down_10bp $ 2,647,058.95 $ 1,027,512.69 down_1bp $ 1,721,941.86 $ 102,395.60 base $ 1,619,546.27 $ 0.00 up_1bp $ 1,517,229.51 $ -102,316.76 up_10bp $ 599,917.33 $-1,019,628.94
PythonIn [39]Expand
# Step 3: Decision analysis
def analyze_unwind_decision(swap_value, dv01, current_rate, original_rate, view):
"""A qualitative receive-fixed risk prompt, not an optimal trading decision.
No probabilities, holding horizon, carry/roll, execution costs or fund
liabilities are modeled. Use a matching current par rate as comparator.
"""
if view not in {"rates_rising", "rates_stable", "rates_falling"}:
raise ValueError("Use rates_rising, rates_stable or rates_falling.")
rate_advantage = original_rate - current_rate
analysis = {
"current_mtm": swap_value,
"rate_advantage_bps": rate_advantage * 10000,
"dv01": dv01,
}
if view == "rates_rising":
analysis["recommendation"] = "DISCUSS REDUCING RATE EXPOSURE"
analysis["reasoning"] = (
f"An instantaneous local 1bp parallel rate rise reduces this "
f"receive-fixed MTM by approximately ${dv01:,.0f}. "
f"Current MTM is ${swap_value:,.0f}; this is not cumulative profit."
)
else:
analysis["recommendation"] = "REVIEW CARRY, COSTS AND HEDGING OBJECTIVE"
analysis["reasoning"] = (
"Same-time flat or downward curve shocks do not imply a future "
"holding-period return. Compare coupon cash flows, curve roll, "
"costs and portfolio liabilities before choosing to hold or unwind. "
f"The contracted fixed coupon is {rate_advantage * 10000:.1f}bp "
"above the matching current par coupon, not a guaranteed net receipt."
)
return analysis
# Assume manager expects rates to rise
decision = analyze_unwind_decision(
valuation["swap_value"],
dv01,
current_7y_swap_rate,
swap_details["original_rate"],
"rates_rising",
)ConsoleOut [40]Expand
5. TRADING DECISION ANALYSIS ----------------------------------------------------------------- Current MTM: $1,619,546.27 Rate Advantage: 13.8 bps DV01 Exposure: $102,356.18 RECOMMENDATION: DISCUSS REDUCING RATE EXPOSURE Reasoning: An instantaneous local 1bp parallel rate rise reduces this receive-fixed MTM by approximately $102,356. Current MTM is $1,619,546; this is not cumulative profit. =================================================================
The remaining receive-fixed position has positive current MTM because its contracted coupon exceeds the matching current par coupon. The inputs do not establish cumulative profit or a same-tenor historical rate decline. Positive DV01 identifies local exposure to an instantaneous parallel rate rise; a hold-or-unwind decision also needs a horizon, expected cash flows and curve roll, execution costs and the fund's wider hedging objective.
VisualizationOut [41]Expand

Limitations and Practical Considerations
While our valuation framework captures the essential mechanics, real-world swap pricing involves several complexities we've simplified. Understanding these limitations is important for bridging the gap between textbook models and market practice.
Credit and Counterparty Risk. Our models assume both parties will honor their obligations. In practice, counterparty credit risk affects swap pricing, particularly for uncollateralized trades. The 2008 financial crisis highlighted how counterparty concerns can destabilize swap markets. Standardized swaps can be centrally cleared. Clearinghouses manage counterparty risk through margin requirements and mutualized default resources; clearing is distinct from where the trade is executed.
Discounting Curve Selection. Our examples use one curve for projection and discounting. In a collateralized valuation, the discount curve must reflect the collateral remuneration convention, while projected coupons must match their actual floating index, such as compounded overnight SOFR. The two roles can use the same curve for a matched SOFR OIS; different indices or collateral conventions can require multiple curves. Term SOFR derivatives have a more limited recommended scope and are not the default model for all SOFR swaps.
Day Count Conventions and Payment Timing. Real swaps involve specific day count conventions (30/360, actual/360, actual/actual) that affect payment amounts. Payment dates may be adjusted for weekends and holidays. Our simplified annual payment assumption works for understanding concepts but requires refinement for actual trading.
Liquidity and Bid-Ask Spreads. Our illustrative calculations omit bid-offer spreads. An executable trade or close-out quote need not equal a model mid-market mark. Its spread depends on the contract and market conditions; being off-market determines a value or upfront adjustment, not by itself a universal rule about bid-offer width.
Basis Risk. When using swaps to hedge, the floating rate on the swap may not perfectly match the floating rate on the underlying exposure. For example, a company with prime-rate borrowing hedging with a SOFR-based swap retains prime-SOFR basis risk. This basis can fluctuate, reducing hedge effectiveness.
The discounted-cash-flow, replication and sensitivity identities provide a conceptual foundation under their stated assumptions. A production implementation must also verify full contractual schedules, curve calibration and quote repricing, fixing state, units and output consistency before adding contract-specific credit, collateral and risk details.
Summary
This chapter developed the analytical tools for valuing and applying interest rate swaps:
- Valuation Approaches: Swaps can be valued as portfolios of bonds (long fixed, short floating) or portfolios of forward rate agreements. Under the stated assumptions, both methods yield identical values. The two approaches offer complementary views of swap economics.
- The Swap Curve: Market par swap quotes can be calibrated into discount factors under a stated schedule and interpolation convention; the resulting discount and projection curves are inputs to an interest-rate valuation model, not a complete model for every derivative. Historically, unsecured IBOR swap curves reflected bank funding premia; RFR OIS curves instead reference near-risk-free overnight rates and do not generally measure unsecured term bank funding.
- Par Swap Rate: For matching payment schedules in the single-curve model, the par swap rate is an accrual- and discount-factor-weighted average of simple forward rates that gives a swap zero initial value. An interpretation as average expected short-term rates requires additional model and risk-premium assumptions.
- Mark-to-Market Valuation: As rates change, existing swap positions can gain, lose or remain unchanged. The displayed positive-DV01 receive-fixed examples gain locally from parallel rate declines; reversing the position reverses that sensitivity. Determine the sign from the remaining cash flows and fixing state, not the receive-fixed or pay-fixed label alone.
- Risk Measures: Signed DV01 is local first-order value sensitivity per basis point under a specified curve shock. Reprice finite moves separately. Notional-normalized swap sensitivity depends on the fixed-bond cash-flow weights, curve and floating-reset exposure. Calculate it from signed DV01 with a stated shock and normalization; there is no universal half-maturity rule.
- Practical Applications: Swaps enable hedging (converting floating exposure to fixed), synthetic asset creation (combining floating assets with swaps to create synthetic fixed instruments), and asset-liability management (adjusting duration gaps).
Understanding swap valuation opens the door to credit derivatives and structured products. In the next chapter, we'll examine credit default swaps, which apply similar contractual structures to credit risk rather than interest rate risk.
Quiz
Ready to test your understanding? Use the following questions to practice the chapter's valuation, curve and risk concepts.
Interest Rate Swap Valuation and Applications
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!