Esc

    The API is under testing and may change before release.

    Inflation curves

    This page lists what you need to build an inflation curve, a price index's level by date: the constructor's arguments, the two ways it reads between nodes, how seasonality reshapes it, and how it is filed in a market under its index.

    Quick example

    This builds a curve from two nodes, reads it linearly and log-linearly, adds seasonality, and files it in a market under its index. The example reads its quotes from the desk sheets; load them once and it runs as shown.

    Rust
    use chrono::NaiveDate;
    use helixio_docs_sheets::SOFR_CLOSE_DATE;
    use helixrs::{
        DayCountFraction, Inflation, InflationCurve, LevelInterpolation, MarketContext,
        PiecewiseInflation,
    };
    
    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 reference = date(SOFR_CLOSE_DATE);
        let nodes = [(date((2026, 8, 1)), 320.0), (date((2031, 8, 1)), 355.0)];
        let (feb, aug) = (date((2029, 2, 1)), date((2029, 8, 1)));
        let act365 = DayCountFraction::Act365Fixed;
        let level = |curve: &PiecewiseInflation, at: NaiveDate| curve.view::<f64>().index_level(at);
    
        // Two nodes: the last published level and a five-year forecast. Linear in the
        // level by default; log-linear grows geometrically between them.
        let linear = PiecewiseInflation::new(reference, &nodes, LevelInterpolation::Linear, act365)?;
        let geometric = PiecewiseInflation::new(reference, &nodes, LevelInterpolation::LogLinear, act365)?;
        println!("linear    Feb 2029  {:.6}", level(&linear, feb)?);
        println!("loglinear Feb 2029  {:.6}", level(&geometric, feb)?);
    
        // Prices run higher in spring and lower in winter. The factors are relative to
        // the anchor's month, August, so August reads unchanged and February is scaled
        // by February's factor over August's.
        let factors = [0.998, 1.000, 1.002, 1.003, 1.002, 1.001, 1.000, 0.999, 0.999, 0.998, 0.998, 0.999];
        let seasonal = linear.clone().with_seasonality(factors);
        println!("seasonal  Feb 2029  {:.6}", level(&seasonal, feb)?);
        println!("seasonal  Aug 2029  {:.6}", level(&seasonal, aug)?);
        println!("linear    Aug 2029  {:.6}", level(&linear, aug)?);
    
        // A market files the curve under its index.
        let market = MarketContext::as_of(reference).with_inflation_curve("USD-CPI-U", Inflation::Piecewise(linear.clone()));
        let (role, key) = &market.contents()[0];
        println!("contents  {role} {key}");
    
        Ok(())
    }
    Python
    import datetime as dt
    
    import helixio as hlx
    from desk_sheets import SOFR_CLOSE_DATE
    
    REFERENCE = SOFR_CLOSE_DATE
    NODES = [(dt.date(2026, 8, 1), 320.0), (dt.date(2031, 8, 1), 355.0)]
    FEB, AUG = dt.date(2029, 2, 1), dt.date(2029, 8, 1)
    
    # Two nodes: the last published level and a five-year forecast. Linear in the
    # level by default; log-linear grows geometrically between them.
    linear = hlx.curves.InflationCurve(REFERENCE, NODES)
    geometric = hlx.curves.InflationCurve(REFERENCE, NODES, level_interpolation="loglinear")
    print(f"linear    Feb 2029  {linear.index_level(FEB):.6f}")
    print(f"loglinear Feb 2029  {geometric.index_level(FEB):.6f}")
    
    # Prices run higher in spring and lower in winter. The factors are relative to
    # the anchor's month, August, so August reads unchanged and February is scaled
    # by February's factor over August's.
    FACTORS = [0.998, 1.000, 1.002, 1.003, 1.002, 1.001, 1.000, 0.999, 0.999, 0.998, 0.998, 0.999]
    seasonal = hlx.curves.InflationCurve(REFERENCE, NODES, seasonality=FACTORS)
    print(f"seasonal  Feb 2029  {seasonal.index_level(FEB):.6f}")
    print(f"seasonal  Aug 2029  {seasonal.index_level(AUG):.6f}")
    print(f"linear    Aug 2029  {linear.index_level(AUG):.6f}")
    
    # A market files the curve under its index.
    market = hlx.MarketContext(REFERENCE).with_inflation_curve("USD-CPI-U", linear)
    print(f"contents  {market.contents()[0][0]} {market.contents()[0][1]}")
    linear    Feb 2029  337.538335
    loglinear Feb 2029  337.084311
    seasonal  Feb 2029  337.876211
    seasonal  Aug 2029  341.007667
    linear    Aug 2029  341.007667
    contents  inflation USD-CPI-U

    How it's priced

    A curve holds levels I(Ti)I(T_i) at dated nodes. Between two nodes, with ww the fraction of the time between them elapsed by tt, it reads either linearly in the level or linearly in its logarithm:

    Ilin(t)=(1−w) I(Ti)+w I(Ti+1),Ilog(t)=I(Ti) 1−w I(Ti+1) w.I_{\text{lin}}(t) = (1 - w)\, I(T_i) + w\, I(T_{i+1}), \qquad I_{\text{log}}(t) = I(T_i)^{\,1 - w}\, I(T_{i+1})^{\,w}.

    Twelve monthly factors σ1,…,σ12\sigma_1, \dots, \sigma_{12} reshape the level relative to the first node's month m0m_0:

    Iseasonal(t)=I(t) σm(t)σm0,I_{\text{seasonal}}(t) = I(t)\, \frac{\sigma_{m(t)}}{\sigma_{m_0}},

    so a date in the anchor's month reads unchanged, and only the factors' relative shape matters. Why prices move by month, and how a desk forecasts the index, is covered in how curves work.

    API

    Symbol Meaning Rust Python
    I(Ti)I(T_i) levels at nodes PiecewiseInflation::new with a LevelInterpolation curves.InflationCurve(reference, nodes)
    lin / log the reading between nodes LevelInterpolation::Linear, LevelInterpolation::LogLinear level_interpolation= with curves.LevelInterpolation.Linear or curves.LevelInterpolation.LogLinear
    σm\sigma_m seasonality PiecewiseInflation::with_seasonality seasonality=
    I(t)I(t) the level to a date InflationCurve::index_level on the curve's view curves.InflationCurve.index_level
    — filed under an index MarketContext::with_inflation_curve, read with MarketContext::inflation_curve MarketContext.with_inflation_curve, read with MarketContext.inflation_curve

    curves.InflationCurve(...):

    Argument Type Default Meaning
    reference date required the curve's reference date
    nodes list[tuple[date, float]] required (date, index level) pairs; at least one
    level_interpolation LevelInterpolation or str "linear" linear in the level, or "loglinear" in its logarithm
    day_count str or DayCountFraction "ACT/365.FIXED" the day count time between nodes is measured on
    seasonality list[float] or None None twelve monthly factors; in Rust, .with_seasonality(...)
    seasonality_base_month int 1 the calendar month the first factor belongs to

    Defaults and limits

    • Outside the nodes: the level is flat beyond either end, before seasonality is applied. A date before the reference is read from the same nodes, flat before the first, not refused, because a lagged observation is in the past by construction.
    • Seasonality is normalised by the first node's month, so that node's level reads unchanged.
    • Solved from swaps: a curve solved from zero-coupon inflation swaps is shown in inflation calibration.

    See also

    • Source: crates/helixrs/src/curves/inflation.rs
    • Related pages:
      • Inflation coupons, for coupons that read the curve.
      • Inflation calibration, for a curve solved from swaps.

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