Skip to content

CPI cap/floor quoted-price interpolation

CpiCapFloor is a single-payment option on cumulative inflation, not a cap or floor on each coupon. It is available in Rust, C, Go and Python together with CpiCapFloorTermPriceSurface and InterpolatingCpiCapFloorEngine.

Supported contract

Area Behavior
Market input Separate cap/floor annual-quoted strikes and price matrices
Matrix layout Strike rows, maturity columns
Maturities At least two increasing positive whole-year tenors
Interpolation Bilinear in time and annual-quoted strike
Extrapolation Always enabled, extending boundary cells; negative values are possible
Observation Flat or Linear CPI interpolation; nonzero lag uses Months or Years
Pricing Price-surface lookup, not a Black/Bachelier volatility model
Expiry Evaluation date strictly after the unadjusted contract maturity

Cap and floor prices must be positive and finite at input. Cap prices cannot increase with strike; floor prices cannot decrease. Each input strike grid must increase and have at least two strikes. The native strike-union convention requires more than two combined strikes: floor strikes followed by cap strikes strictly above the largest floor strike (with the native 1e-7 separation). Overlapping quoted strikes retain their respective cap and floor quotes.

Price units are important

The engine returns the surface quote directly. It does not multiply by the instrument nominal, base CPI or contract start date. The surface nominal is also descriptive metadata, not a multiplier in cap/floor parity completion. These are inherited QuantLib engine conventions, not an assertion that changing notional leaves an economic option's value unchanged. Supply quotes in the required units and scale externally if appropriate for the application.

Missing cap or floor cells are completed using native put/call parity and the nominal discount curve plus the zero-inflation index's ATM inflation rate. This uses compounded annual quoted strikes, not a direct CPI-ratio strike.

Immutable snapshot

The surface captures its reference date, parity-completed grids and time grid at construction. Rebuild it after evaluation-date, fixing or curve changes. It retains no unused live pricing graph and exposes no dynamic ATM selector. An engine retains its surface; an instrument retains its engine, index, calendars and explicit settings. Releasing the corresponding C/Go/Python wrapper does not invalidate these retained dependencies.

This intentionally differs from native evaluation-date rolling: the pinned QuantLib surface constructs its parity grids once, but moves its reference date without rebuilding those grids. Our surface freezes both to avoid mixing stale grids and a new time origin. Instrument expiry still follows its live settings. Rebuild instruments when changing calendar conventions or holiday definitions.

Engine timing

The instrument observation lag must be no shorter than the surface lag. Their normalized month difference shifts the adjusted payment date backwards to obtain the effective maturity. Flat observation queries the start of its inflation period. Linear observation interpolates prices at that period's start and the next period's start using the effective maturity's day weight.

The index availability lag must be covered: Flat allows equality, while Linear requires a strictly longer observation lag. Deprecated AsIndex is not exposed. Mixed settings, incompatible lag units, invalid dates, non-finite values and unavailable calendar years return ordinary errors. C outputs are unchanged when an operation fails. The usual C pointer-validity and Go session rules apply.

Python example

from itofin.instruments import CpiCapFloor, OptionType
from itofin.pricingengines import InterpolatingCpiCapFloorEngine
from itofin.termstructures import CpiCapFloorTermPriceSurface

# index, nominal discount curve, calendars, day counter and explicit settings
# are existing objects. Matrices are quoted prices, not volatilities.
surface = CpiCapFloorTermPriceSurface(
    nominal=100000.0, base_rate=0.025, observation_lag=lag,
    calendar=calendar, convention=convention, day_counter=day_counter,
    index=index, interpolation=interpolation, discount=discount,
    cap_strikes=[0.03, 0.04], floor_strikes=[0.01, 0.02],
    maturity_years=[1, 3, 5],
    cap_prices=[[0.08, 0.15, 0.24], [0.04, 0.10, 0.16]],
    floor_prices=[[0.02, 0.04, 0.06], [0.035, 0.065, 0.095]],
    settings=settings,
)
engine = InterpolatingCpiCapFloorEngine(surface)
option = CpiCapFloor(
    OptionType.Call, 100000.0, start, 100.0, maturity,
    calendar, convention, calendar, convention, 0.025,
    index, lag, interpolation, settings, engine,
)
quote_value = option.npv()

Go uses Session.NewCpiCapFloorTermPriceSurface(CpiPriceSurfaceConfig{...}), NewInterpolatingCpiCapFloorEngine, and NewCpiCapFloor(CpiCapFloorConfig{...}). Close objects and sessions explicitly. Calls from different sessions are rejected. C uses itofin_cpi_price_surface_* and itofin_cpi_capfloor_* with typed handles.

Numerical evidence and limits

The tracked native fixture is sdk/go/testdata/cpi-capfloor-native.tsv with a matching Python fixture. Its generator is scripts/generate_cpi_capfloor_oracle.py and the adjacent C++ source. Provenance is in sdk/go/testdata/cpi-capfloor-native.provenance.json.

The harness combines the protected QuantLib 1.43 wheel's exported native symbols with surface-template code compiled from source revision 9863b578af0caa4cecabf697196533e84a8308b6. Three independent ABI sanity values match the wheel's Python queries. This is mixed source/binary provenance, not a claim that the wheel was built from that revision.

Fixtures include quoted/parity cells, off-grid interpolation, extrapolation, Flat/Linear timing, differing lags, ignored nominal/base/start metadata, immutable old grids versus rebuilt market data, and raw-maturity expiry. Native month-tenor exponent behavior and native moving-reference behavior are recorded as unsupported/divergent, not silently repaired or counted as parity. There is no CPI volatility stripping, calibration, coupon cap/floor engine, AsIndex, nonyear-tenor support or full QuantLib class parity in this slice.