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);
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
290
291
292
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
303 vector<QuantLib::ext::shared_ptr<helper> > helpers(volatilityColumn.tenors.size());
304
305
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
313 Real volatility = volatilityColumn.volatilities[i];
314
315
316 CapFloor::Type capFloorType = CapFloor::Cap;
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
335 RelinkableHandle<Quote> quote;
337 quote.linkTo(QuantLib::ext::make_shared<SimpleQuote>(volatility));
338 } else {
339 quote.linkTo(QuantLib::ext::make_shared<SimpleQuote>(flatNpvs[i]));
340 }
341
342
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
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");
358 settlementDays, helpers, calendar, bdc, dayCounter, curveVolatilityType,
359 curveDisplacement, flatFirstPeriod));
360 } else {
361 BOOST_TEST_MESSAGE("Using Linear interpolation with a fixed reference date");
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");
371 settlementDays, helpers, calendar, bdc, dayCounter, curveVolatilityType,
372 curveDisplacement, flatFirstPeriod));
373 } else {
374 BOOST_TEST_MESSAGE("Using BackwardFlat interpolation with a fixed reference date");
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");
384 settlementDays, helpers, calendar, bdc, dayCounter, curveVolatilityType,
385 curveDisplacement, flatFirstPeriod));
386 } else {
387 BOOST_TEST_MESSAGE("Using LinearFlat interpolation with a fixed reference date");
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(
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(
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(
419 settlementDays, helpers, calendar, bdc, dayCounter, curveVolatilityType, curveDisplacement,
423 accuracy, globalAccuracy)));
424 } else {
425 BOOST_TEST_MESSAGE("Using CubicFlat interpolation with a fixed reference date");
426 BOOST_REQUIRE_NO_THROW(
428 referenceDate, helpers, calendar, bdc, dayCounter, curveVolatilityType, curveDisplacement,
432 accuracy, globalAccuracy)));
433 }
434 break;
435 default:
436 BOOST_FAIL("Unexpected interpolation type");
437 }
438 Handle<OptionletVolatilityStructure> hovs(ovCurve);
439
440
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
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
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
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.