Interest Rate Swap Valuation and Applications › Worked Example: Existing Swap Value and Rate Risk

Michael BrenndoerferUpdated January 5, 20267 min read
Quantitative FinanceAll chapters
Part 4 of 15Swaps and Credit Derivatives
Chapter 21Interest Rate Swap Valuation and Applications › Worked Example: Existing Swap Value and Rate Risk
Reading section 14 of 17 · Worked Example: Existing Swap Value and Rate Risk · 7 minSection 14 of 17 · 7 min

Book contents

Section 14 of 17 · 7 min read

Interest Rate Swap Valuation and Applications › Worked Example: Existing Swap Value and Rate RiskOne section · Sections in this chapter14 / 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

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:

  1. Value the existing swap position
  2. Calculate the DV01 exposure
  3. Discuss the position's conditional rate risk and the additional inputs needed for a hold-or-unwind decision
In[34]:
Code
# 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
)
In[35]:
Code
# 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)
Out[36]:
Console
=================================================================
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
In[37]:
Code
# 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)
Out[38]:
Console

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
In[39]:
Code
# 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",
)
Out[40]:
Console

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.

Out[41]:
Visualization
Line chart showing swap mark-to-market value across yield curve shifts from minus 50 to plus 50 basis points.
Mark-to-market sensitivity of the hypothetical 7-year receive-fixed swap to instantaneous parallel continuous-zero-curve shifts, holding the current known floating coupon fixed. Local sensitivity is about $1.02 million per 10bp; finite changes differ because of convexity. These same-time valuations do not include holding-period coupon receipts or curve roll.