import QuantLib as ql
import pandas as pd
Inflation indexes and curves
= ql.Date(11, ql.May, 2024)
today = today ql.Settings.instance().evaluationDate
Inflation indexes
The library provides classes to model some predefined inflation indexes, such as EUHICP
or UKRPI
; any missing one can be defined using the base class ZeroInflationIndex
.
= ql.EUHICP() index
Historical fixings can be saved (for all instances of the same index) by calling the addFixing
method.
The method is inherited from the base Index
class, so it requires a specific date even if an inflation fixing corresponds to a whole month. By convention, the date we’ll pass together with a fixing must be the first day of the corresponding month (not the day of publishing, which would be in the following month).
= [
inflation_fixings 2022, ql.January), 110.70),
((2022, ql.February), 111.74),
((2022, ql.March), 114.46),
((2022, ql.April), 115.11),
((2022, ql.May), 116.07),
((2022, ql.June), 117.01),
((2022, ql.July), 117.14),
((2022, ql.August), 117.85),
((2022, ql.September), 119.26),
((2022, ql.October), 121.03),
((2022, ql.November), 120.95),
((2022, ql.December), 120.52),
((2023, ql.January), 120.27),
((2023, ql.February), 121.24),
((2023, ql.March), 122.34),
((2023, ql.April), 123.12),
((2023, ql.May), 123.15),
((2023, ql.June), 123.47),
((2023, ql.July), 123.36),
((2023, ql.August), 124.03),
((2023, ql.September), 124.43),
((2023, ql.October), 124.54),
((2023, ql.November), 123.85),
((2023, ql.December), 124.05),
((2024, ql.January), 123.60),
((2024, ql.February), 124.37),
((2024, ql.March), 125.31),
((2024, ql.April), 126.05),
((
]
for (year, month), fixing in inflation_fixings:
1, month, year), fixing) index.addFixing(ql.Date(
Asking for a fixing for any past date will return the fixing for the month:
15, ql.March, 2024)) index.fixing(ql.Date(
125.31
Of course, some past dates still don’t have an inflation fixing available: at the time of this writing, in the middle of May 2024, the index for this month is not published yet. Fixings for these dates, as well as for future dates, will need to be forecast: and for that, we require an inflation term structure.
Another note: in the past, the index could also return interpolated fixings; however, the result was not always right, since the index didn’t have the correct information on the number of days to use while interpolating. In version 1.29, the interpolation logic was moved into inflation coupons, where the information is available; the corresponding logic in the index was deprecated in the same release and removed in version 1.34.
The logic behind the interpolation is also available as a static method in the CPI
class, used by inflation coupons:
= ql.Period(3, ql.Months)
observation_lag
ql.CPI.laggedFixing(15, ql.May, 2024), observation_lag, ql.CPI.Linear
index, ql.Date( )
124.79451612903226
For instance, the call above interpolates linearly a fixing for May 15th, 2024 with an observation lag of three months. This means that the fixings to be interpolated will be those for February and March 2024, but their weights in the interpolation will be based on the number of days between May 1st and May 15th and between May 15th and June 1st.
Inflation curves
As for other kinds of term structures, it’s possible to create inflation curves by bootstrapping over a number of quoted instruments; at this time, the library provides helpers for zero-coupon inflation swaps. The information needed to build them includes, besides more common data such as the calendar and day count convention, an observation lag for the fixing of the underlying inflation index and the interpolation to be used between fixings.
The helpers will also need an external nominal curve of interest rates. This used to be stored into the inflation curve itself, but for consistency with other helpers and term structures it was moved into the helpers in version 1.15.
= [
inflation_quotes 1, ql.Years), 2.93),
(ql.Period(2, ql.Years), 2.95),
(ql.Period(3, ql.Years), 2.965),
(ql.Period(4, ql.Years), 2.98),
(ql.Period(5, ql.Years), 3.0),
(ql.Period(7, ql.Years), 3.06),
(ql.Period(10, ql.Years), 3.175),
(ql.Period(12, ql.Years), 3.243),
(ql.Period(15, ql.Years), 3.293),
(ql.Period(20, ql.Years), 3.338),
(ql.Period(25, ql.Years), 3.348),
(ql.Period(30, ql.Years), 3.348),
(ql.Period(40, ql.Years), 3.308),
(ql.Period(50, ql.Years), 3.228),
(ql.Period(
]
= ql.TARGET()
calendar = ql.Period(3, ql.Months)
observation_lag = ql.Thirty360(ql.Thirty360.BondBasis)
day_counter = ql.CPI.Linear
interpolation
= ql.YieldTermStructureHandle(
nominal_curve 0.03, ql.Actual365Fixed())
ql.FlatForward(today,
)
= []
helpers
for tenor, quote in inflation_quotes:
= calendar.advance(today, tenor)
maturity
helpers.append(
ql.ZeroCouponInflationSwapHelper(/ 100),
ql.makeQuoteHandle(quote
observation_lag,
maturity,
calendar,
ql.Following,
day_counter,
index,
interpolation,
nominal_curve,
) )
Now, inflation curves are kind of odd compared to other curves. Usually, term structures in QuantLib (e.g. for interest rates or default probabilities) forecast their underlying quantities starting from today; yesterday’s rates are assumed to be known, and so is whether an issuer defaulted. As I mentioned, though, inflation curves can also be used to forecast still unpublished fixings corresponding to past dates. Therefore, they have a so-called base date in the past which separates known and unknown fixings.
Up to version 1.33, though, the base date was not specified explicitly. Instead, the constructors of most inflation curves required an observation lag, probably because of some past confusion with the observation lag of the instruments used for bootstrapping; the base date was calculated by starting from today’s date, subtracting the lag and taking the first date of the resulting month.
However, the only base date that makes sense is the date of the last available inflation fixing: the fixings are known up to that point and need to be forecast after it. Therefore, the observation lag could not be a constant attribute of a given curve, but had to be calculated based on the available data: at the beginning of May, when the April fixing was not published yet, we needed a lag of two months to get to March, while mid-May we had to switch to a lag of one month to get an April base date as soon as the corresponding fixing was published. The curve also needed a base rate to be used for \(t=0\); in the current implementation, the base rate is still required when using the old constructor but is ignored by the bootstrapping process.
= ql.Period(1, ql.Months)
availability_lag = ql.Monthly
fixing_frequency = 0.029
base_rate
= ql.PiecewiseZeroInflation(
inflation_curve
today,
calendar,
ql.Actual365Fixed(),
availability_lag,
fixing_frequency,
base_rate,
helpers, )
pd.DataFrame(=["node", "rate"]
inflation_curve.nodes(), columnsformat({"rate": "{:.4%}"}) ).style.
node | rate | |
---|---|---|
0 | April 1st, 2024 | 2.0985% |
1 | March 1st, 2025 | 2.0985% |
2 | March 1st, 2026 | 2.5889% |
3 | March 1st, 2027 | 2.7210% |
4 | March 1st, 2028 | 2.7980% |
5 | March 1st, 2029 | 2.8545% |
6 | March 1st, 2031 | 2.9586% |
7 | March 1st, 2034 | 3.1057% |
8 | March 1st, 2036 | 3.1858% |
9 | March 1st, 2039 | 3.2467% |
10 | March 1st, 2044 | 3.3029% |
11 | March 1st, 2049 | 3.3190% |
12 | March 1st, 2054 | 3.3235% |
13 | March 1st, 2064 | 3.2888% |
14 | March 1st, 2074 | 3.2117% |
Using the curve, an inflation index can forecast future fixings:
= ql.EUHICP(ql.ZeroInflationTermStructureHandle(inflation_curve)) hicp
1, ql.February, 2027)) hicp.fixing(ql.Date(
135.99222083993126
Starting with version 1.34, though, it’s finally possible to specify the base date explicitly; in order to determine it, we also added a lastFixingDate
method to inflation indexes for convenience.
= ql.PiecewiseZeroInflation(
inflation_curve
today,
hicp.lastFixingDate(),
fixing_frequency,
ql.Actual365Fixed(),
helpers, )
pd.DataFrame(=["node", "rate"]
inflation_curve.nodes(), columnsformat({"rate": "{:.4%}"}) ).style.
node | rate | |
---|---|---|
0 | April 1st, 2024 | 2.0985% |
1 | March 1st, 2025 | 2.0985% |
2 | March 1st, 2026 | 2.5889% |
3 | March 1st, 2027 | 2.7210% |
4 | March 1st, 2028 | 2.7980% |
5 | March 1st, 2029 | 2.8545% |
6 | March 1st, 2031 | 2.9586% |
7 | March 1st, 2034 | 3.1057% |
8 | March 1st, 2036 | 3.1858% |
9 | March 1st, 2039 | 3.2467% |
10 | March 1st, 2044 | 3.3029% |
11 | March 1st, 2049 | 3.3190% |
12 | March 1st, 2054 | 3.3235% |
13 | March 1st, 2064 | 3.2888% |
14 | March 1st, 2074 | 3.2117% |
As you can see above, the result of the bootstrapping process is the same, and so are the fixings forecast by the index:
= ql.EUHICP(ql.ZeroInflationTermStructureHandle(inflation_curve)) hicp
1, ql.February, 2027)) hicp.fixing(ql.Date(
135.99222083993126
The old constructors for the various inflation curves available in the library are now deprecated and will be removed in QuantLib 1.39.