A receive-fixed swap: cashflows, value and risk

Consider a five-year USD swap that receives a fixed coupon of 4% on a USD 1 million notional. We value it at origination using the illustrative 24 February 2026 SOFR market, then reconstruct its PV from the reported cashflows and examine its quote sensitivities.

The fixed leg pays semiannually and the floating leg quarterly, with unadjusted dates and Actual/360 accrual. A single SOFR curve supplies projection and USD collateral discounting. The Swap defines the contract, and Trade records our receive-fixed position. Setup · Par-rate calculation.

Computational study. Canonical notebook · Companion files and setup. Download this notebook.

This page renders saved outputs; the documentation build does not execute it. Run the canonical notebook from its own directory with the checkout-built binding to reproduce the computation. Each study records its market inputs and numerical assumptions.

import pandas as pd
import quantsupport as qs
from market_data import load_market

market = load_market()
ref = market.quotes.reference_date
print("Market date:", ref)
market.quotes.to_dataframe().head(8)
Market date: 2026-02-24
identifier mid bid ask
0 CapletFloorlet_USD_SOFR_3M_10Y_Absolute_0.035_... 0.20 None None
1 CapletFloorlet_USD_SOFR_3M_10Y_Absolute_0.045_... 0.18 None None
2 CapletFloorlet_USD_SOFR_3M_10Y_Absolute_0.055_... 0.20 None None
3 CapletFloorlet_USD_SOFR_3M_12Y_Absolute_0.035_... 0.19 None None
4 CapletFloorlet_USD_SOFR_3M_12Y_Absolute_0.045_... 0.17 None None
5 CapletFloorlet_USD_SOFR_3M_12Y_Absolute_0.055_... 0.19 None None
6 CapletFloorlet_USD_SOFR_3M_1Y_Absolute_0.035_S... 0.33 None None
7 CapletFloorlet_USD_SOFR_3M_1Y_Absolute_0.045_S... 0.29 None None

Dates and rate conventions

The contract needs dates, an accrual basis and payment frequencies. Adding a Period to a Date performs calendar arithmetic. Apply a Calendar separately when a date requires business-day adjustment. A DayCounter then measures the year fraction used for interest accrual.

We use typed enums for these conventions. Rates are decimal fractions: 0.04 means 4%, and 0.0001 means one basis point.

six_months = qs.Period(6, qs.TimeUnit.Months)
calendar = qs.Calendar("UnitedStates(Sofr)")
end = ref + six_months
print("Unadjusted end:", end)
print("Adjusted end:", calendar.adjust(end, qs.BusinessDayConvention.ModifiedFollowing))
print("Actual/360 accrual:", qs.DayCounter.Actual360.year_fraction(ref, end))
print("Receive / pay signs:", qs.Side.LongReceive.sign(), qs.Side.PayShort.sign())
Unadjusted end: 2026-08-24
Adjusted end: 2026-08-24
Actual/360 accrual: 0.5027777777777778
Receive / pay signs: 1.0 -1.0

The curve and its market index

MarketIndex.SOFR identifies the curve used by the product and its market configuration. The deposit and OIS quotes are calibration pillars, and the solved discount factors form the curve’s interpolation nodes.

The USD CSA policy selects SOFR for discounting in this example. The same curve projects floating coupons. A multi-curve setup can assign separate curves to those roles.

print(market.csa_policy)
market.curves[0].to_dict()
SingleCurveCSADiscountPolicy(collateral_currency='USD', collateral_index='SOFR')
{'day_counter': 'Actual360',
 'enable_extrapolation': True,
 'interpolator': 'LogLinear',
 'market_index': 'SOFR',
 'quotes': ['FixedRateDeposit_USD_SOFR_1D',
  'OIS_USD_SOFR_1Y',
  'OIS_USD_SOFR_2Y',
  'OIS_USD_SOFR_3Y',
  'OIS_USD_SOFR_5Y',
  'OIS_USD_SOFR_7Y',
  'OIS_USD_SOFR_10Y',
  'OIS_USD_SOFR_30Y']}

Defining the position

We construct the five-year contract, then create a Trade with LongReceive to receive fixed and pay floating. Its identifier will link the valuation and risk tables back to this position.

swap = qs.Swap(
    identifier="USD-RECEIVE-FIXED-5Y",
    start_date=ref,
    maturity_date=ref + "5Y",
    notional=1_000_000,
    fixed_rate=0.04,
    currency=qs.Currency.USD,
    market_index=qs.MarketIndex.SOFR,
    day_counter=qs.DayCounter.Actual360,
    fixed_leg_frequency=qs.Frequency.Semiannual,
    floating_leg_frequency=qs.Frequency.Quarterly,
)
trade = qs.Trade(swap, ref, qs.Side.LongReceive)
trade
Trade(instrument=Swap(compounding='Simple', currency='USD', day_counter='Actual360', fixed_leg_frequency='Semiannual', fixed_rate=0.04, floating_leg_frequency='Quarterly', identifier='USD-RECEIVE-FIXED-5Y', market_index='SOFR', maturity_date='2031-02-24', notional=1000000.0, rate_frequency='Semiannual', spread=None, start_date='2026-02-24'), trade_date='2026-02-24', side=LongReceive)

Valuation in a pricing context

The context holds the market inputs and constructs the curves requested by the swap pricer. Inside with context:, we request value, par rate, cashflows and sensitivities. Curve queries also belong inside this scope because they use its constructed market and differentiation state.

We retain scalar results and copied pandas tables for analysis after the scope closes.

context = qs.PricingContext(
    quotes=market.quotes, curves=market.curves, csa_policy=market.csa_policy
)

with context:
    sofr = context.curve(qs.MarketIndex.SOFR)
    five_year_df = sofr.discount_factor(ref + "5Y")
    nodes = sofr.nodes()
    result = context.evaluate(
        trade,
        [
            qs.Request.Value,
            qs.Request.FairRate(0),
            qs.Request.Cashflows,
            qs.Request.Sensitivities,
        ],
    )
    cashflows = result.cashflows
    risk = result.sensitivities

pd.Series(
    {
        "position_pv_usd": result.price,
        "par_fixed_rate": result.fair_rate,
        "five_year_discount_factor": five_year_df,
    }
)
position_pv_usd             -1664.991324
par_fixed_rate                  0.040368
five_year_discount_factor       0.817367
dtype: float64

Interpreting the valuation

price is the signed USD position PV. The fair_rate is the fixed coupon that balances this vanilla swap under the selected market and conventions. Comparing it with our agreed 4% coupon explains the position’s value.

The cashflow table contains undiscounted contractual amounts. Floating coupons use projected rates for future observations, so the table describes payments under today’s curve assumptions. We apply direction and discounting explicitly in the reconciliation below.

cashflows[["payment_date", "leg_index", "type", "amount", "currency"]].head(10)
payment_date leg_index type amount currency
0 2026-02-24 0 Disbursement -1000000.000000 USD
1 2031-02-24 0 Redemption 1000000.000000 USD
2 2026-08-24 0 FixedRateCoupon 20111.111111 USD
3 2027-02-24 0 FixedRateCoupon 20444.444444 USD
4 2027-08-24 0 FixedRateCoupon 20111.111111 USD
5 2028-02-24 0 FixedRateCoupon 20444.444444 USD
6 2028-08-24 0 FixedRateCoupon 20222.222222 USD
7 2029-02-26 0 FixedRateCoupon 20666.666667 USD
8 2029-08-24 0 FixedRateCoupon 19888.888889 USD
9 2030-02-25 0 FixedRateCoupon 20555.555556 USD

The sensitivity table identifies the original calibration quotes. Each value is the PV derivative per unit change in its input quote. All inputs here are decimal interest rates, so multiplying by 1e-4 estimates the signed USD change for a one-basis-point increase in each quote.

quote_risk = risk.assign(pnl_for_plus_1bp_usd=risk["value"] * 1e-4)
quote_risk.sort_values("pnl_for_plus_1bp_usd")
pillar value pnl_for_plus_1bp_usd
6 OIS_USD_SOFR_5Y -4.418095e+06 -441.809500
5 OIS_USD_SOFR_3Y -6.761138e+03 -0.676114
2 OIS_USD_SOFR_1Y -3.050580e+03 -0.305058
0 FixedRateDeposit_USD_SOFR_1D -2.772806e+03 -0.277281
1 OIS_USD_SOFR_10Y 0.000000e+00 0.000000
4 OIS_USD_SOFR_30Y 0.000000e+00 0.000000
7 OIS_USD_SOFR_7Y 0.000000e+00 0.000000
3 OIS_USD_SOFR_2Y 1.333279e+03 0.133328

Reconciling PV from cashflows

For leg signs \(s_\ell\), the swap value is \(V=\sum_{\ell,i}s_\ell C_{\ell i}P(0,T_{\ell i})\). The native table already contains signed amounts: leg 0 is fixed and leg 1 is floating. Receipts are positive, payments are negative, and initial advances have the opposite sign to repayments. We discount each reported amount directly. Matching principal exchanges cancel at origination and maturity.

reconciliation = cashflows.copy()
reconciliation["signed_amount_usd"] = reconciliation["amount"]
with context:
    curve = context.curve(qs.MarketIndex.SOFR)
    reconciliation["discount_factor"] = reconciliation["payment_date"].map(
        curve.discount_factor
    )
    opposite = context.evaluate(
        qs.Trade(swap, ref, qs.Side.PayShort), [qs.Request.Value]
    )
reconciliation["pv_usd"] = (
    reconciliation["signed_amount_usd"] * reconciliation["discount_factor"]
)
print(f"Receive-fixed PV: USD {result.price:,.2f}; par coupon: {result.fair_rate:.6%}.")
print(
    f"Cashflow reconstruction residual: USD {reconciliation['pv_usd'].sum() - result.price:.3e}."
)
print(
    "The 4% contract is above par."
    if result.fair_rate < 0.04
    else "The 4% contract is below par."
)
reconciliation.groupby("leg_index")[["signed_amount_usd", "pv_usd"]].sum()
Receive-fixed PV: USD -1,664.99; par coupon: 4.036802%.
Cashflow reconstruction residual: USD -1.637e-11.
The 4% contract is below par.
signed_amount_usd pv_usd
leg_index
0 202888.888889 -1.664991e+03
1 -202698.050026 -1.164153e-10

The reconstruction shows how each leg contributes to the position value. Reversing the trade side should reverse that value, which provides a second check on direction. To change the agreed coupon while holding the market fixed, continue with par-rate mathematics. For market moves, see quote shocks and portfolio risk.