[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) 2009 Chris Kenyon
5
6 This file is part of QuantLib, a free-software/open-source library
7 for financial quantitative analysts and developers - http://quantlib.org/
8
9 QuantLib is free software: you can redistribute it and/or modify it
10 under the terms of the QuantLib license. You should have received a
11 copy of the license along with this program; if not, please email
12 <quantlib-dev@lists.sf.net>. The license is also available online at
13 <http://quantlib.org/license.shtml>.
14
15 This program is distributed in the hope that it will be useful, but WITHOUT
16 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
17 FOR A PARTICULAR PURPOSE. See the license for more details.
18 */
19
20#include "inflationvolatility.hpp"
21#include "utilities.hpp"
22
23#include <ql/math/interpolations/cubicinterpolation.hpp>
24#include <ql/math/interpolations/bicubicsplineinterpolation.hpp>
25#include <ql/termstructures/yield/zerocurve.hpp>
26#include <ql/termstructures/inflation/interpolatedyoyinflationcurve.hpp>
27
28#include <ql/cashflows/inflationcoupon.hpp>
29#include <ql/cashflows/inflationcouponpricer.hpp>
30
31#include <ql/experimental/inflation/yoycapfloortermpricesurface.hpp>
32#include <ql/pricingengines/inflation/inflationcapfloorengines.hpp>
33#include <ql/experimental/inflation/yoyoptionletstripper.hpp>
34
35#include <ql/experimental/inflation/kinterpolatedyoyoptionletvolatilitysurface.hpp>
36#include <ql/experimental/inflation/interpolatedyoyoptionletstripper.hpp>
37
38#include <ql/cashflows/capflooredinflationcoupon.hpp>
39#include <ql/indexes/inflation/euhicp.hpp>
40#include <ql/indexes/inflation/ukrpi.hpp>
41
42#include <iostream>
43
44
45// local namespace for data
46//*************************************************************************
47namespace inflation_volatility_test {
48
49 using namespace QuantLib;
50
51 // local data globals
52 Handle<YieldTermStructure> nominalEUR;
53 Handle<YieldTermStructure> nominalGBP;
54
55 RelinkableHandle<YoYInflationTermStructure> yoyEU;
56 RelinkableHandle<YoYInflationTermStructure> yoyUK;
57
58 std::vector<Rate> cStrikesEU;
59 std::vector<Rate> fStrikesEU;
60 std::vector<Period> cfMaturitiesEU;
61 ext::shared_ptr<Matrix> cPriceEU;
62 ext::shared_ptr<Matrix> fPriceEU;
63
64 ext::shared_ptr<YoYInflationIndex> yoyIndexUK;
65 ext::shared_ptr<YoYInflationIndex> yoyIndexEU;
66
67 std::vector<Rate> cStrikesUK;
68 std::vector<Rate> fStrikesUK;
69 std::vector<Period> cfMaturitiesUK;
70 ext::shared_ptr<Matrix> cPriceUK;
71 ext::shared_ptr<Matrix> fPriceUK;
72
73 ext::shared_ptr<InterpolatedYoYCapFloorTermPriceSurface<Bicubic,Cubic> > priceSurfEU;
74
75 void reset() {
76 nominalEUR = Handle<YieldTermStructure>();
77 nominalGBP = Handle<YieldTermStructure>();
78 priceSurfEU.reset();
79 yoyEU.linkTo(h: ext::shared_ptr<YoYInflationTermStructure>());
80 yoyUK.linkTo(h: ext::shared_ptr<YoYInflationTermStructure>());
81 yoyIndexUK.reset();
82 yoyIndexEU.reset();
83 cPriceEU.reset();
84 fPriceEU.reset();
85 cPriceUK.reset();
86 fPriceUK.reset();
87 yoyIndexUK.reset();
88
89 cStrikesEU.clear();
90 fStrikesEU.clear();
91 cStrikesUK.clear();
92 fStrikesUK.clear();
93 cfMaturitiesEU.clear();
94 cfMaturitiesUK.clear();
95 }
96
97 void setup() {
98
99 // make sure of the evaluation date
100 Date eval = Date(Day(23), Month(11), Year(2007));
101 Settings::instance().evaluationDate() = eval;
102
103 yoyIndexUK = ext::make_shared<YoYInflationIndex>(args: ext::make_shared<UKRPI>(), args: true, args&: yoyUK);
104 yoyIndexEU = ext::make_shared<YoYInflationIndex>(args: ext::make_shared<EUHICP>(), args: true, args&: yoyEU);
105
106 // nominal yield curve (interpolated; times assume year parts have 365 days)
107 Real timesEUR[] = {0.0109589, 0.0684932, 0.263014, 0.317808, 0.567123, 0.816438,
108 1.06575, 1.31507, 1.56438, 2.0137, 3.01918, 4.01644,
109 5.01644, 6.01644, 7.01644, 8.01644, 9.02192, 10.0192,
110 12.0192, 15.0247, 20.0301, 25.0356, 30.0329, 40.0384,
111 50.0466};
112 Real ratesEUR[] = {0.0415600, 0.0426840, 0.0470980, 0.0458506, 0.0449550, 0.0439784,
113 0.0431887, 0.0426604, 0.0422925, 0.0424591, 0.0421477, 0.0421853,
114 0.0424016, 0.0426969, 0.0430804, 0.0435011, 0.0439368, 0.0443825,
115 0.0452589, 0.0463389, 0.0472636, 0.0473401, 0.0470629, 0.0461092,
116 0.0450794};
117
118 Real timesGBP[] = {0.008219178, 0.010958904, 0.01369863, 0.019178082, 0.073972603,
119 0.323287671, 0.57260274, 0.821917808, 1.071232877, 1.320547945,
120 1.506849315, 2.002739726, 3.002739726, 4.002739726, 5.005479452,
121 6.010958904, 7.008219178, 8.005479452, 9.008219178, 10.00821918,
122 12.01369863, 15.0109589, 20.01369863, 25.01917808, 30.02191781,
123 40.03287671, 50.03561644, 60.04109589, 70.04931507};
124 Real ratesGBP[] = {0.0577363, 0.0582314, 0.0585265, 0.0587165, 0.0596598,
125 0.0612506, 0.0589676, 0.0570512, 0.0556147, 0.0546082,
126 0.0549492, 0.053801, 0.0529333, 0.0524068, 0.0519712,
127 0.0516615, 0.0513711, 0.0510433, 0.0507974, 0.0504833,
128 0.0498998, 0.0490464, 0.04768, 0.0464862, 0.045452,
129 0.0437699, 0.0425311, 0.0420073, 0.041151};
130
131 std::vector <Real> r;
132 std::vector <Date> d;
133 Size nTimesEUR = LENGTH(timesEUR);
134 Size nTimesGBP = LENGTH(timesGBP);
135 for (Size i = 0; i < nTimesEUR; i++) {
136 r.push_back(x: ratesEUR[i]);
137 Size ys = (Size)floor(x: timesEUR[i]);
138 Size ds = (Size)((timesEUR[i]-(Real)ys)*365);
139 Date dd = eval + Period(ys,Years) + Period(ds,Days);
140 d.push_back( x: dd );
141 }
142
143 ext::shared_ptr<InterpolatedZeroCurve<Cubic> >
144 euriborTS(new InterpolatedZeroCurve<Cubic>(d, r, Actual365Fixed()));
145 Handle<YieldTermStructure> nominalHeur(euriborTS, false);
146 nominalEUR = nominalHeur; // copy to global
147
148 d.clear();
149 r.clear();
150 for (Size i = 0; i < nTimesGBP; i++) {
151 r.push_back(x: ratesGBP[i]);
152 Size ys = (Size)floor(x: timesGBP[i]);
153 Size ds = (Size)((timesGBP[i]-(Real)ys)*365);
154 Date dd = eval + Period(ys,Years) + Period(ds,Days);
155 d.push_back( x: dd );
156 }
157
158 ext::shared_ptr<InterpolatedZeroCurve<Cubic> >
159 gbpLiborTS(new InterpolatedZeroCurve<Cubic>(d, r, Actual365Fixed()));
160 Handle<YieldTermStructure> nominalHgbp(gbpLiborTS, false);
161 nominalGBP = nominalHgbp; // copy to global
162
163 // times = years - lag, where the lag is 2 months or 2/12
164 // because this data is derived from cap/floor data that
165 // is based on a 2 month lag.
166
167 // note that these are NOT swap rates
168 // also not that the first value MUST be in the base period
169 // i.e. the first rate is for a negative time
170 Real yoyEUrates[] = {0.0237951,
171 0.0238749, 0.0240334, 0.0241934, 0.0243567, 0.0245323,
172 0.0247213, 0.0249348, 0.0251768, 0.0254337, 0.0257258,
173 0.0260217, 0.0263006, 0.0265538, 0.0267803, 0.0269378,
174 0.0270608, 0.0271363, 0.0272, 0.0272512, 0.0272927,
175 0.027317, 0.0273615, 0.0273811, 0.0274063, 0.0274307,
176 0.0274625, 0.027527, 0.0275952, 0.0276734, 0.027794};
177
178 d.clear();
179 r.clear();
180 Date baseDate = TARGET().advance(eval, n: -2, unit: Months, convention: ModifiedFollowing);
181 for (Size i = 0; i < LENGTH(yoyEUrates); i++) {
182 Date dd = TARGET().advance(baseDate, n: i, unit: Years, convention: ModifiedFollowing);
183 d.push_back(x: dd);
184 r.push_back(x: yoyEUrates[i]);
185 }
186
187 bool indexIsInterpolated = true; // actually false for UKRPI but smooth surfaces are
188 // better for finding intersections etc
189
190 ext::shared_ptr<InterpolatedYoYInflationCurve<Linear> >
191 pYTSEU( new InterpolatedYoYInflationCurve<Linear>(
192 eval, TARGET(), Actual365Fixed(), Period(2,Months), Monthly,
193 indexIsInterpolated, d, r) );
194 yoyEU.linkTo(h: pYTSEU);
195
196 // price data
197 const Size ncStrikesEU = 6;
198 const Size nfStrikesEU = 6;
199 const Size ncfMaturitiesEU = 7;
200 Real capStrikesEU[ncStrikesEU] = {0.02, 0.025, 0.03, 0.035, 0.04, 0.05};
201 Period capMaturitiesEU[ncfMaturitiesEU] = {3*Years, 5*Years, 7*Years,
202 10*Years, 15*Years, 20*Years, 30*Years};
203 Real capPricesEU[ncStrikesEU][ncfMaturitiesEU] =
204 {{116.225, 204.945, 296.285, 434.29, 654.47, 844.775, 1132.33},
205 {34.305, 71.575, 114.1, 184.33, 307.595, 421.395, 602.35},
206 {6.37, 19.085, 35.635, 66.42, 127.69, 189.685, 296.195},
207 {1.325, 5.745, 12.585, 26.945, 58.95, 94.08, 158.985},
208 {0.501, 2.37, 5.38, 13.065, 31.91, 53.95, 96.97},
209 {0.501, 0.695, 1.47, 4.415, 12.86, 23.75, 46.7}};
210
211 Real floorStrikesEU[nfStrikesEU] = {-0.01, 0.00, 0.005, 0.01, 0.015, 0.02};
212 Real floorPricesEU[nfStrikesEU][ncfMaturitiesEU] =
213 {{0.501, 0.851, 2.44, 6.645, 16.23, 26.85, 46.365},
214 {0.501, 2.236, 5.555, 13.075, 28.46, 44.525, 73.08},
215 {1.025, 3.935, 9.095, 19.64, 39.93, 60.375, 96.02},
216 {2.465, 7.885, 16.155, 31.6, 59.34, 86.21, 132.045},
217 {6.9, 17.92, 32.085, 56.08, 95.95, 132.85, 194.18},
218 {23.52, 47.625, 74.085, 114.355, 175.72, 229.565, 316.285}};
219
220 // now load the data into vector and Matrix classes
221 cStrikesEU.clear();
222 fStrikesEU.clear();
223 cfMaturitiesEU.clear();
224 for (Real& i : capStrikesEU)
225 cStrikesEU.push_back(x: i);
226 for (Real& i : floorStrikesEU)
227 fStrikesEU.push_back(x: i);
228 for (auto& i : capMaturitiesEU)
229 cfMaturitiesEU.push_back(x: i);
230 ext::shared_ptr<Matrix> tcPriceEU(new Matrix(ncStrikesEU, ncfMaturitiesEU));
231 ext::shared_ptr<Matrix> tfPriceEU(new Matrix(nfStrikesEU, ncfMaturitiesEU));
232 for(Size i = 0; i < ncStrikesEU; i++) {
233 for(Size j = 0; j < ncfMaturitiesEU; j++) {
234 (*tcPriceEU)[i][j] = capPricesEU[i][j];
235 }
236 }
237 for(Size i = 0; i < nfStrikesEU; i++) {
238 for(Size j = 0; j < ncfMaturitiesEU; j++) {
239 (*tfPriceEU)[i][j] = floorPricesEU[i][j];
240 }
241 }
242 cPriceEU = tcPriceEU; // copy to global
243 fPriceEU = tfPriceEU;
244 }
245
246
247 void setupPriceSurface() {
248
249 // construct:
250 // calendar, business day convention, and day counter are
251 // taken from the nominal base give the reference date for
252 // the inflation options (generally 2 or 3 months before
253 // nominal reference date)
254 Natural fixingDays = 0;
255 Size lag = 3;// must be 3 because we use an interpolated index (EU)
256 Period yyLag = Period(lag,Months);
257 Rate baseRate = 1; // not really used
258 DayCounter dc = Actual365Fixed();
259 TARGET cal;
260 BusinessDayConvention bdc = ModifiedFollowing;
261 const ext::shared_ptr<QuantLib::YieldTermStructure>& pn = nominalEUR.currentLink();
262 Handle<QuantLib::YieldTermStructure> n(pn,false);
263 ext::shared_ptr<InterpolatedYoYCapFloorTermPriceSurface<Bicubic,Cubic> >
264 cfEUprices(new InterpolatedYoYCapFloorTermPriceSurface<Bicubic,Cubic>(
265 fixingDays,
266 yyLag, yoyIndexEU, baseRate,
267 n, dc,
268 cal, bdc,
269 cStrikesEU, fStrikesEU, cfMaturitiesEU,
270 (*cPriceEU), (*fPriceEU)));
271
272 priceSurfEU = cfEUprices;
273 }
274
275}
276
277//***************************************************************************
278
279
280
281void InflationVolTest::testYoYPriceSurfaceToVol() {
282 BOOST_TEST_MESSAGE("Testing conversion from YoY price surface "
283 "to YoY volatility surface...");
284
285 using namespace inflation_volatility_test;
286
287 setup();
288
289 // first get the price surface set up
290 setupPriceSurface();
291
292 // caplet pricer, recall that setCapletVolatility(Handle<YoYOptionletVolatilitySurface>)
293 // exists ... we'll use it with the -Curve variant of the surface
294 // test UNIT DISPLACED pricer
295 ext::shared_ptr<YoYOptionletVolatilitySurface> pVS;
296 Handle<YoYOptionletVolatilitySurface> hVS(pVS, false); // pVS does NOT own whatever it points to later, hence the handle does not either
297 ext::shared_ptr<YoYInflationUnitDisplacedBlackCapFloorEngine>
298 yoyPricerUD(new YoYInflationUnitDisplacedBlackCapFloorEngine(yoyIndexEU,hVS,nominalEUR)); //hVS
299 // N.B. the vol gets set in the stripper ... else no point!
300
301 // cap stripper
302 ext::shared_ptr<YoYOptionletStripper> yoyOptionletStripper(
303 new InterpolatedYoYOptionletStripper<Linear>() );
304
305 // now set up all the variables for the stripping
306 Natural settlementDays = 0;
307 TARGET cal;
308 BusinessDayConvention bdc = ModifiedFollowing;
309 DayCounter dc = Actual365Fixed();
310
311 ext::shared_ptr<YoYCapFloorTermPriceSurface> capFloorPrices = priceSurfEU;
312 Period lag = priceSurfEU->observationLag();
313
314 Real slope = -0.5; //when you have bad data, i.e. very low/constant
315 //prices for short dated extreem strikes
316 //then you cannot assume constant caplet vol
317 //(else arbitrage)
318 // N.B. if this is too extreme then can't
319 // get a no-arbitrage solution anyway
320 // the way the slope is used means that the slope is
321 // proportional to the level so higher slopes at
322 // the edges when things are more volatile
323
324 // Actually is doesn't matter what the interpolation is because we only
325 // intend to use the K values that correspond to quotes ... for model fitting.
326 ext::shared_ptr<KInterpolatedYoYOptionletVolatilitySurface<Linear> > yoySurf(new
327 KInterpolatedYoYOptionletVolatilitySurface<Linear>(settlementDays,
328 cal, bdc, dc, lag, capFloorPrices, yoyPricerUD, yoyOptionletStripper,
329 slope) );
330
331 // now use it for something ... like stating what the T=const lines look like
332 const Real volATyear1[] = {
333 0.0128, 0.0093, 0.0083, 0.0073, 0.0064,
334 0.0058, 0.0042, 0.0046, 0.0053, 0.0064,
335 0.0098
336 };
337 const Real volATyear3[] = {
338 0.0079, 0.0058, 0.0051, 0.0045, 0.0039,
339 0.0035, 0.0026, 0.0028, 0.0033, 0.0039,
340 0.0060
341 };
342
343 Date d = yoySurf->baseDate() + Period(1,Years);
344 auto someSlice = yoySurf->Dslice(d);
345
346 Size n = someSlice.first.size();
347 Real eps = 0.0001;
348 for(Size i = 0; i < n; i++){
349 QL_REQUIRE( fabs(someSlice.second[i] - volATyear1[i]) < eps,
350 " could not recover 1yr vol: " << someSlice.second[i]
351 << " vs " << volATyear1[i] );
352 }
353
354 d = yoySurf->baseDate() + Period(3,Years);
355 auto someOtherSlice = yoySurf->Dslice(d);
356 n = someOtherSlice.first.size();
357 for(Size i = 0; i < n; i++){
358 QL_REQUIRE(fabs(someOtherSlice.second[i]-volATyear3[i]) < eps,
359 "could not recover 3yr vol: "
360 << someOtherSlice.second[i]<< " vs " << volATyear3[i] );
361 }
362
363 reset();
364}
365
366
367
368
369void InflationVolTest::testYoYPriceSurfaceToATM() {
370 BOOST_TEST_MESSAGE("Testing conversion from YoY cap-floor surface "
371 "to YoY inflation term structure...");
372
373 using namespace inflation_volatility_test;
374
375 setup();
376
377 setupPriceSurface();
378
379 auto yyATMt = priceSurfEU->atmYoYSwapTimeRates();
380 auto yyATMd = priceSurfEU->atmYoYSwapDateRates();
381
382 // Real dy = (Real)lag / 12.0;
383 const Real crv[] = {0.024586, 0.0247575, 0.0249396, 0.0252596,
384 0.0258498, 0.0262883, 0.0267915};
385 const Real swaps[] = {0.024586, 0.0247575, 0.0249396, 0.0252596,
386 0.0258498, 0.0262883, 0.0267915};
387 const Real ayoy[] = {0.0247659, 0.0251437, 0.0255945, 0.0265234,
388 0.0280457, 0.0285534, 0.0295884};
389 Real eps = 2e-5;
390 for(Size i = 0; i < yyATMt.first.size(); i++) {
391 QL_REQUIRE(fabs( yyATMt.second[i] - crv[i] ) < eps,
392 "could not recover cached yoy swap curve "
393 << yyATMt.second[i]<< " vs " << crv[i]);
394 }
395
396 for(Size i = 0; i < yyATMd.first.size(); i++) {
397 QL_REQUIRE(fabs( priceSurfEU->atmYoYSwapRate(yyATMd.first[i]) - swaps[i] ) < eps,
398 "could not recover yoy swap curve "
399 << priceSurfEU->atmYoYSwapRate(yyATMd.first[i]) << " vs " << swaps[i]);
400 }
401 for(Size i = 0; i < yyATMd.first.size(); i++) {
402 QL_REQUIRE(fabs( priceSurfEU->atmYoYRate(yyATMd.first[i]) - ayoy[i] ) < eps,
403 " could not recover cached yoy curve "
404 << priceSurfEU->atmYoYRate(yyATMd.first[i]) << " vs " << ayoy[i]
405 <<" at "<<yyATMd.first[i]);
406 }
407 reset();
408}
409
410
411boost::unit_test_framework::test_suite* InflationVolTest::suite() {
412 auto* suite = BOOST_TEST_SUITE("yoyOptionletStripper (yoy inflation vol) tests");
413
414 suite->add(QUANTLIB_TEST_CASE(&InflationVolTest::testYoYPriceSurfaceToATM));
415 suite->add(QUANTLIB_TEST_CASE(&InflationVolTest::testYoYPriceSurfaceToVol));
416
417 return suite;
418}
419
420
421

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