Logo
Fully annotated reference manual - version 1.8.12
Loading...
Searching...
No Matches
Namespaces | Typedefs | Functions
piecewiseoptionletcurve.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 <qle/math/flatextrapolation.hpp>
#include <qle/termstructures/capfloorhelper.hpp>
#include <qle/termstructures/piecewiseoptionletcurve.hpp>

Go to the source code of this file.

Namespaces

namespace  boost
 
namespace  boost::test_tools
 
namespace  boost::test_tools::tt_detail
 

Typedefs

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

Functions

 BOOST_DATA_TEST_CASE_F (CommonVars, testPiecewiseOptionletStripping, bdata::make(generateVolatilityColumns()) *bdata::make(helperTypes) *bdata::make(quoteTypes) *bdata::make(interpolationTypes) *bdata::make(isMovingValues) *bdata::make(flatFirstPeriodValues), volatilityColumn, helperType, quoteType, interpolationType, isMoving, flatFirstPeriod)
 
 BOOST_DATA_TEST_CASE_F (CommonVars, testCachedValues, bdata::make(interpolationTypesCached) *bdata::make(flatFirstPeriodValues), interpolationType, flatFirstPeriod)
 

Typedef Documentation

◆ helper

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

Definition at line 43 of file piecewiseoptionletcurve.cpp.

Function Documentation

◆ BOOST_DATA_TEST_CASE_F() [1/2]

BOOST_DATA_TEST_CASE_F ( CommonVars  ,
testPiecewiseOptionletStripping  ,
bdata::make(generateVolatilityColumns()) *bdata::make(helperTypes) *bdata::make(quoteTypes) *bdata::make(interpolationTypes) *bdata::make(isMovingValues) *bdata::make(flatFirstPeriodValues)  ,
volatilityColumn  ,
helperType  ,
quoteType  ,
interpolationType  ,
isMoving  ,
flatFirstPeriod   
)

Definition at line 269 of file piecewiseoptionletcurve.cpp.

273 {
274
275 BOOST_TEST_MESSAGE("Testing piecewise optionlet stripping of cap floor quotes along a strike column");
276
277 BOOST_TEST_MESSAGE("Test inputs are:");
278 BOOST_TEST_MESSAGE(" Cap floor helper type: " << helperType);
279 BOOST_TEST_MESSAGE(" Cap floor strike: " << volatilityColumn.strike);
280 BOOST_TEST_MESSAGE(" Quote type: " << quoteType);
281 if (quoteType == CapFloorHelper::Volatility) {
282 BOOST_TEST_MESSAGE(" Quote volatility type: " << volatilityColumn.type);
283 BOOST_TEST_MESSAGE(" Quote displacement: " << volatilityColumn.displacement);
284 }
285 BOOST_TEST_MESSAGE(" Interpolation type: " << to_string(interpolationType));
286 BOOST_TEST_MESSAGE(" Floating reference date: " << boolalpha << isMoving);
287 BOOST_TEST_MESSAGE(" Flat first period: " << boolalpha << flatFirstPeriod);
288
289 if (quoteType == CapFloorHelper::Premium && helperType == CapFloorHelper::Automatic) {
290
291 // This is a combination that should throw an error when creating the helper
292 // Don't care about the value of the premium quote
293 Handle<Quote> quote(QuantLib::ext::make_shared<SimpleQuote>(0.01));
294 BOOST_REQUIRE_THROW(
295 QuantLib::ext::make_shared<CapFloorHelper>(helperType, volatilityColumn.tenors.front(), volatilityColumn.strike,
296 quote, iborIndex, testYieldCurves.discountEonia, isMoving, Date(),
297 quoteType, volatilityColumn.type, volatilityColumn.displacement),
298 Error);
299
300 } else {
301
302 // Form the cap floor helper instrument for each tenor in the strike column
303 vector<QuantLib::ext::shared_ptr<helper> > helpers(volatilityColumn.tenors.size());
304
305 // Store each cap floor instrument in the strike column and its NPV using the flat cap floor volatilities
306 vector<QuantLib::ext::shared_ptr<CapFloor> > instruments(volatilityColumn.tenors.size());
307 vector<Real> flatNpvs(volatilityColumn.tenors.size());
308
309 BOOST_TEST_MESSAGE("The input values at each tenor are:");
310 for (Size i = 0; i < volatilityColumn.tenors.size(); i++) {
311
312 // Get the relevant quote value
313 Real volatility = volatilityColumn.volatilities[i];
314
315 // Create the cap floor instrument and store its price using the quoted flat volatility
316 CapFloor::Type capFloorType = CapFloor::Cap;
317 if (helperType == CapFloorHelper::Floor) {
318 capFloorType = CapFloor::Floor;
319 }
320 instruments[i] = MakeCapFloor(capFloorType, volatilityColumn.tenors[i], iborIndex, volatilityColumn.strike);
321 if (volatilityColumn.type == ShiftedLognormal) {
322 instruments[i]->setPricingEngine(QuantLib::ext::make_shared<BlackCapFloorEngine>(
323 testYieldCurves.discountEonia, volatility, dayCounter, volatilityColumn.displacement));
324 } else {
325 instruments[i]->setPricingEngine(
326 QuantLib::ext::make_shared<BachelierCapFloorEngine>(testYieldCurves.discountEonia, volatility, dayCounter));
327 }
328 flatNpvs[i] = instruments[i]->NPV();
329
330 BOOST_TEST_MESSAGE(" (Cap/Floor, Tenor, Volatility, Flat NPV) = ("
331 << capFloorType << ", " << volatilityColumn.tenors[i] << ", " << fixed
332 << setprecision(13) << volatility << ", " << flatNpvs[i] << ")");
333
334 // Create a volatility or premium quote
335 RelinkableHandle<Quote> quote;
336 if (quoteType == CapFloorHelper::Volatility) {
337 quote.linkTo(QuantLib::ext::make_shared<SimpleQuote>(volatility));
338 } else {
339 quote.linkTo(QuantLib::ext::make_shared<SimpleQuote>(flatNpvs[i]));
340 }
341
342 // Create the helper instrument
343 helpers[i] =
344 QuantLib::ext::make_shared<CapFloorHelper>(helperType, volatilityColumn.tenors[i], volatilityColumn.strike,
345 quote, iborIndex, testYieldCurves.discountEonia, isMoving, Date(),
346 quoteType, volatilityColumn.type, volatilityColumn.displacement);
347 }
348
349 // Create the piecewise optionlet curve, with the given interpolation type, and fail if it is not created
350 VolatilityType curveVolatilityType = Normal;
351 Real curveDisplacement = 0.0;
352 QuantLib::ext::shared_ptr<OptionletVolatilityStructure> ovCurve;
353 switch (interpolationType.which()) {
354 case 0:
355 if (isMoving) {
356 BOOST_TEST_MESSAGE("Using Linear interpolation with a moving reference date");
357 BOOST_REQUIRE_NO_THROW(ovCurve = QuantLib::ext::make_shared<PiecewiseOptionletCurve<Linear> >(
358 settlementDays, helpers, calendar, bdc, dayCounter, curveVolatilityType,
359 curveDisplacement, flatFirstPeriod));
360 } else {
361 BOOST_TEST_MESSAGE("Using Linear interpolation with a fixed reference date");
362 BOOST_REQUIRE_NO_THROW(ovCurve = QuantLib::ext::make_shared<PiecewiseOptionletCurve<Linear> >(
363 referenceDate, helpers, calendar, bdc, dayCounter, curveVolatilityType,
364 curveDisplacement, flatFirstPeriod));
365 }
366 break;
367 case 1:
368 if (isMoving) {
369 BOOST_TEST_MESSAGE("Using BackwardFlat interpolation with a moving reference date");
370 BOOST_REQUIRE_NO_THROW(ovCurve = QuantLib::ext::make_shared<PiecewiseOptionletCurve<BackwardFlat> >(
371 settlementDays, helpers, calendar, bdc, dayCounter, curveVolatilityType,
372 curveDisplacement, flatFirstPeriod));
373 } else {
374 BOOST_TEST_MESSAGE("Using BackwardFlat interpolation with a fixed reference date");
375 BOOST_REQUIRE_NO_THROW(ovCurve = QuantLib::ext::make_shared<PiecewiseOptionletCurve<BackwardFlat> >(
376 referenceDate, helpers, calendar, bdc, dayCounter, curveVolatilityType,
377 curveDisplacement, flatFirstPeriod));
378 }
379 break;
380 case 2:
381 if (isMoving) {
382 BOOST_TEST_MESSAGE("Using LinearFlat interpolation with a moving reference date");
383 BOOST_REQUIRE_NO_THROW(ovCurve = QuantLib::ext::make_shared<PiecewiseOptionletCurve<LinearFlat> >(
384 settlementDays, helpers, calendar, bdc, dayCounter, curveVolatilityType,
385 curveDisplacement, flatFirstPeriod));
386 } else {
387 BOOST_TEST_MESSAGE("Using LinearFlat interpolation with a fixed reference date");
388 BOOST_REQUIRE_NO_THROW(ovCurve = QuantLib::ext::make_shared<PiecewiseOptionletCurve<LinearFlat> >(
389 referenceDate, helpers, calendar, bdc, dayCounter, curveVolatilityType,
390 curveDisplacement, flatFirstPeriod));
391 }
392 break;
393 case 3:
394 if (isMoving) {
395 BOOST_TEST_MESSAGE("Using Cubic interpolation with a moving reference date");
396 BOOST_REQUIRE_NO_THROW(
397 ovCurve = QuantLib::ext::make_shared<PiecewiseOptionletCurve<Cubic> >(
398 settlementDays, helpers, calendar, bdc, dayCounter, curveVolatilityType, curveDisplacement,
399 flatFirstPeriod, Cubic(),
402 globalAccuracy)));
403 } else {
404 BOOST_TEST_MESSAGE("Using Cubic interpolation with a fixed reference date");
405 BOOST_REQUIRE_NO_THROW(
406 ovCurve = QuantLib::ext::make_shared<PiecewiseOptionletCurve<Cubic> >(
407 referenceDate, helpers, calendar, bdc, dayCounter, curveVolatilityType, curveDisplacement,
408 flatFirstPeriod, Cubic(),
411 globalAccuracy)));
412 }
413 break;
414 case 4:
415 if (isMoving) {
416 BOOST_TEST_MESSAGE("Using CubicFlat interpolation with a moving reference date");
417 BOOST_REQUIRE_NO_THROW(
418 ovCurve = QuantLib::ext::make_shared<PiecewiseOptionletCurve<CubicFlat> >(
419 settlementDays, helpers, calendar, bdc, dayCounter, curveVolatilityType, curveDisplacement,
420 flatFirstPeriod, CubicFlat(),
423 accuracy, globalAccuracy)));
424 } else {
425 BOOST_TEST_MESSAGE("Using CubicFlat interpolation with a fixed reference date");
426 BOOST_REQUIRE_NO_THROW(
427 ovCurve = QuantLib::ext::make_shared<PiecewiseOptionletCurve<CubicFlat> >(
428 referenceDate, helpers, calendar, bdc, dayCounter, curveVolatilityType, curveDisplacement,
429 flatFirstPeriod, CubicFlat(),
432 accuracy, globalAccuracy)));
433 }
434 break;
435 default:
436 BOOST_FAIL("Unexpected interpolation type");
437 }
438 Handle<OptionletVolatilityStructure> hovs(ovCurve);
439
440 // Price each cap floor instrument using the piecewise optionlet curve and check it against the flat NPV
441 BOOST_TEST_MESSAGE("The stripped values and differences at each tenor are:");
442 Real strippedNpv;
443 for (Size i = 0; i < volatilityColumn.tenors.size(); i++) {
444
445 // May need to update instruments type if it is being chosen automatically in the bootstrap
446 if (helperType == CapFloorHelper::Automatic && quoteType != CapFloorHelper::Premium) {
447 Real volatility = volatilityColumn.volatilities[i];
448 CapFloor::Type capFloorType =
449 QuantLib::ext::dynamic_pointer_cast<CapFloorHelper>(helpers[i])->capFloor()->type();
450 if (capFloorType != instruments[i]->type()) {
451 // Need to update the instrument and the flat NPV for the test
452 instruments[i] =
453 MakeCapFloor(capFloorType, volatilityColumn.tenors[i], iborIndex, volatilityColumn.strike);
454 if (volatilityColumn.type == ShiftedLognormal) {
455 instruments[i]->setPricingEngine(QuantLib::ext::make_shared<BlackCapFloorEngine>(
456 testYieldCurves.discountEonia, volatility, dayCounter, volatilityColumn.displacement));
457 } else {
458 instruments[i]->setPricingEngine(QuantLib::ext::make_shared<BachelierCapFloorEngine>(
459 testYieldCurves.discountEonia, volatility, dayCounter));
460 }
461 flatNpvs[i] = instruments[i]->NPV();
462 }
463 }
464
465 // Price the instrument using the stripped optionlet structure
466 if (ovCurve->volatilityType() == ShiftedLognormal) {
467 instruments[i]->setPricingEngine(
468 QuantLib::ext::make_shared<BlackCapFloorEngine>(testYieldCurves.discountEonia, hovs));
469 } else {
470 instruments[i]->setPricingEngine(
471 QuantLib::ext::make_shared<BachelierCapFloorEngine>(testYieldCurves.discountEonia, hovs));
472 }
473 strippedNpv = instruments[i]->NPV();
474
475 BOOST_TEST_MESSAGE(" (Cap/Floor, Tenor, Volatility, Flat NPV, Stripped NPV, Flat - Stripped) = ("
476 << instruments[i]->type() << ", " << volatilityColumn.tenors[i] << ", " << fixed
477 << setprecision(13) << volatilityColumn.volatilities[i] << ", " << flatNpvs[i] << ", "
478 << strippedNpv << ", " << (flatNpvs[i] - strippedNpv) << ")");
479
480 BOOST_CHECK_SMALL(fabs(flatNpvs[i] - strippedNpv), tolerance);
481 }
482 }
483}
Cubic interpolation and flat extrapolation factory and traits.

◆ BOOST_DATA_TEST_CASE_F() [2/2]

BOOST_DATA_TEST_CASE_F ( CommonVars  ,
testCachedValues  ,
bdata::make(interpolationTypesCached) *bdata::make(flatFirstPeriodValues)  ,
interpolationType  ,
flatFirstPeriod   
)

Definition at line 485 of file piecewiseoptionletcurve.cpp.

487 {
488
489 BOOST_TEST_MESSAGE("Testing stripping of single strike column against cached values");
490
493
494 // Use EUR cap floor test volatility data from file test/capfloormarketdata.hpp
495 // Take the highest strike column of the normal volatility matrix
496 CapFloorVolatilityEUR testVols;
497 vector<Period> tenors = testVols.tenors;
498 vector<Real> volatilities(tenors.size());
499 Size strikeIdx = testVols.strikes.size() - 1;
500 Rate strike = testVols.strikes[strikeIdx];
501 for (Size i = 0; i < tenors.size(); i++) {
502 volatilities[i] = testVols.nVols[i][strikeIdx];
503 }
504 VolatilityType volatilityType = Normal;
505 Real displacement = 0.0;
506
507 // Make first tenor 18M highlight differences introduced by flatFirstPeriod setting.
508 // With first tenor = 1Y and index tenor = 6M, we were not seeing a difference.
509 tenors[0] = 18 * Months;
510
511 BOOST_TEST_MESSAGE("Test inputs are:");
512 BOOST_TEST_MESSAGE(" Cap floor helper type: " << helperType);
513 BOOST_TEST_MESSAGE(" Cap floor strike: " << strike);
514 BOOST_TEST_MESSAGE(" Quote type: " << quoteType);
515 BOOST_TEST_MESSAGE(" Quote volatility type: " << volatilityType);
516 BOOST_TEST_MESSAGE(" Quote displacement: " << displacement);
517 BOOST_TEST_MESSAGE(" Interpolation type: " << to_string(interpolationType));
518 BOOST_TEST_MESSAGE(" Flat first period: " << boolalpha << flatFirstPeriod);
519
520 // Form the cap floor helper instrument for each tenor in the strike column
521 vector<QuantLib::ext::shared_ptr<helper> > helpers(tenors.size());
522 for (Size i = 0; i < tenors.size(); i++) {
523 Handle<Quote> quote(QuantLib::ext::make_shared<SimpleQuote>(volatilities[i]));
524 helpers[i] = QuantLib::ext::make_shared<CapFloorHelper>(helperType, tenors[i], strike, quote, iborIndex,
525 testYieldCurves.discountEonia, true, Date(), quoteType,
526 volatilityType, displacement);
527 }
528
529 // Create the piecewise optionlet curve, with the given interpolation type.
530 // Store the nodes of this optionlet curve to compare with cached values
531 VolatilityType curveVolatilityType = Normal;
532 Real curveDisplacement = 0.0;
533 QuantLib::ext::shared_ptr<OptionletVolatilityStructure> ovs;
534 vector<pair<Date, Real> > curveNodes;
535 switch (interpolationType.which()) {
536 case 0: {
537 BOOST_TEST_MESSAGE("Using Linear interpolation");
538 QuantLib::ext::shared_ptr<PiecewiseOptionletCurve<Linear> > ovCurve =
539 QuantLib::ext::make_shared<PiecewiseOptionletCurve<Linear> >(referenceDate, helpers, calendar, bdc, dayCounter,
540 curveVolatilityType, curveDisplacement,
541 flatFirstPeriod);
542 curveNodes = ovCurve->nodes();
543 ovs = ovCurve;
544 } break;
545 case 1: {
546 BOOST_TEST_MESSAGE("Using BackwardFlat interpolation");
547 QuantLib::ext::shared_ptr<PiecewiseOptionletCurve<BackwardFlat> > ovCurve =
548 QuantLib::ext::make_shared<PiecewiseOptionletCurve<BackwardFlat> >(referenceDate, helpers, calendar, bdc,
549 dayCounter, curveVolatilityType,
550 curveDisplacement, flatFirstPeriod);
551 curveNodes = ovCurve->nodes();
552 ovs = ovCurve;
553 } break;
554 case 2: {
555 BOOST_TEST_MESSAGE("Using LinearFlat interpolation");
556 QuantLib::ext::shared_ptr<PiecewiseOptionletCurve<LinearFlat> > ovCurve =
557 QuantLib::ext::make_shared<PiecewiseOptionletCurve<LinearFlat> >(referenceDate, helpers, calendar, bdc, dayCounter,
558 curveVolatilityType, curveDisplacement,
559 flatFirstPeriod);
560 curveNodes = ovCurve->nodes();
561 ovs = ovCurve;
562 } break;
563 default:
564 BOOST_FAIL("Unexpected interpolation type");
565 }
566
567 // Get the key for the cached results for the current test
568 Size key = 2 * interpolationType.which() + static_cast<Size>(flatFirstPeriod);
569
570 // Check stripped optionlet volatilities against cached values
571 BOOST_REQUIRE_EQUAL(curveNodes.size(), cachedDates.size());
572 BOOST_REQUIRE(cachedValues.count(key) == 1);
573 BOOST_REQUIRE_EQUAL(curveNodes.size(), cachedValues.at(key).size());
574 BOOST_TEST_MESSAGE("node_date,node_vol");
575 for (Size i = 0; i < curveNodes.size(); i++) {
576 // Check the date
577 BOOST_CHECK_EQUAL(curveNodes[i].first, cachedDates[i]);
578 // Check the value
579 BOOST_CHECK_SMALL(fabs(curveNodes[i].second - cachedValues.at(key)[i]), accuracy);
580 // Print out the curve
581 BOOST_TEST_MESSAGE(io::iso_date(curveNodes[i].first)
582 << "," << fixed << setprecision(12) << curveNodes[i].second);
583 }
584
585 // Check stripped optionlet volatilities on non-node dates against cached values
586 BOOST_REQUIRE(cachedNonNodeValues.count(key) == 1);
587 BOOST_TEST_MESSAGE("date,vol,cached_vol,diff");
588 for (Size i = 0; i < cachedNonNodeDates.size(); i++) {
589 Date d = cachedNonNodeDates[i];
590
591 // The last date has been picked past the maximum curve date to check extrapolation. Check that we get an
592 // error and then turn on extrapolation.
593 if (i == cachedNonNodeDates.size() - 1) {
594 BOOST_CHECK_THROW(ovs->volatility(d, strike), Error);
595 ovs->enableExtrapolation();
596 }
597
598 Volatility vol = ovs->volatility(d, strike);
599 Volatility cachedVol = cachedNonNodeValues.at(key)[i];
600 Volatility diff = fabs(vol - cachedVol);
601 // Check the value
602 BOOST_CHECK_SMALL(diff, accuracy);
603 // Print out the curve
604 BOOST_TEST_MESSAGE(io::iso_date(d)
605 << "," << fixed << setprecision(12) << vol << "," << cachedVol << "," << diff);
606
607 // The strike should not matter so check that the same test passes above and below the strike
608 Real shift = 0.0010;
609 diff = fabs(ovs->volatility(d, strike - shift) - cachedVol);
610 BOOST_CHECK_SMALL(diff, accuracy);
611 diff = fabs(ovs->volatility(d, strike + shift) - cachedVol);
612 BOOST_CHECK_SMALL(diff, accuracy);
613 }
614}
QuoteType
Enum to indicate the type of the quote provided with the CapFloorHelper.