Logo
Fully annotated reference manual - version 1.8.12
Loading...
Searching...
No Matches
piecewiseoptionletstripper.cpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2019 Quaternion Risk Management Ltd
3 All rights reserved.
4
5 This file is part of ORE, a free-software/open-source library
6 for transparent pricing and risk analysis - http://opensourcerisk.org
7
8 ORE is free software: you can redistribute it and/or modify it
9 under the terms of the Modified BSD License. You should have received a
10 copy of the license along with this program.
11 The license is also available online at <http://opensourcerisk.org>
12
13 This program is distributed on the basis that it will form a useful
14 contribution to risk analytics and model standardisation, but WITHOUT
15 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
16 FITNESS FOR A PARTICULAR PURPOSE. See the license for more details.
17*/
18
19#include <boost/assign.hpp>
20// clang-format off
21#include <boost/test/unit_test.hpp>
22#include <boost/test/data/test_case.hpp>
23// clang-format on
24#include <boost/variant.hpp>
25
29
30#include <ql/instruments/makecapfloor.hpp>
31#include <ql/pricingengines/capfloor/bacheliercapfloorengine.hpp>
32#include <ql/pricingengines/capfloor/blackcapfloorengine.hpp>
39
40using namespace QuantExt;
41using namespace QuantLib;
42using namespace boost::unit_test_framework;
43
45typedef QuantLib::BootstrapHelper<QuantLib::OptionletVolatilityStructure> helper;
47using boost::assign::list_of;
48using boost::assign::map_list_of;
49using std::boolalpha;
50using std::fixed;
51using std::map;
52using std::ostream;
53using std::pair;
54using std::setprecision;
55using std::string;
56using std::vector;
57
58namespace {
59
60// Variables to be used in the test
61struct CommonVars : public qle::test::TopLevelFixture {
62
63 // Constructor
64 CommonVars()
65 : referenceDate(5, Feb, 2016), settlementDays(0), calendar(TARGET()), bdc(Following),
66 dayCounter(Actual365Fixed()), accuracy(1.0e-12), globalAccuracy(1.0e-10), tolerance(1.0e-10) {
67
68 // Reference date
69 Settings::instance().evaluationDate() = referenceDate;
70
71 // Set cap floor ibor index to EUR-EURIBOR-6M and attach a forwarding curve
72 iborIndex = QuantLib::ext::make_shared<Euribor6M>(testYieldCurves.forward6M);
73 }
74
75 // Valuation date for the test
76 Date referenceDate;
77
78 // Variables used in the optionlet volatility structure creation
79 Natural settlementDays;
80 Calendar calendar;
81 BusinessDayConvention bdc;
82 DayCounter dayCounter;
83
84 // Accuracy for optionlet stripping
85 Real accuracy;
86
87 // Global accuracy for optionlet stripping
88 Real globalAccuracy;
89
90 // Test tolerance for comparing the NPVs
91 Real tolerance;
92
93 // Cap floor ibor index
94 QuantLib::ext::shared_ptr<IborIndex> iborIndex;
95
96 // EUR discount curve test data from file test/yieldcurvemarketdata.hpp
97 YieldCurveEUR testYieldCurves;
98
99 // EUR cap floor test volatility data from file test/capfloormarketdata.hpp
100 CapFloorVolatilityEUR testVols;
101};
102
103// Type of input cap floor volatility
104vector<VolatilityType> volatilityTypes = list_of(Normal)(ShiftedLognormal);
105
106// Interpolation types for the data driven test case
107typedef boost::variant<Linear, BackwardFlat, QuantExt::LinearFlat, Cubic, QuantExt::CubicFlat> InterpolationType;
108
109vector<InterpolationType> timeInterpolationTypes =
110 list_of(InterpolationType(Linear()))(InterpolationType(BackwardFlat()))(InterpolationType(QuantExt::LinearFlat()))(
111 InterpolationType(Cubic()))(InterpolationType(QuantExt::CubicFlat()));
112
113vector<InterpolationType> smileInterpolationTypes = list_of(InterpolationType(Linear()))(
114 InterpolationType(QuantExt::LinearFlat()))(InterpolationType(Cubic()))(InterpolationType(QuantExt::CubicFlat()));
115
116// If the optionlet structure has a flat first period or not
117vector<bool> flatFirstPeriodValues = list_of(true)(false);
118
119// If the built optionlet structure in the test has a floating or fixed reference date
120vector<bool> isMovingValues = list_of(true)(false);
121
122// Whether or not to try to add ATM values to the surface stripping
123vector<bool> addAtmValues = list_of(true)(false);
124
125// False to interpolate on cap floor term volatilities before bootstrapping
126// True to interpolate on optionlet volatilities.
127vector<bool> interpOnOptionletValues = list_of(true)(false);
128
129// The interpolation method on the cap floor term volatility surface
130vector<TermVolSurface::InterpolationMethod> vsInterpMethods =
132
133// So that I can reuse below
134string to_string(const InterpolationType& interpolationType) {
135 string result;
136 switch (interpolationType.which()) {
137 case 0:
138 result = "Linear";
139 break;
140 case 1:
141 result = "BackwardFlat";
142 break;
143 case 2:
144 result = "LinearFlat";
145 break;
146 case 3:
147 result = "Cubic";
148 break;
149 case 4:
150 result = "CubicFlat";
151 break;
152 default:
153 BOOST_FAIL("Unexpected interpolation type");
154 }
155 return result;
156}
157
158// Create the OptionletVolatilityStructure using a PiecewiseOptionletStripper
159template <class TI, class SI>
160Handle<OptionletVolatilityStructure> createOvs(VolatilityType volatilityType, bool flatFirstPeriod, bool isMoving,
162 bool interpOnOptionlet, bool withAtm) {
163
164 // Another instance of CommonVars - works but a bit inefficient
165 CommonVars vars;
166
167 // Decide on input volatilities depending on type requested
168 Matrix vols;
169 Real displacement = 0.0;
170 if (volatilityType == Normal) {
171 vols = vars.testVols.nVols;
172 } else {
173 vols = vars.testVols.slnVols_1;
174 displacement = vars.testVols.shift_1;
175 }
176
177 // Create the cap floor term vol surface
178 QuantLib::ext::shared_ptr<TermVolSurface> cfts;
179 if (isMoving) {
180 cfts = QuantLib::ext::make_shared<TermVolSurface>(vars.settlementDays, vars.calendar, vars.bdc, vars.testVols.tenors,
181 vars.testVols.strikes, vols, vars.dayCounter, vsInterpMethod);
182 } else {
183 cfts = QuantLib::ext::make_shared<TermVolSurface>(vars.referenceDate, vars.calendar, vars.bdc, vars.testVols.tenors,
184 vars.testVols.strikes, vols, vars.dayCounter, vsInterpMethod);
185 }
186
187 // Create the piecewise optionlet stripper
188 VolatilityType ovType = Normal;
189 QuantLib::ext::shared_ptr<QuantExt::OptionletStripper> pwos = QuantLib::ext::make_shared<PiecewiseOptionletStripper<TI> >(
190 cfts, vars.iborIndex, vars.testYieldCurves.discountEonia, flatFirstPeriod, volatilityType, displacement, ovType,
191 0.0, interpOnOptionlet, TI(),
194 vars.accuracy, vars.globalAccuracy));
195
196 // If true, we overlay ATM volatilities
197 vector<Handle<Quote> > atmVolquotes(vars.testVols.atmTenors.size());
198 if (withAtm) {
199
200 QuantLib::ext::shared_ptr<CapFloorTermVolCurve> atmVolCurve;
201
202 if (volatilityType == Normal) {
203
204 for (Size i = 0; i < atmVolquotes.size(); ++i) {
205 atmVolquotes[i] = Handle<Quote>(QuantLib::ext::make_shared<SimpleQuote>(vars.testVols.nAtmVols[i]));
206 }
207
208 if (isMoving) {
209 if (vsInterpMethod == TermVolSurface::Bilinear) {
210 atmVolCurve = QuantLib::ext::make_shared<InterpolatedCapFloorTermVolCurve<Linear> >(
211 vars.settlementDays, vars.calendar, vars.bdc, vars.testVols.atmTenors, atmVolquotes,
212 vars.dayCounter, flatFirstPeriod);
213 } else {
214 atmVolCurve = QuantLib::ext::make_shared<InterpolatedCapFloorTermVolCurve<Cubic> >(
215 vars.settlementDays, vars.calendar, vars.bdc, vars.testVols.atmTenors, atmVolquotes,
216 vars.dayCounter, flatFirstPeriod);
217 }
218 } else {
219 if (vsInterpMethod == TermVolSurface::Bilinear) {
220 atmVolCurve = QuantLib::ext::make_shared<InterpolatedCapFloorTermVolCurve<Linear> >(
221 vars.referenceDate, vars.calendar, vars.bdc, vars.testVols.atmTenors, atmVolquotes,
222 vars.dayCounter, flatFirstPeriod);
223 } else {
224 atmVolCurve = QuantLib::ext::make_shared<InterpolatedCapFloorTermVolCurve<Cubic> >(
225 vars.referenceDate, vars.calendar, vars.bdc, vars.testVols.atmTenors, atmVolquotes,
226 vars.dayCounter, flatFirstPeriod);
227 }
228 }
229 } else {
230
231 for (Size i = 0; i < atmVolquotes.size(); ++i) {
232 atmVolquotes[i] = Handle<Quote>(QuantLib::ext::make_shared<SimpleQuote>(vars.testVols.slnAtmVols_1[i]));
233 }
234
235 if (isMoving) {
236 if (vsInterpMethod == TermVolSurface::Bilinear) {
237 atmVolCurve = QuantLib::ext::make_shared<InterpolatedCapFloorTermVolCurve<Linear> >(
238 vars.settlementDays, vars.calendar, vars.bdc, vars.testVols.atmTenors, atmVolquotes,
239 vars.dayCounter, flatFirstPeriod);
240 } else {
241 atmVolCurve = QuantLib::ext::make_shared<InterpolatedCapFloorTermVolCurve<Cubic> >(
242 vars.settlementDays, vars.calendar, vars.bdc, vars.testVols.atmTenors, atmVolquotes,
243 vars.dayCounter, flatFirstPeriod);
244 }
245 } else {
246 if (vsInterpMethod == TermVolSurface::Bilinear) {
247 atmVolCurve = QuantLib::ext::make_shared<InterpolatedCapFloorTermVolCurve<Linear> >(
248 vars.referenceDate, vars.calendar, vars.bdc, vars.testVols.atmTenors, atmVolquotes,
249 vars.dayCounter, flatFirstPeriod);
250 } else {
251 atmVolCurve = QuantLib::ext::make_shared<InterpolatedCapFloorTermVolCurve<Cubic> >(
252 vars.referenceDate, vars.calendar, vars.bdc, vars.testVols.atmTenors, atmVolquotes,
253 vars.dayCounter, flatFirstPeriod);
254 }
255 }
256 }
257
258 Handle<CapFloorTermVolCurve> atmVolCurveH(atmVolCurve);
259 pwos = QuantLib::ext::make_shared<OptionletStripperWithAtm<TI, SI> >(
260 pwos, atmVolCurveH, vars.testYieldCurves.discountEonia, volatilityType, displacement);
261 }
262
263 // Create the OptionletVolatilityStructure
264 QuantLib::ext::shared_ptr<OptionletVolatilityStructure> ovs;
265 if (isMoving) {
266 ovs = QuantLib::ext::make_shared<QuantExt::StrippedOptionletAdapter<TI, SI> >(pwos);
267 } else {
268 ovs = QuantLib::ext::make_shared<QuantExt::StrippedOptionletAdapter<TI, SI> >(vars.referenceDate, pwos);
269 }
270
271 return Handle<OptionletVolatilityStructure>(ovs);
272}
273
274// Variables for cached value tests
275
276// Strikes: below min (-0.01), on pillar strikes (-0.005 & 0.03), between pillar strikes (0.015), above max strike
277// (0.035)
278vector<Rate> cachedStrikes = list_of(-0.01)(-0.005)(0.015)(0.030)(0.035);
279
280// Cached optionlet fixing dates
281vector<Date> cachedOptionletFixingDates = list_of(Date(5, Aug, 2016))(Date(7, Feb, 2017))(Date(7, Aug, 2017))(Date(
282 7, Feb, 2018))(Date(7, Aug, 2018))(Date(7, Feb, 2019))(Date(7, Aug, 2019))(Date(6, Feb, 2020))(Date(6, Aug, 2020))(
283 Date(5, Feb, 2021))(Date(5, Aug, 2021))(Date(7, Feb, 2022))(Date(5, Aug, 2022))(Date(7, Feb, 2023))(
284 Date(7, Aug, 2023))(Date(7, Feb, 2024))(Date(7, Aug, 2024))(Date(6, Feb, 2025))(Date(7, Aug, 2025))(
285 Date(5, Feb, 2026))(Date(6, Aug, 2026))(Date(5, Feb, 2027))(Date(5, Aug, 2027))(Date(7, Feb, 2028))(
286 Date(7, Aug, 2028))(Date(7, Feb, 2029))(Date(7, Aug, 2029))(Date(7, Feb, 2030))(Date(7, Aug, 2030))(
287 Date(6, Feb, 2031))(Date(7, Aug, 2031))(Date(5, Feb, 2032))(Date(5, Aug, 2032))(Date(7, Feb, 2033))(
288 Date(5, Aug, 2033))(Date(7, Feb, 2034))(Date(7, Aug, 2034))(Date(7, Feb, 2035))(Date(7, Aug, 2035));
289
290// Cached optionlet values at optionlet fixing dates
291vector<Real> cachedValues =
292 list_of(0.002457000000)(0.002457000000)(0.006386500000)(0.009938000000)(0.009938000000)(0.002880443155)(
293 0.002880443155)(0.006459363495)(0.009751440414)(0.009751440414)(0.003292503430)(0.003292503430)(0.006530268295)(
294 0.009569895870)(0.009569895870)(0.003711393433)(0.003711393433)(0.006602348312)(0.009385342300)(0.009385342300)(
295 0.004123453708)(0.004123453708)(0.006673253111)(0.009203797756)(0.009203797756)(0.004542343711)(0.004542343711)(
296 0.006745333128)(0.009019244187)(0.009019244187)(0.004954403986)(0.004954403986)(0.006816237927)(0.008837699643)(
297 0.008837699643)(0.005371017413)(0.005371017413)(0.006887926205)(0.008654149082)(0.008654149082)(0.005785354264)(
298 0.005785354264)(0.006959222744)(0.008471601530)(0.008471601530)(0.006092137648)(0.006092137648)(0.006976645818)(
299 0.008397716202)(0.008397716202)(0.006395568208)(0.006395568208)(0.006993878476)(0.008324638363)(0.008324638363)(
300 0.006707380827)(0.006707380827)(0.007011587175)(0.008249541800)(0.008249541800)(0.007007458563)(0.007007458563)(
301 0.007028629417)(0.008177271451)(0.008177271451)(0.006955894311)(0.006955894311)(0.006917620059)(0.007958482968)(
302 0.007958482968)(0.006905716195)(0.006905716195)(0.006809594824)(0.007745575895)(0.007745575895)(0.006854706398)(
303 0.006854706398)(0.006699779115)(0.007529139976)(0.007529139976)(0.006804251054)(0.006804251054)(0.006591157055)(
304 0.007315056621)(0.007315056621)(0.006753518484)(0.006753518484)(0.006481938171)(0.007099796984)(0.007099796984)(
305 0.006703063140)(0.006703063140)(0.006373316111)(0.006885713629)(0.006885713629)(0.006623889360)(0.006623889360)(
306 0.006363776326)(0.006870339773)(0.006870339773)(0.006544715579)(0.006544715579)(0.006354236541)(0.006854965916)(
307 0.006854965916)(0.006465106778)(0.006465106778)(0.006344644340)(0.006839507588)(0.006839507588)(0.006386368018)(
308 0.006386368018)(0.006335156972)(0.006824218203)(0.006824218203)(0.006305454154)(0.006305454154)(0.006325407521)(
309 0.006808506460)(0.006808506460)(0.006226280373)(0.006226280373)(0.006315867737)(0.006793132603)(0.006793132603)(
310 0.006146236551)(0.006146236551)(0.006306223119)(0.006777589803)(0.006777589803)(0.006067497791)(0.006067497791)(
311 0.006296735750)(0.006762300419)(0.006762300419)(0.005987453969)(0.005987453969)(0.006287091133)(0.006746757619)(
312 0.006746757619)(0.005908715209)(0.005908715209)(0.006277603764)(0.006731468234)(0.006731468234)(0.005829106407)(
313 0.005829106407)(0.006268011563)(0.006716009906)(0.006716009906)(0.005749932627)(0.005749932627)(0.006258471778)(
314 0.006700636049)(0.006700636049)(0.005670758846)(0.005670758846)(0.006248931994)(0.006685262193)(0.006685262193)(
315 0.005591585065)(0.005591585065)(0.006239392209)(0.006669888336)(0.006669888336)(0.005510671202)(0.005510671202)(
316 0.006229642758)(0.006654176593)(0.006654176593)(0.005432802483)(0.005432802483)(0.006220260223)(0.006639056152)(
317 0.006639056152)(0.005351888619)(0.005351888619)(0.006210510772)(0.006623344408)(0.006623344408)(0.005273149860)(
318 0.005273149860)(0.006201023404)(0.006608055023)(0.006608055023)(0.005193106037)(0.005193106037)(0.006191378786)(
319 0.006592512223)(0.006592512223)(0.005114367277)(0.005114367277)(0.006181891418)(0.006577222839)(0.006577222839);
320
321// Cached ad-hoc dates: before first fixing, between fixing dates, after max date
322vector<Date> cachedAdHocDates = list_of(Date(5, May, 2016))(Date(5, May, 2026))(Date(5, May, 2036));
323
324// Cached values at ad-hoc dates
325vector<Real> cachedAdHocValues = list_of(0.002457000000)(0.002457000000)(0.006386500000)(0.009938000000)(
326 0.009938000000)(0.006585172511)(0.006585172511)(0.006359111267)(0.006862821788)(0.006862821788)(0.005114367277)(
327 0.005114367277)(0.006181891418)(0.006577222839)(0.006577222839);
328} // namespace
329
330// Needed for BOOST_DATA_TEST_CASE below
331// https://stackoverflow.com/a/33965517/1771882
332namespace boost {
333namespace test_tools {
334namespace tt_detail {
335template <> struct print_log_value<InterpolationType> {
336 void operator()(ostream& os, const InterpolationType& interpolationType) { os << to_string(interpolationType); }
337};
338} // namespace tt_detail
339} // namespace test_tools
340} // namespace boost
341
342BOOST_FIXTURE_TEST_SUITE(QuantExtTestSuite, qle::test::TopLevelFixture)
343
344BOOST_AUTO_TEST_SUITE(PiecewiseOptionletStripperTests)
345
346BOOST_DATA_TEST_CASE_F(CommonVars, testPiecewiseOptionletSurfaceStripping,
347 bdata::make(volatilityTypes) * bdata::make(timeInterpolationTypes) *
348 bdata::make(smileInterpolationTypes) * bdata::make(flatFirstPeriodValues) *
349 bdata::make(isMovingValues) * bdata::make(vsInterpMethods) *
350 bdata::make(interpOnOptionletValues) * bdata::make(addAtmValues),
351 volatilityType, timeInterp, smileInterp, flatFirstPeriod, isMoving, vsInterpMethod,
352 interpOnOptionlet, addAtm) {
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 // Create the piecewise optionlet stripper from the surface and wrap in an adapter
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 // Price all of the input surface instruments using the cap floor term volatilities and again with the optionlet
459 // volatilities and check that the NPVs match
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 // Create the OTM cap floor instrument that we will price
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 // Price the instrument using the flat term volatility
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 // Price the instrument using the stripped (Normal) optionlet surface
487 capFloor->setPricingEngine(QuantLib::ext::make_shared<BachelierCapFloorEngine>(discount, ovs));
488 Real strippedNpv = capFloor->NPV();
489
490 // Check that the difference is within the tolerance
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 // If we have added in ATM, test the ATM value also
501 if (addAtm) {
502
503 for (Size i = 0; i < testVols.atmTenors.size(); i++) {
504
505 // Create the ATM cap (with a dummy strike first)
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 // Price the instrument using the flat term volatility
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 // Price the instrument using the stripped (Normal) optionlet surface
523 capFloor->setPricingEngine(QuantLib::ext::make_shared<BachelierCapFloorEngine>(discount, ovs));
524 Real strippedNpv = capFloor->NPV();
525
526 // Check that the difference is within the tolerance
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}
536
537BOOST_FIXTURE_TEST_CASE(testExtrapolation, CommonVars) {
538
539 BOOST_TEST_MESSAGE("Testing extrapolation settings");
540
541 // A shift of 1bp that will be used below
542 Real shift = 0.001;
543
544 // Pick one configuration and check that extrapolation works as expected
545 Handle<OptionletVolatilityStructure> ovs =
546 createOvs<LinearFlat, LinearFlat>(Normal, true, false, TermVolSurface::Bilinear, true, false);
547
548 // Boundaries
549 Date maxDate = ovs->maxDate();
550 Rate minStrike = ovs->minStrike();
551 Rate maxStrike = ovs->maxStrike();
552
553 // Trivial check
554 BOOST_REQUIRE(maxStrike > minStrike);
555
556 // Asking for vol before reference date throws
557 Date testDate = referenceDate - 1 * Days;
558 Rate testStrike = (maxStrike + minStrike) / 2;
559 BOOST_CHECK_THROW(ovs->volatility(testDate, testStrike, false), Error);
560 BOOST_CHECK_THROW(ovs->volatility(testDate, testStrike, true), Error);
561
562 // Check that asking for a volatility within the boundary does not throw
563 testDate = referenceDate + 1 * Days;
564 BOOST_CHECK_NO_THROW(ovs->volatility(testDate, testStrike, false));
565
566 testDate = maxDate - 1 * Days;
567 BOOST_CHECK_NO_THROW(ovs->volatility(testDate, testStrike, false));
568
569 // Check that asking for a volatility outside the boundary throws
570 testDate = maxDate + 1 * Days;
571 BOOST_CHECK_THROW(ovs->volatility(testDate, testStrike, false), Error);
572
573 testDate = referenceDate + 1 * Days;
574 testStrike = minStrike - shift;
575 BOOST_CHECK_THROW(ovs->volatility(testDate, testStrike, false), Error);
576
577 testStrike = maxStrike + shift;
578 BOOST_CHECK_THROW(ovs->volatility(testDate, testStrike, false), Error);
579
580 // Check that asking for a volatility outside the boundary, with explicit extrapolation on, does not throw
581 testDate = maxDate + 1 * Days;
582 testStrike = (maxStrike + minStrike) / 2;
583 BOOST_CHECK_NO_THROW(ovs->volatility(testDate, testStrike, true));
584
585 testDate = referenceDate + 1 * Days;
586 testStrike = minStrike - shift;
587 BOOST_CHECK_NO_THROW(ovs->volatility(testDate, testStrike, true));
588
589 testStrike = maxStrike + shift;
590 BOOST_CHECK_NO_THROW(ovs->volatility(testDate, testStrike, true));
591
592 // Check that asking for a volatility outside the boundary, after turning on extrapolation, does not throw
593 ovs->enableExtrapolation();
594
595 testDate = maxDate + 1 * Days;
596 testStrike = (maxStrike + minStrike) / 2;
597 BOOST_CHECK_NO_THROW(ovs->volatility(testDate, testStrike, false));
598
599 testDate = referenceDate + 1 * Days;
600 testStrike = minStrike - shift;
601 BOOST_CHECK_NO_THROW(ovs->volatility(testDate, testStrike, false));
602
603 testStrike = maxStrike + shift;
604 BOOST_CHECK_NO_THROW(ovs->volatility(testDate, testStrike, false));
605}
606
607// Test cached values with LinearFlat time and smile interpolation
608BOOST_FIXTURE_TEST_CASE(testCachedLinearFlat, CommonVars) {
609
610 BOOST_TEST_MESSAGE("Testing against cached optionlet volatilities with LinearFlat time and smile interpolation");
611
612 // Create the cap floor term vol surface
613 QuantLib::ext::shared_ptr<TermVolSurface> cfts =
614 QuantLib::ext::make_shared<TermVolSurface>(referenceDate, calendar, bdc, testVols.tenors, testVols.strikes,
615 testVols.nVols, dayCounter, TermVolSurface::Bilinear);
616
617 // Create the piecewise optionlet stripper
618 QuantLib::ext::shared_ptr<QuantExt::OptionletStripper> pwos = QuantLib::ext::make_shared<PiecewiseOptionletStripper<LinearFlat> >(
619 cfts, iborIndex, testYieldCurves.discountEonia, true, Normal, 0.0, Normal);
620
621 // Create the OptionletVolatilityStructure
622 Handle<OptionletVolatilityStructure> ovs = Handle<OptionletVolatilityStructure>(
623 QuantLib::ext::make_shared<QuantExt::StrippedOptionletAdapter<LinearFlat, LinearFlat> >(referenceDate, pwos));
624 ovs->enableExtrapolation();
625
626 // Check optionlet fixing dates against cached fixing dates
627 BOOST_CHECK_EQUAL_COLLECTIONS(cachedOptionletFixingDates.begin(), cachedOptionletFixingDates.end(),
628 pwos->optionletFixingDates().begin(), pwos->optionletFixingDates().end());
629
630 // Check cached optionlet values at optionlet fixing dates
631 Matrix cached(cachedOptionletFixingDates.size(), cachedStrikes.size(), cachedValues.begin(), cachedValues.end());
632 BOOST_TEST_MESSAGE("Optionlet volatilities at the fixing dates");
633 BOOST_TEST_MESSAGE("date,strike,volatility,cached,diff");
634 for (Size i = 0; i < pwos->optionletFixingDates().size(); i++) {
635 for (Size j = 0; j < cachedStrikes.size(); j++) {
636 Date d = pwos->optionletFixingDates()[i];
637 Rate s = cachedStrikes[j];
638 Volatility v = ovs->volatility(d, s);
639 Volatility diff = fabs(v - cached[i][j]);
640 BOOST_CHECK_SMALL(diff, tolerance);
641 BOOST_TEST_MESSAGE(io::iso_date(d) << "," << s << "," << fixed << setprecision(12) << v << ","
642 << cached[i][j] << "," << diff);
643 }
644 }
645
646 // Check cached optionlet values at ad-hoc dates
647 cached = Matrix(cachedAdHocDates.size(), cachedStrikes.size(), cachedAdHocValues.begin(), cachedAdHocValues.end());
648 BOOST_TEST_MESSAGE("Optionlet volatilities at the ad-hoc dates");
649 BOOST_TEST_MESSAGE("date,strike,volatility,cached,diff");
650 for (Size i = 0; i < cachedAdHocDates.size(); i++) {
651 for (Size j = 0; j < cachedStrikes.size(); j++) {
652 Date d = cachedAdHocDates[i];
653 Rate s = cachedStrikes[j];
654 Volatility v = ovs->volatility(d, s);
655 Volatility diff = fabs(v - cached[i][j]);
656 BOOST_CHECK_SMALL(diff, tolerance);
657 BOOST_TEST_MESSAGE(io::iso_date(d) << "," << s << "," << fixed << setprecision(12) << v << ","
658 << cached[i][j] << "," << diff);
659 }
660 }
661}
662
663BOOST_FIXTURE_TEST_CASE(testChangingCapFloorSurface, CommonVars) {
664
665 BOOST_TEST_MESSAGE("Testing changing the input cap floor surface");
666
667 // Take four normal volatilities from test data and create quotes
668 Size lastTenorIdx = testVols.tenors.size() - 1;
669 Size lastStrikeIdx = testVols.strikes.size() - 1;
670
671 vector<Period> tenors = list_of(testVols.tenors[0])(testVols.tenors[lastTenorIdx]);
672 vector<Rate> strikes = list_of(testVols.strikes[0])(testVols.strikes[lastStrikeIdx]);
673
674 vector<vector<QuantLib::ext::shared_ptr<SimpleQuote> > > quotes(2);
675 quotes[0].push_back(QuantLib::ext::make_shared<SimpleQuote>(testVols.nVols[0][0]));
676 quotes[0].push_back(QuantLib::ext::make_shared<SimpleQuote>(testVols.nVols[0][lastStrikeIdx]));
677 quotes[1].push_back(QuantLib::ext::make_shared<SimpleQuote>(testVols.nVols[lastTenorIdx][0]));
678 quotes[1].push_back(QuantLib::ext::make_shared<SimpleQuote>(testVols.nVols[lastTenorIdx][lastStrikeIdx]));
679
680 vector<vector<Handle<Quote> > > quoteHs(2);
681 quoteHs[0].push_back(Handle<Quote>(quotes[0][0]));
682 quoteHs[0].push_back(Handle<Quote>(quotes[0][1]));
683 quoteHs[1].push_back(Handle<Quote>(quotes[1][0]));
684 quoteHs[1].push_back(Handle<Quote>(quotes[1][1]));
685
686 // Create the cap floor term vol surface using the quotes
687 QuantLib::ext::shared_ptr<TermVolSurface> cfts = QuantLib::ext::make_shared<TermVolSurface>(
688 settlementDays, calendar, bdc, tenors, strikes, quoteHs, dayCounter, TermVolSurface::Bilinear);
689
690 // Create the piecewise optionlet stripper
691 QuantLib::ext::shared_ptr<QuantExt::OptionletStripper> pwos = QuantLib::ext::make_shared<PiecewiseOptionletStripper<LinearFlat> >(
692 cfts, iborIndex, testYieldCurves.discountEonia, true, Normal, 0.0, Normal);
693
694 // Create the OptionletVolatilityStructure
695 QuantLib::ext::shared_ptr<OptionletVolatilityStructure> ovs =
696 QuantLib::ext::make_shared<QuantExt::StrippedOptionletAdapter<LinearFlat, LinearFlat> >(pwos);
697 ovs->enableExtrapolation();
698
699 // Request optionlet volatility at test date
700 Date testDate = pwos->optionletFixingDates().back();
701 Volatility initialVol = ovs->volatility(testDate, strikes[0]);
702 BOOST_TEST_MESSAGE("Test vol before shift is: " << fixed << setprecision(12) << initialVol);
703 Volatility expInitialVol = 0.007941492816;
704 BOOST_CHECK_SMALL(fabs(expInitialVol - initialVol), tolerance);
705
706 // Shift the input quote and request the same optionlet volatility
707 Rate shift = 1.1;
708 quotes[1][0]->setValue(shift * quotes[1][0]->value());
709 Volatility shiftedVol = ovs->volatility(testDate, strikes[0]);
710 BOOST_TEST_MESSAGE("Test vol after shift is: " << fixed << setprecision(12) << shiftedVol);
711 Volatility expShiftedVol = 0.008926338986;
712 BOOST_CHECK_SMALL(fabs(expShiftedVol - shiftedVol), tolerance);
713
714 // Reset the input quote
715 quotes[1][0]->setValue(quotes[1][0]->value() / shift);
716 initialVol = ovs->volatility(testDate, strikes[0]);
717 BOOST_TEST_MESSAGE("Test vol after reset is: " << fixed << setprecision(12) << initialVol);
718 BOOST_CHECK_SMALL(fabs(expInitialVol - initialVol), tolerance);
719
720 // Change the evaluation date
721 Date newDate = referenceDate + 1 * Weeks;
722 Settings::instance().evaluationDate() = newDate;
723
724 // Check that the optionlet volatility structure's reference date has moved
725 // Only the case because we used a "moving" adapter
726 BOOST_CHECK_EQUAL(ovs->referenceDate(), newDate);
727
728 // Check that one of the optionlet fixing dates in the PiecewiseOptionletStripper has moved
729 // Only the case because we used a "moving" cap floor volatility term surface as input
730 Date newLastOptionletDate = pwos->optionletFixingDates().back();
731 BOOST_TEST_MESSAGE("Last fixing date moved from " << io::iso_date(testDate) << " to "
732 << io::iso_date(newLastOptionletDate));
733 BOOST_CHECK(newLastOptionletDate > testDate);
734
735 // Check the newly calculated optionlet vol for the old test date
736 Volatility newVol = ovs->volatility(testDate, strikes[0]);
737 BOOST_TEST_MESSAGE("Test vol after moving evaluation date is: " << fixed << setprecision(12) << newVol);
738 Volatility expNewVol = 0.007932365669;
739 BOOST_CHECK_SMALL(fabs(expNewVol - newVol), tolerance);
740}
741
742BOOST_AUTO_TEST_SUITE_END()
743
744BOOST_AUTO_TEST_SUITE_END()
Helper for bootstrapping optionlet volatilities from cap floor volatilities.
structs containing capfloor market data that can be used in tests
Cap floor at-the-money term volatility curve.
Cap/floor smile volatility surface.
Cubic interpolation and flat extrapolation factory and traits.
Linear-interpolation and flat extrapolation factory and traits
flat interpolation decorator
Optionlet stripper that amends existing stripped optionlets to incorporate ATM cap floor volatilities...
BOOST_FIXTURE_TEST_CASE(testExtrapolation, CommonVars)
BOOST_DATA_TEST_CASE_F(CommonVars, testPiecewiseOptionletSurfaceStripping, bdata::make(volatilityTypes) *bdata::make(timeInterpolationTypes) *bdata::make(smileInterpolationTypes) *bdata::make(flatFirstPeriodValues) *bdata::make(isMovingValues) *bdata::make(vsInterpMethods) *bdata::make(interpOnOptionletValues) *bdata::make(addAtmValues), volatilityType, timeInterp, smileInterp, flatFirstPeriod, isMoving, vsInterpMethod, interpOnOptionlet, addAtm)
QuantLib::BootstrapHelper< QuantLib::OptionletVolatilityStructure > helper
Strip optionlet volatility surface from cap floor volatility term surface.
Convert a StrippedOptionletBase in to an OptionletVolatilityStructure.
vector< Real > strikes
Fixture that can be used at top level.
structs containing yield curve market data that can be used in tests