GuidesRates

Bootstrap & Parametric Curves

Use bootstrap for node-by-node curve stripping via Brent root-finding, and the NelsonSiegel family for analytical curve models with closed-form zero rate formulas.


Iterative Bootstrap

The bootstrap() function calibrates a curve one node at a time using Brent's method, compared to the Solver which optimizes all nodes simultaneously via matrix-based methods. Bootstrap is conceptually simpler and well-suited for single-curve scenarios where each instrument pins exactly one node.

Setup

import datetime
from vade import bootstrap, DiscountCurve, Deposit, FRA, IRS

effective = datetime.date(2025, 6, 16)

curve = DiscountCurve(
    {
        effective: 1.0,
        datetime.date(2025, 12, 16): 1.0,
        datetime.date(2026, 6, 16): 1.0,
        datetime.date(2027, 6, 16): 1.0,
        datetime.date(2028, 6, 16): 1.0,
        datetime.date(2030, 6, 16): 1.0,
    },
    interpolation="log_linear",
    convention="act360",
)

dep_6m = Deposit(effective=effective, termination="6m", rate=0.0, convention="act360")
irs_1y = IRS(effective=effective, termination="1y", frequency="a", fixed_rate=0.0, convention="act360", float_convention="act360")
irs_2y = IRS(effective=effective, termination="2y", frequency="a", fixed_rate=0.0, convention="act360", float_convention="act360")
irs_3y = IRS(effective=effective, termination="3y", frequency="a", fixed_rate=0.0, convention="act360", float_convention="act360")
irs_5y = IRS(effective=effective, termination="5y", frequency="a", fixed_rate=0.0, convention="act360", float_convention="act360")

Calibration

Unlike the Solver which takes (instrument, target) tuples, bootstrap() takes a list of dicts with "instrument", "target", and "disc_curve_idx" keys. The instrument value must be the internal Rust binding accessed via ._inner.

calibrated = bootstrap(
    curves=[curve],
    instruments=[
        {"instrument": dep_6m._inner, "target": 0.0425, "disc_curve_idx": 0},
        {"instrument": irs_1y._inner, "target": 4.00, "disc_curve_idx": 0},
        {"instrument": irs_2y._inner, "target": 3.85, "disc_curve_idx": 0},
        {"instrument": irs_3y._inner, "target": 3.75, "disc_curve_idx": 0},
        {"instrument": irs_5y._inner, "target": 3.70, "disc_curve_idx": 0},
    ],
    target_curve_idx=0,
    bracket_low=0.5,
    bracket_high=1.5,
)

float(calibrated.discount_factor(datetime.date(2026, 6, 16)))  # 0.9610212146820518
float(calibrated.discount_factor(datetime.date(2030, 6, 16)))  # 0.8319003344593215

# Verify repricing: instrument rates match the original targets
abs(float(dep_6m.rate(calibrated)) - 0.0425) < 1e-6  # True
abs(float(irs_1y.rate(calibrated)) - 4.00) < 1e-4    # True
abs(float(irs_5y.rate(calibrated)) - 3.70) < 1e-4    # True

Parametric Curves

Parametric curves use analytical formulas with a small number of parameters instead of interpolating between node-based discount factors. They produce smooth yield curves and are commonly used for scenario analysis, regulatory reporting, and fitting observed market yields.

Nelson-Siegel

The Nelson-Siegel model has four parameters: beta0 (long-term rate), beta1 (short-term spread), beta2 (curvature hump), and tau (decay factor controlling hump location).

from vade import NelsonSiegel, NelsonSiegelSvensson, SmithWilson

ns = NelsonSiegel(
    beta0=0.04, beta1=-0.02, beta2=0.03, tau=1.5,
    base_date=effective, convention="act365f",
)

float(ns.zero_rate(effective, datetime.date(2026, 6, 16)))  # 0.03189622964353336
float(ns.zero_rate(effective, datetime.date(2030, 6, 16)))  # 0.041823322038103426
float(ns.zero_rate(effective, datetime.date(2045, 6, 16)))  # 0.0407494373039477

float(ns.discount_factor(datetime.date(2030, 6, 16)))  # 0.8112076671017623

Nelson-Siegel-Svensson

The Svensson extension adds beta3 and tau2 for a second curvature hump, providing more flexibility at intermediate tenors.

nss = NelsonSiegelSvensson(
    beta0=0.04, beta1=-0.02, beta2=0.03, beta3=0.01,
    tau1=1.5, tau2=5.0,
    base_date=effective, convention="act365f",
)

float(nss.zero_rate(effective, datetime.date(2026, 6, 16)))  # 0.032772384458854435
float(nss.zero_rate(effective, datetime.date(2030, 6, 16)))  # 0.04446630078480113
float(nss.zero_rate(effective, datetime.date(2045, 6, 16)))  # 0.04301943830572509

Smith-Wilson

The SmithWilson model is used in Solvency II regulation. It takes market data points (dates and rates) and extrapolates toward an ultimate forward rate (UFR). The alpha parameter controls how quickly the curve converges to the UFR beyond the last liquid point.

dates = [
    datetime.date(2026, 6, 16),
    datetime.date(2028, 6, 16),
    datetime.date(2030, 6, 16),
    datetime.date(2035, 6, 16),
]
rates = [0.038, 0.039, 0.037, 0.036]

sw = SmithWilson(
    dates=dates, rates=rates, ufr=0.042, alpha=0.1,
    base_date=effective, convention="act365f",
)

float(sw.discount_factor(datetime.date(2030, 6, 16)))  # 0.8310200391947283
float(sw.discount_factor(datetime.date(2050, 6, 16)))  # 0.3866408502017419
float(sw.discount_factor(datetime.date(2075, 6, 16)))  # 0.1366164115809102

Next Steps

On this page