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