Logo
Fully annotated reference manual - version 1.8.12
Loading...
Searching...
No Matches
Namespaces | Typedefs | Functions
piecewiseatmoptionletcurve.cpp File Reference
#include <boost/assign.hpp>
#include <boost/test/unit_test.hpp>
#include <boost/test/data/test_case.hpp>
#include <boost/variant.hpp>
#include <test/capfloormarketdata.hpp>
#include <test/toplevelfixture.hpp>
#include <test/yieldcurvemarketdata.hpp>
#include <ql/instruments/makecapfloor.hpp>
#include <ql/pricingengines/capfloor/bacheliercapfloorengine.hpp>
#include <ql/pricingengines/capfloor/blackcapfloorengine.hpp>
#include <ql/time/date.hpp>
#include <ql/tuple.hpp>
#include <qle/math/flatextrapolation.hpp>
#include <qle/termstructures/capfloorhelper.hpp>
#include <qle/termstructures/capfloortermvolcurve.hpp>
#include <qle/termstructures/piecewiseatmoptionletcurve.hpp>

Go to the source code of this file.

Namespaces

namespace  boost
 
namespace  boost::test_tools
 
namespace  boost::test_tools::tt_detail
 
namespace  boost::unit_test
 
namespace  boost::unit_test::data
 
namespace  boost::unit_test::data::monomorphic
 

Typedefs

typedef QuantLib::BootstrapHelper< QuantLib::OptionletVolatilityStructure > helper
 

Functions

 BOOST_DATA_TEST_CASE_F (CommonVars, testPiecewiseAtmOptionletStripping, bdata::make(generateAtmVolData()) *InterpDataset() *bdata::make(isMovingValues) *bdata::make(flatFirstPeriodValues), atmVolData, interpSample, isMoving, flatFirstPeriod)
 
 BOOST_FIXTURE_TEST_CASE (testAtmStrippingExceptions, CommonVars)
 

Typedef Documentation

◆ helper

typedef QuantLib::BootstrapHelper<QuantLib::OptionletVolatilityStructure> helper

Definition at line 45 of file piecewiseatmoptionletcurve.cpp.

Function Documentation

◆ BOOST_DATA_TEST_CASE_F()

BOOST_DATA_TEST_CASE_F ( CommonVars  ,
testPiecewiseAtmOptionletStripping  ,
bdata::make(generateAtmVolData()) *InterpDataset() *bdata::make(isMovingValues) *bdata::make(flatFirstPeriodValues)  ,
atmVolData  ,
interpSample  ,
isMoving  ,
flatFirstPeriod   
)

Definition at line 235 of file piecewiseatmoptionletcurve.cpp.

238 {
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}

◆ BOOST_FIXTURE_TEST_CASE()

BOOST_FIXTURE_TEST_CASE ( testAtmStrippingExceptions  ,
CommonVars   
)

Definition at line 488 of file piecewiseatmoptionletcurve.cpp.

488 {
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}
Linear-interpolation and flat extrapolation factory and traits