[go: up one dir, main page]

1/* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2
3/*
4 Copyright (C) 2011 Chris Kenyon
5 Copyright (C) 2012 StatPro Italia srl
6
7 This file is part of QuantLib, a free-software/open-source library
8 for financial quantitative analysts and developers - http://quantlib.org/
9
10 QuantLib is free software: you can redistribute it and/or modify it
11 under the terms of the QuantLib license. You should have received a
12 copy of the license along with this program; if not, please email
13 <quantlib-dev@lists.sf.net>. The license is also available online at
14 <http://quantlib.org/license.shtml>.
15
16 This program is distributed in the hope that it will be useful, but WITHOUT
17 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
18 FOR A PARTICULAR PURPOSE. See the license for more details.
19*/
20
21#include "inflationcpibond.hpp"
22#include "utilities.hpp"
23#include <ql/indexes/inflation/ukrpi.hpp>
24#include <ql/time/calendars/unitedkingdom.hpp>
25#include <ql/time/daycounters/actualactual.hpp>
26#include <ql/time/daycounters/actual365fixed.hpp>
27#include <ql/termstructures/yield/flatforward.hpp>
28#include <ql/indexes/ibor/gbplibor.hpp>
29#include <ql/termstructures/inflation/inflationhelpers.hpp>
30#include <ql/termstructures/inflation/piecewisezeroinflationcurve.hpp>
31#include <ql/cashflows/indexedcashflow.hpp>
32#include <ql/pricingengines/swap/discountingswapengine.hpp>
33#include <ql/instruments/zerocouponinflationswap.hpp>
34#include <ql/pricingengines/bond/discountingbondengine.hpp>
35#include <ql/cashflows/cpicoupon.hpp>
36#include <ql/cashflows/cpicouponpricer.hpp>
37#include <ql/instruments/cpiswap.hpp>
38#include <ql/instruments/bonds/cpibond.hpp>
39#include <ql/cashflows/cashflows.hpp>
40
41using namespace QuantLib;
42using namespace boost::unit_test_framework;
43
44#include <iostream>
45
46namespace inflation_cpi_bond_test {
47
48 struct Datum {
49 Date date;
50 Rate rate;
51 };
52
53 typedef BootstrapHelper<ZeroInflationTermStructure> Helper;
54
55 std::vector<ext::shared_ptr<Helper> > makeHelpers(
56 const std::vector<Datum>& iiData,
57 const ext::shared_ptr<ZeroInflationIndex>& ii,
58 const Period& observationLag,
59 const Calendar& calendar,
60 const BusinessDayConvention& bdc,
61 const DayCounter& dc,
62 const Handle<YieldTermStructure>& yTS) {
63
64 std::vector<ext::shared_ptr<Helper> > instruments;
65 for (Datum datum : iiData) {
66 Date maturity = datum.date;
67 Handle<Quote> quote(ext::shared_ptr<Quote>(
68 new SimpleQuote(datum.rate/100.0)));
69 ext::shared_ptr<Helper> h(new ZeroCouponInflationSwapHelper(
70 quote, observationLag, maturity, calendar, bdc, dc, ii, CPI::AsIndex, yTS));
71 instruments.push_back(x: h);
72 }
73 return instruments;
74 }
75
76
77 struct CommonVars { // NOLINT(cppcoreguidelines-special-member-functions)
78
79 Calendar calendar;
80 BusinessDayConvention convention;
81 Date evaluationDate;
82 Period observationLag;
83 DayCounter dayCounter;
84
85 ext::shared_ptr<UKRPI> ii;
86
87 RelinkableHandle<YieldTermStructure> yTS;
88 RelinkableHandle<ZeroInflationTermStructure> cpiTS;
89
90 // setup
91 CommonVars() {
92 // usual setup
93 calendar = UnitedKingdom();
94 convention = ModifiedFollowing;
95 Date today(25, November, 2009);
96 evaluationDate = calendar.adjust(today);
97 Settings::instance().evaluationDate() = evaluationDate;
98 dayCounter = ActualActual(ActualActual::ISDA);
99
100 Date from(20, July, 2007);
101 Date to(20, November, 2009);
102 Schedule rpiSchedule =
103 MakeSchedule().from(effectiveDate: from).to(terminationDate: to)
104 .withTenor(1*Months)
105 .withCalendar(UnitedKingdom())
106 .withConvention(ModifiedFollowing);
107
108 ii = ext::make_shared<UKRPI>(args&: cpiTS);
109
110 Real fixData[] = {
111 206.1, 207.3, 208.0, 208.9, 209.7, 210.9,
112 209.8, 211.4, 212.1, 214.0, 215.1, 216.8,
113 216.5, 217.2, 218.4, 217.7, 216,
114 212.9, 210.1, 211.4, 211.3, 211.5,
115 212.8, 213.4, 213.4, 213.4, 214.4
116 };
117 for (Size i=0; i<LENGTH(fixData); ++i) {
118 ii->addFixing(fixingDate: rpiSchedule[i], fixing: fixData[i]);
119 }
120
121 yTS.linkTo(h: ext::shared_ptr<YieldTermStructure>(
122 new FlatForward(evaluationDate, 0.05, dayCounter)));
123
124 // now build the zero inflation curve
125 observationLag = Period(2,Months);
126
127 std::vector<Datum> zciisData = {
128 { .date: Date(25, November, 2010), .rate: 3.0495 },
129 { .date: Date(25, November, 2011), .rate: 2.93 },
130 { .date: Date(26, November, 2012), .rate: 2.9795 },
131 { .date: Date(25, November, 2013), .rate: 3.029 },
132 { .date: Date(25, November, 2014), .rate: 3.1425 },
133 { .date: Date(25, November, 2015), .rate: 3.211 },
134 { .date: Date(25, November, 2016), .rate: 3.2675 },
135 { .date: Date(25, November, 2017), .rate: 3.3625 },
136 { .date: Date(25, November, 2018), .rate: 3.405 },
137 { .date: Date(25, November, 2019), .rate: 3.48 },
138 { .date: Date(25, November, 2021), .rate: 3.576 },
139 { .date: Date(25, November, 2024), .rate: 3.649 },
140 { .date: Date(26, November, 2029), .rate: 3.751 },
141 { .date: Date(27, November, 2034), .rate: 3.77225 },
142 { .date: Date(25, November, 2039), .rate: 3.77 },
143 { .date: Date(25, November, 2049), .rate: 3.734 },
144 { .date: Date(25, November, 2059), .rate: 3.714 },
145 };
146
147 std::vector<ext::shared_ptr<Helper> > helpers =
148 makeHelpers(iiData: zciisData, ii,
149 observationLag, calendar, bdc: convention, dc: dayCounter, yTS);
150
151 Rate baseZeroRate = zciisData[0].rate/100.0;
152 cpiTS.linkTo(h: ext::shared_ptr<ZeroInflationTermStructure>(
153 new PiecewiseZeroInflationCurve<Linear>(
154 evaluationDate, calendar, dayCounter, observationLag,
155 ii->frequency(), baseZeroRate, helpers)));
156 }
157
158 // teardown
159 ~CommonVars() {
160 // break circular references and allow curves to be destroyed
161 cpiTS.linkTo(h: ext::shared_ptr<ZeroInflationTermStructure>());
162 }
163 };
164
165}
166
167
168void InflationCPIBondTest::testCleanPrice() {
169 BOOST_TEST_MESSAGE("Checking cached pricers for CPI bond...");
170
171 using namespace inflation_cpi_bond_test;
172
173 CommonVars common;
174
175 Real notional = 1000000.0;
176 std::vector<Rate> fixedRates(1, 0.1);
177 DayCounter fixedDayCount = Actual365Fixed();
178 BusinessDayConvention fixedPaymentConvention = ModifiedFollowing;
179 Calendar fixedPaymentCalendar = UnitedKingdom();
180 ext::shared_ptr<ZeroInflationIndex> fixedIndex = common.ii;
181 Period contractObservationLag = Period(3,Months);
182 CPI::InterpolationType observationInterpolation = CPI::Flat;
183 Natural settlementDays = 3;
184 bool growthOnly = true;
185
186 Real baseCPI = 206.1;
187 // set the schedules
188 Date startDate(2, October, 2007);
189 Date endDate(2, October, 2052);
190 Schedule fixedSchedule =
191 MakeSchedule().from(effectiveDate: startDate).to(terminationDate: endDate)
192 .withTenor(Period(6,Months))
193 .withCalendar(UnitedKingdom())
194 .withConvention(Unadjusted)
195 .backwards();
196
197 CPIBond bond(settlementDays, notional, growthOnly,
198 baseCPI, contractObservationLag, fixedIndex,
199 observationInterpolation, fixedSchedule,
200 fixedRates, fixedDayCount, fixedPaymentConvention);
201
202 auto engine = ext::make_shared<DiscountingBondEngine>(args&: common.yTS);
203 bond.setPricingEngine(engine);
204
205 Real storedPrice = 384.71666770;
206 Real calculated = bond.dirtyPrice();
207 Real tolerance = 1.0e-8;
208 if (std::fabs(x: calculated-storedPrice) > tolerance) {
209 BOOST_FAIL("failed to reproduce expected CPI-bond dirty price"
210 << std::fixed << std::setprecision(12)
211 << "\n expected: " << storedPrice
212 << "\n calculated: " << calculated);
213 }
214
215 storedPrice = 383.04297558;
216 calculated = bond.cleanPrice();
217 if (std::fabs(x: calculated-storedPrice) > tolerance) {
218 BOOST_FAIL("failed to reproduce expected CPI-bond clean price"
219 << std::fixed << std::setprecision(12)
220 << "\n expected: " << storedPrice
221 << "\n calculated: " << calculated);
222 }
223}
224
225
226void InflationCPIBondTest::testCPILegWithoutBaseCPI() {
227 BOOST_TEST_MESSAGE("Checking CPI leg with or without explicit base CPI fixing...");
228
229 using namespace inflation_cpi_bond_test;
230
231 CommonVars common;
232
233 Real notional = 1000000.0;
234 std::vector<Rate> fixedRates(1, 0.1);
235 DayCounter fixedDayCount = Actual365Fixed();
236 BusinessDayConvention fixedPaymentConvention = ModifiedFollowing;
237 Calendar fixedPaymentCalendar = UnitedKingdom();
238 ext::shared_ptr<ZeroInflationIndex> fixedIndex = common.ii;
239 Period contractObservationLag = Period(3, Months);
240 CPI::InterpolationType observationInterpolation = CPI::Flat;
241 Natural settlementDays = 3;
242 bool growthOnly = true;
243 Real baseCPI = 206.1;
244 // set the schedules
245 Date baseDate(1, July, 2007);
246 Date startDate(2, October, 2007);
247 Date endDate(2, October, 2052);
248 Schedule fixedSchedule = MakeSchedule()
249 .from(effectiveDate: startDate)
250 .to(terminationDate: endDate)
251 .withTenor(Period(6, Months))
252 .withCalendar(fixedPaymentCalendar)
253 .withConvention(Unadjusted)
254 .backwards();
255
256 Leg legWithBaseDate = CPILeg(fixedSchedule, fixedIndex, Null<Real>(), contractObservationLag)
257 .withSubtractInflationNominal(growthOnly)
258 .withNotionals(notional)
259 .withBaseDate(baseDate)
260 .withFixedRates(fixedRates)
261 .withPaymentDayCounter(fixedDayCount)
262 .withObservationInterpolation(observationInterpolation)
263 .withPaymentAdjustment(fixedPaymentConvention)
264 .withPaymentCalendar(fixedPaymentCalendar);
265
266 Leg legWithBaseCPI = CPILeg(fixedSchedule, fixedIndex, baseCPI, contractObservationLag)
267 .withSubtractInflationNominal(growthOnly)
268 .withNotionals(notional)
269 .withFixedRates(fixedRates)
270 .withPaymentDayCounter(fixedDayCount)
271 .withObservationInterpolation(observationInterpolation)
272 .withPaymentAdjustment(fixedPaymentConvention)
273 .withPaymentCalendar(fixedPaymentCalendar);
274
275 Date settlementDate = fixedPaymentCalendar.advance(date: common.evaluationDate, period: settlementDays * Days,
276 convention: fixedPaymentConvention);
277
278 Real npvWithBaseDate =
279 CashFlows::npv(leg: legWithBaseDate, discountCurve: **common.yTS, includeSettlementDateFlows: false, settlementDate, npvDate: settlementDate);
280 Real accruedsBaseDate = CashFlows::accruedAmount(leg: legWithBaseDate, includeSettlementDateFlows: false, settlementDate);
281
282 Real npvWithBaseCPI =
283 CashFlows::npv(leg: legWithBaseCPI, discountCurve: **common.yTS, includeSettlementDateFlows: false, settlementDate, npvDate: settlementDate);
284 Real accruedsBaseCPI = CashFlows::accruedAmount(leg: legWithBaseCPI, includeSettlementDateFlows: false, settlementDate);
285
286
287 Real cleanPriceWithBaseDate = (npvWithBaseDate - accruedsBaseDate) * 100. / notional;
288 Real cleanPriceWithBaseCPI = (npvWithBaseCPI - accruedsBaseCPI) * 100. / notional;
289
290 Real tolerance = 1.0e-8;
291 if (std::fabs(x: cleanPriceWithBaseDate - cleanPriceWithBaseCPI) > tolerance) {
292 BOOST_FAIL("prices of CPI leg with base date and explicit base CPI fixing are not equal "
293 << std::fixed << std::setprecision(12)
294 << "\n clean npv of leg with baseDate: " << cleanPriceWithBaseDate
295 << "\n clean npv of leg with explicit baseCPI: " << cleanPriceWithBaseCPI);
296 }
297 // Compare to expected price
298 Real storedPrice = 383.04297558;
299 if (std::fabs(x: cleanPriceWithBaseDate - storedPrice) > tolerance) {
300 BOOST_FAIL("failed to reproduce expected CPI-bond clean price"
301 << std::fixed << std::setprecision(12) << "\n expected: " << storedPrice
302 << "\n calculated: " << cleanPriceWithBaseDate);
303 }
304}
305
306test_suite* InflationCPIBondTest::suite() {
307 auto* suite = BOOST_TEST_SUITE("CPI bond tests");
308
309 suite->add(QUANTLIB_TEST_CASE(&InflationCPIBondTest::testCleanPrice));
310 suite->add(QUANTLIB_TEST_CASE(&InflationCPIBondTest::testCPILegWithoutBaseCPI));
311 return suite;
312}
313
314

source code of quantlib/test-suite/inflationcpibond.cpp