|
From: Luigi B. <lui...@gm...> - 2024-08-28 15:08:21
|
Glad it worked out — and thanks, it was an interesting check. Luigi On Tue, Aug 27, 2024 at 12:32 PM Charles Allderman <ch...@al...> wrote: > I have found the source of the difference. The section below needs a > compounded annual rate and not a continuous rate. > > ql.ZeroCouponInflationSwapHelper( > ql.makeQuoteHandle(implied_infl_curve.zeroRate(maturity,ql. > Actual365Fixed(),ql.Compounded).rate()), # Continuous, Annual > observation_lag, > maturity, > calendar, > ql.Following, > day_counter, > index, > interpolation, > nominal_curve, > ) > > > > > > On Tue, Aug 27, 2024 at 10:11 AM Charles Allderman <ch...@al...> > wrote: > >> @Luigi Ballabio <lui...@gm...> posted this very >> informative piece, >> https://www.implementingquantlib.com/2024/05/inflation-curves.html >> on Inflation indexes and curves which has been of great help. >> >> What I am struggling with, is that when I price a CPI bond, I would >> expect the value of the bond priced off the real curve to equal the price >> of the CPI bond priced off the nominal curve with coupons increased by the >> forecasted inflation rate as defined by implied inflation being the >> difference between the nominal and real zero curves. >> >> I have investigated this here by adapting the Python code from >> https://www.implementingquantlib.com/2024/05/inflation-curves.html, by >> defining a Nominal and Real curve, with implied inflation being the >> geometric diff of the two curves. >> >> The problem is that I was expecting ql.CPI.laggedFixing(hicp, some_date, >> observation_lag, ql.CPI.Linear))/ ValueBase to be very close to 1.0/ >> implied_infl_curve.discount(some_date)) however this does not seem to be >> the case: >> >> >> node nom real imp_inf base_val inf diff >> May 11th, 2024 1.0000000 1.0000000 1.0000000 124.6732258 1.0000000 >> - >> May 12th, 2025 1.0620111 1.0298161 1.0312628 124.6732258 1.0308708 >> -0.0003802 >> May 11th, 2026 1.1274969 1.0607752 1.0628989 124.6732258 1.0620177 >> -0.0008291 >> May 11th, 2027 1.1972174 1.0930260 1.0953238 124.6732258 1.0939309 >> -0.0012716 >> May 11th, 2028 1.2714581 1.1266872 1.1284926 124.6732258 1.1264751 >> -0.0017878 >> May 11th, 2029 1.3500807 1.1619297 1.1619297 124.6732258 1.1594619 >> -0.0021240 >> May 12th, 2031 1.5224620 1.2390781 1.2287054 124.6732258 1.2251594 >> -0.0028860 >> May 11th, 2034 1.8227180 1.3739282 1.3266471 124.6732258 1.3215592 >> -0.0038352 >> May 12th, 2036 2.0557845 1.4762652 1.3925577 124.6732258 1.3863294 >> -0.0044726 >> May 11th, 2039 2.4608164 1.6392202 1.5012116 124.6732258 1.4932337 >> -0.0053143 >> May 11th, 2044 3.3228469 1.9504444 1.7036358 124.6732258 1.6918066 >> -0.0069435 >> May 11th, 2049 4.4861115 2.3106996 1.9414516 124.6732258 1.9248788 >> -0.0085363 >> May 11th, 2054 6.0566127 2.7320222 2.2168973 124.6732258 2.1941767 >> -0.0102488 >> May 12th, 2064 11.0431267 3.7591653 2.9376540 124.6732258 2.8960907 -0.0141484 >> >> May 11th, 2074 20.1251968 5.0281959 4.0024687 124.6732258 3.9279265 -0.0186241 >> >> >> To replicate please see the Python below: >> >> import QuantLib as ql >> import pandas as pd >> today = ql.Date(11, ql.May, 2024) >> ql.Settings.instance().evaluationDate = today >> >> >> index = ql.EUHICP() >> >> 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: >> index.addFixing(ql.Date(1, month, year), fixing) >> >> index.fixing(ql.Date(15, ql.March, 2024)) >> >> >> observation_lag = ql.Period(3, ql.Months) >> >> ql.CPI.laggedFixing( >> index, ql.Date(15, ql.May, 2024), observation_lag, ql.CPI.Linear >> ) >> >> real_quotes = [ >> (ql.Period(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), >> ] >> >> calendar = ql.TARGET() >> observation_lag = ql.Period(3, ql.Months) >> day_counter = ql.Actual365Fixed() >> interpolation = ql.CPI.Linear >> >> nominal_curve = ql.YieldTermStructureHandle( >> ql.FlatForward(today, 0.06, ql.Actual365Fixed()) # Continuous, Annual >> ) >> >> >> rates,dates = [],[] >> for tenor, quote in real_quotes: >> maturity = calendar.advance(today, tenor) >> rates.append(quote/100.0) >> dates.append(maturity) >> >> real_curve = ql.ZeroCurve([today] + dates,[0.0] + rates,day_counter) # >> Continuous, Annual >> implied_infl_curve = ql.DiscountCurve([today] + dates,[1.0]+[ >> nominal_curve.discount(d)/real_curve.discount(d) for d in dates],ql. >> Actual365Fixed()) >> >> helpers = [] >> >> for tenor, quote in real_quotes: >> maturity = calendar.advance(today, tenor) >> helpers.append( >> ql.ZeroCouponInflationSwapHelper( >> ql.makeQuoteHandle(implied_infl_curve.zeroRate(maturity,ql. >> Actual365Fixed(),ql.Continuous).rate()), # Continuous, Annual >> observation_lag, >> maturity, >> calendar, >> ql.Following, >> day_counter, >> index, >> interpolation, >> nominal_curve, >> ) >> ) >> >> ql.Date(1,1,2024) >> fixing_frequency = ql.Monthly >> >> inflation_curve = ql.PiecewiseZeroInflation( >> today,index.lastFixingDate(), fixing_frequency, ql.Actual365Fixed(), >> helpers >> ) >> hicp = ql.EUHICP(ql.ZeroInflationTermStructureHandle(inflation_curve)) >> hicp.fixing(ql.Date(2, ql.February, 2074)) >> hicp.fixing(ql.Date(11, ql.May, 2024)) >> >> pd.DataFrame(inflation_curve.nodes(), columns=["node", "rate"]).style. >> format( >> {"rate": "{:.4%}"} >> ) >> >> df = pd.DataFrame(real_curve.nodes(), columns=["node", "yreal"]) >> df['nom'] = df.node.map(lambda x: 1.0/nominal_curve.discount(x)) >> df['real'] = df.node.map(lambda x: 1.0/real_curve.discount(x)) >> df['imp_inf'] = df.node.map(lambda x: 1.0/implied_infl_curve.discount(x)) >> df['base_val'] = df.node.map(lambda x: ql.CPI.laggedFixing(index, df.loc[ >> 0,'node'], observation_lag, ql.CPI.Linear)) >> df['inf'] = df.node.map(lambda x: ql.CPI.laggedFixing(hicp, x, >> observation_lag, ql.CPI.Linear)) >> df['inf'] = df.inf/df.base_val >> df['diff'] = df.inf/df.imp_inf-1 >> df >> >> Am I on the right track or am I missing something? Any feedback would be >> much appreciated. >> >> |