This chapter requires IR-01 (single-curve bootstrap). You should understand how to extract $P(0,T)$ from swap quotes. The motivation for splitting curves (the 2008 crisis reform) is explained here; no prior knowledge of multi-curve frameworks is assumed.
By the end, you have two distinct curves: OIS for discounting, EURIBOR for projection, consistent with post-2008 market practice.
Why multi-curve?
In Chapter 01 we built a single yield curve from market instruments using Newton-Raphson and autograd. That single curve served as both the discount curve and the forward curve. Before 2008, this was standard practice: a single LIBOR curve did double duty for present-value calculations and floating-leg projections.
The 2008 financial crisis shattered that assumption. The basis between overnight indexed swap (OIS) rates and LIBOR widened to tens of basis points. Banks suddenly questioned whether LIBOR was a fair discount rate. If I can fund at OIS + 25 bp, why should I discount at LIBOR? The answer: I should not.
Today's standard is a paradigm shift:
Discount curve: OIS (SOFR in USD, ESTR in EUR); the true cost of funding. Used for all present-value calculations. Forward curve: The specific tenor you are projecting (EURIBOR 3M, SOFR 3M, etc.); reflects credit and liquidity relative to the discount curve. The difference is the basis.
The economic origin of the OIS-IBOR (Interbank Offered Rate) basis is bank credit risk and a term-liquidity premium embedded in term lending rates; the relative weight of the two components is regime-dependent and contested in the literature. Filipović and Trolle (2013) decompose the basis into a credit and a non-trivial liquidity component, and Gallitschke et al. (2014) show that liquidity dominated during the post-2008 stress period. Before 2008, overnight and term rates co-moved tightly because unsecured interbank lending was assumed essentially risk-free. After the Lehman failure, three-month LIBOR incorporated both the default risk of lending to a bank for three months rather than overnight and the cost of tying up funding for a longer horizon. The spread is not an arbitrage; it compensates for real risk. A discounting framework that ignores it misprices collateralised derivatives. Ametrano and Bianchetti (2013) formalise the multiple-curve arbitrage-free framework; Crépey (2015) embeds it in a general CSA-consistent XVA framework.
This chapter shows you how to build both curves from market instruments, and how they interact. The mathematics is more complex than single-curve bootstrapping; the Jacobian becomes lower-triangular rather than diagonal. The payoff is that every floating-leg payment is priced correctly relative to your funding curve.
We now distinguish two curves. $P_D(0,T)$ and $R_D(0,T)$: discount curve (ESTR OIS) bond prices and zero rates. $P_F(0,T)$ and $R_F(0,T)$: forward curve (EURIBOR 3M) bond prices and zero rates. In the Jacobian derivation below, we abbreviate $R_k := R_F(0, T_k)$ for the unknown forward-curve zero rates being calibrated.
Multi-curve swap pricing
In multi-curve pricing, each cash flow is projected using the forward curve (EURIBOR 3M, blue) but discounted using a separate risk-free curve (OIS, red dashed). The forward rates $F_i$ come from $P_F$; the present values are computed with $P_D$. This separation is the defining feature of post-2008 derivatives pricing.
The diagram above shows the two-curve mechanism in action. At each quarterly floating-leg date, the forward rate $F_i$ is computed from the EURIBOR 3M curve: $F_i = (P_F(0, t_{i-1}) / P_F(0, t_i) - 1) / \delta_i$. Each resulting cash flow is then discounted to today using the OIS curve: $P_D(0, t_i)$. The fixed-leg payments are similarly discounted with $P_D$. The key difference from single-curve is that the curve generating the forward rates (blue) is not the same curve used for discounting (red). This coupling means the Jacobian is no longer diagonal.
In the single-curve world, the floating leg telescopes to $P(0,0) - P(0,T_n)$ and no explicit forward rates are needed. In multi-curve, that simplification breaks down because discounting and projection use different curves. We must compute forward rates explicitly.
For an interest rate swap with fixed leg paying on dates $T_{\alpha+1}, \ldots, T_\beta$ and floating leg resetting on dates $t_0, t_1, \ldots, t_m$, the pricing splits into two curves. Per the notation used in Chapter 01, $\tau_j$ is the fixed-leg accrual on $[T_{j-1}, T_j]$ and $\delta_i$ is the floating-leg accrual on $[t_{i-1}, t_i]$:
where the forward rate from the projection curve is:
Key insight: $P_D(0,T)$ comes from the known discount curve (ESTR OIS). $P_F(0,T)$ comes from the unknown forward curve (EURIBOR 3M) being bootstrapped. This is the source of the coupling: each swap's PV depends on all forward-curve nodes through the forward rates $F_i$.
At par (the bootstrapping condition), $\text{PV} = 0$:
where $R_k := R_F(0, T_k)$ are the unknown zero rates of the forward curve, and each $F_i$ depends on them via the bond prices $P_F(0, T_k) = e^{-R_k \, T_k}$.
The system of equations and Jacobian
Consider three swaps (1Y, 2Y, 3Y) calibrating the forward curve. The unknowns are the forward-curve zero rates $\mathbf{R} = (R_1, R_2, R_3)$, where $P_F(0, T_k) = e^{-R_k \cdot T_k}$ for $k = 1, 2, 3$. The discount curve $P_D$ is known. Write $D_k = P_D(0, T_k)$ for brevity.
The simply-compounded forward rate for period $[T_{i-1}, T_i]$ is:
$F_i$ is shorthand for $F(0;\,T_{i-1}, T_i)$, the simply-compounded forward rate for the accrual period $[T_{i-1}, T_i]$. The full Brigo-Mercurio notation is $F(t;\,T_\alpha, T_\beta)$.
The residual of swap $k$ (floating PV minus fixed PV at par) is, with the matched annual schedule collapsing $\delta_i = \tau_j = T_i - T_{i-1}$ to a single accrual $\tau_i$ on the common pillar grid:
For annual periods ($\tau_i = 1$), the Jacobian $J_{ki} = \partial g_k / \partial R_i$ assembles into a $3 \times 3$ lower-triangular matrix:
The general diagonal entry is:
$J$ is lower-triangular because swap $k$ depends only on rates $R_1, \ldots, R_k$: the forward rate $F_i$ for period $[T_{i-1}, T_i]$ involves only $P_F(0, T_{i-1})$ and $P_F(0, T_i)$, so $R_{k+1}$ never appears in residual $k$. The diagonal is positive ($D_k > 0$ and every exponential is positive), so $J$ is non-singular at every iterate and the Newton step is well-defined. Convergence is locally quadratic by the Kantorovich theorem (Ortega and Rheinboldt, 1970, Thm. 12.6.1) provided the initial guess lies in the basin of attraction; in practice, initialising $R_k$ at the quoted swap rate is well inside that basin for any plausible market.
Newton-Raphson with autograd
The Newton update rule is standard:
Computing $J$ by hand is tedious and error-prone, as the 3-swap example above shows. Finite differences require $2n$ function evaluations per iteration and introduce truncation error from the choice of $\varepsilon$. Reverse-mode automatic differentiation (autograd) computes the full Jacobian in $n$ backward passes with machine precision and zero tuning parameters.
PyTorch's autograd is reverse-mode AD (Adjoint Algorithmic Differentiation). torch.autograd.functional.jacobian(f, x) evaluates the full $n \times n$ Jacobian in one call. The gradients are exact to machine precision, with no $\varepsilon$ to tune. This is the same machinery used in production XVA engines to compute sensitivities to market moves. We use it again in Chapter 05 and the XVA series.
The bootstrapping algorithm itself is identical to the one we built in Chapter 01. The Stripper class, YieldCurve, and instrument classes are all reused from the xvafoundations library. The only new concept is the lambda capture pattern for multi-curve calibration, which we introduce below.
Market data
The multi-curve bootstrap uses two sets of instruments: 14 ESTR OIS instruments for the discount curve and 10 EURIBOR 3M swaps for the forward curve. All rates are as of the same valuation date.
Step 1: ESTR OIS discount curve
We bootstrap the ESTR discount curve first. This follows the single-curve method from Chapter 01 exactly: 1 cash deposit, 3 FRAs, and 10 OIS swaps give us 14 pillar points. The day count convention is ACT/360 throughout.
The Stripper converges in 3 Newton-Raphson iterations. The resulting 1Y discount factor is $P_D(0,1) = 0.97749319$.
Step 2: EURIBOR 3M forward curve
With the discount curve frozen, we now bootstrap the EURIBOR 3M forward curve from 10 annual EURIBOR swap quotes. The swap rates are consistently higher than OIS across all tenors, reflecting the credit and liquidity premium embedded in 3-month unsecured lending.
The key multi-curve idea is the lambda capture pattern. The Stripper expects an instrument factory: a function that takes a curve and returns a list of instruments. In single-curve mode, the factory creates instruments that use the same curve for both discounting and projection. In multi-curve mode, the factory captures the already-calibrated discount curve from the enclosing scope, while the Stripper passes the forward curve being calibrated as the argument:
The factory function captures the frozen discount curve and receives the forward curve from the Stripper. Each OISSwap instrument sees both curves.
The Stripper converges in 3 Newton-Raphson iterations. The first EURIBOR pillar (at $T = 1.014$, the swap payment date) gives $P_F(0,\, 1.014) = 0.97287437$.
Basis spread
The basis spread measures the difference between EURIBOR and ESTR forward rates for each annual period. It captures the credit and liquidity premium embedded in unsecured term lending relative to overnight funding.
The basis is tenor-dependent: widest at 1Y (~45 bp), reflecting the near-term credit risk premium for 3-month unsecured lending, and compressing toward ~18 bp at 10Y as the credit component is amortised over a longer horizon. This is the realistic structure observed in EUR markets.
Charts
Bond prices $P_D(0,T)$ versus $P_F(0,T)$. The forward curve lies below the discount curve everywhere; a higher rate means a lower bond price.
Zero rates from both curves. The gap between the two lines is the basis: widest at the front end and compressing with tenor.
Python implementation
All instrument classes and the Stripper come from the xvafoundations library. Nothing is redefined here. For the underlying formulas (deposit, FRA, swap pricing, Newton-Raphson), see Chapter 01.
Snippet 1: Imports
import torch
from xvafoundations.curves import YieldCurve, InterpolationMethod
from xvafoundations.instruments import build_instruments, pillar_maturities, initial_rates
from xvafoundations.calibration import Stripper
from xvafoundations.data.estr import ESTR_INSTRUMENTS, EURIBOR_INSTRUMENTS, VALUATION_DATE
torch.set_default_dtype(torch.float64)
Snippet 2: ESTR OIS discount curve
The instrument factory is identical in structure to Chapter 01. One cash deposit, three FRAs, and ten OIS swaps.
# ── Step 1: Bootstrap the ESTR OIS discount curve ──
pillars = torch.tensor(pillar_maturities(ESTR_INSTRUMENTS, VALUATION_DATE))
rates = torch.tensor(initial_rates(ESTR_INSTRUMENTS))
def estr_factory(curve: YieldCurve) -> list:
return build_instruments(ESTR_INSTRUMENTS, VALUATION_DATE, curve)
disc_stripper = Stripper(pillars, rates, estr_factory)
disc_stripper.calibrate()
disc_curve = disc_stripper.get_curve()
t0 = torch.tensor(0.0)
print(f"P_D(0, 1) = {disc_curve.zc_price(t0, torch.tensor(1.0)):.8f}")
Snippet 3: Lambda capture for EURIBOR forward curve
This is the key new pattern. The factory function captures disc_curve from the enclosing scope. The Stripper passes fwd_curve as the argument being calibrated. Each OISSwap receives both curves: the frozen discount curve for PV computation, and the forward curve for rate projection.
# ── Step 2: Bootstrap the EURIBOR 3M forward curve ──
fwd_pillars = torch.tensor(pillar_maturities(EURIBOR_INSTRUMENTS, VALUATION_DATE))
fwd_rates = torch.tensor(initial_rates(EURIBOR_INSTRUMENTS))
def euribor_factory(fwd_curve: YieldCurve) -> list:
"""disc_curve captured from enclosing scope; fwd_curve being calibrated."""
return build_instruments(
EURIBOR_INSTRUMENTS, VALUATION_DATE,
curve=disc_curve, # captured (frozen)
fwd_curve=fwd_curve, # being calibrated
)
fwd_stripper = Stripper(fwd_pillars, fwd_rates, euribor_factory)
fwd_stripper.calibrate()
fwd_curve = fwd_stripper.get_curve()
print(f"P_F(0, 1) = {fwd_curve.zc_price(t0, torch.tensor(1.0)):.8f}")
Snippet 4: Basis spread computation
# ── Basis spread: F_EURIBOR(T) - F_ESTR(T) for each annual period ──
t0 = torch.tensor(0.0)
print("\nBasis spread (EURIBOR 3M vs ESTR):")
for T in range(1, 11):
T_prev = torch.tensor(float(T - 1))
T_i = torch.tensor(float(T))
F_eur = fwd_curve.forward_rate(t0, T_prev, T_i)
F_estr = disc_curve.forward_rate(t0, T_prev, T_i)
spread_bp = (F_eur - F_estr).item() * 10_000
print(f" {T:2d}Y: F_EUR={F_eur.item()*100:.4f}%, "
f"F_ESTR={F_estr.item()*100:.4f}%, "
f"spread={spread_bp:.1f}bp")
What this enables
Multi-curve bootstrapping is the foundation for everything that follows in the series:
- Swaption pricing (Chapter 03): The forward swap rate now comes from the forward curve. The ATMF strike is $S_{\alpha,\beta}(0)$, now a function of the forward-curve zero rates, and you calibrate the vol surface to it.
- Hull-White calibration (Chapter 05, Draft): The model must reproduce a single initial term structure exactly. In a fully multi-curve workflow that target is the projection (EURIBOR 3M) curve, with OIS used for discounting; the pedagogical Hull-White examples in this series simplify by passing a single curve to the model and treating the multi-curve extension as a layer on top.
- XVA (XVA Series): Multi-curve is essential. Your funding curve is OIS, but you are pricing floating-leg exposure at tenor rates. The basis enters your valuation adjustment.
Summary
The post-2008 framework forces us to separate discounting from rate projection. A single curve no longer suffices. In Chapter 01 we built one curve; here we build two and show how they interact. The Jacobian becomes lower-triangular instead of diagonal, and PyTorch autograd makes its computation exact and efficient. The tenor-dependent basis between ESTR and EURIBOR 3M (~45 bp at 1Y compressing to ~18 bp at 10Y) is not a curiosity; it is fundamental to modern pricing, and it shows up everywhere downstream.
We now have two curves: a discount curve (OIS/ESTR) and a projection curve (EURIBOR). In IR-03, we use the forward swap rates derived from these curves to price swaptions and construct the ATM volatility surface, which becomes the calibration target in IR-06 (draft).
References
- D. Brigo and F. Mercurio, Interest Rate Models, Second Edition: Theory and Implementation, Springer, 2006.
- M. Henrard, "The Irony in the Derivatives Discounting Part II: The Crisis," SSRN, 2009.
- F. Ametrano and M. Bianchetti, "Everything You Always Wanted to Know About Multiple Interest Rate Curve Bootstrapping but Were Afraid to Ask," SSRN, 2013.
- L. B. G. Andersen and V. V. Piterbarg, Interest Rate Modeling, Volume I: Foundations and Vanilla Models, Atlantic Financial Press, 2010.
- S. Crépey, "Bilateral Counterparty Risk under Funding Constraints," Mathematical Finance, 25(1), 2015.
- D. Filipović and A. B. Trolle, "The Term Structure of Interbank Risk," Journal of Financial Economics, 109(3), 2013.
- J. M. Ortega and W. C. Rheinboldt, Iterative Solution of Nonlinear Equations in Several Variables, Academic Press, 1970.
All views expressed are strictly personal and do not represent any past or current employer. Content is educational; nothing here constitutes financial, investment, or trading advice.