Chapter 7 — The Yield Curve
"The yield curve is the single most important graph in all of finance."
After this chapter you will be able to:
- Explain the relationships between spot rates, forward rates, par rates, and discount factors, and convert between them
- Bootstrap a zero-coupon yield curve from a set of coupon bonds or interest rate swaps
- Apply linear, log-linear, and cubic spline interpolation to a discount factor curve and explain the consequences of each for forward rates
- Fit a Nelson-Siegel-Svensson model to yield curve data and interpret its four parameters
- Perform PCA on historical yield curve moves and identify the three dominant factors
In the summer of 2006, the US yield curve inverted: short-term Treasury yields climbed above long-term yields, a configuration that historically precedes recessions. Economists, traders, and central bankers watched it closely. Eighteen months later, the global financial crisis began. The yield curve had, as it often does, seen it coming first.
The yield curve is a snapshot of borrowing costs across all maturities — from overnight to thirty years. It summarises everything markets believe about the future path of interest rates, inflation, economic growth, and monetary policy into a single curve. When the curve slopes upward (the normal shape), investors demand higher rates for longer loans to compensate for uncertainty and the opportunity cost of tying up capital. When the curve flattens or inverts, it signals either that short-term rates have been pushed up artificially by central bank policy, or that markets expect rates to fall in the future — often because they expect recession.
For quantitative practitioners, the yield curve has a more immediate role: it is the machine that converts future cash flows into present values. Every fixed income instrument, interest rate derivative, mortgage MBS, pension liability, and credit product is ultimately priced by discounting cash flows off some form of the yield curve. If the curve is wrong by even a few basis points, mis-pricings compound into large errors on portfolios with long duration.
This chapter builds the tools to construct, represent, and analyse yield curves from market data. We start from the fundamental relationships between spot rates, forward rates, discount factors, and par rates, then move to bootstrapping — the market standard method for extracting a zero-coupon curve from coupon bonds or swaps. We cover interpolation methods, parametric models (Nelson-Siegel), and principal component analysis of yield curve moves.
7.1 The Structure of Interest Rates
The yield curve (term structure of interest rates) describes how interest rates vary with maturity. It is the foundation for pricing every fixed income instrument and many derivatives. The same economic information can be expressed in three equivalent forms, each suited to a different task:
| Rate Type | Definition | Primary Use |
|---|---|---|
| Spot rate $r(t)$ | Rate for a zero-coupon investment from today to time $t$ | Discounting |
| Forward rate $f(t, T)$ | Rate agreed today for investment from $t$ to $T$ | Forward pricing |
| Par rate $c(T)$ | Coupon rate that prices a $T$-maturity bond at par | Quoting bonds |
These are not three different things — they are three views of the same discount factor curve. Given any one, you can recover the others exactly. The spot rate is most fundamental for pricing; forward rates are used for floating rate products and options; par rates are what bond traders quote daily in the market.
7.2 Relationships Between Rate Types
7.2.1 Spot to Discount Factor
The discount factor is related to the continuously compounded spot rate by:
$$DF(t) = e^{-r(t) \cdot t}$$
or equivalently, for semi-annual compounding:
$$DF(t) = \left(1 + \frac{r(t)}{2}\right)^{-2t}$$
7.2.2 Spot to Forward Rate
The instantaneous forward rate is the slope of the log discount factor:
$$f(t) = -\frac{d}{dt}\ln DF(t) = r(t) + t \cdot r'(t)$$
The discrete forward rate between $t_1$ and $t_2$:
$$f(t_1, t_2) = \frac{1}{t_2 - t_1} \cdot \ln\frac{DF(t_1)}{DF(t_2)}$$
7.2.3 Par Rate from Spot Rates
The par coupon rate for maturity $T$ (semi-annual coupons):
$$c(T) = \frac{2 \cdot (1 - DF(T))}{\sum_{i=1}^{2T} DF(i/2)}$$
(** Yield curve representation *)
type discount_curve = {
tenors : float array; (* sorted maturities in years *)
dfs : float array; (* discount factors DF(t) *)
}
(** Spot rate from discount factor (continuously compounded) *)
let spot_rate_cc { tenors; dfs } t =
let df = linear_interpolate (Array.to_list (Array.map2 (fun t d -> (t,d)) tenors dfs)) t in
if t < 1e-6 then 0.0
else -. log df /. t
(** Forward rate between t1 and t2 *)
let forward_rate curve t1 t2 =
assert (t2 > t1);
let df1 = exp (-. spot_rate_cc curve t1 *. t1) in
let df2 = exp (-. spot_rate_cc curve t2 *. t2) in
log (df1 /. df2) /. (t2 -. t1)
(** Par coupon rate for maturity T (semi-annual) *)
let par_rate curve t_years =
let n = int_of_float (t_years *. 2.0) in
let sum_dfs = ref 0.0 in
for i = 1 to n do
let ti = float_of_int i /. 2.0 in
let df = exp (-. spot_rate_cc curve ti *. ti) in
sum_dfs := !sum_dfs +. df
done;
let df_n = exp (-. spot_rate_cc curve t_years *. t_years) in
2.0 *. (1.0 -. df_n) /. !sum_dfs
7.3 Bootstrapping the Yield Curve
Markets don't directly trade zero-coupon bonds for every maturity. What trades are coupon bonds, interest rate swaps, and short-term money market instruments — all of which are packages of multiple cash flows at different dates. To get the zero-coupon discount factors we need for pricing, we must strip them from these instruments, maturity by maturity. This process is called bootstrapping.
The intuition is sequential. The 6-month deposit rate gives us $DF(0.5)$ directly (one cash flow, one unknown). The 1-year swap has two cash flows — at 6 months and 1 year. We already know $DF(0.5)$, so we can solve for $DF(1.0)$. The 2-year swap has four cash flows; we know $DF(0.5)$, $DF(1.0)$, $DF(1.5)$ from the 18-month instrument, so we solve for $DF(2.0)$. And so on, working forward one maturity at a time.
7.3.1 The Bootstrap Algorithm
Starting from a set of coupon bonds with prices $P_i$ and maturities $T_1 < T_2 < \cdots < T_n$:
For the first instrument (assume zero-coupon or very short): $$DF(T_1) = P_1 / F$$
For each subsequent instrument: $$DF(T_i) = \frac{P_i - \sum_{t < T_i} CF_t \cdot DF(t)}{CF_{T_i}}$$
The denominator is the last cash flow (coupon plus principal). The numerator strips out the present value already accounted for by previously bootstrapped discount factors.
(** A market instrument for curve building *)
type curve_instrument =
| Deposit of {
maturity : float;
rate : float;
day_count : day_count_convention;
}
| FRA of {
start_date : float;
end_date : float;
rate : float;
}
| Swap of {
maturity : float;
fixed_rate : float;
frequency : int;
day_count : day_count_convention;
}
type curve_point = {
maturity : float;
df : float;
spot_cc : float;
}
(**
Bootstrap a yield curve from a list of instruments.
Instruments must be sorted by maturity.
*)
let bootstrap instruments =
let points = ref [] in
(* Interpolate DF from already-built points *)
let interp_df t =
match !points with
| [] -> 1.0
| pts ->
let knots = List.map (fun p -> (p.maturity, p.df)) pts in
(* Log-linear interpolation: ln(DF) linear in t *)
let log_dfs = List.map (fun (ti, di) -> (ti, log di)) knots in
exp (linear_interpolate log_dfs t)
in
List.iter (fun instrument ->
match instrument with
| Deposit { maturity; rate; day_count = _ } ->
(* Simple interest: DF = 1 / (1 + r*t) *)
let df = 1.0 /. (1.0 +. rate *. maturity) in
let r_cc = -. log df /. maturity in
points := { maturity; df; spot_cc = r_cc } :: !points
| FRA { start_date; end_date; rate } ->
let df_start = interp_df start_date in
let tau = end_date -. start_date in
let df_end = df_start /. (1.0 +. rate *. tau) in
let r_cc = -. log df_end /. end_date in
points := { maturity = end_date; df = df_end; spot_cc = r_cc } :: !points
| Swap { maturity; fixed_rate; frequency; _ } ->
(*
Fixed leg PV = floating leg PV = 1 - DF(T) (assuming SOFR = 0 spread)
fixed_rate / m * sum_{t} DF(t) + DF(T) = 1
Solve for DF(T):
*)
let m = float_of_int frequency in
let n = int_of_float (Float.round (maturity *. m)) in
let coupon = fixed_rate /. m in
let sum_dfs = ref 0.0 in
for i = 1 to n - 1 do
let ti = float_of_int i /. m in
sum_dfs := !sum_dfs +. interp_df ti
done;
let df_t = (1.0 -. coupon *. !sum_dfs) /. (1.0 +. coupon) in
let r_cc = -. log df_t /. maturity in
points := { maturity; df = df_t; spot_cc = r_cc } :: !points
) instruments;
let pts = List.sort (fun a b -> compare a.maturity b.maturity) !points in
{
tenors = Array.of_list (List.map (fun p -> p.maturity) pts);
dfs = Array.of_list (List.map (fun p -> p.df) pts);
}
(** Example: USD SOFR curve bootstrap *)
let usd_sofr_instruments = [
Deposit { maturity = 1.0 /. 12.0; rate = 0.0530; day_count = Act360 };
Deposit { maturity = 3.0 /. 12.0; rate = 0.0528; day_count = Act360 };
Deposit { maturity = 6.0 /. 12.0; rate = 0.0520; day_count = Act360 };
Swap { maturity = 1.0; fixed_rate = 0.0510; frequency = 4; day_count = Act360 };
Swap { maturity = 2.0; fixed_rate = 0.0490; frequency = 4; day_count = Act360 };
Swap { maturity = 5.0; fixed_rate = 0.0460; frequency = 4; day_count = Act360 };
Swap { maturity = 10.0; fixed_rate = 0.0450; frequency = 4; day_count = Act360 };
Swap { maturity = 30.0; fixed_rate = 0.0430; frequency = 4; day_count = Act360 };
]
Figure 7.1 — USD SOFR curve bootstrapped from the instruments above. Forward rates (orange) show the inversion between 1Y and 5Y.
7.4 Interpolation Methods for Curves
Once we have bootstrapped discount factors at the market instrument maturities (1M, 3M, 6M, 1Y, 2Y, 5Y, 10Y, 30Y), we need a method to find $DF(t)$ at any maturity $t$ — because real instruments often have maturities that do not coincide with the bootstrapped pillars. The choice of interpolation method is far more important than it might appear, because the instantaneous forward rate $f(t) = -d\ln DF(t)/dt$ is the derivative of the interpolated function. Small differences in the shape of $DF(t)$ produce large differences in forward rates.
Why forward rates matter: A caplet's value depends on the forward rate from 2Y to 2.25Y. A swaption prices off a swap from 3Y to 8Y, itself constructed from discount factors between 3Y and 8Y. If the interpolation method produces a wavy or kinked forward curve, caplets and swaptions on adjacent maturities will have inconsistent implied forward rates, creating arbitrage opportunities between instruments. This is why naive linear interpolation of discount factors is prohibited in professional systems.
The consequence of linear interpolation on discount factors. If you interpolate $DF(t)$ as a straight line between $DF(1.0)$ and $DF(2.0)$, the implied forward rate $f(t) = -d\ln DF/dt$ will have a polynomial shape over that interval. Worse, it will jump discontinuously at the knot points (1Y, 2Y, etc.) — the derivative of a piecewise linear function is piecewise constant with jumps. Instruments priced off the 1.5Y forward rate versus the 2.0Y forward rate will show unphysical discontinuities.
Log-linear interpolation on $\ln DF(t)$ produces piecewise constant forward rates — constant within each interval, jumping at knots. This is analytically clean and guarantees positive forward rates (since $\ln DF$ is negative and decreasing), but still has the kink problem. It is the most common method in practice for its simplicity and stability.
Cubic spline on $\ln DF(t)$ produces smooth, twice-differentiable forward rates. The spline minimises the integrated squared curvature of $\ln DF$, distributing curvature evenly across the curve. The result is a smooth forward curve with no kinks at knot points — much better for hedging. The weakness is that cubic splines are global: changing one market input (say, the 5Y swap rate) shifts the entire spline, including maturities far from 5Y. This "Runge phenomenon" makes hedging more complex.
Monotone convex interpolation (Hagan-West 2006) is the industry standard for sophisticated systems. It guarantees positive forward rates, a continuous forward curve, and locality (changing one input affects only the surrounding maturity region). It is more complex to implement but better for pricing books with thousands of instruments across the full maturity spectrum.
The choice of interpolation method profoundly affects:
- The smoothness of the forward rate curve
- Absence of arbitrage (forward rates must be positive)
- Hedging stability
Figure 7.2 — Log-linear vs cubic spline interpolation. Both fit the discount factors exactly, but log-linear produces piecewise constant forward rates with unphysical jumps. Cubic spline produces a physically realistic continuous forward curve.
7.4.1 Log-Linear Interpolation
Interpolate $\ln DF(t)$ linearly. Produces piecewise constant forward rates:
let log_linear_df curve t =
let log_dfs = Array.map2 (fun ti di -> (ti, log di))
curve.tenors curve.dfs
|> Array.to_list in
exp (linear_interpolate log_dfs t)
Pros: Positive forward rates guaranteed; simple
Cons: Discontinuous forward rate curve (kinks at knots)
7.4.2 Cubic Spline on Log Discount Factors
Interpolate $\ln DF(t)$ with a cubic spline. Produces smooth continuous forward rates:
let cubic_spline_df curve =
let xs = curve.tenors in
let ys = Array.map log curve.dfs in
let spline = make_spline xs ys in
fun t -> exp (eval_spline spline t)
let forward_rate_from_spline df_fn t dt =
let ln_df1 = log (df_fn (t -. dt /. 2.0)) in
let ln_df2 = log (df_fn (t +. dt /. 2.0)) in
-. (ln_df2 -. ln_df1) /. dt
7.4.3 Monotone Convex Interpolation (Hagan-West)
The industry standard in many sell-side systems. Ensures:
- Positive forward rates
- Continuity of the forward curve
- Localisation (a change in one instrument only affects nearby forwards)
7.5 Nelson-Siegel and Svensson Models
Parametric curve models fit a functional form to observed rates. They are used in central banks, academia, and for regulatory reporting.
7.5.1 Nelson-Siegel
The Nelson-Siegel model parameterises the spot rate as:
$$r(t) = \beta_0 + \beta_1 \cdot \frac{1 - e^{-t/\tau}}{t/\tau} + \beta_2 \cdot \left(\frac{1 - e^{-t/\tau}}{t/\tau} - e^{-t/\tau}\right)$$
- $\beta_0$: long-run level (long rate)
- $\beta_1$: slope (short rate − long rate)
- $\beta_2$: hump (curvature)
- $\tau$: decay factor
Figure 7.3 — The four parameters of the Nelson-Siegel model. Note how variations in $\beta_0, \beta_1, \beta_2,$ and $\tau$ allow the single functional form to exhibit distinct parallel (level), steepning (slope), twisting (curvature), and hump-location profile shifts respectively.
type nelson_siegel_params = {
beta0 : float;
beta1 : float;
beta2 : float;
tau : float;
}
let nelson_siegel { beta0; beta1; beta2; tau } t =
if t < 1e-6 then beta0 +. beta1 (* limit as t -> 0 *)
else
let x = t /. tau in
let decay = (1.0 -. exp (-. x)) /. x in
beta0 +. beta1 *. decay +. beta2 *. (decay -. exp (-. x))
(** Calibrate Nelson-Siegel to market data via least-squares *)
let calibrate_nelson_siegel ~tenors ~market_rates =
let n = Array.length tenors in
assert (n = Array.length market_rates);
let objective params =
let p = { beta0 = params.(0); beta1 = params.(1);
beta2 = params.(2); tau = params.(3) } in
Array.fold_left2 (fun acc t r ->
let err = nelson_siegel p t -. r in
acc +. err *. err
) 0.0 tenors market_rates
in
(* Optimise using Nelder-Mead or L-BFGS — simplified version *)
let initial = [|0.05; -0.01; 0.02; 2.0|] in
let _ = objective initial in (* placeholder: use Owl.Optimise *)
{ beta0 = initial.(0); beta1 = initial.(1);
beta2 = initial.(2); tau = initial.(3) }
7.5.2 Svensson Extension
Adds a second hump term for better fit over the full maturity spectrum:
$$r(t) = \beta_0 + \beta_1 \cdot L_1(t) + \beta_2 \cdot L_2(t) + \beta_3 \cdot L_3(t)$$
where $L_3(t) = \frac{1 - e^{-t/\tau_2}}{t/\tau_2} - e^{-t/\tau_2}$.
type svensson_params = {
beta0 : float; beta1 : float; beta2 : float; beta3 : float;
tau1 : float; tau2 : float;
}
let svensson { beta0; beta1; beta2; beta3; tau1; tau2 } t =
let ns = nelson_siegel { beta0; beta1; beta2; tau = tau1 } t in
let x2 = t /. tau2 in
let l3 = (1.0 -. exp (-. x2)) /. x2 -. exp (-. x2) in
ns +. beta3 *. l3
7.6 Principal Component Analysis of Yield Curves
PCA reveals that ~99% of yield curve variation is explained by three factors:
- PC1 (level, ~85%): parallel shift — all yields move together
- PC2 (slope, ~10%): short rates vs long rates diverge
- PC3 (curvature, ~4%): middle of curve moves vs short and long ends
(**
PCA of historical yield curves.
returns_matrix: (n_days × n_tenors) matrix of daily yield changes.
*)
let yield_curve_pca returns_matrix =
let eigenvalues, eigenvectors = pca returns_matrix in
let ev = explained_variance eigenvalues in
Printf.printf "Yield Curve PCA:\n";
Array.iteri (fun i evr ->
if i < 5 then
Printf.printf " PC%d: %.2f%% variance explained\n" (i + 1) (evr *. 100.0)
) ev;
Printf.printf " Top 3 cumulative: %.2f%%\n"
((ev.(0) +. ev.(1) +. ev.(2)) *. 100.0);
(eigenvalues, eigenvectors, ev)
Figure 7.4 — Stylised first three principal components of yield curve changes. The first component (PC1) accounts for ~85% of movement and acts as a parallel 'Level' shift.
(** Reconstruct curve from PCA factors *) let reconstruct_curve ~mean_curve ~factors ~pcs = let n_tenors = Array.length mean_curve in let curve = Array.copy mean_curve in Array.iteri (fun i factor -> let pc = pcs.(i) in Array.iteri (fun j _ -> curve.(j) <- curve.(j) +. factor *. (Mat.get pc j 0) ) mean_curve ) factors; curve
---
---
## 7.8 First-Class Modules for Runtime Curve Selection
A real pricing library must support multiple yield curve models simultaneously — different desks use different conventions, different regulatory calculations require different curve construction methods, and the same desk may switch between a bootstrapped and a parametric curve depending on the product being priced. OCaml's first-class modules solve this cleanly: each curve implementation exports the same `YIELD_CURVE` signature, and any curve can be packaged as a runtime value and selected from a registry.
This pattern, introduced conceptually in Chapter 2 (§2.12), applies directly here. The `Make_yield_curve` functor instantiates any interpolation scheme into a complete curve module; the module can then be registered and retrieved at runtime:
```ocaml
(** Unified interface: any yield curve must satisfy this signature *)
module type YIELD_CURVE = sig
val name : string
val calibrate : (float * float) array -> unit (* (maturity, rate) -> () *)
val discount : float -> float
val zero_rate : float -> float
val forward_rate : float -> float -> float
val par_rate : float -> float
end
(** Log-linear interpolated bootstrapped curve *)
module Log_linear_curve : YIELD_CURVE = struct
let name = "log_linear"
let knots : (float * float) array ref = ref [||]
let calibrate pairs = knots := pairs
let zero_rate t =
if Array.length !knots = 0 then failwith "curve not calibrated";
let log_knots = Array.map (fun (ti, ri) -> (ti, -. ri *. ti)) !knots in
let log_df_t = linear_interpolate (Array.to_list log_knots) t in
-. log_df_t /. t
let discount t = exp (-. zero_rate t *. t)
let forward_rate t1 t2 =
-. (log (discount t2) -. log (discount t1)) /. (t2 -. t1)
let par_rate t =
let n = int_of_float (t *. 2.0) in
let annuity = List.init n (fun i -> discount (float_of_int (i+1) /. 2.0))
|> List.fold_left ( +. ) 0.0 in
2.0 *. (1.0 -. discount t) /. annuity
end
(** Nelson-Siegel parametric curve *)
module Nelson_siegel_curve : YIELD_CURVE = struct
let name = "nelson_siegel"
let params : nelson_siegel_params ref =
ref { beta0 = 0.05; beta1 = -0.01; beta2 = 0.01; tau = 2.0 }
let calibrate pairs =
params := calibrate_nelson_siegel
~tenors:(Array.map fst pairs)
~market_rates:(Array.map snd pairs)
let zero_rate t = nelson_siegel !params t
let discount t = exp (-. zero_rate t *. t)
let forward_rate t1 t2 =
-. (log (discount t2) -. log (discount t1)) /. (t2 -. t1)
let par_rate t =
let n = int_of_float (t *. 2.0) in
let annuity = List.init n (fun i -> discount (float_of_int (i+1) /. 2.0))
|> List.fold_left ( +. ) 0.0 in
2.0 *. (1.0 -. discount t) /. annuity
end
(** Runtime registry: name -> packaged module *)
type curve = (module YIELD_CURVE)
let curve_registry : (string, curve) Hashtbl.t = Hashtbl.create 4
let () =
Hashtbl.set curve_registry ~key:"log_linear" ~data:(module Log_linear_curve);
Hashtbl.set curve_registry ~key:"nelson_siegel" ~data:(module Nelson_siegel_curve)
(** Price any bond using whatever curve is configured in the system *)
let price_bond_from_registry ~curve_name ~face ~coupon_rate ~maturity =
match Hashtbl.find curve_registry curve_name with
| None -> Error (Printf.sprintf "Curve '%s' not found in registry" curve_name)
| Some m ->
let module C = (val m : YIELD_CURVE) in
let n = int_of_float (maturity *. 2.0) in
let coupon = face *. coupon_rate /. 2.0 in
let cf_pv = List.init n (fun i ->
let t = float_of_int (i + 1) /. 2.0 in
coupon *. C.discount t
) |> List.fold_left ( +. ) 0.0 in
let total = cf_pv +. face *. C.discount maturity in
Ok (total, C.name)
The key advantage is that price_bond_from_registry does not need to import or know about Log_linear_curve or Nelson_siegel_curve. The curve is retrieved as a first-class module and unpacked at the call site. Adding a new curve model is a matter of implementing YIELD_CURVE and registering it — no existing code changes. This is the extensibility pattern that makes large fixed income libraries maintainable: new instruments, new curves, and new models can be added without modifying the core pricing engine.
7.9 Chapter Summary
The yield curve is not a single number but a function of maturity, and getting it right matters for every pricing, hedging, and risk management calculation in fixed income. This chapter covered the complete toolkit: the fundamental rate relationships, the bootstrap algorithm, interpolation, parametric fitting, and principal component analysis.
The bootstrap produces a piecewise exact curve by construction — it reprices every input instrument perfectly. This is essential for trading desks that need their curve to be consistent with market quotes. The interpolation method applied between bootstrap nodes determines forward rate smoothness: log-linear interpolation produces flat piecewise forwards (simple but with jumps), while cubic spline or monotone-convex interpolation produces smoother, better-behaved forward curves.
Nelson-Siegel and Svensson parametric models sacrifice exact repricing for smoothness and interpretability. Their factor structure — level, slope, curvature — aligns with how economists think about monetary policy (level reflects the long-run neutral rate; slope reflects the stance of policy; curvature reflects humps from near-term expectations). Central banks and regulatory bodies (ECB, BIS) publish official yield curves using these models.
PCA of yield curve changes reveals that three factors explain over 99% of daily moves in historical data, with the first factor (parallel shift) dominating at 80–90%. This has direct implications for risk management: a portfolio hedged against parallel shifts is not yet fully hedged against the smaller but non-trivial slope and curvature moves.
From a software design perspective, the YIELD_CURVE module interface + first-class modules pattern (§7.8) is the standard OCaml approach to plugin architectures. It provides the flexibility of runtime model selection without sacrificing any compile-time type safety. Chapter 8 uses this curve to price the full universe of interest rate derivatives.
Exercises
7.1 Given spot rates at 1y=4%, 2y=4.5%, 3y=5%, compute: (a) discount factors; (b) 1y×1y and 2y×1y forward rates; (c) 2-year par coupon rate.
7.2 Bootstrap a SOFR swap curve using the instruments in the chapter example. Plot the resulting spot curve, forward curve, and compare to a Nelson-Siegel fit.
7.3 Implement log-linear and cubic spline interpolation for the same curve. Plot the instantaneous forward rates from both methods and explain the differences.
7.4 Download 5 years of daily 10-year Treasury yields. Perform PCA on daily changes. How many principal components explain 95% of variance?
7.5 Register a third curve model — Cubic_spline_curve — in the curve registry from §7.8. Calibrate all three curves to the SOFR instruments and compare: (a) 5Y zero rates; (b) 5Y instantaneous forward rates; (c) 10Y par rates. Explain which method you would use for pricing a 5Y10Y swaption.