Esc

    The API is under testing and may change before release.

    Inflation calibration

    This page lists what you need to solve an inflation curve from zero-coupon inflation swaps quoted at breakeven rates: the ladder, its lagged pillars, the base prints, and what the solve hands back.

    Quick example

    This solves a US CPI curve from three breakevens, files it in the market and prices each swap back to nothing. The example reads its rates from the desk sheets; load them once and it runs as shown.

    Rust
    use chrono::NaiveDate;
    use helixio_docs_sheets::{SOFR_CLOSE_DATE, USD_SOFR_CLOSE};
    use helixrs::{
        AccrualCompounding, AnyInstrument, CollateralSpec, Currency, DayCountFraction, FixingKey,
        FixingStore, FxRates, InflationCurve, LevelInterpolation, MarketContext, QuoteSheet, Rung,
        Tolerance, ZeroCouponInflationSwap, calibrate_inflation, lagged_observation,
    };
    
    type Outcome<T> = Result<T, Box<dyn std::error::Error>>;
    
    fn date((year, month, day): (i32, u32, u32)) -> NaiveDate {
        NaiveDate::from_ymd_opt(year, month, day).expect("a real calendar date")
    }
    
    fn main() -> Outcome<()> {
        let close = date(SOFR_CLOSE_DATE);
        let usd: Currency = "USD".parse()?;
        let act365 = DayCountFraction::Act365Fixed;
    
        // Two published prints of the index, and the discount curve from the sheet.
        let prints = vec![
            (FixingKey::Typed("US-CPI-U".to_owned()), date((2026, 6, 1)), 324.10),
            (FixingKey::Typed("US-CPI-U".to_owned()), date((2026, 7, 1)), 324.90),
        ];
        let fixings = FixingStore::from_observations_checked(&prints)?;
        let sheet = QuoteSheet::from_rows(USD_SOFR_CLOSE.iter().copied())?;
        let market = MarketContext::as_of(close)
            .with_fixings(fixings.clone())
            .with_recipe_quotes(&CollateralSpec::Currency(usd), &sheet)?;
        let (discount, _) = market.curve(&"discount(USD)".parse()?).expect("the USD curve");
    
        // Zero-coupon inflation swaps at breakeven rates, each pillar the maturity read
        // three months back.
        let mut swaps = Vec::new();
        for (years, breakeven) in [(2, 0.0245), (5, 0.0238), (10, 0.0241)] {
            let maturity = date((2026 + years, 10, 1));
            let swap = ZeroCouponInflationSwap::new(usd, "US-CPI-U", close, maturity, 1.0, breakeven, 3)
                .accruing(act365, AccrualCompounding::Compounded);
            swaps.push(Rung::par(
                lagged_observation(maturity, 3),
                AnyInstrument::ZcInflationSwap(Box::new(swap)),
                breakeven,
            ));
        }
        let fx = FxRates::default();
        let inflation = calibrate_inflation(close, &discount, &swaps, &fixings, &fx, Tolerance::default(), LevelInterpolation::Linear, act365)?;
    
        // Filed under its index, the curve prices every swap back to nothing.
        let curve = inflation.curve.clone();
        let priced = market.with_inflation_curve("US-CPI-U", inflation);
        let level = |at: NaiveDate| curve.view::<f64>().index_level(at);
        for rung in &swaps {
            println!(
                "{}  breakeven {:.4}  level {:.4}  value {:.6}",
                rung.pillar,
                rung.quote,
                level(rung.pillar)?,
                rung.instrument.npv(&priced)?.abs()
            );
        }
    
        Ok(())
    }
    Python
    import datetime as dt
    
    import helixio as hlx
    from desk_sheets import SOFR_CLOSE_DATE, USD_SOFR_CLOSE
    
    # Two published prints of the index, and the discount curve from the sheet.
    prints = [("US-CPI-U", dt.date(2026, 6, 1), 324.10), ("US-CPI-U", dt.date(2026, 7, 1), 324.90)]
    market = hlx.MarketContext(SOFR_CLOSE_DATE).with_fixings(prints).with_recipe_quotes("USD", USD_SOFR_CLOSE)
    discount = market.curve("discount(USD)")
    
    # Zero-coupon inflation swaps at breakeven rates, each pillar the maturity read
    # three months back.
    swaps = []
    for years, breakeven in [(2, 0.0245), (5, 0.0238), (10, 0.0241)]:
        maturity = dt.date(2026 + years, 10, 1)
        swap = hlx.products.zero_coupon_inflation_swap(
            "USD", "US-CPI-U", SOFR_CLOSE_DATE, maturity, notional=1.0, fixed_rate=breakeven
        )
        swaps.append((hlx.dates.lagged_observation(maturity, 3), swap, breakeven))
    inflation = hlx.calibration.inflation(SOFR_CLOSE_DATE, discount, swaps, market=market)
    
    # Filed under its index, the curve prices every swap back to nothing.
    priced = market.with_inflation_curve("US-CPI-U", inflation.curve())
    for pillar, swap, breakeven in swaps:
        print(f"{pillar}  breakeven {breakeven:.4f}  level {inflation.curve().index_level(pillar):.4f}  value {abs(swap.npv(priced)):.6f}")
    2028-07-01  breakeven 0.0245  level 341.0377  value 0.000000
    2031-07-01  breakeven 0.0238  level 365.4713  value 0.000000
    2036-07-01  breakeven 0.0241  level 412.3428  value 0.000000

    Each pillar is the maturity read three months back, the index's lag, so a swap maturing in October 2028 pins the level for July 2028.

    How it's priced

    A zero-coupon inflation swap with base level I0I_0, breakeven kk and year fraction τ\tau is worth nothing when

    I(T)I0=(1+k)τ,\frac{I(T)}{I_0} = (1 + k)^{\tau},

    so each quote fixes one index level I(T)I(T) at its lagged pillar, and the curve reads levels between pillars linearly (or log-linearly, if you ask). Why the index is read with a lag is in how calibration works.

    API

    Symbol Meaning Rust Python
    rung a zero-coupon inflation swap at its breakeven ZeroCouponInflationSwap::new, Rung::par products.zero_coupon_inflation_swap
    lagged pillar the observation date of a maturity lagged_observation dates.lagged_observation
    I(T)I(T) the inflation solve calibrate_inflation calibration.inflation
    filed curve the curve under its index MarketContext::with_inflation_curve MarketContext.with_inflation_curve
    level the index level at a date InflationCurve::index_level curves.InflationCurve.index_level
    Argument Type Default Meaning
    reference date required the curve's reference date
    discount DiscountCurve or None required the nominal curve; may be None when every rung is zero-coupon
    rungs list[Rung] or list[tuple[date, Instrument, float]] required one swap per lagged pillar with its breakeven
    market MarketContext or None None where the base prints are read
    level_interpolation LevelInterpolation or str "linear" how levels are read between pillars: "linear" or "loglinear"
    day_count str or DayCountFraction "ACT/365.FIXED" the day count the curve measures time on
    tolerance Tolerance or None the defaults how hard the solve tries; see bootstrapping

    The result, a CalibratedInflation, has curve(), rungs(), solves() and jacobian(), how far each level moves per unit move in each breakeven.

    Defaults and limits

    • Base prints: the market must hold the index prints the swaps' base dates read; a missing print is refused with an error naming it.
    • Discount curve: a zero-coupon swap's one payment cancels its discount factor in the fair rate, so any nominal curve solves the same levels. A year-on-year rung needs the real curve.
    • Pillars: must rise strictly, each at a swap's lagged observation.

    See also

    • Source: crates/helixrs/src/calibration.rs, crates/helixrs/src/curves/inflation.rs
    • Related pages:
      • Inflation curves, for reading an inflation curve.
      • Inflation coupons, the coupons the curve projects.
      • Credit calibration, the same idea for survival.

    From the 0.0 line at revision bf7f2e2. Every printed number is that revision's own output.