19#include <boost/assign.hpp>
21#include <boost/test/unit_test.hpp>
22#include <boost/test/data/test_case.hpp>
24#include <boost/variant.hpp>
30#include <ql/instruments/makecapfloor.hpp>
31#include <ql/pricingengines/capfloor/bacheliercapfloorengine.hpp>
32#include <ql/pricingengines/capfloor/blackcapfloorengine.hpp>
33#include <ql/time/date.hpp>
40using namespace boost::unit_test_framework;
43typedef QuantLib::BootstrapHelper<QuantLib::OptionletVolatilityStructure>
helper;
44using boost::assign::list_of;
45using boost::assign::map_list_of;
51using std::setprecision;
62 : referenceDate(5, Feb, 2016), settlementDays(0), calendar(TARGET()), bdc(Following),
63 dayCounter(Actual365Fixed()), accuracy(1.0e-12), globalAccuracy(1.0e-10), tolerance(1.0e-10) {
66 Settings::instance().evaluationDate() = referenceDate;
69 iborIndex = QuantLib::ext::make_shared<Euribor6M>(testYieldCurves.forward6M);
76 Natural settlementDays;
78 BusinessDayConvention bdc;
79 DayCounter dayCounter;
91 QuantLib::ext::shared_ptr<IborIndex> iborIndex;
98struct VolatilityColumn {
100 vector<Period> tenors;
101 vector<Real> volatilities;
107ostream&
operator<<(ostream& os,
const VolatilityColumn& vc) {
108 return os <<
"Column with strike: " << vc.strike <<
", volatility type: " << vc.type
109 <<
", shift: " << vc.displacement;
114vector<VolatilityColumn> generateVolatilityColumns() {
117 vector<VolatilityColumn> volatilityColumns;
122 VolatilityColumn volatilityColumn;
123 vector<Real> volatilities(testVols.
tenors.size());
124 volatilityColumn.tenors = testVols.
tenors;
127 volatilityColumn.type = Normal;
128 volatilityColumn.displacement = 0.0;
129 for (Size j = 0; j < testVols.
strikes.size(); j++) {
130 for (Size i = 0; i < testVols.
tenors.size(); i++) {
131 volatilities[i] = testVols.
nVols[i][j];
133 volatilityColumn.strike = testVols.
strikes[j];
134 volatilityColumn.volatilities = volatilities;
135 volatilityColumns.push_back(volatilityColumn);
139 volatilityColumn.type = ShiftedLognormal;
140 volatilityColumn.displacement = testVols.
shift_1;
141 for (Size j = 0; j < testVols.
strikes.size(); j++) {
142 for (Size i = 0; i < testVols.
tenors.size(); i++) {
143 volatilities[i] = testVols.
slnVols_1[i][j];
145 volatilityColumn.strike = testVols.
strikes[j];
146 volatilityColumn.volatilities = volatilities;
147 volatilityColumns.push_back(volatilityColumn);
151 volatilityColumn.type = ShiftedLognormal;
152 volatilityColumn.displacement = testVols.
shift_2;
153 for (Size j = 0; j < testVols.
strikes.size(); j++) {
154 for (Size i = 0; i < testVols.
tenors.size(); i++) {
155 volatilities[i] = testVols.
slnVols_2[i][j];
157 volatilityColumn.strike = testVols.
strikes[j];
158 volatilityColumn.volatilities = volatilities;
159 volatilityColumns.push_back(volatilityColumn);
162 return volatilityColumns;
166vector<CapFloorHelper::Type> helperTypes =
173typedef boost::variant<Linear, BackwardFlat, QuantExt::LinearFlat, Cubic, QuantExt::CubicFlat> InterpolationType;
175vector<InterpolationType> interpolationTypes = list_of(InterpolationType(Linear()))(InterpolationType(BackwardFlat()))(
178vector<InterpolationType> interpolationTypesCached =
179 list_of(InterpolationType(Linear()))(InterpolationType(BackwardFlat()))(InterpolationType(
QuantExt::LinearFlat()));
182vector<bool> isMovingValues = list_of(
true)(
false);
185vector<bool> flatFirstPeriodValues = list_of(
true)(
false);
188string to_string(
const InterpolationType& interpolationType) {
190 switch (interpolationType.which()) {
195 result =
"BackwardFlat";
198 result =
"LinearFlat";
204 result =
"CubicFlat";
207 BOOST_FAIL(
"Unexpected interpolation type");
215vector<Date> cachedDates = list_of(Date(5, Feb, 2016))(Date(7, Feb, 2017))(Date(6, Aug, 2020))(Date(5, Aug, 2022))(Date(7, Aug, 2025))(Date(7, Aug, 2035));
216map<Size, vector<Real> > cachedValues = map_list_of
218 (0, list_of(0.000000000000)(0.009939243164)(0.008398540935)(0.008216105988)(0.006859464219)(0.006598726907).convert_to_container<vector<Real> >())
220 (1, list_of(0.009938000000)(0.009938000000)(0.008399019469)(0.008215852284)(0.006859635836)(0.006598586367).convert_to_container<vector<Real> >())
222 (2, list_of(0.000000000000)(0.009938000000)(0.008799306892)(0.008279139515)(0.007401656494)(0.006715983817).convert_to_container<vector<Real> >())
224 (3, list_of(0.009938000000)(0.009938000000)(0.008799306892)(0.008279139515)(0.007401656494)(0.006715983817).convert_to_container<vector<Real> >())
226 (4, list_of(0.000000000000)(0.009939243164)(0.008398540935)(0.008216105988)(0.006859464219)(0.006598726907).convert_to_container<vector<Real> >())
228 (5, list_of(0.009938000000)(0.009938000000)(0.008399019469)(0.008215852284)(0.006859635836)(0.006598586367).convert_to_container<vector<Real> >());
235vector<Date> cachedNonNodeDates = list_of(Date(5, Aug, 2016))(Date(6, Aug, 2021))(Date(7, Aug, 2036));
236map<Size, vector<Real> > cachedNonNodeValues = map_list_of
238 (0, list_of(0.004915603956)(0.008307198335)(0.006572596059).convert_to_container<vector<Real> >())
240 (1, list_of(0.009938000000)(0.008307310247)(0.006572424235).convert_to_container<vector<Real> >())
242 (2, list_of(0.009938000000)(0.008279139515)(0.006715983817).convert_to_container<vector<Real> >())
244 (3, list_of(0.009938000000)(0.008279139515)(0.006715983817).convert_to_container<vector<Real> >())
246 (4, list_of(0.004915603956)(0.008307198335)(0.006598726907).convert_to_container<vector<Real> >())
248 (5, list_of(0.009938000000)(0.008307310247)(0.006598586367).convert_to_container<vector<Real> >());
256namespace test_tools {
258template <>
struct print_log_value<InterpolationType> {
259 void operator()(ostream& os,
const InterpolationType& interpolationType) { os << to_string(interpolationType); }
267BOOST_AUTO_TEST_SUITE(PiecewiseOptionletCurveTests)
270 bdata::make(generateVolatilityColumns()) *
bdata::make(helperTypes) *
bdata::make(quoteTypes) *
271 bdata::make(interpolationTypes) *
bdata::make(isMovingValues) *
272 bdata::make(flatFirstPeriodValues),
273 volatilityColumn, helperType, quoteType, interpolationType, isMoving, flatFirstPeriod) {
275 BOOST_TEST_MESSAGE(
"Testing piecewise optionlet stripping of cap floor quotes along a strike column");
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);
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);
293 Handle<Quote> quote(QuantLib::ext::make_shared<SimpleQuote>(0.01));
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),
303 vector<QuantLib::ext::shared_ptr<helper> > helpers(volatilityColumn.tenors.size());
306 vector<QuantLib::ext::shared_ptr<CapFloor> > instruments(volatilityColumn.tenors.size());
307 vector<Real> flatNpvs(volatilityColumn.tenors.size());
309 BOOST_TEST_MESSAGE(
"The input values at each tenor are:");
310 for (Size i = 0; i < volatilityColumn.tenors.size(); i++) {
313 Real volatility = volatilityColumn.volatilities[i];
316 CapFloor::Type capFloorType = CapFloor::Cap;
318 capFloorType = CapFloor::Floor;
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));
325 instruments[i]->setPricingEngine(
326 QuantLib::ext::make_shared<BachelierCapFloorEngine>(testYieldCurves.discountEonia, volatility, dayCounter));
328 flatNpvs[i] = instruments[i]->NPV();
330 BOOST_TEST_MESSAGE(
" (Cap/Floor, Tenor, Volatility, Flat NPV) = ("
331 << capFloorType <<
", " << volatilityColumn.tenors[i] <<
", " << fixed
332 << setprecision(13) << volatility <<
", " << flatNpvs[i] <<
")");
335 RelinkableHandle<Quote> quote;
337 quote.linkTo(QuantLib::ext::make_shared<SimpleQuote>(volatility));
339 quote.linkTo(QuantLib::ext::make_shared<SimpleQuote>(flatNpvs[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);
350 VolatilityType curveVolatilityType = Normal;
351 Real curveDisplacement = 0.0;
352 QuantLib::ext::shared_ptr<OptionletVolatilityStructure> ovCurve;
353 switch (interpolationType.which()) {
356 BOOST_TEST_MESSAGE(
"Using Linear interpolation with a moving reference date");
358 settlementDays, helpers, calendar, bdc, dayCounter, curveVolatilityType,
359 curveDisplacement, flatFirstPeriod));
361 BOOST_TEST_MESSAGE(
"Using Linear interpolation with a fixed reference date");
363 referenceDate, helpers, calendar, bdc, dayCounter, curveVolatilityType,
364 curveDisplacement, flatFirstPeriod));
369 BOOST_TEST_MESSAGE(
"Using BackwardFlat interpolation with a moving reference date");
371 settlementDays, helpers, calendar, bdc, dayCounter, curveVolatilityType,
372 curveDisplacement, flatFirstPeriod));
374 BOOST_TEST_MESSAGE(
"Using BackwardFlat interpolation with a fixed reference date");
376 referenceDate, helpers, calendar, bdc, dayCounter, curveVolatilityType,
377 curveDisplacement, flatFirstPeriod));
382 BOOST_TEST_MESSAGE(
"Using LinearFlat interpolation with a moving reference date");
384 settlementDays, helpers, calendar, bdc, dayCounter, curveVolatilityType,
385 curveDisplacement, flatFirstPeriod));
387 BOOST_TEST_MESSAGE(
"Using LinearFlat interpolation with a fixed reference date");
389 referenceDate, helpers, calendar, bdc, dayCounter, curveVolatilityType,
390 curveDisplacement, flatFirstPeriod));
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(),
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(),
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)));
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)));
436 BOOST_FAIL(
"Unexpected interpolation type");
438 Handle<OptionletVolatilityStructure> hovs(ovCurve);
441 BOOST_TEST_MESSAGE(
"The stripped values and differences at each tenor are:");
443 for (Size i = 0; i < volatilityColumn.tenors.size(); i++) {
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()) {
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));
458 instruments[i]->setPricingEngine(QuantLib::ext::make_shared<BachelierCapFloorEngine>(
459 testYieldCurves.discountEonia, volatility, dayCounter));
461 flatNpvs[i] = instruments[i]->NPV();
466 if (ovCurve->volatilityType() == ShiftedLognormal) {
467 instruments[i]->setPricingEngine(
468 QuantLib::ext::make_shared<BlackCapFloorEngine>(testYieldCurves.discountEonia, hovs));
470 instruments[i]->setPricingEngine(
471 QuantLib::ext::make_shared<BachelierCapFloorEngine>(testYieldCurves.discountEonia, hovs));
473 strippedNpv = instruments[i]->NPV();
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) <<
")");
480 BOOST_CHECK_SMALL(fabs(flatNpvs[i] - strippedNpv), tolerance);
486 bdata::make(interpolationTypesCached) * bdata::make(flatFirstPeriodValues), interpolationType,
489 BOOST_TEST_MESSAGE(
"Testing stripping of single strike column against cached values");
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];
504 VolatilityType volatilityType = Normal;
505 Real displacement = 0.0;
509 tenors[0] = 18 * Months;
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);
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);
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()) {
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,
542 curveNodes = ovCurve->nodes();
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();
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,
560 curveNodes = ovCurve->nodes();
564 BOOST_FAIL(
"Unexpected interpolation type");
568 Size key = 2 * interpolationType.which() +
static_cast<Size
>(flatFirstPeriod);
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++) {
577 BOOST_CHECK_EQUAL(curveNodes[i].first, cachedDates[i]);
579 BOOST_CHECK_SMALL(fabs(curveNodes[i].second - cachedValues.at(key)[i]), accuracy);
581 BOOST_TEST_MESSAGE(io::iso_date(curveNodes[i].first)
582 <<
"," << fixed << setprecision(12) << curveNodes[i].second);
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];
593 if (i == cachedNonNodeDates.size() - 1) {
594 BOOST_CHECK_THROW(ovs->volatility(d, strike), Error);
595 ovs->enableExtrapolation();
598 Volatility vol = ovs->volatility(d, strike);
599 Volatility cachedVol = cachedNonNodeValues.at(key)[i];
600 Volatility diff = fabs(vol - cachedVol);
602 BOOST_CHECK_SMALL(diff, accuracy);
604 BOOST_TEST_MESSAGE(io::iso_date(d)
605 <<
"," << fixed << setprecision(12) << vol <<
"," << cachedVol <<
"," << diff);
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);
616BOOST_AUTO_TEST_SUITE_END()
618BOOST_AUTO_TEST_SUITE_END()
Helper for bootstrapping optionlet volatilities from cap floor volatilities.
structs containing capfloor market data that can be used in tests
QuoteType
Enum to indicate the type of the quote provided with the CapFloorHelper.
Cubic interpolation and flat extrapolation factory and traits.
Linear-interpolation and flat extrapolation factory and traits
std::ostream & operator<<(std::ostream &out, EquityReturnType t)
QuantLib::BootstrapHelper< QuantLib::OptionletVolatilityStructure > helper
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)
One-dimensional curve of bootstrapped optionlet volatilities.
Fixture that can be used at top level.
structs containing yield curve market data that can be used in tests