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.
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(())
}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 at dated nodes. Between two nodes, with the fraction of the time between them elapsed by , it reads either linearly in the level or linearly in its logarithm:
Twelve monthly factors reshape the level relative to the first node's month :
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 |
|---|---|---|---|
| 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 |
| seasonality | PiecewiseInflation::with_seasonality |
seasonality= |
|
| 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.