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/interpolatedswaptionvolatilitycube.hpp>
36#include <ql/termstructures/volatility/swaption/sabrswaptionvolatilitycube.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
43using namespace QuantLib;
44using namespace boost::unit_test_framework;
45
46namespace 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 // setup
63 CommonVars() {
64
65 Calendar calendar = TARGET();
66
67 Date referenceDate = calendar.adjust(Date::todaysDate());
68 Settings::instance().evaluationDate() = referenceDate;
69
70 termStructure.linkTo(h: flatRate(today: referenceDate, forward: 0.05,
71 dc: Actual365Fixed()));
72
73 // ATM Volatility structure
74 std::vector<Period> atmOptionTenors = {1 * Months, 6 * Months, 1 * Years,
75 5 * Years, 10 * Years, 30 * Years};
76
77 std::vector<Period> atmSwapTenors = {1 * Years, 5 * Years, 10 * Years, 30 * Years};
78
79 Matrix m(atmOptionTenors.size(), atmSwapTenors.size());
80 m[0][0]=0.1300; m[0][1]=0.1560; m[0][2]=0.1390; m[0][3]=0.1220;
81 m[1][0]=0.1440; m[1][1]=0.1580; m[1][2]=0.1460; m[1][3]=0.1260;
82 m[2][0]=0.1600; m[2][1]=0.1590; m[2][2]=0.1470; m[2][3]=0.1290;
83 m[3][0]=0.1640; m[3][1]=0.1470; m[3][2]=0.1370; m[3][3]=0.1220;
84 m[4][0]=0.1400; m[4][1]=0.1300; m[4][2]=0.1250; m[4][3]=0.1100;
85 m[5][0]=0.1130; m[5][1]=0.1090; m[5][2]=0.1070; m[5][3]=0.0930;
86
87 atmVol = Handle<SwaptionVolatilityStructure>(
88 ext::shared_ptr<SwaptionVolatilityStructure>(new
89 SwaptionVolatilityMatrix(calendar,
90 Following,
91 atmOptionTenors,
92 atmSwapTenors,
93 m,
94 Actual365Fixed())));
95
96 // Vol cubes
97 std::vector<Period> optionTenors = {{1, Years}, {10, Years}, {30, Years}};
98 std::vector<Period> swapTenors = {{2, Years}, {10, Years}, {30, Years}};
99 std::vector<Spread> strikeSpreads = {-0.020, -0.005, 0.000, 0.005, 0.020};
100
101 Size nRows = optionTenors.size()*swapTenors.size();
102 Size nCols = strikeSpreads.size();
103 Matrix volSpreadsMatrix(nRows, nCols);
104 volSpreadsMatrix[0][0] = 0.0599;
105 volSpreadsMatrix[0][1] = 0.0049;
106 volSpreadsMatrix[0][2] = 0.0000;
107 volSpreadsMatrix[0][3] = -0.0001;
108 volSpreadsMatrix[0][4] = 0.0127;
109
110 volSpreadsMatrix[1][0] = 0.0729;
111 volSpreadsMatrix[1][1] = 0.0086;
112 volSpreadsMatrix[1][2] = 0.0000;
113 volSpreadsMatrix[1][3] = -0.0024;
114 volSpreadsMatrix[1][4] = 0.0098;
115
116 volSpreadsMatrix[2][0] = 0.0738;
117 volSpreadsMatrix[2][1] = 0.0102;
118 volSpreadsMatrix[2][2] = 0.0000;
119 volSpreadsMatrix[2][3] = -0.0039;
120 volSpreadsMatrix[2][4] = 0.0065;
121
122 volSpreadsMatrix[3][0] = 0.0465;
123 volSpreadsMatrix[3][1] = 0.0063;
124 volSpreadsMatrix[3][2] = 0.0000;
125 volSpreadsMatrix[3][3] = -0.0032;
126 volSpreadsMatrix[3][4] = -0.0010;
127
128 volSpreadsMatrix[4][0] = 0.0558;
129 volSpreadsMatrix[4][1] = 0.0084;
130 volSpreadsMatrix[4][2] = 0.0000;
131 volSpreadsMatrix[4][3] = -0.0050;
132 volSpreadsMatrix[4][4] = -0.0057;
133
134 volSpreadsMatrix[5][0] = 0.0576;
135 volSpreadsMatrix[5][1] = 0.0083;
136 volSpreadsMatrix[5][2] = 0.0000;
137 volSpreadsMatrix[5][3] = -0.0043;
138 volSpreadsMatrix[5][4] = -0.0014;
139
140 volSpreadsMatrix[6][0] = 0.0437;
141 volSpreadsMatrix[6][1] = 0.0059;
142 volSpreadsMatrix[6][2] = 0.0000;
143 volSpreadsMatrix[6][3] = -0.0030;
144 volSpreadsMatrix[6][4] = -0.0006;
145
146 volSpreadsMatrix[7][0] = 0.0533;
147 volSpreadsMatrix[7][1] = 0.0078;
148 volSpreadsMatrix[7][2] = 0.0000;
149 volSpreadsMatrix[7][3] = -0.0045;
150 volSpreadsMatrix[7][4] = -0.0046;
151
152 volSpreadsMatrix[8][0] = 0.0545;
153 volSpreadsMatrix[8][1] = 0.0079;
154 volSpreadsMatrix[8][2] = 0.0000;
155 volSpreadsMatrix[8][3] = -0.0042;
156 volSpreadsMatrix[8][4] = -0.0020;
157
158 std::vector<std::vector<Handle<Quote> > > volSpreads(nRows);
159 for (Size i=0; i<nRows; ++i){
160 volSpreads[i] = std::vector<Handle<Quote> >(nCols);
161 for (Size j=0; j<nCols; ++j) {
162 volSpreads[i][j] = Handle<Quote>(ext::shared_ptr<Quote>(
163 new SimpleQuote(volSpreadsMatrix[i][j])));
164 }
165 }
166
167 iborIndex = ext::shared_ptr<IborIndex>(new Euribor6M(termStructure));
168 ext::shared_ptr<SwapIndex> swapIndexBase(new
169 EuriborSwapIsdaFixA(10*Years, termStructure));
170 ext::shared_ptr<SwapIndex> shortSwapIndexBase(new
171 EuriborSwapIsdaFixA(2*Years, termStructure));
172
173 bool vegaWeightedSmileFit = false;
174
175 SabrVolCube2 = Handle<SwaptionVolatilityStructure>(
176 ext::make_shared<InterpolatedSwaptionVolatilityCube>(args&: atmVol,
177 args&: optionTenors,
178 args&: swapTenors,
179 args&: strikeSpreads,
180 args&: volSpreads,
181 args&: swapIndexBase,
182 args&: shortSwapIndexBase,
183 args&: vegaWeightedSmileFit));
184 SabrVolCube2->enableExtrapolation();
185
186 std::vector<std::vector<Handle<Quote> > > guess(nRows);
187 for (Size i=0; i<nRows; ++i) {
188 guess[i] = std::vector<Handle<Quote> >(4);
189 guess[i][0] =
190 Handle<Quote>(ext::shared_ptr<Quote>(new SimpleQuote(0.2)));
191 guess[i][1] =
192 Handle<Quote>(ext::shared_ptr<Quote>(new SimpleQuote(0.5)));
193 guess[i][2] =
194 Handle<Quote>(ext::shared_ptr<Quote>(new SimpleQuote(0.4)));
195 guess[i][3] =
196 Handle<Quote>(ext::shared_ptr<Quote>(new SimpleQuote(0.0)));
197 }
198 std::vector<bool> isParameterFixed(4, false);
199 isParameterFixed[1] = true;
200
201 // FIXME
202 bool isAtmCalibrated = false;
203
204 SabrVolCube1 = Handle<SwaptionVolatilityStructure>(
205 ext::make_shared<SabrSwaptionVolatilityCube>(args&: atmVol,
206 args&: optionTenors,
207 args&: swapTenors,
208 args&: strikeSpreads,
209 args&: volSpreads,
210 args&: swapIndexBase,
211 args&: shortSwapIndexBase,
212 args&: vegaWeightedSmileFit,
213 args&: guess,
214 args&: isParameterFixed,
215 args&: isAtmCalibrated));
216 SabrVolCube1->enableExtrapolation();
217
218 yieldCurveModels = {GFunctionFactory::Standard,
219 GFunctionFactory::ExactYield,
220 GFunctionFactory::ParallelShifts,
221 GFunctionFactory::NonParallelShifts,
222 GFunctionFactory::NonParallelShifts};
223
224 Handle<Quote> zeroMeanRev(ext::make_shared<SimpleQuote>(args: 0.0));
225
226 numericalPricers.clear();
227 analyticPricers.clear();
228 for (Size j = 0; j < yieldCurveModels.size(); ++j) {
229 if (j < yieldCurveModels.size() - 1)
230 numericalPricers.push_back(
231 x: ext::shared_ptr<CmsCouponPricer>(new NumericHaganPricer(
232 atmVol, yieldCurveModels[j], zeroMeanRev)));
233 else
234 numericalPricers.push_back(x: ext::shared_ptr<CmsCouponPricer>(
235 new LinearTsrPricer(atmVol, zeroMeanRev)));
236
237 analyticPricers.push_back(x: ext::shared_ptr<CmsCouponPricer>(new
238 AnalyticHaganPricer(atmVol, yieldCurveModels[j],
239 zeroMeanRev)));
240 }
241 }
242 };
243
244}
245
246
247void CmsTest::testFairRate() {
248
249 BOOST_TEST_MESSAGE("Testing Hagan-pricer flat-vol equivalence for coupons (lognormal case)...");
250
251 using namespace cms_test;
252
253 CommonVars vars;
254
255 ext::shared_ptr<SwapIndex> swapIndex(new SwapIndex("EuriborSwapIsdaFixA",
256 10*Years,
257 vars.iborIndex->fixingDays(),
258 vars.iborIndex->currency(),
259 vars.iborIndex->fixingCalendar(),
260 1*Years,
261 Unadjusted,
262 vars.iborIndex->dayCounter(),//??
263 vars.iborIndex));
264 // FIXME
265 //ext::shared_ptr<SwapIndex> swapIndex(new
266 // EuriborSwapIsdaFixA(10*Years, vars.iborIndex->termStructure()));
267 Date startDate = vars.termStructure->referenceDate() + 20*Years;
268 Date paymentDate = startDate + 1*Years;
269 Date endDate = paymentDate;
270 Real nominal = 1.0;
271 Rate infiniteCap = Null<Real>();
272 Rate infiniteFloor = Null<Real>();
273 Real gearing = 1.0;
274 Spread spread = 0.0;
275 CappedFlooredCmsCoupon coupon(paymentDate, nominal,
276 startDate, endDate,
277 swapIndex->fixingDays(), swapIndex,
278 gearing, spread,
279 infiniteCap, infiniteFloor,
280 startDate, endDate,
281 vars.iborIndex->dayCounter());
282 for (Size j=0; j<vars.yieldCurveModels.size(); ++j) {
283 vars.numericalPricers[j]->setSwaptionVolatility(vars.atmVol);
284 coupon.setPricer(vars.numericalPricers[j]);
285 Rate rate0 = coupon.rate();
286
287 vars.analyticPricers[j]->setSwaptionVolatility(vars.atmVol);
288 coupon.setPricer(vars.analyticPricers[j]);
289 Rate rate1 = coupon.rate();
290
291 Spread difference = std::fabs(x: rate1-rate0);
292 Spread tol = 2.0e-4;
293 bool linearTsr = j==vars.yieldCurveModels.size()-1;
294
295 if (difference > tol)
296 BOOST_FAIL("\nCoupon payment date: " << paymentDate <<
297 "\nCoupon start date: " << startDate <<
298 "\nCoupon floor: " << io::rate(infiniteFloor) <<
299 "\nCoupon gearing: " << io::rate(gearing) <<
300 "\nCoupon swap index: " << swapIndex->name() <<
301 "\nCoupon spread: " << io::rate(spread) <<
302 "\nCoupon cap: " << io::rate(infiniteCap) <<
303 "\nCoupon DayCounter: " << vars.iborIndex->dayCounter()<<
304 "\nYieldCurve Model: " << vars.yieldCurveModels[j] <<
305 "\nNumerical Pricer: " << io::rate(rate0) <<
306 (linearTsr ? " (Linear TSR Model)" : "") <<
307 "\nAnalytic Pricer: " << io::rate(rate1) <<
308 "\ndifference: " << io::rate(difference) <<
309 "\ntolerance: " << io::rate(tol));
310 }
311}
312
313void CmsTest::testCmsSwap() {
314
315 BOOST_TEST_MESSAGE("Testing Hagan-pricer flat-vol equivalence for swaps (lognormal case)...");
316
317 using namespace cms_test;
318
319 CommonVars vars;
320
321 ext::shared_ptr<SwapIndex> swapIndex(new SwapIndex("EuriborSwapIsdaFixA",
322 10*Years,
323 vars.iborIndex->fixingDays(),
324 vars.iborIndex->currency(),
325 vars.iborIndex->fixingCalendar(),
326 1*Years,
327 Unadjusted,
328 vars.iborIndex->dayCounter(),//??
329 vars.iborIndex));
330 // FIXME
331 //ext::shared_ptr<SwapIndex> swapIndex(new
332 // EuriborSwapIsdaFixA(10*Years, vars.iborIndex->termStructure()));
333 Spread spread = 0.0;
334 std::vector<Size> swapLengths = {1, 5, 6, 10};
335 Size n = swapLengths.size();
336 std::vector<ext::shared_ptr<Swap> > cms(n);
337 for (Size i=0; i<n; ++i)
338 // no cap, floor
339 // no gearing, spread
340 cms[i] = MakeCms(Period(swapLengths[i], Years),
341 swapIndex,
342 vars.iborIndex, spread,
343 10*Days);
344
345 for (Size j=0; j<vars.yieldCurveModels.size(); ++j) {
346 vars.numericalPricers[j]->setSwaptionVolatility(vars.atmVol);
347 vars.analyticPricers[j]->setSwaptionVolatility(vars.atmVol);
348 for (Size sl=0; sl<n; ++sl) {
349 setCouponPricer(leg: cms[sl]->leg(j: 0), vars.numericalPricers[j]);
350 Real priceNum = cms[sl]->NPV();
351 setCouponPricer(leg: cms[sl]->leg(j: 0), vars.analyticPricers[j]);
352 Real priceAn = cms[sl]->NPV();
353
354 Real difference = std::fabs(x: priceNum-priceAn);
355 Real tol = 2.0e-4;
356 bool linearTsr = j==vars.yieldCurveModels.size()-1;
357 if (difference > tol)
358 BOOST_FAIL("\nLength in Years: " << swapLengths[sl] <<
359 //"\nfloor: " << io::rate(infiniteFloor) <<
360 //"\ngearing: " << io::rate(gearing) <<
361 "\nswap index: " << swapIndex->name() <<
362 "\nibor index: " << vars.iborIndex->name() <<
363 "\nspread: " << io::rate(spread) <<
364 //"\ncap: " << io::rate(infiniteCap) <<
365 "\nYieldCurve Model: " << vars.yieldCurveModels[j] <<
366 "\nNumerical Pricer: " << io::rate(priceNum) <<
367 (linearTsr ? " (Linear TSR Model)" : "") <<
368 "\nAnalytic Pricer: " << io::rate(priceAn) <<
369 "\ndifference: " << io::rate(difference) <<
370 "\ntolerance: " << io::rate(tol));
371 }
372 }
373
374}
375
376void CmsTest::testParity() {
377
378 BOOST_TEST_MESSAGE("Testing put-call parity for capped-floored CMS coupons (lognormal case)...");
379
380 using namespace cms_test;
381
382 CommonVars vars;
383
384 std::vector<Handle<SwaptionVolatilityStructure> > swaptionVols = {
385 vars.atmVol, vars.SabrVolCube1, vars.SabrVolCube2};
386
387 ext::shared_ptr<SwapIndex> swapIndex(new
388 EuriborSwapIsdaFixA(10*Years,
389 vars.iborIndex->forwardingTermStructure()));
390 Date startDate = vars.termStructure->referenceDate() + 20*Years;
391 Date paymentDate = startDate + 1*Years;
392 Date endDate = paymentDate;
393 Real nominal = 1.0;
394 Rate infiniteCap = Null<Real>();
395 Rate infiniteFloor = Null<Real>();
396 Real gearing = 1.0;
397 Spread spread = 0.0;
398 DiscountFactor discount = vars.termStructure->discount(d: paymentDate);
399 CappedFlooredCmsCoupon swaplet(paymentDate, nominal,
400 startDate, endDate,
401 swapIndex->fixingDays(),
402 swapIndex,
403 gearing, spread,
404 infiniteCap, infiniteFloor,
405 startDate, endDate,
406 vars.iborIndex->dayCounter());
407 for (Rate strike = .02; strike<.12; strike+=0.05) {
408 CappedFlooredCmsCoupon caplet(paymentDate, nominal,
409 startDate, endDate,
410 swapIndex->fixingDays(),
411 swapIndex,
412 gearing, spread,
413 strike, infiniteFloor,
414 startDate, endDate,
415 vars.iborIndex->dayCounter());
416 CappedFlooredCmsCoupon floorlet(paymentDate, nominal,
417 startDate, endDate,
418 swapIndex->fixingDays(),
419 swapIndex,
420 gearing, spread,
421 infiniteCap, strike,
422 startDate, endDate,
423 vars.iborIndex->dayCounter());
424
425 for (auto& swaptionVol : swaptionVols) {
426 for (Size j=0; j<vars.yieldCurveModels.size(); ++j) {
427 vars.numericalPricers[j]->setSwaptionVolatility(swaptionVol);
428 vars.analyticPricers[j]->setSwaptionVolatility(swaptionVol);
429 std::vector<ext::shared_ptr<CmsCouponPricer> > pricers(2);
430 pricers[0] = vars.numericalPricers[j];
431 pricers[1] = vars.analyticPricers[j];
432 for (Size k=0; k<pricers.size(); ++k) {
433 swaplet.setPricer(pricers[k]);
434 caplet.setPricer(pricers[k]);
435 floorlet.setPricer(pricers[k]);
436 Real swapletPrice = swaplet.price(discountingCurve: vars.termStructure) +
437 nominal * swaplet.accrualPeriod() * strike * discount;
438 Real capletPrice = caplet.price(discountingCurve: vars.termStructure);
439 Real floorletPrice = floorlet.price(discountingCurve: vars.termStructure);
440 Real difference = std::fabs(x: capletPrice + floorletPrice -
441 swapletPrice);
442 Real tol = 2.0e-5;
443 bool linearTsr = k==0 && j==vars.yieldCurveModels.size()-1;
444 if(linearTsr)
445 tol = 1.0e-7;
446 if (difference > tol)
447 BOOST_FAIL("\nCoupon payment date: " << paymentDate <<
448 "\nCoupon start date: " << startDate <<
449 "\nCoupon gearing: " << io::rate(gearing) <<
450 "\nCoupon swap index: " << swapIndex->name() <<
451 "\nCoupon spread: " << io::rate(spread) <<
452 "\nstrike: " << io::rate(strike) <<
453 "\nCoupon DayCounter: " << vars.iborIndex->dayCounter() <<
454 "\nYieldCurve Model: " << vars.yieldCurveModels[j] <<
455 (k==0 ? "\nNumerical Pricer" : "\nAnalytic Pricer") <<
456 (linearTsr ? " (Linear TSR Model)" : "") <<
457 "\nSwaplet price: " << io::rate(swapletPrice) <<
458 "\nCaplet price: " << io::rate(capletPrice) <<
459 "\nFloorlet price: " << io::rate(floorletPrice) <<
460 "\ndifference: " << difference <<
461 "\ntolerance: " << io::rate(tol));
462 }
463 }
464 }
465 }
466}
467
468test_suite* CmsTest::suite() {
469 auto* suite = BOOST_TEST_SUITE("Cms tests");
470 suite->add(QUANTLIB_TEST_CASE(&CmsTest::testFairRate));
471 suite->add(QUANTLIB_TEST_CASE(&CmsTest::testCmsSwap));
472 suite->add(QUANTLIB_TEST_CASE(&CmsTest::testParity));
473 return suite;
474}
475

Provided by KDAB

Privacy Policy
Learn to use CMake with our Intro Training
Find out more

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