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 | |
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 | // 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 | |
247 | void 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 | |
313 | void 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 | |
376 | void 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 | |
468 | test_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 | |