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
254 vector<QuantLib::ext::shared_ptr<CapFloor> > instruments(atmVolData.tenors.size());
255 vector<Real> flatNpvs(atmVolData.tenors.size());
256
257
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
265 Real volatility = atmVolData.volatilities[i];
266 volQuotes[i] = QuantLib::ext::make_shared<SimpleQuote>(volatility);
267 volHandles[i] = Handle<Quote>(volQuotes[i]);
268
269
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
288
289
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
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
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
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
446
447
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
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}