1 /* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2
3 /*
4 Copyright (C) 2007 Ferdinando Ametrano
5 Copyright (C) 2006 Giorgio Facchinetti
6 Copyright (C) 2006 Mario Pucci
7 Copyright (C) 2014 Peter Caspers
8
9 This file is part of QuantLib, a free-software/open-source library
10 for financial quantitative analysts and developers - http://quantlib.org/
11
12 QuantLib is free software: you can redistribute it and/or modify it
13 under the terms of the QuantLib license. You should have received a
14 copy of the license along with this program; if not, please email
15 <quantlib-dev@lists.sf.net>. The license is also available online at
16 <http://quantlib.org/license.shtml>.
17
18 This program is distributed in the hope that it will be useful, but WITHOUT
19 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
20 FOR A PARTICULAR PURPOSE. See the license for more details.
21 */
22
23 #include "cms.hpp"
24 #include "utilities.hpp"
25 #include <ql/instruments/swap.hpp>
26 #include <ql/pricingengines/swap/discountingswapengine.hpp>
27 #include <ql/indexes/ibor/euribor.hpp>
28 #include <ql/indexes/swap/euriborswap.hpp>
29 #include <ql/cashflows/capflooredcoupon.hpp>
30 #include <ql/cashflows/conundrumpricer.hpp>
31 #include <ql/cashflows/cashflowvectors.hpp>
32 #include <ql/cashflows/lineartsrpricer.hpp>
33 #include <ql/quotes/simplequote.hpp>
34 #include <ql/termstructures/volatility/swaption/swaptionvolmatrix.hpp>
35 #include <ql/termstructures/volatility/swaption/swaptionvolcube2.hpp>
36 #include <ql/termstructures/volatility/swaption/swaptionvolcube1.hpp>
37 #include <ql/time/calendars/target.hpp>
38 #include <ql/time/daycounters/thirty360.hpp>
39 #include <ql/time/schedule.hpp>
40 #include <ql/utilities/dataformatters.hpp>
41 #include <ql/instruments/makecms.hpp>
42
43 using namespace QuantLib;
44 using namespace boost::unit_test_framework;
45
46 namespace cms_test {
47
48 struct CommonVars {
49 // global data
50 RelinkableHandle<YieldTermStructure> termStructure;
51
52 ext::shared_ptr<IborIndex> iborIndex;
53
54 Handle<SwaptionVolatilityStructure> atmVol;
55 Handle<SwaptionVolatilityStructure> SabrVolCube1;
56 Handle<SwaptionVolatilityStructure> SabrVolCube2;
57
58 std::vector<GFunctionFactory::YieldCurveModel> yieldCurveModels;
59 std::vector<ext::shared_ptr<CmsCouponPricer> > numericalPricers;
60 std::vector<ext::shared_ptr<CmsCouponPricer> > analyticPricers;
61
62 // cleanup
63 SavedSettings backup;
64
65 // setup
CommonVarscms_test::CommonVars66 CommonVars() {
67
68 Calendar calendar = TARGET();
69
70 Date referenceDate = calendar.adjust(Date::todaysDate());
71 Settings::instance().evaluationDate() = referenceDate;
72
73 termStructure.linkTo(flatRate(referenceDate, 0.05,
74 Actual365Fixed()));
75
76 // ATM Volatility structure
77 std::vector<Period> atmOptionTenors;
78 atmOptionTenors.push_back(Period(1, Months));
79 atmOptionTenors.push_back(Period(6, Months));
80 atmOptionTenors.push_back(Period(1, Years));
81 atmOptionTenors.push_back(Period(5, Years));
82 atmOptionTenors.push_back(Period(10, Years));
83 atmOptionTenors.push_back(Period(30, Years));
84
85 std::vector<Period> atmSwapTenors;
86 atmSwapTenors.push_back(Period(1, Years));
87 atmSwapTenors.push_back(Period(5, Years));
88 atmSwapTenors.push_back(Period(10, Years));
89 atmSwapTenors.push_back(Period(30, Years));
90
91 Matrix m(atmOptionTenors.size(), atmSwapTenors.size());
92 m[0][0]=0.1300; m[0][1]=0.1560; m[0][2]=0.1390; m[0][3]=0.1220;
93 m[1][0]=0.1440; m[1][1]=0.1580; m[1][2]=0.1460; m[1][3]=0.1260;
94 m[2][0]=0.1600; m[2][1]=0.1590; m[2][2]=0.1470; m[2][3]=0.1290;
95 m[3][0]=0.1640; m[3][1]=0.1470; m[3][2]=0.1370; m[3][3]=0.1220;
96 m[4][0]=0.1400; m[4][1]=0.1300; m[4][2]=0.1250; m[4][3]=0.1100;
97 m[5][0]=0.1130; m[5][1]=0.1090; m[5][2]=0.1070; m[5][3]=0.0930;
98
99 atmVol = Handle<SwaptionVolatilityStructure>(
100 ext::shared_ptr<SwaptionVolatilityStructure>(new
101 SwaptionVolatilityMatrix(calendar,
102 Following,
103 atmOptionTenors,
104 atmSwapTenors,
105 m,
106 Actual365Fixed())));
107
108 // Vol cubes
109 std::vector<Period> optionTenors;
110 optionTenors.push_back(Period(1, Years));
111 optionTenors.push_back(Period(10, Years));
112 optionTenors.push_back(Period(30, Years));
113
114 std::vector<Period> swapTenors;
115 swapTenors.push_back(Period(2, Years));
116 swapTenors.push_back(Period(10, Years));
117 swapTenors.push_back(Period(30, Years));
118
119 std::vector<Spread> strikeSpreads;
120 strikeSpreads.push_back(-0.020);
121 strikeSpreads.push_back(-0.005);
122 strikeSpreads.push_back(+0.000);
123 strikeSpreads.push_back(+0.005);
124 strikeSpreads.push_back(+0.020);
125
126 Size nRows = optionTenors.size()*swapTenors.size();
127 Size nCols = strikeSpreads.size();
128 Matrix volSpreadsMatrix(nRows, nCols);
129 volSpreadsMatrix[0][0] = 0.0599;
130 volSpreadsMatrix[0][1] = 0.0049;
131 volSpreadsMatrix[0][2] = 0.0000;
132 volSpreadsMatrix[0][3] = -0.0001;
133 volSpreadsMatrix[0][4] = 0.0127;
134
135 volSpreadsMatrix[1][0] = 0.0729;
136 volSpreadsMatrix[1][1] = 0.0086;
137 volSpreadsMatrix[1][2] = 0.0000;
138 volSpreadsMatrix[1][3] = -0.0024;
139 volSpreadsMatrix[1][4] = 0.0098;
140
141 volSpreadsMatrix[2][0] = 0.0738;
142 volSpreadsMatrix[2][1] = 0.0102;
143 volSpreadsMatrix[2][2] = 0.0000;
144 volSpreadsMatrix[2][3] = -0.0039;
145 volSpreadsMatrix[2][4] = 0.0065;
146
147 volSpreadsMatrix[3][0] = 0.0465;
148 volSpreadsMatrix[3][1] = 0.0063;
149 volSpreadsMatrix[3][2] = 0.0000;
150 volSpreadsMatrix[3][3] = -0.0032;
151 volSpreadsMatrix[3][4] = -0.0010;
152
153 volSpreadsMatrix[4][0] = 0.0558;
154 volSpreadsMatrix[4][1] = 0.0084;
155 volSpreadsMatrix[4][2] = 0.0000;
156 volSpreadsMatrix[4][3] = -0.0050;
157 volSpreadsMatrix[4][4] = -0.0057;
158
159 volSpreadsMatrix[5][0] = 0.0576;
160 volSpreadsMatrix[5][1] = 0.0083;
161 volSpreadsMatrix[5][2] = 0.0000;
162 volSpreadsMatrix[5][3] = -0.0043;
163 volSpreadsMatrix[5][4] = -0.0014;
164
165 volSpreadsMatrix[6][0] = 0.0437;
166 volSpreadsMatrix[6][1] = 0.0059;
167 volSpreadsMatrix[6][2] = 0.0000;
168 volSpreadsMatrix[6][3] = -0.0030;
169 volSpreadsMatrix[6][4] = -0.0006;
170
171 volSpreadsMatrix[7][0] = 0.0533;
172 volSpreadsMatrix[7][1] = 0.0078;
173 volSpreadsMatrix[7][2] = 0.0000;
174 volSpreadsMatrix[7][3] = -0.0045;
175 volSpreadsMatrix[7][4] = -0.0046;
176
177 volSpreadsMatrix[8][0] = 0.0545;
178 volSpreadsMatrix[8][1] = 0.0079;
179 volSpreadsMatrix[8][2] = 0.0000;
180 volSpreadsMatrix[8][3] = -0.0042;
181 volSpreadsMatrix[8][4] = -0.0020;
182
183 std::vector<std::vector<Handle<Quote> > > volSpreads(nRows);
184 for (Size i=0; i<nRows; ++i){
185 volSpreads[i] = std::vector<Handle<Quote> >(nCols);
186 for (Size j=0; j<nCols; ++j) {
187 volSpreads[i][j] = Handle<Quote>(ext::shared_ptr<Quote>(
188 new SimpleQuote(volSpreadsMatrix[i][j])));
189 }
190 }
191
192 iborIndex = ext::shared_ptr<IborIndex>(new Euribor6M(termStructure));
193 ext::shared_ptr<SwapIndex> swapIndexBase(new
194 EuriborSwapIsdaFixA(10*Years, termStructure));
195 ext::shared_ptr<SwapIndex> shortSwapIndexBase(new
196 EuriborSwapIsdaFixA(2*Years, termStructure));
197
198 bool vegaWeightedSmileFit = false;
199
200 SabrVolCube2 = Handle<SwaptionVolatilityStructure>(
201 ext::make_shared<SwaptionVolCube2>(atmVol,
202 optionTenors,
203 swapTenors,
204 strikeSpreads,
205 volSpreads,
206 swapIndexBase,
207 shortSwapIndexBase,
208 vegaWeightedSmileFit));
209 SabrVolCube2->enableExtrapolation();
210
211 std::vector<std::vector<Handle<Quote> > > guess(nRows);
212 for (Size i=0; i<nRows; ++i) {
213 guess[i] = std::vector<Handle<Quote> >(4);
214 guess[i][0] =
215 Handle<Quote>(ext::shared_ptr<Quote>(new SimpleQuote(0.2)));
216 guess[i][1] =
217 Handle<Quote>(ext::shared_ptr<Quote>(new SimpleQuote(0.5)));
218 guess[i][2] =
219 Handle<Quote>(ext::shared_ptr<Quote>(new SimpleQuote(0.4)));
220 guess[i][3] =
221 Handle<Quote>(ext::shared_ptr<Quote>(new SimpleQuote(0.0)));
222 }
223 std::vector<bool> isParameterFixed(4, false);
224 isParameterFixed[1] = true;
225
226 // FIXME
227 bool isAtmCalibrated = false;
228
229 SabrVolCube1 = Handle<SwaptionVolatilityStructure>(
230 ext::shared_ptr<SwaptionVolCube1>(new
231 SwaptionVolCube1(atmVol,
232 optionTenors,
233 swapTenors,
234 strikeSpreads,
235 volSpreads,
236 swapIndexBase,
237 shortSwapIndexBase,
238 vegaWeightedSmileFit,
239 guess,
240 isParameterFixed,
241 isAtmCalibrated)));
242 SabrVolCube1->enableExtrapolation();
243
244 yieldCurveModels.clear();
245 yieldCurveModels.push_back(GFunctionFactory::Standard);
246 yieldCurveModels.push_back(GFunctionFactory::ExactYield);
247 yieldCurveModels.push_back(GFunctionFactory::ParallelShifts);
248 yieldCurveModels.push_back(GFunctionFactory::NonParallelShifts);
249 yieldCurveModels.push_back(GFunctionFactory::NonParallelShifts); // for linear tsr model
250
251 Handle<Quote> zeroMeanRev(ext::shared_ptr<Quote>(new SimpleQuote(0.0)));
252
253 numericalPricers.clear();
254 analyticPricers.clear();
255 for (Size j = 0; j < yieldCurveModels.size(); ++j) {
256 if (j < yieldCurveModels.size() - 1)
257 numericalPricers.push_back(
258 ext::shared_ptr<CmsCouponPricer>(new NumericHaganPricer(
259 atmVol, yieldCurveModels[j], zeroMeanRev)));
260 else
261 numericalPricers.push_back(ext::shared_ptr<CmsCouponPricer>(
262 new LinearTsrPricer(atmVol, zeroMeanRev)));
263
264 analyticPricers.push_back(ext::shared_ptr<CmsCouponPricer>(new
265 AnalyticHaganPricer(atmVol, yieldCurveModels[j],
266 zeroMeanRev)));
267 }
268 }
269 };
270
271 }
272
273
testFairRate()274 void CmsTest::testFairRate() {
275
276 BOOST_TEST_MESSAGE("Testing Hagan-pricer flat-vol equivalence for coupons...");
277
278 using namespace cms_test;
279
280 CommonVars vars;
281
282 ext::shared_ptr<SwapIndex> swapIndex(new SwapIndex("EuriborSwapIsdaFixA",
283 10*Years,
284 vars.iborIndex->fixingDays(),
285 vars.iborIndex->currency(),
286 vars.iborIndex->fixingCalendar(),
287 1*Years,
288 Unadjusted,
289 vars.iborIndex->dayCounter(),//??
290 vars.iborIndex));
291 // FIXME
292 //ext::shared_ptr<SwapIndex> swapIndex(new
293 // EuriborSwapIsdaFixA(10*Years, vars.iborIndex->termStructure()));
294 Date startDate = vars.termStructure->referenceDate() + 20*Years;
295 Date paymentDate = startDate + 1*Years;
296 Date endDate = paymentDate;
297 Real nominal = 1.0;
298 Rate infiniteCap = Null<Real>();
299 Rate infiniteFloor = Null<Real>();
300 Real gearing = 1.0;
301 Spread spread = 0.0;
302 CappedFlooredCmsCoupon coupon(paymentDate, nominal,
303 startDate, endDate,
304 swapIndex->fixingDays(), swapIndex,
305 gearing, spread,
306 infiniteCap, infiniteFloor,
307 startDate, endDate,
308 vars.iborIndex->dayCounter());
309 for (Size j=0; j<vars.yieldCurveModels.size(); ++j) {
310 vars.numericalPricers[j]->setSwaptionVolatility(vars.atmVol);
311 coupon.setPricer(vars.numericalPricers[j]);
312 Rate rate0 = coupon.rate();
313
314 vars.analyticPricers[j]->setSwaptionVolatility(vars.atmVol);
315 coupon.setPricer(vars.analyticPricers[j]);
316 Rate rate1 = coupon.rate();
317
318 Spread difference = std::fabs(rate1-rate0);
319 Spread tol = 2.0e-4;
320 bool linearTsr = j==vars.yieldCurveModels.size()-1;
321
322 if (difference > tol)
323 BOOST_FAIL("\nCoupon payment date: " << paymentDate <<
324 "\nCoupon start date: " << startDate <<
325 "\nCoupon floor: " << io::rate(infiniteFloor) <<
326 "\nCoupon gearing: " << io::rate(gearing) <<
327 "\nCoupon swap index: " << swapIndex->name() <<
328 "\nCoupon spread: " << io::rate(spread) <<
329 "\nCoupon cap: " << io::rate(infiniteCap) <<
330 "\nCoupon DayCounter: " << vars.iborIndex->dayCounter()<<
331 "\nYieldCurve Model: " << vars.yieldCurveModels[j] <<
332 "\nNumerical Pricer: " << io::rate(rate0) <<
333 (linearTsr ? " (Linear TSR Model)" : "") <<
334 "\nAnalytic Pricer: " << io::rate(rate1) <<
335 "\ndifference: " << io::rate(difference) <<
336 "\ntolerance: " << io::rate(tol));
337 }
338 }
339
testCmsSwap()340 void CmsTest::testCmsSwap() {
341
342 BOOST_TEST_MESSAGE("Testing Hagan-pricer flat-vol equivalence for swaps...");
343
344 using namespace cms_test;
345
346 CommonVars vars;
347
348 ext::shared_ptr<SwapIndex> swapIndex(new SwapIndex("EuriborSwapIsdaFixA",
349 10*Years,
350 vars.iborIndex->fixingDays(),
351 vars.iborIndex->currency(),
352 vars.iborIndex->fixingCalendar(),
353 1*Years,
354 Unadjusted,
355 vars.iborIndex->dayCounter(),//??
356 vars.iborIndex));
357 // FIXME
358 //ext::shared_ptr<SwapIndex> swapIndex(new
359 // EuriborSwapIsdaFixA(10*Years, vars.iborIndex->termStructure()));
360 Spread spread = 0.0;
361 std::vector<Size> swapLengths;
362 swapLengths.push_back(1);
363 swapLengths.push_back(5);
364 swapLengths.push_back(6);
365 swapLengths.push_back(10);
366 Size n = swapLengths.size();
367 std::vector<ext::shared_ptr<Swap> > cms(n);
368 for (Size i=0; i<n; ++i)
369 // no cap, floor
370 // no gearing, spread
371 cms[i] = MakeCms(Period(swapLengths[i], Years),
372 swapIndex,
373 vars.iborIndex, spread,
374 10*Days);
375
376 for (Size j=0; j<vars.yieldCurveModels.size(); ++j) {
377 vars.numericalPricers[j]->setSwaptionVolatility(vars.atmVol);
378 vars.analyticPricers[j]->setSwaptionVolatility(vars.atmVol);
379 for (Size sl=0; sl<n; ++sl) {
380 setCouponPricer(cms[sl]->leg(0), vars.numericalPricers[j]);
381 Real priceNum = cms[sl]->NPV();
382 setCouponPricer(cms[sl]->leg(0), vars.analyticPricers[j]);
383 Real priceAn = cms[sl]->NPV();
384
385 Real difference = std::fabs(priceNum-priceAn);
386 Real tol = 2.0e-4;
387 bool linearTsr = j==vars.yieldCurveModels.size()-1;
388 if (difference > tol)
389 BOOST_FAIL("\nLength in Years: " << swapLengths[sl] <<
390 //"\nfloor: " << io::rate(infiniteFloor) <<
391 //"\ngearing: " << io::rate(gearing) <<
392 "\nswap index: " << swapIndex->name() <<
393 "\nibor index: " << vars.iborIndex->name() <<
394 "\nspread: " << io::rate(spread) <<
395 //"\ncap: " << io::rate(infiniteCap) <<
396 "\nYieldCurve Model: " << vars.yieldCurveModels[j] <<
397 "\nNumerical Pricer: " << io::rate(priceNum) <<
398 (linearTsr ? " (Linear TSR Model)" : "") <<
399 "\nAnalytic Pricer: " << io::rate(priceAn) <<
400 "\ndifference: " << io::rate(difference) <<
401 "\ntolerance: " << io::rate(tol));
402 }
403 }
404
405 }
406
testParity()407 void CmsTest::testParity() {
408
409 BOOST_TEST_MESSAGE("Testing put-call parity for capped-floored CMS coupons...");
410
411 using namespace cms_test;
412
413 CommonVars vars;
414
415 std::vector<Handle<SwaptionVolatilityStructure> > swaptionVols;
416 swaptionVols.push_back(vars.atmVol);
417 swaptionVols.push_back(vars.SabrVolCube1);
418 swaptionVols.push_back(vars.SabrVolCube2);
419
420 ext::shared_ptr<SwapIndex> swapIndex(new
421 EuriborSwapIsdaFixA(10*Years,
422 vars.iborIndex->forwardingTermStructure()));
423 Date startDate = vars.termStructure->referenceDate() + 20*Years;
424 Date paymentDate = startDate + 1*Years;
425 Date endDate = paymentDate;
426 Real nominal = 1.0;
427 Rate infiniteCap = Null<Real>();
428 Rate infiniteFloor = Null<Real>();
429 Real gearing = 1.0;
430 Spread spread = 0.0;
431 DiscountFactor discount = vars.termStructure->discount(paymentDate);
432 CappedFlooredCmsCoupon swaplet(paymentDate, nominal,
433 startDate, endDate,
434 swapIndex->fixingDays(),
435 swapIndex,
436 gearing, spread,
437 infiniteCap, infiniteFloor,
438 startDate, endDate,
439 vars.iborIndex->dayCounter());
440 for (Rate strike = .02; strike<.12; strike+=0.05) {
441 CappedFlooredCmsCoupon caplet(paymentDate, nominal,
442 startDate, endDate,
443 swapIndex->fixingDays(),
444 swapIndex,
445 gearing, spread,
446 strike, infiniteFloor,
447 startDate, endDate,
448 vars.iborIndex->dayCounter());
449 CappedFlooredCmsCoupon floorlet(paymentDate, nominal,
450 startDate, endDate,
451 swapIndex->fixingDays(),
452 swapIndex,
453 gearing, spread,
454 infiniteCap, strike,
455 startDate, endDate,
456 vars.iborIndex->dayCounter());
457
458 for (Size i=0; i<swaptionVols.size(); ++i) {
459 for (Size j=0; j<vars.yieldCurveModels.size(); ++j) {
460 vars.numericalPricers[j]->setSwaptionVolatility(swaptionVols[i]);
461 vars.analyticPricers[j]->setSwaptionVolatility(swaptionVols[i]);
462 std::vector<ext::shared_ptr<CmsCouponPricer> > pricers(2);
463 pricers[0] = vars.numericalPricers[j];
464 pricers[1] = vars.analyticPricers[j];
465 for (Size k=0; k<pricers.size(); ++k) {
466 swaplet.setPricer(pricers[k]);
467 caplet.setPricer(pricers[k]);
468 floorlet.setPricer(pricers[k]);
469 Real swapletPrice = swaplet.price(vars.termStructure) +
470 nominal * swaplet.accrualPeriod() * strike * discount;
471 Real capletPrice = caplet.price(vars.termStructure);
472 Real floorletPrice = floorlet.price(vars.termStructure);
473 Real difference = std::fabs(capletPrice + floorletPrice -
474 swapletPrice);
475 Real tol = 2.0e-5;
476 bool linearTsr = k==0 && j==vars.yieldCurveModels.size()-1;
477 if(linearTsr)
478 tol = 1.0e-7;
479 if (difference > tol)
480 BOOST_FAIL("\nCoupon payment date: " << paymentDate <<
481 "\nCoupon start date: " << startDate <<
482 "\nCoupon gearing: " << io::rate(gearing) <<
483 "\nCoupon swap index: " << swapIndex->name() <<
484 "\nCoupon spread: " << io::rate(spread) <<
485 "\nstrike: " << io::rate(strike) <<
486 "\nCoupon DayCounter: " << vars.iborIndex->dayCounter() <<
487 "\nYieldCurve Model: " << vars.yieldCurveModels[j] <<
488 (k==0 ? "\nNumerical Pricer" : "\nAnalytic Pricer") <<
489 (linearTsr ? " (Linear TSR Model)" : "") <<
490 "\nSwaplet price: " << io::rate(swapletPrice) <<
491 "\nCaplet price: " << io::rate(capletPrice) <<
492 "\nFloorlet price: " << io::rate(floorletPrice) <<
493 "\ndifference: " << difference <<
494 "\ntolerance: " << io::rate(tol));
495 }
496 }
497 }
498 }
499 }
500
suite()501 test_suite* CmsTest::suite() {
502 test_suite* suite = BOOST_TEST_SUITE("Cms tests");
503 suite->add(QUANTLIB_TEST_CASE(&CmsTest::testFairRate));
504 suite->add(QUANTLIB_TEST_CASE(&CmsTest::testCmsSwap));
505 suite->add(QUANTLIB_TEST_CASE(&CmsTest::testParity));
506 return suite;
507 }
508