Prerequisites

This chapter assumes you have read IR-00 and understand why a discount curve is needed. You should know what a swap and a bond are. No prior knowledge of bootstrapping or numerical solvers is assumed.

By the end, you have $P(0,T)$ bootstrapped from observable swap quotes using cash deposits, FRAs, and par swaps.

The problem

To price any instrument with future cash flows, you need to know what those cash flows are worth today. That requires a zero-coupon bond price $P(0,T)$ for every future date $T$: the price today of receiving one unit of currency at time $T$. The market does not quote bond prices directly. It quotes rates on traded instruments: overnight lending rates, forward rate agreements, and par swap rates. Bootstrapping extracts a complete set of $\{P(0,T_j)\}_{j=1}^{n}$ from those quotes, working sequentially from the shortest maturity outward.

Notation (following Brigo and Mercurio [1], Ch. 1)

$P(t,T)$: zero-coupon bond price at time $t$ for maturity $T$. Today $t = 0$; later in the series $t > 0$.
$R(t,T) = -\ln P(t,T) / (T - t)$: continuously compounded zero rate. Equivalently, $P(t,T) = e^{-R(t,T)(T-t)}$.
$r(t)$: instantaneous short rate (a stochastic process; appears from Chapter 04 onward).
$F(t;\,T_\alpha, T_\beta)$: simply compounded forward rate for the accrual period $[T_\alpha, T_\beta]$.
$\tau(T_\alpha, T_\beta)$: day count fraction between two dates.
$S_{\alpha,\beta}(0)$: par swap rate for a swap with fixed-leg dates $T_{\alpha+1}, \ldots, T_\beta$, observed today. For a spot-starting $n$-year swap, $\alpha = 0$ and $\beta = n$.

From any bond price we recover the zero rate $R(0,T) = -\ln P(0,T) / T$, and conversely $P(0,T) = e^{-R(0,T) \cdot T}$. All zero rates in this chapter are continuously compounded.

We use 14 instruments to build the curve: 1 cash deposit (the 7-day point), 3 FRAs (extending from 7 days out to 270 days), and 10 par swaps (annual maturities from 1 year to 10 years). Each instrument adds one pillar to the curve, and each step depends only on values already determined.

Market data

We use 14 instruments to build the SOFR OIS curve. The full data set is shown below; each step in the bootstrap consumes a subset of these instruments in order.

SOFR OIS Market Data (14 instruments)
#TypeInstrumentTenorRateDay Count
1DepositSOFR O/N7 days4.33%ACT/360
2FRA7d × 30d23 days4.35%ACT/360
3FRA30d × 90d60 days4.38%ACT/360
4FRA180d × 270d90 days4.25%ACT/360
5SwapSOFR OIS1Y4.20%ACT/360
6SwapSOFR OIS2Y3.95%ACT/360
7SwapSOFR OIS3Y3.85%ACT/360
8SwapSOFR OIS4Y3.88%ACT/360
9SwapSOFR OIS5Y3.92%ACT/360
10SwapSOFR OIS6Y3.97%ACT/360
11SwapSOFR OIS7Y4.02%ACT/360
12SwapSOFR OIS8Y4.06%ACT/360
13SwapSOFR OIS9Y4.09%ACT/360
14SwapSOFR OIS10Y4.12%ACT/360

Step 1: Cash deposit

Cash deposit: lend 1 today, receive $1 + r\tau$ at maturity

The deposit rate $r$ tells you the growth factor $1 + r\tau$ over $[0, T]$. The bond price $P(0,T)$ is its reciprocal: how much 1 unit at $T$ is worth today.

A cash deposit is a zero-coupon lending agreement. You lend 1 at time $0$ and receive $1 + r \cdot \tau(0,T)$ at maturity $T$, where $r$ is the quoted rate and $\tau(0,T) = \text{days}/360$ is the ACT/360 day count fraction. By no-arbitrage, the price today of one unit at $T$ is the reciprocal of the growth factor:

Bond price from a cash deposit $$P(0,T) = \frac{1}{1 + r \cdot \tau(0,T)}$$

For the 7-day deposit at 4.33%: $\tau = 7/360 = 0.019444$, giving $P(0,\,0.019444) = 1/(1 + 0.0433 \times 0.019444) = 0.99915876$. This is the first pillar. The corresponding zero rate is $R(0, T) = -\ln(0.99915876) / 0.019444 = 4.3282\%$. See Brigo and Mercurio [1], Section 1.2.

Step 2: Forward Rate Agreements

FRA timeline: forward rate locks in growth over $[T_\alpha, T_\beta]$

The FRA fixes the growth rate over the forward period $[T_\alpha, T_\beta]$. Combined with the known bond price at $T_\alpha$, it determines the bond price at $T_\beta$.

A FRA locks in a simply compounded forward rate $F(0;\, T_\alpha, T_\beta)$ over a future accrual period $[T_\alpha, T_\beta]$. By no-arbitrage, lending from $0$ to $T_\beta$ must produce the same result as lending to $T_\alpha$ and rolling forward at rate $F$:

No-arbitrage relation for a FRA $$\frac{P(0, T_\alpha)}{P(0, T_\beta)} = 1 + F(0;\, T_\alpha, T_\beta) \cdot \tau(T_\alpha, T_\beta)$$

If $P(0, T_\alpha)$ is already known, we rearrange to solve for $P(0, T_\beta)$:

Bond price from a FRA $$P(0, T_\beta) = \frac{P(0, T_\alpha)}{1 + F(0;\, T_\alpha, T_\beta) \cdot \tau(T_\alpha, T_\beta)}$$

Each FRA extends the curve by one accrual period. The first uses the deposit's $P(0, 7/360)$ to produce $P(0, 30/360)$. The second chains to $P(0, 90/360)$. The third reaches 270 days. Note that the third FRA starts at 180 days, which is not a pillar; $P(0, 0.5)$ is obtained by interpolation (see below). See Brigo and Mercurio [1], Section 1.3.

Step 3: Interest rate swaps

5-year par swap (fixed-rate payer perspective): annual fixed vs quarterly floating

Each red arrow is a known annual fixed payment $-N \cdot K \cdot \tau_j$. Each blue arrow is a quarterly floating payment $+N \cdot F_i \cdot \delta_i$, where $\delta_i$ is the ACT/360 day count fraction for the quarterly period. The 20 short blue arrows reflect the higher payment frequency of the floating leg. At par ($K = S_{\alpha,\beta}(0)$), the two legs have equal present value. This diagram illustrates a general swap structure; in the SOFR OIS market bootstrapped below, both legs pay annually ($m = n_{\text{fix}}$).

Beyond the FRA maturities, the liquid instruments are interest rate swaps. A swap exchanges fixed-rate payments for floating-rate payments over a specified tenor. In the SOFR OIS market, both legs pay annually, and the floating rate is the compounded overnight SOFR rate over each accrual period.

Swap mechanics

Consider a swap on the period $[T_\alpha, T_\beta]$ with notional $N$. The fixed leg pays $N \cdot K \cdot \tau_j$ at dates $T_{\alpha+1}, T_{\alpha+2}, \ldots, T_\beta$, where $K$ is the fixed rate, $\tau_j$ is the day count fraction for fixed-leg period $j$, and there are $n_{\text{fix}} = \beta - \alpha$ fixed payments. The floating leg pays $N \cdot F_i \cdot \delta_i$ at dates $t_1, t_2, \ldots, t_m$, where $F_i$ is the simply compounded forward rate for $[t_{i-1}, t_i]$, $\delta_i$ is the floating-leg day count fraction, and there are $m$ floating payments. In general $m \neq n_{\text{fix}}$: the diagram above shows $n_{\text{fix}} = 5$ annual fixed payments and $m = 20$ quarterly floating payments.

For a spot-starting SOFR OIS swap with annual payments on both legs, both legs share the same dates: $T_\alpha = 0$, $t_i = T_i$ for $i = 1, \ldots, n$, and $m = n_{\text{fix}} = n$. The fixed rate that sets the swap value to zero at inception is the par swap rate $S_{\alpha,\beta}(0)$.

The present value formulas

The fixed leg present value is the sum of each fixed coupon discounted to today:

Fixed leg PV (general) $$\text{PV}_{\text{fixed}} = N \cdot K \cdot \sum_{j=\alpha+1}^{\beta} \tau_j \cdot P(0, T_j) = N \cdot K \cdot A_{\alpha,\beta}(0)$$

The sum $A_{\alpha,\beta}(0) = \sum_{j=\alpha+1}^{\beta} \tau_j \cdot P(0, T_j)$ is called the annuity (also known as the level or PVBP). It measures the present value of receiving one basis point per year over the life of the swap.

The floating leg present value is the sum of the simply compounded forward rates, each weighted by $\delta_i$ and discounted:

Floating leg PV (general) $$\text{PV}_{\text{float}} = N \sum_{i=1}^{m} \delta_i \cdot P(0, t_i) \cdot F_i$$

where $F_i = F(0;\,t_{i-1}, t_i) = \frac{1}{\delta_i}\left(\frac{P(0, t_{i-1})}{P(0, t_i)} - 1\right)$ is the simply compounded forward rate for the floating-leg period $[t_{i-1}, t_i]$, with $t_0 = T_\alpha$ and $t_m = T_\beta$. In the single-curve setting (one curve for both discounting and projection), substitute $F_i$ into the sum:

Floating leg PV (single-curve, telescoped) $$\text{PV}_{\text{float}} = N \sum_{i=1}^{m} \delta_i \, P(0, t_i) \cdot \frac{1}{\delta_i}\!\left(\frac{P(0, t_{i-1})}{P(0, t_i)} - 1\right) = N \sum_{i=1}^{m} \bigl(P(0, t_{i-1}) - P(0, t_i)\bigr) = N\bigl(P(0, T_\alpha) - P(0, T_\beta)\bigr)$$

The $\delta_i$ cancel on substitution, and the sum telescopes regardless of the number of floating payments $m$ or the specific dates $t_1, \ldots, t_m$. For spot-starting swaps, $P(0, T_\alpha) = P(0, 0) = 1$. This is a standard result; see Brigo and Mercurio [1], Section 1.4. The telescoping holds in any single-curve setting; in the multi-curve framework of Chapter 02, discounting and projection use different curves, so $\delta_i$ no longer cancels and the floating leg must be computed forward rate by forward rate.

The par condition and bootstrap formula

At inception, the swap has zero value, so $\text{PV}_{\text{fixed}} = \text{PV}_{\text{float}}$:

Par swap condition $$N \cdot S_{\alpha,\beta}(0) \cdot A_{\alpha,\beta}(0) = N\bigl(1 - P(0, T_\beta)\bigr)$$

The notional $N$ cancels from both sides, leaving:

$$S_{\alpha,\beta}(0) = \frac{1 - P(0, T_\beta)}{A_{\alpha,\beta}(0)}$$

When bootstrapping, we process swaps in order of increasing tenor. For a swap maturing at $T_\beta$, all bond prices $P(0, T_1), \ldots, P(0, T_{\beta-1})$ are already known from shorter instruments. The only unknown is $P(0, T_\beta)$, which appears on both sides of the equation. Rearranging:

Bootstrap formula for the swap maturity $$P(0, T_\beta) = \frac{1 - S_{\alpha,\beta}(0) \cdot \displaystyle\sum_{j=\alpha+1}^{\beta-1} \tau_j \cdot P(0, T_j)}{1 + S_{\alpha,\beta}(0) \cdot \tau_\beta}$$

With annual coupons ($\tau_j = 1$ for all $j$), spot-starting swaps ($T_\alpha = 0$), and consecutive annual maturities ($T_1 = 1, \ldots, T_\beta = n$), each new swap adds exactly one unknown bond price, which is solved immediately. This is the standard sequential bootstrap; see Brigo and Mercurio [1], Section 1.4.

In Chapter 02, we abandon the single-curve assumption and introduce separate discount and projection curves, at which point the telescoping property no longer holds and the floating leg must be computed forward rate by forward rate.

A note on day count conventions

All rates use ACT/360, the standard for SOFR-linked instruments. The year fractions for money-market instruments are computed as actual days / 360. For the swap legs, we use annual accrual factors of $\delta_i = \tau_j = 1$, which simplifies the algebra. In production systems, the exact day count for each coupon period is computed from the payment schedule and can differ from 1.0 by a few basis points (e.g. $365/360 \approx 1.0139$ for a full calendar year under ACT/360). These details matter for precision but do not change the bootstrapping logic. For the full treatment of day count conventions, see Brigo and Mercurio [1], Section 1.2.

Why two symbols for day count fractions?

In the formulas above, $\tau_j$ denotes the fixed-leg day count fraction and $\delta_i$ denotes the floating-leg day count fraction. For a single-curve OIS swap where both legs pay at the same frequency (annual), $\delta_i = \tau_j$ and the distinction is invisible. The difference matters in Chapter 02, where the fixed leg pays annually (30/360) and the floating leg pays quarterly (ACT/360), giving $\delta_i \neq \tau_j$. Separating the notation now avoids a silent redefinition later.

Time-$t$ pricing and the forward swap rate

The par condition above prices the swap at time $t = 0$ with $K = S_{\alpha,\beta}(0)$, giving zero value. More generally, for a payer swap with fixed rate $K$ valued at time $t$, the present value is (Brigo and Mercurio [1], Section 1.5):

Payer swap value at time $t$ $$V_{\text{payer}}(t) = P(t, T_\alpha) - P(t, T_\beta) - K \cdot A_{\alpha,\beta}(t)$$
$A_{\alpha,\beta}(t) = \sum_{j=\alpha+1}^{\beta} \tau_j\, P(t, T_j)$ is the annuity (level) of the swap. $T_\alpha$ is the swap start date, $T_\beta$ is the final payment date.

At $t = 0$ with $K = S_{\alpha,\beta}(0)$, the par condition forces $V = 0$. For $t > 0$ or $K \neq S_{\alpha,\beta}(0)$, the swap has nonzero value. The rate $K$ that sets $V_{\text{payer}}(t) = 0$ at any time $t$ is the forward swap rate:

Forward swap rate $$S_{\alpha,\beta}(t) = \frac{P(t, T_\alpha) - P(t, T_\beta)}{A_{\alpha,\beta}(t)}$$
The forward swap rate is the fixed rate that makes the swap fair at time $t$. It is a martingale under the annuity measure (Chapter 03).

At $t = 0$ with spot-starting swaps ($T_\alpha = 0$, so $P(0, T_\alpha) = 1$), this recovers the par rate $S_{\alpha,\beta}(0)$. The forward swap rate will reappear in Chapter 03, where it serves as the underlying of the swaption payoff.

Interpolation

Bootstrapping produces bond prices only at the pillar dates: the maturities of the instruments you used. In practice, you need $P(0, T)$ at dates between pillars (for example, the third FRA requires $P(0, 0.5)$, which is not a pillar). The standard approach is log-linear interpolation on bond prices:

Log-linear interpolation $$P(0, T) = P(0, T_1)^{1-\alpha} \cdot P(0, T_2)^{\alpha}, \qquad \alpha = \frac{T - T_1}{T_2 - T_1}$$

where $T_1$ and $T_2$ are the two surrounding pillar dates. Taking logarithms, this is equivalent to linear interpolation on $\ln P(0, T)$, which in turn is equivalent to linear interpolation on the product $R(0,T) \cdot T$. The key property: log-linear interpolation on bond prices produces piecewise-constant instantaneous forward rates $f(0,t)$ between pillars, provided the bootstrapped bond prices are strictly monotone-decreasing ($P(0, T_{j+1}) < P(0, T_j)$ at every pillar), which is guaranteed in any positive-rate environment. Under that monotonicity assumption the piecewise-constant forwards are positive everywhere, preventing negative-rate arbitrage. Linear interpolation on zero rates does not have this property and can produce forward rates that oscillate or go negative, even when the zero curve itself looks smooth. For a detailed treatment, see Hagan and West [2].

Outside the pillar range, the implementation extrapolates by holding the product $R(0,T) \cdot T$ constant at its boundary value (the linear interpolant on $r \cdot T$ is clamped at the first and last pillar). For $T > T_n$ this means $R(0, T) = R(0, T_n) \cdot T_n / T$, which is not flat in the zero rate but is flat in the cumulative log-discount; this is the standard log-linear extrapolation convention and matches the YieldCurve.zc_price implementation used throughout this series.

The Newton-Raphson solver

In principle, cash deposits and FRAs have closed-form solutions for $P(0,T)$, and swaps can be solved sequentially. In practice, it is cleaner to treat the entire bootstrap as a single root-finding problem: find the vector of continuously compounded zero rates $\mathbf{R} = \bigl(R(0,T_1),\, R(0,T_2),\, \ldots,\, R(0,T_n)\bigr)$ such that every instrument reprices to zero residual simultaneously.

Each instrument defines a residual function $g_j(\mathbf{R})$ that equals zero when the curve is consistent with the $j$-th market quote. The explicit forms are:

Instrument residual functions $$\begin{aligned} g_{\text{dep}}(\mathbf{R}) &= P(0,T)\bigl(1 + r\,\tau\bigr) - 1 \\[6pt] g_{\text{FRA}}(\mathbf{R}) &= \frac{P(0, T_\alpha)}{P(0, T_\beta)} - \bigl(1 + F\,\tau(T_\alpha, T_\beta)\bigr) \\[6pt] g_{\text{swap}}(\mathbf{R}) &= \bigl(1 - P(0, T_\beta)\bigr) \;-\; S_{\alpha,\beta}(0)\, \sum_{j=\alpha+1}^{\beta} \tau_j\, P(0, T_j) \end{aligned}$$
$g_{\text{dep}}$: the deposit residual. $P(0,T)$ is the model bond price; $r$ and $\tau$ are the quoted rate and ACT/360 day count fraction. $g_{\text{FRA}}$: the FRA residual. $T_\alpha, T_\beta$ are the FRA start and end dates; $F$ is the quoted FRA rate. $g_{\text{swap}}$: the spot-starting swap residual. The first term is the telescoped floating-leg PV (single-curve only); the second is the fixed-leg PV $S_{\alpha,\beta}(0) \cdot A_{\alpha,\beta}(0)$ with $\tau_j$ the fixed-leg day count fraction. $S_{\alpha,\beta}(0)$ is the quoted par rate.

Each $g_j$ equals zero when the curve is consistent with the $j$-th market quote. Setting all residuals to zero simultaneously defines the bootstrap problem. See Brigo and Mercurio [1], Section 1.4 for the derivation of the swap residual from the par condition.

Stacking the residuals into a vector $\mathbf{g}(\mathbf{R}) = (g_1, g_2, \ldots, g_n)^T$, the bootstrap reduces to solving $\mathbf{g}(\mathbf{R}) = \mathbf{0}$. Newton-Raphson iterates:

Newton-Raphson iteration $$\mathbf{R}^{(k+1)} = \mathbf{R}^{(k)} - J(\mathbf{R}^{(k)})^{-1} \cdot \mathbf{g}(\mathbf{R}^{(k)})$$

where $J$ is the Jacobian matrix $J_{ij} = \partial g_i / \partial R(0,T_j)$. The Jacobian is computed exactly using torch.autograd.functional.jacobian, so there is no finite-difference approximation. This is an AAD-native solver: the same automatic differentiation graph that produces the Jacobian also provides curve sensitivities (DV01, key-rate durations) for free.

The implementation uses each instrument's quoted simple-compounded rate $S_j$ as the starting value for the continuously compounded zero rate $R(0,T_j)$. Strictly speaking these are two different objects (the unit conversion is $R = \ln(1 + S\tau)/\tau$), but for the rates and tenors in this dataset the gap is sub-percent of a percent and well inside the basin of attraction. Empirically, for this 14-instrument set with this initialisation, the solver converges in 4 iterations to residuals $\|\mathbf{g}\|_2 < 10^{-12}$. Newton-Raphson is locally quadratic when the Jacobian is non-singular at the root and the initial guess lies in its basin of attraction (Ortega-Rheinboldt 1970, Thm. 12.6.1); the lower-triangular Jacobian structure that guarantees non-singularity is treated in Chapter 02.

Python implementation

The implementation lives in the xvafoundations package (PyTorch, float64 throughout). Below are the key methods; each snippet shows only the mathematical core. See the repository for the full source.

Instrument residuals

Each instrument defines a residual() that returns zero when the curve is consistent with its market quote. This uniform interface lets the solver treat all instruments identically.

Python · xvafoundations.instruments
# Deposit: P(0,T) = 1 / (1 + r * tau)
def residual(self):  # Deposit
    P = self.curve.zc_price(t0, self.maturity)
    return P * (1.0 + self.rate * self.accrual) - 1.0

# FRA: P(0, T_alpha) / P(0, T_beta) = 1 + F * tau
def residual(self):  # FRA
    P_a = self.curve.zc_price(t0, self.time_t_alpha)
    P_b = self.curve.zc_price(t0, self.time_t_beta)
    return P_a / P_b - (1.0 + self.rate * self.accrual)

# OIS Swap: PV_float - PV_fixed = 0
def residual(self):  # OISSwap
    float_pv = annuity = torch.tensor(0.0)
    for i, T_i in enumerate(self.payment_times):
        T_prev = self.payment_times[i - 1] if i > 0 else 0.0
        tau_i = T_i - T_prev
        P_D = self.disc_curve.zc_price(t0, torch.tensor(T_i))
        F_i = (self.fwd_curve.zc_price(t0, torch.tensor(T_prev))
               / self.fwd_curve.zc_price(t0, torch.tensor(T_i)) - 1) / tau_i
        float_pv = float_pv + tau_i * P_D * F_i
        annuity  = annuity  + tau_i * P_D
    return float_pv - self.par_rate * annuity

The Newton-Raphson solver

The Stripper drives all residuals to zero simultaneously. It uses torch.autograd.functional.jacobian for the exact Jacobian, making it an AAD-native solver. The instrument factory pattern decouples the solver from the instrument set: in Chapter 02, the same Stripper calibrates a forward curve by passing a factory that captures the discount curve via closure.

Python · xvafoundations.calibration
def _residual_vector(self, zero_rates):
    """zero_rates -> stacked residuals (fresh curve each call for autograd)."""
    curve = YieldCurve(self.maturities, zero_rates, self.method)
    return torch.stack([inst.residual()
                        for inst in self.instrument_factory(curve)])

def calibrate(self, max_iter=50, tol=1e-12):
    rates = self.curve.zero_rates.clone()
    for iteration in range(1, max_iter + 1):
        g = self._residual_vector(rates)
        if g.norm().item() < tol:
            self.curve.update(self.maturities, rates)
            return iteration
        J = torch.autograd.functional.jacobian(
            self._residual_vector, rates)       # exact Jacobian via AAD
        rates = rates - torch.linalg.solve(J, g)

    # Final convergence check: raise rather than return silently.
    g = self._residual_vector(rates)
    if g.norm().item() < tol:
        self.curve.update(self.maturities, rates)
        return max_iter
    raise RuntimeError(
        f"Newton-Raphson did not converge after {max_iter} iterations "
        f"(||g||_2 = {g.norm().item():.2e})."
    )

Bootstrap driver

Python · bootstrap driver
from xvafoundations.instruments import build_instruments, pillar_maturities, initial_rates
from xvafoundations.calibration import Stripper
from xvafoundations.data.sofr import INSTRUMENTS, VALUATION_DATE

pillars = torch.tensor(pillar_maturities(INSTRUMENTS, VALUATION_DATE))
rates   = torch.tensor(initial_rates(INSTRUMENTS))

stripper = Stripper(pillars, rates,
    instrument_factory=lambda c: build_instruments(INSTRUMENTS, VALUATION_DATE, c))
stripper.calibrate()
curve = stripper.get_curve()

Results

Running the bootstrap driver produces the following calibrated curve. Every bond price and zero rate below is reproduced exactly by the code above.

Bootstrapped Curve: All 14 Pillars
#PillarT (yrs)P(0,T)R(0,T)
17d0.0194440.999158764.3282%
230d0.0833330.996389634.3403%
390d0.2500000.989168704.3562%
4270d0.7500000.968479184.2704%
51Y1.0138890.959155944.1130%
62Y2.0277780.924559803.8682%
73Y3.0444440.891572063.7698%
84Y4.0583330.857013223.8021%
95Y5.0722220.822835973.8444%
106Y6.0861110.788824933.8976%
117Y7.1027780.755296333.9512%
128Y8.1166670.723096023.9944%
139Y9.1305560.692339954.0269%
1410Y10.1444440.662412814.0600%

Visualisation

The two charts below show the same bootstrapped curve in two representations. The bond price curve $P(0,T)$ declines monotonically: money further in the future is worth less today. The zero-rate curve $R(0,T)$ shows the term structure of rates. The current shape is inverted at the short end (the highest rates are under 1 year), dipping to a minimum of 3.77% at 3Y, then rising gradually through the long end. This is consistent with a market pricing in near-term rate cuts followed by a gradual normalisation.

Bond Price Curve: P(0,T)
Zero Rate Curve: R(0,T)

SOFR OIS curve, Q1 2026. 14 instruments: 1 cash deposit, 3 FRAs, 10 par swaps. Hover for exact values.

Par rate verification

A calibrated curve must reprice every input instrument exactly. The strongest test for the swap portion is the par rate roundtrip: compute the par swap rate from the bootstrapped curve and verify it matches the input quote to machine precision. Using the formula $S_{\alpha,\beta}(0) = (1 - P(0, T_\beta)) / A_{\alpha,\beta}(0)$ where $A_{\alpha,\beta}(0) = \sum_{j=\alpha+1}^{\beta} \tau_j \, P(0, T_j)$:

Par Rate Roundtrip: Selected Tenors
TenorInput RateComputed Rate|Error|
1Y4.2000%4.2000%< 10-12
2Y3.9500%3.9500%< 10-12
3Y3.8500%3.8500%< 10-12
5Y3.9200%3.9200%< 10-12
10Y4.1200%4.1200%< 10-12

All 10 par swaps reprice to within $10^{-12}$ of their input quotes. The deposit and FRA residuals are likewise at machine precision. This confirms that the bootstrap is exact.

What this curve is for

The bond price curve $\{P(0, T)\}$ built here is the input to everything that follows in this series. To calibrate a Hull-White model (Chapter 05, Draft), you need the initial instantaneous forward rate $f(0,T) = -\partial \ln P(0,T) / \partial T$, which is derived from these bond prices. To simulate future rates in a Monte Carlo engine (Chapter 08, Draft), the model must reproduce today's curve exactly. To compute CVA (XVA Chapter 03), you discount future expected exposures back to today using $P(0, T)$. If the curve is wrong, every number downstream is wrong.

That is why production systems invest heavily in curve construction: the choice of instruments, the interpolation method, the handling of turn-of-year effects, and the treatment of collateral discounting (OIS vs. LIBOR, single-curve vs. multi-curve) all matter. This chapter builds the simplest correct version: a single-curve OIS bootstrap with no multi-curve complications. Chapter 02 introduces the dual-curve framework, where discounting and projection use separate curves, and the telescoping property no longer holds.

References

  1. D. Brigo and F. Mercurio, Interest Rate Models: Theory and Practice, 2nd edition, Springer, 2006. Chapters 1 and 2 cover discount factors, swap pricing, and curve construction in detail.
  2. P. S. Hagan and G. West, "Interpolation Methods for Curve Construction," Applied Mathematical Finance, 13(2), 2006. The standard reference on interpolation methods and their no-arbitrage properties.
  3. L. B. G. Andersen and V. V. Piterbarg, Interest Rate Modeling, Atlantic Financial Press, 2010. Volume I, Part I covers single-curve and multi-curve bootstrapping in full generality.