Logo
Fully annotated reference manual - version 1.8.12
Loading...
Searching...
No Matches
piecewiseatmoptionletcurve.cpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2019 Quaternion Risk Management Ltd
3 All rights reserved.
4
5 This file is part of ORE, a free-software/open-source library
6 for transparent pricing and risk analysis - http://opensourcerisk.org
7
8 ORE is free software: you can redistribute it and/or modify it
9 under the terms of the Modified BSD License. You should have received a
10 copy of the license along with this program.
11 The license is also available online at <http://opensourcerisk.org>
12
13 This program is distributed on the basis that it will form a useful
14 contribution to risk analytics and model standardisation, but WITHOUT
15 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
16 FITNESS FOR A PARTICULAR PURPOSE. See the license for more details.
17*/
18
19#include <boost/assign.hpp>
20// clang-format off
21#include <boost/test/unit_test.hpp>
22#include <boost/test/data/test_case.hpp>
23// clang-format on
24#include <boost/variant.hpp>
25
29
30#include <ql/instruments/makecapfloor.hpp>
31#include <ql/pricingengines/capfloor/bacheliercapfloorengine.hpp>
32#include <ql/pricingengines/capfloor/blackcapfloorengine.hpp>
33#include <ql/time/date.hpp>
34#include <ql/tuple.hpp>
39
40using namespace QuantExt;
41using namespace QuantLib;
42using namespace boost::unit_test_framework;
43
45typedef QuantLib::BootstrapHelper<QuantLib::OptionletVolatilityStructure> helper;
46using boost::assign::list_of;
47using boost::assign::map_list_of;
48using std::boolalpha;
49using std::fixed;
50using std::map;
51using std::ostream;
52using std::pair;
53using std::setprecision;
54using std::string;
55using std::vector;
56
57namespace {
58
59// Variables to be used in the test
60struct CommonVars : public qle::test::TopLevelFixture {
61
62 // Constructor
63 CommonVars()
64 : referenceDate(5, Feb, 2016), settlementDays(0), calendar(TARGET()), bdc(Following),
65 dayCounter(Actual365Fixed()), accuracy(1.0e-12), tolerance(1.0e-10) {
66
67 // Reference date
68 Settings::instance().evaluationDate() = referenceDate;
69
70 // Set cap floor ibor index to EUR-EURIBOR-6M and attach a forwarding curve
71 iborIndex = QuantLib::ext::make_shared<Euribor6M>(testYieldCurves.forward6M);
72 }
73
74 // Valuation date for the test
75 Date referenceDate;
76
77 // Variables used in the optionlet volatility structure creation
78 Natural settlementDays;
79 Calendar calendar;
80 BusinessDayConvention bdc;
81 DayCounter dayCounter;
82
83 // Accuracy for optionlet stripping
84 Real accuracy;
85
86 // Test tolerance for comparing the NPVs
87 Real tolerance;
88
89 // Cap floor ibor index
90 QuantLib::ext::shared_ptr<IborIndex> iborIndex;
91
92 // EUR discount curve test data from file test/yieldcurvemarketdata.hpp
93 YieldCurveEUR testYieldCurves;
94};
95
96// Struct to hold a cap floor volatility column and some associated meta data
97struct AtmVolData {
98 vector<Period> tenors;
99 vector<Real> volatilities;
100 VolatilityType type;
101 Real displacement;
102};
103
104// Needed for data driven test case below as it writes out the VolatilityColumn
105ostream& operator<<(ostream& os, const AtmVolData& vc) {
106 return os << "Atm volatility data with volatility type: " << vc.type << ", shift: " << vc.displacement;
107}
108
109// From the EUR cap floor test volatility data in file test/capfloormarketdata.hpp, create a vector of
110// AtmVolData which will be the data in the data driven test below
111vector<AtmVolData> generateAtmVolData() {
112
113 // Will hold the generated ATM volatility data
114 vector<AtmVolData> data;
115
116 // EUR cap floor test volatility data from file test/capfloormarketdata.hpp
117 CapFloorVolatilityEUR testVols;
118
119 // All the ATM data has shared tenors
120 AtmVolData datum;
121 datum.tenors = testVols.atmTenors;
122
123 // The normal ATM volatility data
124 datum.volatilities = testVols.nAtmVols;
125 datum.type = Normal;
126 datum.displacement = 0.0;
127 data.push_back(datum);
128
129 // The shifted lognormal volatilities with shift 1
130 datum.volatilities = testVols.slnAtmVols_1;
131 datum.type = ShiftedLognormal;
132 datum.displacement = testVols.shift_1;
133 data.push_back(datum);
134
135 // The shifted lognormal volatilities with shift 2
136 datum.volatilities = testVols.slnAtmVols_2;
137 datum.type = ShiftedLognormal;
138 datum.displacement = testVols.shift_2;
139 data.push_back(datum);
140
141 return data;
142}
143
144// Interpolation types for the data driven test case
145typedef boost::variant<Linear, BackwardFlat, QuantExt::LinearFlat, Cubic, QuantExt::CubicFlat> InterpolationType;
146
147// BackwardFlat does not work well with interpolation on term cap floors so this allows us to exclude it
148class InterpDataset {
149public:
150 enum { arity = 1 };
151
152 InterpDataset() {
153 samples_.push_back(QuantLib::ext::make_tuple(InterpolationType(Linear()), true));
154 samples_.push_back(QuantLib::ext::make_tuple(InterpolationType(QuantExt::LinearFlat()), true));
155 samples_.push_back(QuantLib::ext::make_tuple(InterpolationType(Cubic()), true));
156 samples_.push_back(QuantLib::ext::make_tuple(InterpolationType(QuantExt::CubicFlat()), true));
157 samples_.push_back(QuantLib::ext::make_tuple(InterpolationType(BackwardFlat()), true));
158 samples_.push_back(QuantLib::ext::make_tuple(InterpolationType(Linear()), false));
159 samples_.push_back(QuantLib::ext::make_tuple(InterpolationType(QuantExt::LinearFlat()), false));
160 samples_.push_back(QuantLib::ext::make_tuple(InterpolationType(Cubic()), false));
161 samples_.push_back(QuantLib::ext::make_tuple(InterpolationType(QuantExt::CubicFlat()), false));
162 }
163
164 bdata::size_t size() const { return samples_.size(); }
165
166 typedef vector<QuantLib::ext::tuple<InterpolationType, bool> >::const_iterator iterator;
167 iterator begin() const { return samples_.begin(); }
168
169private:
170 vector<QuantLib::ext::tuple<InterpolationType, bool> > samples_;
171};
172
173// If the built optionlet structure in the test has a floating or fixed reference date
174vector<bool> isMovingValues = list_of(true)(false);
175
176// If the optionlet structure has a flat first period or not
177vector<bool> flatFirstPeriodValues = list_of(true)(false);
178
179// So that I can reuse below
180string to_string(const InterpolationType& interpolationType) {
181 string result;
182 switch (interpolationType.which()) {
183 case 0:
184 result = "Linear";
185 break;
186 case 1:
187 result = "BackwardFlat";
188 break;
189 case 2:
190 result = "LinearFlat";
191 break;
192 case 3:
193 result = "Cubic";
194 break;
195 case 4:
196 result = "CubicFlat";
197 break;
198 default:
199 BOOST_FAIL("Unexpected interpolation type");
200 }
201 return result;
202}
203
204} // namespace
205
206// Needed for BOOST_DATA_TEST_CASE below
207// https://stackoverflow.com/a/33965517/1771882
208namespace boost {
209
210namespace test_tools {
211namespace tt_detail {
212template <> struct print_log_value<QuantLib::ext::tuple<InterpolationType, bool> > {
213 void operator()(ostream& os, const QuantLib::ext::tuple<InterpolationType, bool>& tp) {
214 os << to_string(QuantLib::ext::get<0>(tp)) << ", " << boolalpha << QuantLib::ext::get<1>(tp);
215 }
216};
217} // namespace tt_detail
218} // namespace test_tools
219
220namespace unit_test {
221namespace data {
222namespace monomorphic {
223// Registering InterpDataset as a dataset
224template <> struct is_dataset<InterpDataset> : boost::mpl::true_ {};
225} // namespace monomorphic
226} // namespace data
227} // namespace unit_test
228
229} // namespace boost
230
231BOOST_FIXTURE_TEST_SUITE(QuantExtTestSuite, qle::test::TopLevelFixture)
232
233BOOST_AUTO_TEST_SUITE(PiecewiseAtmOptionletCurveTests)
234
235BOOST_DATA_TEST_CASE_F(CommonVars, testPiecewiseAtmOptionletStripping,
236 bdata::make(generateAtmVolData()) * InterpDataset() * bdata::make(isMovingValues) *
237 bdata::make(flatFirstPeriodValues),
238 atmVolData, interpSample, isMoving, flatFirstPeriod) {
239
240 BOOST_TEST_MESSAGE("Testing piecewise optionlet stripping of ATM cap floor curve");
241
242 InterpolationType interpolationType = QuantLib::ext::get<0>(interpSample);
243 bool interpOnOptionlets = QuantLib::ext::get<1>(interpSample);
244
245 BOOST_TEST_MESSAGE("Test inputs are:");
246 BOOST_TEST_MESSAGE(" Quote volatility type: " << atmVolData.type);
247 BOOST_TEST_MESSAGE(" Quote displacement: " << atmVolData.displacement);
248 BOOST_TEST_MESSAGE(" Interpolation type: " << to_string(interpolationType));
249 BOOST_TEST_MESSAGE(" Interp on optionlets: " << boolalpha << interpOnOptionlets);
250 BOOST_TEST_MESSAGE(" Floating reference date: " << boolalpha << isMoving);
251 BOOST_TEST_MESSAGE(" Flat first period: " << boolalpha << flatFirstPeriod);
252
253 // Store each cap floor instrument in the strike column and its NPV using the flat cap floor volatilities
254 vector<QuantLib::ext::shared_ptr<CapFloor> > instruments(atmVolData.tenors.size());
255 vector<Real> flatNpvs(atmVolData.tenors.size());
256
257 // Store the ATM volatility quotes and handles
258 vector<QuantLib::ext::shared_ptr<SimpleQuote> > volQuotes(atmVolData.tenors.size());
259 vector<Handle<Quote> > volHandles(atmVolData.tenors.size());
260
261 BOOST_TEST_MESSAGE("The input values at each tenor are:");
262 for (Size i = 0; i < atmVolData.tenors.size(); i++) {
263
264 // Get the relevant quote value
265 Real volatility = atmVolData.volatilities[i];
266 volQuotes[i] = QuantLib::ext::make_shared<SimpleQuote>(volatility);
267 volHandles[i] = Handle<Quote>(volQuotes[i]);
268
269 // Create the ATM cap instrument and store its price using the quoted flat volatility
270 instruments[i] = MakeCapFloor(CapFloor::Cap, atmVolData.tenors[i], iborIndex, 0.01);
271 Rate atm = instruments[i]->atmRate(**testYieldCurves.discountEonia);
272 instruments[i] = MakeCapFloor(CapFloor::Cap, atmVolData.tenors[i], iborIndex, atm);
273 if (atmVolData.type == ShiftedLognormal) {
274 instruments[i]->setPricingEngine(QuantLib::ext::make_shared<BlackCapFloorEngine>(
275 testYieldCurves.discountEonia, volatility, dayCounter, atmVolData.displacement));
276 } else {
277 instruments[i]->setPricingEngine(
278 QuantLib::ext::make_shared<BachelierCapFloorEngine>(testYieldCurves.discountEonia, volatility, dayCounter));
279 }
280 flatNpvs[i] = instruments[i]->NPV();
281
282 BOOST_TEST_MESSAGE(" (Cap/Floor, Tenor, Strike, Volatility, Flat NPV) = (Cap, "
283 << atmVolData.tenors[i] << ", " << atm << ", " << fixed << setprecision(13) << volatility
284 << ", " << flatNpvs[i] << ")");
285 }
286
287 // Create the ATM optionlet curve, with the given interpolation type.
288 // Doesn't matter what interpolation we use here for the cap floor term surface so just use the same
289 // interpolation as the optionlet curve being stripped
290 VolatilityType curveVolatilityType = Normal;
291 Real curveDisplacement = 0.0;
292 QuantLib::ext::shared_ptr<CapFloorTermVolCurve> cftvc;
293 QuantLib::ext::shared_ptr<OptionletVolatilityStructure> ovCurve;
294 switch (interpolationType.which()) {
295 case 0:
296 if (isMoving) {
297 BOOST_TEST_MESSAGE("Using Linear interpolation with a moving reference date");
298 cftvc = QuantLib::ext::make_shared<InterpolatedCapFloorTermVolCurve<Linear> >(
299 settlementDays, calendar, bdc, atmVolData.tenors, volHandles, dayCounter, flatFirstPeriod);
300 ovCurve = QuantLib::ext::make_shared<PiecewiseAtmOptionletCurve<Linear> >(
301 settlementDays, cftvc, iborIndex, testYieldCurves.discountEonia, flatFirstPeriod, atmVolData.type,
302 atmVolData.displacement, curveVolatilityType, curveDisplacement, interpOnOptionlets);
303 } else {
304 BOOST_TEST_MESSAGE("Using Linear interpolation with a fixed reference date");
305 cftvc = QuantLib::ext::make_shared<InterpolatedCapFloorTermVolCurve<Linear> >(
306 referenceDate, calendar, bdc, atmVolData.tenors, volHandles, dayCounter, flatFirstPeriod);
307 ovCurve = QuantLib::ext::make_shared<PiecewiseAtmOptionletCurve<Linear> >(
308 referenceDate, cftvc, iborIndex, testYieldCurves.discountEonia, flatFirstPeriod, atmVolData.type,
309 atmVolData.displacement, curveVolatilityType, curveDisplacement, interpOnOptionlets);
310 }
311 break;
312 case 1:
313 if (isMoving) {
314 BOOST_TEST_MESSAGE("Using BackwardFlat interpolation with a moving reference date");
315 cftvc = QuantLib::ext::make_shared<InterpolatedCapFloorTermVolCurve<BackwardFlat> >(
316 settlementDays, calendar, bdc, atmVolData.tenors, volHandles, dayCounter, flatFirstPeriod);
317 ovCurve = QuantLib::ext::make_shared<PiecewiseAtmOptionletCurve<BackwardFlat> >(
318 settlementDays, cftvc, iborIndex, testYieldCurves.discountEonia, flatFirstPeriod, atmVolData.type,
319 atmVolData.displacement, curveVolatilityType, curveDisplacement, interpOnOptionlets);
320 } else {
321 BOOST_TEST_MESSAGE("Using BackwardFlat interpolation with a fixed reference date");
322 cftvc = QuantLib::ext::make_shared<InterpolatedCapFloorTermVolCurve<BackwardFlat> >(
323 referenceDate, calendar, bdc, atmVolData.tenors, volHandles, dayCounter, flatFirstPeriod);
324 ovCurve = QuantLib::ext::make_shared<PiecewiseAtmOptionletCurve<BackwardFlat> >(
325 referenceDate, cftvc, iborIndex, testYieldCurves.discountEonia, flatFirstPeriod, atmVolData.type,
326 atmVolData.displacement, curveVolatilityType, curveDisplacement, interpOnOptionlets);
327 }
328 break;
329 case 2:
330 if (isMoving) {
331 BOOST_TEST_MESSAGE("Using LinearFlat interpolation with a moving reference date");
332 cftvc = QuantLib::ext::make_shared<InterpolatedCapFloorTermVolCurve<LinearFlat> >(
333 settlementDays, calendar, bdc, atmVolData.tenors, volHandles, dayCounter, flatFirstPeriod);
334 ovCurve = QuantLib::ext::make_shared<PiecewiseAtmOptionletCurve<LinearFlat> >(
335 settlementDays, cftvc, iborIndex, testYieldCurves.discountEonia, flatFirstPeriod, atmVolData.type,
336 atmVolData.displacement, curveVolatilityType, curveDisplacement, interpOnOptionlets);
337 } else {
338 BOOST_TEST_MESSAGE("Using LinearFlat interpolation with a fixed reference date");
339 cftvc = QuantLib::ext::make_shared<InterpolatedCapFloorTermVolCurve<LinearFlat> >(
340 referenceDate, calendar, bdc, atmVolData.tenors, volHandles, dayCounter, flatFirstPeriod);
341 ovCurve = QuantLib::ext::make_shared<PiecewiseAtmOptionletCurve<LinearFlat> >(
342 referenceDate, cftvc, iborIndex, testYieldCurves.discountEonia, flatFirstPeriod, atmVolData.type,
343 atmVolData.displacement, curveVolatilityType, curveDisplacement, interpOnOptionlets);
344 }
345 break;
346 case 3:
347 if (isMoving) {
348 BOOST_TEST_MESSAGE("Using Cubic interpolation with a moving reference date");
349 cftvc = QuantLib::ext::make_shared<InterpolatedCapFloorTermVolCurve<Cubic> >(
350 settlementDays, calendar, bdc, atmVolData.tenors, volHandles, dayCounter, flatFirstPeriod);
351 ovCurve = QuantLib::ext::make_shared<PiecewiseAtmOptionletCurve<Cubic> >(
352 settlementDays, cftvc, iborIndex, testYieldCurves.discountEonia, flatFirstPeriod, atmVolData.type,
353 atmVolData.displacement, curveVolatilityType, curveDisplacement, interpOnOptionlets);
354 } else {
355 BOOST_TEST_MESSAGE("Using Cubic interpolation with a fixed reference date");
356 cftvc = QuantLib::ext::make_shared<InterpolatedCapFloorTermVolCurve<Cubic> >(
357 referenceDate, calendar, bdc, atmVolData.tenors, volHandles, dayCounter, flatFirstPeriod);
358 ovCurve = QuantLib::ext::make_shared<PiecewiseAtmOptionletCurve<Cubic> >(
359 referenceDate, cftvc, iborIndex, testYieldCurves.discountEonia, flatFirstPeriod, atmVolData.type,
360 atmVolData.displacement, curveVolatilityType, curveDisplacement, interpOnOptionlets);
361 }
362 break;
363 case 4:
364 if (isMoving) {
365 BOOST_TEST_MESSAGE("Using CubicFlat interpolation with a moving reference date");
366 cftvc = QuantLib::ext::make_shared<InterpolatedCapFloorTermVolCurve<CubicFlat> >(
367 settlementDays, calendar, bdc, atmVolData.tenors, volHandles, dayCounter, flatFirstPeriod);
368 ovCurve = QuantLib::ext::make_shared<PiecewiseAtmOptionletCurve<CubicFlat> >(
369 settlementDays, cftvc, iborIndex, testYieldCurves.discountEonia, flatFirstPeriod, atmVolData.type,
370 atmVolData.displacement, curveVolatilityType, curveDisplacement, interpOnOptionlets);
371 } else {
372 BOOST_TEST_MESSAGE("Using CubicFlat interpolation with a fixed reference date");
373 cftvc = QuantLib::ext::make_shared<InterpolatedCapFloorTermVolCurve<CubicFlat> >(
374 referenceDate, calendar, bdc, atmVolData.tenors, volHandles, dayCounter, flatFirstPeriod);
375 ovCurve = QuantLib::ext::make_shared<PiecewiseAtmOptionletCurve<CubicFlat> >(
376 referenceDate, cftvc, iborIndex, testYieldCurves.discountEonia, flatFirstPeriod, atmVolData.type,
377 atmVolData.displacement, curveVolatilityType, curveDisplacement, interpOnOptionlets);
378 }
379 break;
380 default:
381 BOOST_FAIL("Unexpected interpolation type");
382 }
383 Handle<OptionletVolatilityStructure> hovs(ovCurve);
384
385 // Price each cap floor instrument using the piecewise optionlet curve and check it against the flat NPV
386 BOOST_TEST_MESSAGE("The stripped values and differences at each tenor are:");
387 Real strippedNpv;
388 for (Size i = 0; i < atmVolData.tenors.size(); i++) {
389
390 // Price the instrument using the stripped optionlet structure
391 if (ovCurve->volatilityType() == ShiftedLognormal) {
392 instruments[i]->setPricingEngine(
393 QuantLib::ext::make_shared<BlackCapFloorEngine>(testYieldCurves.discountEonia, hovs));
394 } else {
395 instruments[i]->setPricingEngine(
396 QuantLib::ext::make_shared<BachelierCapFloorEngine>(testYieldCurves.discountEonia, hovs));
397 }
398 strippedNpv = instruments[i]->NPV();
399
400 BOOST_TEST_MESSAGE(" (Cap/Floor, Tenor, Strike, Volatility, Flat NPV, Stripped NPV, Flat - Stripped) = ("
401 << instruments[i]->type() << ", " << atmVolData.tenors[i] << ", "
402 << instruments[i]->capRates()[0] << ", " << fixed << setprecision(13)
403 << atmVolData.volatilities[i] << ", " << flatNpvs[i] << ", " << strippedNpv << ", "
404 << (flatNpvs[i] - strippedNpv) << ")");
405
406 BOOST_CHECK_SMALL(fabs(flatNpvs[i] - strippedNpv), tolerance);
407 }
408
409 // Pick 10Y (arbitrary choice - 5th element) ATM vol, bump it and ensure stripping still works.
410 BOOST_TEST_MESSAGE("Testing that stripping still works after bumping volatility quote");
411 Size idx = 4;
412 Volatility bumpedVol = volQuotes[idx]->value() * 1.10;
413 volQuotes[idx]->setValue(bumpedVol);
414 strippedNpv = instruments[idx]->NPV();
415 if (atmVolData.type == ShiftedLognormal) {
416 instruments[idx]->setPricingEngine(QuantLib::ext::make_shared<BlackCapFloorEngine>(
417 testYieldCurves.discountEonia, bumpedVol, dayCounter, atmVolData.displacement));
418 } else {
419 instruments[idx]->setPricingEngine(
420 QuantLib::ext::make_shared<BachelierCapFloorEngine>(testYieldCurves.discountEonia, bumpedVol, dayCounter));
421 }
422 Real newFlatNpv = instruments[idx]->NPV();
423 BOOST_TEST_MESSAGE(" (Cap/Floor, Tenor, Strike, Volatility, Flat NPV, Stripped NPV, Flat - Stripped) = ("
424 << instruments[idx]->type() << ", " << atmVolData.tenors[idx] << ", "
425 << instruments[idx]->capRates()[0] << ", " << fixed << setprecision(13) << bumpedVol << ", "
426 << newFlatNpv << ", " << strippedNpv << ", " << (newFlatNpv - strippedNpv) << ")");
427 BOOST_CHECK_GT(newFlatNpv, flatNpvs[idx]);
428 BOOST_CHECK_SMALL(fabs(newFlatNpv - strippedNpv), tolerance);
429
430 BOOST_TEST_MESSAGE("Test extrapolation settings with out of range date");
431 Date oorDate = ovCurve->maxDate() + 1 * Months;
432 BOOST_CHECK_NO_THROW(ovCurve->volatility(oorDate, 0.01, true));
433 BOOST_CHECK_THROW(ovCurve->volatility(oorDate, 0.01), Error);
434 ovCurve->enableExtrapolation();
435 BOOST_CHECK_NO_THROW(ovCurve->volatility(oorDate, 0.01));
436
437 BOOST_TEST_MESSAGE("Test term structure stripping still works after changing evaluation date");
438 Date newDate = calendar.advance(referenceDate, 1 * Months);
439 Settings::instance().evaluationDate() = newDate;
440
441 for (Size i = 0; i < atmVolData.tenors.size(); i++) {
442
443 Real volatility = volQuotes[i]->value();
444
445 // Cap floor set up is different depending on whether we are testing the moving term structure or not.
446 // Empty startDate, i.e. moving, means that cap floor will be relative to the global evaluation date.
447 // If not moving, we keep the instrument's original start date.
448 Date startDate;
449 if (!isMoving) {
450 startDate = iborIndex->fixingCalendar().adjust(referenceDate);
451 startDate = iborIndex->fixingCalendar().advance(startDate, iborIndex->fixingDays() * Days);
452 }
453 instruments[i] =
454 MakeCapFloor(CapFloor::Cap, atmVolData.tenors[i], iborIndex, 0.01).withEffectiveDate(startDate, true);
455 Rate atm = instruments[i]->atmRate(**testYieldCurves.discountEonia);
456 instruments[i] =
457 MakeCapFloor(CapFloor::Cap, atmVolData.tenors[i], iborIndex, atm).withEffectiveDate(startDate, true);
458
459 if (atmVolData.type == ShiftedLognormal) {
460 instruments[i]->setPricingEngine(QuantLib::ext::make_shared<BlackCapFloorEngine>(
461 testYieldCurves.discountEonia, volatility, dayCounter, atmVolData.displacement));
462 } else {
463 instruments[i]->setPricingEngine(
464 QuantLib::ext::make_shared<BachelierCapFloorEngine>(testYieldCurves.discountEonia, volatility, dayCounter));
465 }
466 newFlatNpv = instruments[i]->NPV();
467
468 // Price the instrument using the stripped optionlet structure
469 if (ovCurve->volatilityType() == ShiftedLognormal) {
470 instruments[i]->setPricingEngine(
471 QuantLib::ext::make_shared<BlackCapFloorEngine>(testYieldCurves.discountEonia, hovs));
472 } else {
473 instruments[i]->setPricingEngine(
474 QuantLib::ext::make_shared<BachelierCapFloorEngine>(testYieldCurves.discountEonia, hovs));
475 }
476 strippedNpv = instruments[i]->NPV();
477
478 BOOST_TEST_MESSAGE(" (Cap/Floor, Tenor, Strike, Volatility, Flat NPV, Stripped NPV, Flat - Stripped) = ("
479 << instruments[i]->type() << ", " << atmVolData.tenors[i] << ", "
480 << instruments[i]->capRates()[0] << ", " << fixed << setprecision(13)
481 << volQuotes[i]->value() << ", " << newFlatNpv << ", " << strippedNpv << ", "
482 << (newFlatNpv - strippedNpv) << ")");
483
484 BOOST_CHECK_SMALL(fabs(newFlatNpv - strippedNpv), tolerance);
485 }
486}
487
488BOOST_FIXTURE_TEST_CASE(testAtmStrippingExceptions, CommonVars) {
489
490 BOOST_TEST_MESSAGE("Testing ATM stripping exception behaviour");
491
492 // Use the normal ATM volatility test data
493 CapFloorVolatilityEUR testVols;
494 vector<Period> tenors = testVols.atmTenors;
495 vector<Real> volatilities = testVols.nAtmVols;
496 VolatilityType type = Normal;
497 Real displacement = 0.0;
498
499 // Store the ATM volatility quotes and handles
500 vector<QuantLib::ext::shared_ptr<SimpleQuote> > volQuotes(tenors.size());
501 vector<Handle<Quote> > volHandles(tenors.size());
502 for (Size i = 0; i < tenors.size(); i++) {
503 Real volatility = volatilities[i];
504 volQuotes[i] = QuantLib::ext::make_shared<SimpleQuote>(volatility);
505 volHandles[i] = Handle<Quote>(volQuotes[i]);
506 }
507
508 // Get configuration values for bootstrap
509 Real globalAccuracy = 1e-10;
510 bool dontThrow = false;
511
512 // Cap floor term curve
513 QuantLib::ext::shared_ptr<CapFloorTermVolCurve> cftvc = QuantLib::ext::make_shared<InterpolatedCapFloorTermVolCurve<LinearFlat> >(
514 settlementDays, calendar, bdc, tenors, volHandles, dayCounter, true);
515
516 // Piecewise curve
517 VolatilityType curveVolatilityType = Normal;
518 Real curveDisplacement = 0.0;
519 bool interpOnOptionlets = true;
520 QuantLib::ext::shared_ptr<OptionletVolatilityStructure> ovCurve =
521 QuantLib::ext::make_shared<PiecewiseAtmOptionletCurve<LinearFlat> >(
522 settlementDays, cftvc, iborIndex, testYieldCurves.discountEonia, true, type, displacement,
523 curveVolatilityType, curveDisplacement, interpOnOptionlets, LinearFlat(),
526 accuracy, globalAccuracy, dontThrow));
527
528 // Checks
529 Period oneYear = 1 * Years;
530 Period fiveYears = 5 * Years;
531 Period eightYears = 8 * Years;
532 Volatility oneYearVol;
533 Volatility fiveYearVol;
534 Volatility eightYearVol;
535 BOOST_CHECK_NO_THROW(oneYearVol = ovCurve->volatility(oneYear, 0.01, true));
536 BOOST_TEST_MESSAGE("1Y vol: " << oneYearVol);
537 BOOST_CHECK_NO_THROW(fiveYearVol = ovCurve->volatility(fiveYears, 0.01, true));
538 BOOST_TEST_MESSAGE("5Y vol: " << fiveYearVol);
539 BOOST_CHECK_NO_THROW(eightYearVol = ovCurve->volatility(eightYears, 0.01, true));
540 BOOST_TEST_MESSAGE("8Y vol: " << eightYearVol);
541
542 // Increase the 5Y volatility to introduce an exception. Can't bootstrap the 7Y and 10Y caps as a result
543 volQuotes[2]->setValue(2 * volQuotes[2]->value());
544 BOOST_CHECK_THROW(ovCurve->volatility(fiveYears, 0.01, true), Error);
545
546 // Rebuild ovCurve with dontThrow set to true
547 dontThrow = true;
548 ovCurve = QuantLib::ext::make_shared<PiecewiseAtmOptionletCurve<LinearFlat> >(
549 settlementDays, cftvc, iborIndex, testYieldCurves.discountEonia, true, type, displacement, curveVolatilityType,
550 curveDisplacement, interpOnOptionlets, LinearFlat(),
553 accuracy, globalAccuracy, dontThrow));
554
555 // Check bootstrap passes and check the values.
556 // - the 1Y optionlet volatility should not have been affected
557 // - the 5Y optionlet volatility should have increased
558 Volatility testVol = 0.0;
559 BOOST_CHECK_NO_THROW(testVol = ovCurve->volatility(oneYear, 0.01, true));
560 BOOST_CHECK_SMALL(fabs(testVol - oneYearVol), tolerance);
561 BOOST_TEST_MESSAGE("1Y vol after bump using previous: " << testVol);
562 BOOST_CHECK_NO_THROW(testVol = ovCurve->volatility(fiveYears, 0.01, true));
563 BOOST_CHECK_GT(testVol, fiveYearVol);
564 BOOST_TEST_MESSAGE("5Y vol after bump using previous: " << testVol);
565}
566
567BOOST_AUTO_TEST_SUITE_END()
568
569BOOST_AUTO_TEST_SUITE_END()
Helper for bootstrapping optionlet volatilities from cap floor volatilities.
structs containing capfloor market data that can be used in tests
Cap floor at-the-money term volatility curve.
Cubic interpolation and flat extrapolation factory and traits.
Linear-interpolation and flat extrapolation factory and traits
flat interpolation decorator
std::ostream & operator<<(std::ostream &out, EquityReturnType t)
BOOST_FIXTURE_TEST_CASE(testAtmStrippingExceptions, CommonVars)
BOOST_DATA_TEST_CASE_F(CommonVars, testPiecewiseAtmOptionletStripping, bdata::make(generateAtmVolData()) *InterpDataset() *bdata::make(isMovingValues) *bdata::make(flatFirstPeriodValues), atmVolData, interpSample, isMoving, flatFirstPeriod)
QuantLib::BootstrapHelper< QuantLib::OptionletVolatilityStructure > helper
Create optionlet volatility structure from at-the-money cap floor term volatility curve.
Fixture that can be used at top level.
structs containing yield curve market data that can be used in tests