Esc

    The API is under testing and may change before release.

    Inflation swaps

    This page lists what you need to value zero-coupon and year-on-year inflation swaps: the booking calls and their arguments, the payoffs, and the defaults you get. An inflation swap exchanges a fixed rate for the growth of a price index.

    Quick example

    The example reads its quotes from the desk sheets; load them once and it runs as shown.

    This values a five-year zero-coupon swap on US CPI paying 2.00% fixed, reads its breakeven and the five-year year-on-year par rate, and sets a one-year year-on-year swap beside a one-year zero-coupon swap.

    Rust
    use chrono::NaiveDate;
    use helixio_docs_sheets::{SOFR_CLOSE_DATE, USD_SOFR_CLOSE};
    use helixrs::{
        AccrualCompounding, AnyInstrument, CollateralSpec, Inflation, MarketContext,
        PiecewiseInflation, QuoteSheet, YoyInflationSwap, ZeroCouponInflationSwap,
    };
    
    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")
    }
    
    const LEVELS: [((i32, u32, u32), f64); 6] = [
        ((2026, 7, 1), 321.0),
        ((2027, 7, 1), 329.0),
        ((2028, 7, 1), 337.2),
        ((2029, 7, 1), 345.5),
        ((2031, 7, 1), 362.6),
        ((2036, 7, 1), 407.0),
    ];
    
    fn cpi_curve(close: NaiveDate) -> Outcome<Inflation> {
        let nodes: Vec<(NaiveDate, f64)> = LEVELS.iter().map(|(d, level)| (date(*d), *level)).collect();
        Ok(Inflation::Piecewise(PiecewiseInflation::new(
            close,
            &nodes,
            "linear".parse()?,
            "ACT/365.FIXED".parse()?,
        )?))
    }
    
    fn zero_coupon(
        close: NaiveDate,
        rate: f64,
        maturity: NaiveDate,
        receiver: bool,
    ) -> Outcome<AnyInstrument> {
        let mut swap = ZeroCouponInflationSwap::new(
            "USD".parse()?,
            "US-CPI-U",
            close,
            maturity,
            10_000_000.0,
            rate,
            3,
        )
        .accruing("ACT/365.FIXED".parse()?, AccrualCompounding::Compounded);
        if receiver {
            swap = swap.receiver();
        }
        Ok(AnyInstrument::ZcInflationSwap(Box::new(swap)))
    }
    
    fn year_on_year(close: NaiveDate, rate: f64, maturity: NaiveDate) -> Outcome<AnyInstrument> {
        let swap = YoyInflationSwap::new(
            "USD".parse()?,
            "US-CPI-U",
            close,
            maturity,
            10_000_000.0,
            rate,
            3,
        )
        .accruing("ACT/365.FIXED".parse()?);
        Ok(AnyInstrument::YoyInflationSwap(Box::new(swap)))
    }
    
    fn main() -> Outcome<()> {
        let close = date(SOFR_CLOSE_DATE);
        let market = MarketContext::as_of(close)
            .with_recipe_quotes(
                &CollateralSpec::Currency("USD".parse()?),
                &QuoteSheet::from_rows(USD_SOFR_CLOSE.iter().copied())?,
            )?
            .with_inflation_curve("US-CPI-U", cpi_curve(close)?);
        let (five_years, one_year) = (date((2031, 10, 1)), date((2027, 10, 1)));
    
        // Pay a fixed rate compounded over five years, receive CPI growth.
        let breakeven = zero_coupon(close, 0.0, five_years, false)?.par_rate(&market)?;
        let payer = zero_coupon(close, 0.0200, five_years, false)?.npv(&market)?;
        println!("5Y breakeven        {breakeven:.8}");
        println!("pay 2.00% fixed     {payer:.2}");
    
        // The year-on-year swap pays each year's growth against a fixed coupon.
        let yoy = year_on_year(close, 0.0, five_years)?.par_rate(&market)?;
        println!("5Y year-on-year par {yoy:.8}");
        // Over one year the two shapes are one payment of the same growth.
        let one_yoy = year_on_year(close, 0.0250, one_year)?.npv(&market)?;
        let one_zc = zero_coupon(close, 0.0250, one_year, false)?.npv(&market)?;
        println!("1Y yoy npv          {one_yoy:.2}");
        println!("1Y zero-coupon npv  {one_zc:.2}");
    
        Ok(())
    }
    Python
    import datetime as dt
    
    import helixio as hlx
    from desk_sheets import SOFR_CLOSE_DATE, USD_SOFR_CLOSE
    
    LEVELS = [
        (dt.date(2026, 7, 1), 321.0),
        (dt.date(2027, 7, 1), 329.0),
        (dt.date(2028, 7, 1), 337.2),
        (dt.date(2029, 7, 1), 345.5),
        (dt.date(2031, 7, 1), 362.6),
        (dt.date(2036, 7, 1), 407.0),
    ]
    market = (
        hlx.MarketContext(SOFR_CLOSE_DATE)
        .with_recipe_quotes("USD", USD_SOFR_CLOSE)
        .with_inflation_curve("US-CPI-U", hlx.curves.InflationCurve(SOFR_CLOSE_DATE, LEVELS))
    )
    FIVE_YEARS, ONE_YEAR = dt.date(2031, 10, 1), dt.date(2027, 10, 1)
    
    def zero_coupon(rate, maturity=FIVE_YEARS, receiver=False):
        return hlx.products.zero_coupon_inflation_swap(
            "USD", "US-CPI-U", SOFR_CLOSE_DATE, maturity, notional=10_000_000.0, fixed_rate=rate,
            receiver=receiver,
        )
    
    def year_on_year(rate, maturity=FIVE_YEARS):
        return hlx.products.yoy_inflation_swap(
            "USD", "US-CPI-U", SOFR_CLOSE_DATE, maturity, notional=10_000_000.0, fixed_rate=rate
        )
    
    # Pay a fixed rate compounded over five years, receive CPI growth.
    breakeven = zero_coupon(0.0).par_rate(market)
    payer = zero_coupon(0.0200).npv(market)
    print(f"5Y breakeven        {breakeven:.8f}")
    print(f"pay 2.00% fixed     {payer:.2f}")
    
    # The year-on-year swap pays each year's growth against a fixed coupon.
    yoy = year_on_year(0.0).par_rate(market)
    print(f"5Y year-on-year par {yoy:.8f}")
    # Over one year the two shapes are one payment of the same growth.
    one_yoy = year_on_year(0.0250, ONE_YEAR).npv(market)
    one_zc = zero_coupon(0.0250, ONE_YEAR).npv(market)
    print(f"1Y yoy npv          {one_yoy:.2f}")
    print(f"1Y zero-coupon npv  {one_zc:.2f}")
    5Y breakeven        0.02465758
    pay 2.00% fixed     201511.31
    5Y year-on-year par 0.02468764
    1Y yoy npv          -745.67
    1Y zero-coupon npv  -745.67

    Over a single year the two shapes pay the same growth once, so they agree.

    How it's priced

    With I(t)I(t) the index level, the zero-coupon swap pays at maturity TT, observed with lag ℓ\ell,

    N(I(T−ℓ)I(T0−ℓ)−(1+K)τ)N\left(\frac{I(T - \ell)}{I(T_0 - \ell)} - (1 + K)^{\tau}\right)

    to the side receiving inflation. The breakeven K∗K^\ast makes it worth nothing. A year-on-year swap pays Nτi(I(ti)/I(ti−1)−1−K)N\tau_i\left(I(t_i)/I(t_{i-1}) - 1 - K\right) each year. Why the zero-coupon swap is the benchmark, and why the index is observed with a lag, is covered in how rates products work.

    API

    Symbol Meaning Rust Python
    I(t)I(t) the index level curve PiecewiseInflation::new, MarketContext::with_inflation_curve curves.InflationCurve, MarketContext.with_inflation_curve
    zero-coupon the swap ZeroCouponInflationSwap::new products.zero_coupon_inflation_swap
    year-on-year the swap YoyInflationSwap::new products.yoy_inflation_swap
    K∗K^\ast the breakeven AnyInstrument::par_rate Instrument.par_rate

    products.zero_coupon_inflation_swap and products.yoy_inflation_swap take:

    Argument Type Default Meaning
    currency str required the currency both legs pay in
    index str required the price index, such as "US-CPI-U"
    base_date date required the start; the index is observed lag_months before it
    maturity date required the end; the last observation is lagged the same way
    notional float required the notional
    fixed_rate float required KK, the fixed rate
    lag_months int 3 the observation lag ℓ\ell, in months
    receives str or None "inflation" the leg you receive: "inflation" or "fixed"
    receiver bool or None None the older spelling of receives="fixed"; don't give both
    day_count str or DayCountFraction "ACT/365.FIXED" the fixed side's accrual
    accrual_compounding str "Compounded" zero-coupon only: how KK accrues over the life
    base_index float or None None zero-coupon only: an agreed base index level in place of the observed one
    index_interpolation str or None None zero-coupon only: "Month" or "Interpolated", where it differs from the index's own rule

    In Rust, ZeroCouponInflationSwap::new and YoyInflationSwap::new take currency, index, base_date, maturity, notional, fixed_rate, lag_months positionally.

    Defaults and limits

    • The curve: the index level curve is added to the market with its own verb, with_inflation_curve; see inflation curves.
    • Coupons: each swap is made of inflation coupons, built and read on inflation coupons.

    See also

    • Source: crates/helixrs/src/instruments/rates.rs
    • Related pages:
      • Inflation curves, for the index levels.
      • Inflation caps and floors, for options on the growth.
      • Inflation-linked bonds, for the cash instrument.

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