Interest Rate Swap Valuation and Applications › Practical Applications

Michael BrenndoerferUpdated January 5, 20269 min read
Quantitative FinanceAll chapters
Part 4 of 15Swaps and Credit Derivatives
Chapter 21Interest Rate Swap Valuation and Applications › Practical Applications
Reading section 13 of 17 · Practical Applications · 9 minSection 13 of 17 · 9 min

Book contents

Section 13 of 17 · 9 min read

Interest Rate Swap Valuation and Applications › Practical ApplicationsOne section · Sections in this chapter13 / 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

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.

In[24]:
Code
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)
Out[25]:
Console
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.

Out[26]:
Visualization
Line chart comparing unhedged floating borrowing cost versus hedged fixed cost across SOFR rates.
Borrowing costs for unhedged and hedged scenarios across a range of SOFR rates. The pay-fixed swap locks in a 6.30% total cost, protecting you from rising rates while forfeiting potential gains if SOFR falls below the 4.8% strike.

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.

In[27]:
Code
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)
Out[28]:
Console
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 AA and LL be asset and liability values and DAD_A and DLD_L their durations for that same shock. The first-order equity change is ΔE≈−(ADA−LDL)Δy\Delta E\approx-(A D_A-L D_L)\Delta y. Equity falls for a small rate rise when ADA>LDLA D_A>L D_L, equivalently when the asset-normalized gap DA−(L/A)DLD_A-(L/A)D_L is positive (A>0A>0). Raw duration ordering alone does not state this condition. The following example has long-duration mortgages funded by short-duration deposits.

In[29]:
Code
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)
Out[30]:
Console
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
Out[31]:
Visualization
Horizontal bar chart of bank asset categories ,  mortgages, commercial loans, and securities ,  by value in billions, annotated with each category's duration in years.
Bank asset duration profile. Long-dated mortgages increase the weighted average asset duration.
Horizontal bar chart of bank liability categories ,  deposits, CDs, and subordinated debt ,  by value in billions, annotated with each category's duration in years.
Bank liability duration profile. Short-term deposits keep liability duration low, creating a gap.

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.

In[32]:
Code
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
)
Out[33]:
Console

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.