352 {
353
354 BOOST_TEST_MESSAGE("Testing piecewise optionlet stripping of cap floor surface");
355
356 BOOST_TEST_MESSAGE("Test inputs are:");
357 BOOST_TEST_MESSAGE(" Input volatility type:" << volatilityType);
358 BOOST_TEST_MESSAGE(" Time Interpolation: " << to_string(timeInterp));
359 BOOST_TEST_MESSAGE(" Smile Interpolation: " << to_string(smileInterp));
360 BOOST_TEST_MESSAGE(" Flat first period: " << boolalpha << flatFirstPeriod);
361 BOOST_TEST_MESSAGE(" Floating reference date: " << boolalpha << isMoving);
362 BOOST_TEST_MESSAGE(" Cap floor term interpolation: " << vsInterpMethod);
363 BOOST_TEST_MESSAGE(" Interpolate on optionlets: " << boolalpha << interpOnOptionlet);
364 BOOST_TEST_MESSAGE(" Add in ATM curve: " << boolalpha << addAtm);
365
366
367 Handle<OptionletVolatilityStructure> ovs;
368 switch (timeInterp.which()) {
369 case 0:
370 if (smileInterp.which() == 0) {
371 ovs = createOvs<Linear, Linear>(volatilityType, flatFirstPeriod, isMoving, vsInterpMethod,
372 interpOnOptionlet, addAtm);
373 } else if (smileInterp.which() == 2) {
374 ovs = createOvs<Linear, LinearFlat>(volatilityType, flatFirstPeriod, isMoving, vsInterpMethod,
375 interpOnOptionlet, addAtm);
376 } else if (smileInterp.which() == 3) {
377 ovs = createOvs<Linear, Cubic>(volatilityType, flatFirstPeriod, isMoving, vsInterpMethod, interpOnOptionlet,
378 addAtm);
379 } else if (smileInterp.which() == 4) {
380 ovs = createOvs<Linear, CubicFlat>(volatilityType, flatFirstPeriod, isMoving, vsInterpMethod,
381 interpOnOptionlet, addAtm);
382 } else {
383 BOOST_FAIL("Unexpected smile interpolation type");
384 }
385 break;
386 case 1:
387 if (smileInterp.which() == 0) {
388 ovs = createOvs<BackwardFlat, Linear>(volatilityType, flatFirstPeriod, isMoving, vsInterpMethod,
389 interpOnOptionlet, addAtm);
390 } else if (smileInterp.which() == 2) {
391 ovs = createOvs<BackwardFlat, LinearFlat>(volatilityType, flatFirstPeriod, isMoving, vsInterpMethod,
392 interpOnOptionlet, addAtm);
393 } else if (smileInterp.which() == 3) {
394 ovs = createOvs<BackwardFlat, Cubic>(volatilityType, flatFirstPeriod, isMoving, vsInterpMethod,
395 interpOnOptionlet, addAtm);
396 } else if (smileInterp.which() == 4) {
397 ovs = createOvs<BackwardFlat, CubicFlat>(volatilityType, flatFirstPeriod, isMoving, vsInterpMethod,
398 interpOnOptionlet, addAtm);
399 } else {
400 BOOST_FAIL("Unexpected smile interpolation type");
401 }
402 break;
403 case 2:
404 if (smileInterp.which() == 0) {
405 ovs = createOvs<LinearFlat, Linear>(volatilityType, flatFirstPeriod, isMoving, vsInterpMethod,
406 interpOnOptionlet, addAtm);
407 } else if (smileInterp.which() == 2) {
408 ovs = createOvs<LinearFlat, LinearFlat>(volatilityType, flatFirstPeriod, isMoving, vsInterpMethod,
409 interpOnOptionlet, addAtm);
410 } else if (smileInterp.which() == 3) {
411 ovs = createOvs<LinearFlat, Cubic>(volatilityType, flatFirstPeriod, isMoving, vsInterpMethod,
412 interpOnOptionlet, addAtm);
413 } else if (smileInterp.which() == 4) {
414 ovs = createOvs<LinearFlat, CubicFlat>(volatilityType, flatFirstPeriod, isMoving, vsInterpMethod,
415 interpOnOptionlet, addAtm);
416 } else {
417 BOOST_FAIL("Unexpected smile interpolation type");
418 }
419 break;
420 case 3:
421 if (smileInterp.which() == 0) {
422 ovs = createOvs<Cubic, Linear>(volatilityType, flatFirstPeriod, isMoving, vsInterpMethod, interpOnOptionlet,
423 addAtm);
424 } else if (smileInterp.which() == 2) {
425 ovs = createOvs<Cubic, LinearFlat>(volatilityType, flatFirstPeriod, isMoving, vsInterpMethod,
426 interpOnOptionlet, addAtm);
427 } else if (smileInterp.which() == 3) {
428 ovs = createOvs<Cubic, Cubic>(volatilityType, flatFirstPeriod, isMoving, vsInterpMethod, interpOnOptionlet,
429 addAtm);
430 } else if (smileInterp.which() == 4) {
431 ovs = createOvs<Cubic, CubicFlat>(volatilityType, flatFirstPeriod, isMoving, vsInterpMethod,
432 interpOnOptionlet, addAtm);
433 } else {
434 BOOST_FAIL("Unexpected smile interpolation type");
435 }
436 break;
437 case 4:
438 if (smileInterp.which() == 0) {
439 ovs = createOvs<CubicFlat, Linear>(volatilityType, flatFirstPeriod, isMoving, vsInterpMethod,
440 interpOnOptionlet, addAtm);
441 } else if (smileInterp.which() == 2) {
442 ovs = createOvs<CubicFlat, LinearFlat>(volatilityType, flatFirstPeriod, isMoving, vsInterpMethod,
443 interpOnOptionlet, addAtm);
444 } else if (smileInterp.which() == 3) {
445 ovs = createOvs<CubicFlat, Cubic>(volatilityType, flatFirstPeriod, isMoving, vsInterpMethod,
446 interpOnOptionlet, addAtm);
447 } else if (smileInterp.which() == 4) {
448 ovs = createOvs<CubicFlat, CubicFlat>(volatilityType, flatFirstPeriod, isMoving, vsInterpMethod,
449 interpOnOptionlet, addAtm);
450 } else {
451 BOOST_FAIL("Unexpected smile interpolation type");
452 }
453 break;
454 default:
455 BOOST_FAIL("Unexpected time interpolation type");
456 }
457
458
459
460 Handle<YieldTermStructure> discount = testYieldCurves.discountEonia;
461 QuantLib::ext::shared_ptr<CapFloor> capFloor;
462
463 for (Size i = 0; i < testVols.tenors.size(); i++) {
464
465 for (Size j = 0; j < testVols.strikes.size(); j++) {
466
467
468 capFloor = MakeCapFloor(CapFloor::Cap, testVols.tenors[i], iborIndex, testVols.strikes[j]);
469 Rate atm = capFloor->atmRate(**discount);
470 if (testVols.strikes[j] < atm) {
471 capFloor = MakeCapFloor(CapFloor::Floor, testVols.tenors[i], iborIndex, testVols.strikes[j]);
472 }
473
474
475 Volatility flatVol;
476 if (volatilityType == ShiftedLognormal) {
477 flatVol = testVols.slnVols_1[i][j];
478 capFloor->setPricingEngine(
479 QuantLib::ext::make_shared<BlackCapFloorEngine>(discount, flatVol, dayCounter, testVols.shift_1));
480 } else {
481 flatVol = testVols.nVols[i][j];
482 capFloor->setPricingEngine(QuantLib::ext::make_shared<BachelierCapFloorEngine>(discount, flatVol, dayCounter));
483 }
484 Real flatNpv = capFloor->NPV();
485
486
487 capFloor->setPricingEngine(QuantLib::ext::make_shared<BachelierCapFloorEngine>(discount, ovs));
488 Real strippedNpv = capFloor->NPV();
489
490
491 Real diff = fabs(flatNpv - strippedNpv);
492 BOOST_CHECK_SMALL(diff, tolerance);
493
494 BOOST_TEST_MESSAGE(" (Cap/Floor, Tenor, Strike, Volatility, Flat NPV, Stripped NPV, Flat - Stripped) = ("
495 << capFloor->type() << ", " << testVols.tenors[i] << ", " << testVols.strikes[j] << ", "
496 << flatVol << ", " << flatNpv << ", " << strippedNpv << ", " << diff << ")");
497 }
498 }
499
500
501 if (addAtm) {
502
503 for (Size i = 0; i < testVols.atmTenors.size(); i++) {
504
505
506 capFloor = MakeCapFloor(CapFloor::Cap, testVols.atmTenors[i], iborIndex, 0.01);
507 Rate atm = capFloor->atmRate(**discount);
508 capFloor = MakeCapFloor(CapFloor::Cap, testVols.atmTenors[i], iborIndex, atm);
509
510
511 Volatility flatVol;
512 if (volatilityType == ShiftedLognormal) {
513 flatVol = testVols.slnAtmVols_1[i];
514 capFloor->setPricingEngine(
515 QuantLib::ext::make_shared<BlackCapFloorEngine>(discount, flatVol, dayCounter, testVols.shift_1));
516 } else {
517 flatVol = testVols.nAtmVols[i];
518 capFloor->setPricingEngine(QuantLib::ext::make_shared<BachelierCapFloorEngine>(discount, flatVol, dayCounter));
519 }
520 Real flatNpv = capFloor->NPV();
521
522
523 capFloor->setPricingEngine(QuantLib::ext::make_shared<BachelierCapFloorEngine>(discount, ovs));
524 Real strippedNpv = capFloor->NPV();
525
526
527 Real diff = fabs(flatNpv - strippedNpv);
528 BOOST_CHECK_SMALL(diff, tolerance);
529
530 BOOST_TEST_MESSAGE(" (Cap/Floor, Tenor, Strike, Volatility, Flat NPV, Stripped NPV, Flat - Stripped) = ("
531 << capFloor->type() << ", " << testVols.atmTenors[i] << ", ATM [" << atm << "], "
532 << flatVol << ", " << flatNpv << ", " << strippedNpv << ", " << diff << ")");
533 }
534 }
535}