[go: up one dir, main page]

1/* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2
3/*
4 Copyright (C) 2008 Ferdinando Ametrano
5 Copyright (C) 2007, 2008 Laurent Hoffmann
6 Copyright (C) 2015, 2016 Michael von den Driesch
7
8 This file is part of QuantLib, a free-software/open-source library
9 for financial quantitative analysts and developers - http://quantlib.org/
10
11 QuantLib is free software: you can redistribute it and/or modify it
12 under the terms of the QuantLib license. You should have received a
13 copy of the license along with this program; if not, please email
14 <quantlib-dev@lists.sf.net>. The license is also available online at
15 <http://quantlib.org/license.shtml>.
16
17 This program is distributed in the hope that it will be useful, but WITHOUT
18 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
19 FOR A PARTICULAR PURPOSE. See the license for more details.
20*/
21
22#include "optionletstripper.hpp"
23#include "utilities.hpp"
24#include <ql/termstructures/volatility/optionlet/optionletstripper1.hpp>
25#include <ql/termstructures/volatility/optionlet/optionletstripper2.hpp>
26#include <ql/termstructures/volatility/optionlet/strippedoptionletadapter.hpp>
27#include <ql/termstructures/volatility/capfloor/constantcapfloortermvol.hpp>
28#include <ql/termstructures/volatility/capfloor/capfloortermvolcurve.hpp>
29#include <ql/termstructures/yield/flatforward.hpp>
30#include <ql/termstructures/yield/zerocurve.hpp>
31#include <ql/time/calendars/target.hpp>
32#include <ql/indexes/ibor/euribor.hpp>
33#include <ql/pricingengines/capfloor/blackcapfloorengine.hpp>
34#include <ql/pricingengines/capfloor/bacheliercapfloorengine.hpp>
35#include <ql/instruments/makecapfloor.hpp>
36#include <ql/quotes/simplequote.hpp>
37#include <algorithm>
38#include <iterator>
39
40using namespace QuantLib;
41using namespace boost::unit_test_framework;
42
43namespace optionlet_stripper_test {
44
45 struct CommonVars {
46 // global data
47 Calendar calendar;
48 DayCounter dayCounter;
49
50 RelinkableHandle<YieldTermStructure> yieldTermStructure;
51 RelinkableHandle< YieldTermStructure > discountingYTS;
52 RelinkableHandle< YieldTermStructure > forwardingYTS;
53
54 std::vector<Rate> strikes;
55 std::vector<Period> optionTenors;
56 Matrix termV;
57 std::vector<Rate> atmTermV;
58 std::vector<Handle<Quote> > atmTermVolHandle;
59
60 Handle<CapFloorTermVolCurve> capFloorVolCurve;
61 Handle<CapFloorTermVolCurve> flatTermVolCurve;
62
63 ext::shared_ptr<CapFloorTermVolSurface> capFloorVolSurface;
64 ext::shared_ptr<CapFloorTermVolSurface> flatTermVolSurface;
65 ext::shared_ptr< CapFloorTermVolSurface > capFloorVolRealSurface;
66
67 Real accuracy;
68 Real tolerance;
69
70 CommonVars() {
71 accuracy = 1.0e-6;
72 tolerance = 2.5e-8;
73 }
74
75 void setTermStructure() {
76
77 calendar = TARGET();
78 dayCounter = Actual365Fixed();
79
80 Rate flatFwdRate = 0.04;
81 yieldTermStructure.linkTo(
82 h: ext::make_shared<FlatForward>(args: 0,
83 args&: calendar,
84 args&: flatFwdRate,
85 args&: dayCounter));
86 }
87
88 void setRealTermStructure() {
89
90 calendar = TARGET();
91 dayCounter = Actual365Fixed();
92
93 std::vector< int > datesTmp = {
94 42124, 42129, 42143, 42221, 42254, 42282, 42313, 42345,
95 42374, 42405, 42465, 42495, 42587, 42681, 42772, 42860, 43227,
96 43956, 44321, 44686, 45051, 45418, 45782, 46147, 46512, 47609,
97 49436, 51263, 53087, 56739, 60392
98 };
99
100 std::vector< Date > dates;
101 dates.reserve(n: datesTmp.size());
102 for (int& it : datesTmp)
103 dates.emplace_back(args&: it);
104
105 std::vector< Rate > rates = {
106 -0.00292, -0.00292, -0.001441, -0.00117, -0.001204,
107 -0.001212, -0.001223, -0.001236, -0.001221, -0.001238,
108 -0.001262, -0.00125, -0.001256, -0.001233, -0.00118, -0.001108,
109 -0.000619, 0.000833, 0.001617, 0.002414, 0.003183, 0.003883,
110 0.004514, 0.005074, 0.005606, 0.006856, 0.00813, 0.008709,
111 0.009136, 0.009601, 0.009384
112 };
113
114 discountingYTS.linkTo(
115 h: ext::make_shared< InterpolatedZeroCurve< Linear > >(
116 args&: dates, args&: rates,
117 args&: dayCounter, args&: calendar));
118
119 datesTmp.clear();
120 dates.clear();
121 rates.clear();
122
123 datesTmp = {
124 42124, 42313, 42436, 42556, 42618, 42800, 42830, 42860,
125 43227, 43591, 43956, 44321, 44686, 45051, 45418, 45782, 46147,
126 46512, 46878, 47245, 47609, 47973, 48339, 48704, 49069, 49436,
127 49800, 50165, 50530, 50895, 51263, 51627, 51991, 52356, 52722,
128 53087, 54913, 56739, 60392, 64045
129 };
130
131 for (int& it : datesTmp)
132 dates.emplace_back(args&: it);
133
134 rates = {
135 0.000649, 0.000649, 0.000684, 0.000717, 0.000745, 0.000872,
136 0.000905, 0.000954, 0.001532, 0.002319, 0.003147, 0.003949,
137 0.004743, 0.00551, 0.006198, 0.006798, 0.007339, 0.007832,
138 0.008242, 0.008614, 0.008935, 0.009205, 0.009443, 0.009651,
139 0.009818, 0.009952, 0.010054, 0.010146, 0.010206, 0.010266,
140 0.010315, 0.010365, 0.010416, 0.010468, 0.010519, 0.010571,
141 0.010757, 0.010806, 0.010423, 0.010217
142 };
143
144 forwardingYTS.linkTo(
145 h: ext::make_shared< InterpolatedZeroCurve< Linear > >(
146 args&: dates, args&: rates, args&: dayCounter, args&: calendar));
147 }
148
149 void setFlatTermVolCurve() {
150
151 setTermStructure();
152
153 optionTenors.resize(new_size: 10);
154 for (Size i = 0; i < optionTenors.size(); ++i)
155 optionTenors[i] = Period(i + 1, Years);
156
157 Volatility flatVol = .18;
158
159 std::vector<Handle<Quote> > curveVHandle(optionTenors.size());
160 for (Size i=0; i<optionTenors.size(); ++i)
161 curveVHandle[i] = Handle<Quote>(ext::shared_ptr<Quote>(new
162 SimpleQuote(flatVol)));
163
164 flatTermVolCurve = Handle<CapFloorTermVolCurve>(
165 ext::make_shared<CapFloorTermVolCurve>(args: 0, args&: calendar, args: Following, args&: optionTenors,
166 args&: curveVHandle, args&: dayCounter));
167
168 }
169
170 void setFlatTermVolSurface() {
171
172 setTermStructure();
173
174 optionTenors.resize(new_size: 10);
175 for (Size i = 0; i < optionTenors.size(); ++i)
176 optionTenors[i] = Period(i + 1, Years);
177
178 strikes.resize(new_size: 10);
179 for (Size j = 0; j < strikes.size(); ++j)
180 strikes[j] = Real(j + 1) / 100.0;
181
182 Volatility flatVol = .18;
183 termV = Matrix(optionTenors.size(), strikes.size(), flatVol);
184 flatTermVolSurface = ext::make_shared<CapFloorTermVolSurface>(args: 0, args&: calendar, args: Following,
185 args&: optionTenors, args&: strikes,
186 args&: termV, args&: dayCounter);
187 }
188
189
190 void setCapFloorTermVolCurve() {
191
192 setTermStructure();
193
194 //atm cap volatility curve
195 optionTenors = {
196 {1, Years},
197 {18, Months},
198 {2, Years},
199 {3, Years},
200 {4, Years},
201 {5, Years},
202 {6, Years},
203 {7, Years},
204 {8, Years},
205 {9, Years},
206 {10, Years},
207 {12, Years},
208 {15, Years},
209 {20, Years},
210 {25, Years},
211 {30, Years}
212 };
213
214 //atm capfloor vols from mkt vol matrix using flat yield curve
215 atmTermV = {
216 0.090304,
217 0.12180,
218 0.13077,
219 0.14832,
220 0.15570,
221 0.15816,
222 0.15932,
223 0.16035,
224 0.15951,
225 0.15855,
226 0.15754,
227 0.15459,
228 0.15163,
229 0.14575,
230 0.14175,
231 0.13889
232 };
233
234 atmTermVolHandle.resize(new_size: optionTenors.size());
235 for (Size i=0; i<optionTenors.size(); ++i) {
236 atmTermVolHandle[i] = Handle<Quote>(ext::shared_ptr<Quote>(new
237 SimpleQuote(atmTermV[i])));
238 }
239
240 capFloorVolCurve = Handle<CapFloorTermVolCurve>(
241 ext::make_shared<CapFloorTermVolCurve>(args: 0, args&: calendar, args: Following,
242 args&: optionTenors, args&: atmTermVolHandle,
243 args&: dayCounter));
244
245 }
246
247 void setCapFloorTermVolSurface() {
248
249 setTermStructure();
250
251 //cap volatility smile matrix
252 optionTenors = {
253 {1, Years},
254 {18, Months},
255 {2, Years},
256 {3, Years},
257 {4, Years},
258 {5, Years},
259 {6, Years},
260 {7, Years},
261 {8, Years},
262 {9, Years},
263 {10, Years},
264 {12, Years},
265 {15, Years},
266 {20, Years},
267 {25, Years},
268 {30, Years}
269 };
270
271 strikes = {
272 0.015,
273 0.0175,
274 0.02,
275 0.0225,
276 0.025,
277 0.03,
278 0.035,
279 0.04,
280 0.05,
281 0.06,
282 0.07,
283 0.08,
284 0.1
285 };
286
287 termV = Matrix(optionTenors.size(), strikes.size());
288 termV[0][0]=0.287; termV[0][1]=0.274; termV[0][2]=0.256; termV[0][3]=0.245; termV[0][4]=0.227; termV[0][5]=0.148; termV[0][6]=0.096; termV[0][7]=0.09; termV[0][8]=0.11; termV[0][9]=0.139; termV[0][10]=0.166; termV[0][11]=0.19; termV[0][12]=0.214;
289 termV[1][0]=0.303; termV[1][1]=0.258; termV[1][2]=0.22; termV[1][3]=0.203; termV[1][4]=0.19; termV[1][5]=0.153; termV[1][6]=0.126; termV[1][7]=0.118; termV[1][8]=0.147; termV[1][9]=0.165; termV[1][10]=0.18; termV[1][11]=0.192; termV[1][12]=0.212;
290 termV[2][0]=0.303; termV[2][1]=0.257; termV[2][2]=0.216; termV[2][3]=0.196; termV[2][4]=0.182; termV[2][5]=0.154; termV[2][6]=0.134; termV[2][7]=0.127; termV[2][8]=0.149; termV[2][9]=0.166; termV[2][10]=0.18; termV[2][11]=0.192; termV[2][12]=0.212;
291 termV[3][0]=0.305; termV[3][1]=0.266; termV[3][2]=0.226; termV[3][3]=0.203; termV[3][4]=0.19; termV[3][5]=0.167; termV[3][6]=0.151; termV[3][7]=0.144; termV[3][8]=0.16; termV[3][9]=0.172; termV[3][10]=0.183; termV[3][11]=0.193; termV[3][12]=0.209;
292 termV[4][0]=0.294; termV[4][1]=0.261; termV[4][2]=0.216; termV[4][3]=0.201; termV[4][4]=0.19; termV[4][5]=0.171; termV[4][6]=0.158; termV[4][7]=0.151; termV[4][8]=0.163; termV[4][9]=0.172; termV[4][10]=0.181; termV[4][11]=0.188; termV[4][12]=0.201;
293 termV[5][0]=0.276; termV[5][1]=0.248; termV[5][2]=0.212; termV[5][3]=0.199; termV[5][4]=0.189; termV[5][5]=0.172; termV[5][6]=0.16; termV[5][7]=0.155; termV[5][8]=0.162; termV[5][9]=0.17; termV[5][10]=0.177; termV[5][11]=0.183; termV[5][12]=0.195;
294 termV[6][0]=0.26; termV[6][1]=0.237; termV[6][2]=0.21; termV[6][3]=0.198; termV[6][4]=0.188; termV[6][5]=0.172; termV[6][6]=0.161; termV[6][7]=0.156; termV[6][8]=0.161; termV[6][9]=0.167; termV[6][10]=0.173; termV[6][11]=0.179; termV[6][12]=0.19;
295 termV[7][0]=0.25; termV[7][1]=0.231; termV[7][2]=0.208; termV[7][3]=0.196; termV[7][4]=0.187; termV[7][5]=0.172; termV[7][6]=0.162; termV[7][7]=0.156; termV[7][8]=0.16; termV[7][9]=0.165; termV[7][10]=0.17; termV[7][11]=0.175; termV[7][12]=0.185;
296 termV[8][0]=0.244; termV[8][1]=0.226; termV[8][2]=0.206; termV[8][3]=0.195; termV[8][4]=0.186; termV[8][5]=0.171; termV[8][6]=0.161; termV[8][7]=0.156; termV[8][8]=0.158; termV[8][9]=0.162; termV[8][10]=0.166; termV[8][11]=0.171; termV[8][12]=0.18;
297 termV[9][0]=0.239; termV[9][1]=0.222; termV[9][2]=0.204; termV[9][3]=0.193; termV[9][4]=0.185; termV[9][5]=0.17; termV[9][6]=0.16; termV[9][7]=0.155; termV[9][8]=0.156; termV[9][9]=0.159; termV[9][10]=0.163; termV[9][11]=0.168; termV[9][12]=0.177;
298 termV[10][0]=0.235; termV[10][1]=0.219; termV[10][2]=0.202; termV[10][3]=0.192; termV[10][4]=0.183; termV[10][5]=0.169; termV[10][6]=0.159; termV[10][7]=0.154; termV[10][8]=0.154; termV[10][9]=0.156; termV[10][10]=0.16; termV[10][11]=0.164; termV[10][12]=0.173;
299 termV[11][0]=0.227; termV[11][1]=0.212; termV[11][2]=0.197; termV[11][3]=0.187; termV[11][4]=0.179; termV[11][5]=0.166; termV[11][6]=0.156; termV[11][7]=0.151; termV[11][8]=0.149; termV[11][9]=0.15; termV[11][10]=0.153; termV[11][11]=0.157; termV[11][12]=0.165;
300 termV[12][0]=0.22; termV[12][1]=0.206; termV[12][2]=0.192; termV[12][3]=0.183; termV[12][4]=0.175; termV[12][5]=0.162; termV[12][6]=0.153; termV[12][7]=0.147; termV[12][8]=0.144; termV[12][9]=0.144; termV[12][10]=0.147; termV[12][11]=0.151; termV[12][12]=0.158;
301 termV[13][0]=0.211; termV[13][1]=0.197; termV[13][2]=0.185; termV[13][3]=0.176; termV[13][4]=0.168; termV[13][5]=0.156; termV[13][6]=0.147; termV[13][7]=0.142; termV[13][8]=0.138; termV[13][9]=0.138; termV[13][10]=0.14; termV[13][11]=0.144; termV[13][12]=0.151;
302 termV[14][0]=0.204; termV[14][1]=0.192; termV[14][2]=0.18; termV[14][3]=0.171; termV[14][4]=0.164; termV[14][5]=0.152; termV[14][6]=0.143; termV[14][7]=0.138; termV[14][8]=0.134; termV[14][9]=0.134; termV[14][10]=0.137; termV[14][11]=0.14; termV[14][12]=0.148;
303 termV[15][0]=0.2; termV[15][1]=0.187; termV[15][2]=0.176; termV[15][3]=0.167; termV[15][4]=0.16; termV[15][5]=0.148; termV[15][6]=0.14; termV[15][7]=0.135; termV[15][8]=0.131; termV[15][9]=0.132; termV[15][10]=0.135; termV[15][11]=0.139; termV[15][12]=0.146;
304
305 capFloorVolSurface = ext::make_shared<CapFloorTermVolSurface>(args: 0, args&: calendar, args: Following,
306 args&: optionTenors, args&: strikes,
307 args&: termV, args&: dayCounter);
308 }
309
310 void setRealCapFloorTermVolSurface() {
311
312 setRealTermStructure();
313
314 // cap volatility smile matrix
315 optionTenors = {
316 {1, Years},
317 {18, Months},
318 {2, Years},
319 {3, Years},
320 {4, Years},
321 {5, Years},
322 {6, Years},
323 {7, Years},
324 {8, Years},
325 {9, Years},
326 {10, Years},
327 {12, Years},
328 {15, Years},
329 {20, Years},
330 {25, Years},
331 {30, Years}
332 };
333 // 16
334
335 strikes = {
336 -0.005,
337 -0.0025,
338 -0.00125,
339 0.0,
340 0.00125,
341 0.0025,
342 0.005,
343 0.01,
344 0.015,
345 0.02,
346 0.03,
347 0.05,
348 0.1
349 };
350 // 13
351
352 std::vector< Real > rawVols = {
353 0.49, 0.39, 0.34, 0.31, 0.34, 0.37, 0.50, 0.75, 0.99, 1.21, 1.64, 2.44, 4.29,
354 0.44, 0.36, 0.33, 0.31, 0.33, 0.35,0.45, 0.65, 0.83, 1.00, 1.32, 1.93, 3.30,
355 0.40, 0.35, 0.33,0.31, 0.33, 0.34, 0.41, 0.55, 0.69, 0.82, 1.08, 1.56, 2.68,
356 0.42, 0.39, 0.38, 0.37, 0.38, 0.39, 0.43, 0.54, 0.64, 0.74,0.94, 1.31, 2.18,
357 0.46, 0.43, 0.42, 0.41, 0.42, 0.43, 0.47,0.56, 0.66, 0.75, 0.93, 1.28, 2.07,
358 0.49, 0.47, 0.46, 0.45,0.46, 0.47, 0.51, 0.59, 0.68, 0.76, 0.93, 1.25, 1.99,
359 0.51, 0.49, 0.49, 0.48, 0.49, 0.50, 0.54, 0.62, 0.70, 0.78, 0.94,1.24, 1.94,
360 0.52, 0.51, 0.51, 0.51, 0.52, 0.53, 0.56, 0.63,0.71, 0.79, 0.94, 1.23, 1.89,
361 0.53, 0.52, 0.52, 0.52, 0.53,0.54, 0.57, 0.65, 0.72, 0.79, 0.94, 1.21, 1.83,
362 0.55, 0.54, 0.54, 0.54, 0.55, 0.56, 0.59, 0.66, 0.72, 0.79, 0.91, 1.15,1.71,
363 0.56, 0.56, 0.56, 0.56, 0.57, 0.58, 0.61, 0.67, 0.72,0.78, 0.89, 1.09, 1.59,
364 0.59, 0.58, 0.58, 0.59, 0.59, 0.60,0.63, 0.68, 0.73, 0.78, 0.86, 1.03, 1.45,
365 0.61, 0.61, 0.61,0.61, 0.62, 0.62, 0.64, 0.69, 0.73, 0.77, 0.85, 1.02, 1.44,
366 0.62, 0.62, 0.63, 0.63, 0.64, 0.64, 0.65, 0.69, 0.72, 0.76,0.82, 0.96, 1.32,
367 0.62, 0.63, 0.63, 0.63, 0.65, 0.66, 0.66,0.68, 0.72, 0.74, 0.80, 0.93, 1.25,
368 0.62, 0.62, 0.62, 0.62,0.66, 0.67, 0.67, 0.67, 0.72, 0.72, 0.78, 0.90, 1.25
369 };
370
371 termV = Matrix(optionTenors.size(), strikes.size());
372 std::copy(first: rawVols.begin(), last: rawVols.end(), result: termV.begin());
373 termV /= 100;
374
375 capFloorVolRealSurface =
376 ext::make_shared< CapFloorTermVolSurface >(
377 args: 0, args&: calendar, args: Following,
378 args&: optionTenors, args&: strikes, args&: termV,
379 args&: dayCounter);
380 }
381 };
382}
383
384void OptionletStripperTest::testFlatTermVolatilityStripping1() {
385
386 BOOST_TEST_MESSAGE(
387 "Testing forward/forward vol stripping from flat term vol "
388 "surface using OptionletStripper1 class...");
389
390 using namespace optionlet_stripper_test;
391
392 CommonVars vars;
393 Settings::instance().evaluationDate() = Date(28, October, 2013);
394
395 vars.setFlatTermVolSurface();
396
397 ext::shared_ptr<IborIndex> iborIndex(new Euribor6M(vars.yieldTermStructure));
398
399 ext::shared_ptr<OptionletStripper> optionletStripper1(new
400 OptionletStripper1(vars.flatTermVolSurface,
401 iborIndex,
402 Null<Rate>(),
403 vars.accuracy));
404
405 ext::shared_ptr<StrippedOptionletAdapter> strippedOptionletAdapter(new
406 StrippedOptionletAdapter(optionletStripper1));
407
408 Handle<OptionletVolatilityStructure> vol(strippedOptionletAdapter);
409
410 vol->enableExtrapolation();
411
412 ext::shared_ptr<BlackCapFloorEngine> strippedVolEngine(new
413 BlackCapFloorEngine(vars.yieldTermStructure,
414 vol));
415
416 ext::shared_ptr<CapFloor> cap;
417 for (Size tenorIndex=0; tenorIndex<vars.optionTenors.size(); ++tenorIndex) {
418 for (Size strikeIndex=0; strikeIndex<vars.strikes.size(); ++strikeIndex) {
419 cap = MakeCapFloor(CapFloor::Cap,
420 vars.optionTenors[tenorIndex],
421 iborIndex,
422 vars.strikes[strikeIndex],
423 0*Days)
424 .withPricingEngine(engine: strippedVolEngine);
425
426 Real priceFromStrippedVolatility = cap->NPV();
427
428 ext::shared_ptr<PricingEngine> blackCapFloorEngineConstantVolatility(new
429 BlackCapFloorEngine(vars.yieldTermStructure,
430 vars.termV[tenorIndex][strikeIndex]));
431
432 cap->setPricingEngine(blackCapFloorEngineConstantVolatility);
433 Real priceFromConstantVolatility = cap->NPV();
434
435 Real error = std::fabs(x: priceFromStrippedVolatility - priceFromConstantVolatility);
436 if (error>vars.tolerance)
437 BOOST_FAIL("\noption tenor: " << vars.optionTenors[tenorIndex] <<
438 "\nstrike: " << io::rate(vars.strikes[strikeIndex]) <<
439 "\nstripped vol price: " << io::rate(priceFromStrippedVolatility) <<
440 "\nconstant vol price: " << io::rate(priceFromConstantVolatility) <<
441 "\nerror: " << io::rate(error) <<
442 "\ntolerance: " << io::rate(vars.tolerance));
443 }
444 }
445}
446
447void OptionletStripperTest::testTermVolatilityStripping1() {
448
449 BOOST_TEST_MESSAGE(
450 "Testing forward/forward vol stripping from non-flat term "
451 "vol surface using OptionletStripper1 class...");
452
453 using namespace optionlet_stripper_test;
454
455 CommonVars vars;
456 Settings::instance().evaluationDate() = Date(28, October, 2013);
457
458 vars.setCapFloorTermVolSurface();
459
460 ext::shared_ptr<IborIndex> iborIndex(new Euribor6M(vars.yieldTermStructure));
461
462 ext::shared_ptr<OptionletStripper> optionletStripper1(new
463 OptionletStripper1(vars.capFloorVolSurface,
464 iborIndex,
465 Null<Rate>(),
466 vars.accuracy));
467
468 ext::shared_ptr<StrippedOptionletAdapter> strippedOptionletAdapter =
469 ext::make_shared<StrippedOptionletAdapter>(args&: optionletStripper1);
470
471 Handle<OptionletVolatilityStructure> vol(strippedOptionletAdapter);
472
473 vol->enableExtrapolation();
474
475 ext::shared_ptr<BlackCapFloorEngine> strippedVolEngine(new
476 BlackCapFloorEngine(vars.yieldTermStructure,
477 vol));
478
479 ext::shared_ptr<CapFloor> cap;
480 for (Size tenorIndex=0; tenorIndex<vars.optionTenors.size(); ++tenorIndex) {
481 for (Size strikeIndex=0; strikeIndex<vars.strikes.size(); ++strikeIndex) {
482 cap = MakeCapFloor(CapFloor::Cap,
483 vars.optionTenors[tenorIndex],
484 iborIndex,
485 vars.strikes[strikeIndex],
486 0*Days)
487 .withPricingEngine(engine: strippedVolEngine);
488
489 Real priceFromStrippedVolatility = cap->NPV();
490
491 ext::shared_ptr<PricingEngine> blackCapFloorEngineConstantVolatility(new
492 BlackCapFloorEngine(vars.yieldTermStructure,
493 vars.termV[tenorIndex][strikeIndex]));
494
495 cap->setPricingEngine(blackCapFloorEngineConstantVolatility);
496 Real priceFromConstantVolatility = cap->NPV();
497
498 Real error = std::fabs(x: priceFromStrippedVolatility - priceFromConstantVolatility);
499 if (error>vars.tolerance)
500 BOOST_FAIL("\noption tenor: " << vars.optionTenors[tenorIndex] <<
501 "\nstrike: " << io::rate(vars.strikes[strikeIndex]) <<
502 "\nstripped vol price: " << io::rate(priceFromStrippedVolatility) <<
503 "\nconstant vol price: " << io::rate(priceFromConstantVolatility) <<
504 "\nerror: " << io::rate(error) <<
505 "\ntolerance: " << io::rate(vars.tolerance));
506 }
507 }
508}
509
510void OptionletStripperTest::testTermVolatilityStrippingNormalVol() {
511
512 BOOST_TEST_MESSAGE(
513 "Testing forward/forward vol stripping from non-flat normal vol term "
514 "vol surface for normal vol setup using OptionletStripper1 class...");
515
516 using namespace optionlet_stripper_test;
517
518 CommonVars vars;
519 Settings::instance().evaluationDate() = Date(30, April, 2015);
520
521 vars.setRealCapFloorTermVolSurface();
522
523 ext::shared_ptr< IborIndex > iborIndex(new Euribor6M(vars.forwardingYTS));
524
525 ext::shared_ptr< OptionletStripper > optionletStripper1(
526 new OptionletStripper1(vars.capFloorVolRealSurface, iborIndex,
527 Null< Rate >(), vars.accuracy, 100,
528 vars.discountingYTS, Normal));
529
530 ext::shared_ptr< StrippedOptionletAdapter > strippedOptionletAdapter =
531 ext::make_shared< StrippedOptionletAdapter >(
532 args&: optionletStripper1);
533
534 Handle< OptionletVolatilityStructure > vol(strippedOptionletAdapter);
535
536 vol->enableExtrapolation();
537
538 ext::shared_ptr< BachelierCapFloorEngine > strippedVolEngine(
539 new BachelierCapFloorEngine(vars.discountingYTS, vol));
540
541 ext::shared_ptr< CapFloor > cap;
542 for (Size tenorIndex = 0; tenorIndex < vars.optionTenors.size();
543 ++tenorIndex) {
544 for (Size strikeIndex = 0; strikeIndex < vars.strikes.size();
545 ++strikeIndex) {
546 cap = MakeCapFloor(CapFloor::Cap, vars.optionTenors[tenorIndex],
547 iborIndex, vars.strikes[strikeIndex],
548 0 * Days).withPricingEngine(engine: strippedVolEngine);
549
550 Real priceFromStrippedVolatility = cap->NPV();
551
552 ext::shared_ptr< PricingEngine >
553 bachelierCapFloorEngineConstantVolatility(
554 new BachelierCapFloorEngine(
555 vars.discountingYTS,
556 vars.termV[tenorIndex][strikeIndex]));
557
558 cap->setPricingEngine(bachelierCapFloorEngineConstantVolatility);
559 Real priceFromConstantVolatility = cap->NPV();
560
561 Real error = std::fabs(x: priceFromStrippedVolatility -
562 priceFromConstantVolatility);
563 if (error > vars.tolerance)
564 BOOST_FAIL(
565 "\noption tenor: "
566 << vars.optionTenors[tenorIndex] << "\nstrike: "
567 << io::rate(vars.strikes[strikeIndex])
568 << "\nstripped vol price: "
569 << io::rate(priceFromStrippedVolatility)
570 << "\nconstant vol price: "
571 << io::rate(priceFromConstantVolatility)
572 << "\nerror: " << io::rate(error)
573 << "\ntolerance: " << io::rate(vars.tolerance));
574 }
575 }
576}
577
578void OptionletStripperTest::testTermVolatilityStrippingShiftedLogNormalVol() {
579
580 BOOST_TEST_MESSAGE(
581 "Testing forward/forward vol stripping from non-flat normal vol term "
582 "vol surface for normal vol setup using OptionletStripper1 class...");
583
584 using namespace optionlet_stripper_test;
585
586 CommonVars vars;
587 Real shift = 0.03;
588 Settings::instance().evaluationDate() = Date(30, April, 2015);
589
590 vars.setRealCapFloorTermVolSurface();
591
592 ext::shared_ptr< IborIndex > iborIndex(new Euribor6M(vars.forwardingYTS));
593
594 ext::shared_ptr< OptionletStripper > optionletStripper1(
595 new OptionletStripper1(vars.capFloorVolRealSurface, iborIndex,
596 Null< Rate >(), vars.accuracy, 100,
597 vars.discountingYTS, ShiftedLognormal, shift,
598 true));
599
600 ext::shared_ptr< StrippedOptionletAdapter > strippedOptionletAdapter =
601 ext::make_shared< StrippedOptionletAdapter >(
602 args&: optionletStripper1);
603
604 Handle< OptionletVolatilityStructure > vol(strippedOptionletAdapter);
605
606 vol->enableExtrapolation();
607
608 ext::shared_ptr< BlackCapFloorEngine > strippedVolEngine(
609 new BlackCapFloorEngine(vars.discountingYTS, vol));
610
611 ext::shared_ptr< CapFloor > cap;
612 for (Size strikeIndex = 0; strikeIndex < vars.strikes.size();
613 ++strikeIndex) {
614 for (Size tenorIndex = 0; tenorIndex < vars.optionTenors.size();
615 ++tenorIndex) {
616 cap = MakeCapFloor(CapFloor::Cap, vars.optionTenors[tenorIndex],
617 iborIndex, vars.strikes[strikeIndex],
618 0 * Days).withPricingEngine(engine: strippedVolEngine);
619
620 Real priceFromStrippedVolatility = cap->NPV();
621
622 ext::shared_ptr< PricingEngine >
623 blackCapFloorEngineConstantVolatility(new BlackCapFloorEngine(
624 vars.discountingYTS, vars.termV[tenorIndex][strikeIndex],
625 vars.capFloorVolRealSurface->dayCounter(), shift));
626
627 cap->setPricingEngine(blackCapFloorEngineConstantVolatility);
628 Real priceFromConstantVolatility = cap->NPV();
629
630 Real error = std::fabs(x: priceFromStrippedVolatility -
631 priceFromConstantVolatility);
632 if (error > vars.tolerance)
633 BOOST_FAIL(
634 "\noption tenor: "
635 << vars.optionTenors[tenorIndex] << "\nstrike: "
636 << io::rate(vars.strikes[strikeIndex])
637 << "\nstripped vol price: "
638 << io::rate(priceFromStrippedVolatility)
639 << "\nconstant vol price: "
640 << io::rate(priceFromConstantVolatility)
641 << "\nerror: " << io::rate(error)
642 << "\ntolerance: " << io::rate(vars.tolerance));
643 }
644 }
645}
646
647void OptionletStripperTest::testFlatTermVolatilityStripping2() {
648
649 BOOST_TEST_MESSAGE(
650 "Testing forward/forward vol stripping from flat term vol "
651 "surface using OptionletStripper2 class...");
652
653 using namespace optionlet_stripper_test;
654
655 CommonVars vars;
656 Settings::instance().evaluationDate() = Date(28, October, 2013);
657
658 vars.setFlatTermVolCurve();
659 vars.setFlatTermVolSurface();
660
661 ext::shared_ptr<IborIndex> iborIndex(new Euribor6M(vars.yieldTermStructure));
662
663 // optionletstripper1
664 ext::shared_ptr<OptionletStripper1> optionletStripper1(new
665 OptionletStripper1(vars.flatTermVolSurface,
666 iborIndex,
667 Null<Rate>(),
668 vars.accuracy));
669
670 ext::shared_ptr<StrippedOptionletAdapter> strippedOptionletAdapter1(new
671 StrippedOptionletAdapter(optionletStripper1));
672
673 Handle<OptionletVolatilityStructure> vol1(strippedOptionletAdapter1);
674
675 vol1->enableExtrapolation();
676
677 // optionletstripper2
678 ext::shared_ptr<OptionletStripper> optionletStripper2(new
679 OptionletStripper2(optionletStripper1, vars.flatTermVolCurve));
680
681 ext::shared_ptr<StrippedOptionletAdapter> strippedOptionletAdapter2(new
682 StrippedOptionletAdapter(optionletStripper2));
683
684 Handle<OptionletVolatilityStructure> vol2(strippedOptionletAdapter2);
685
686 vol2->enableExtrapolation();
687
688 // consistency check: diff(stripped vol1-stripped vol2)
689 for (Size strikeIndex=0; strikeIndex<vars.strikes.size(); ++strikeIndex) {
690 for (Size tenorIndex=0; tenorIndex<vars.optionTenors.size(); ++tenorIndex) {
691
692 Volatility strippedVol1 = vol1->volatility(optionTenor: vars.optionTenors[tenorIndex],
693 strike: vars.strikes[strikeIndex], extrapolate: true);
694
695 Volatility strippedVol2 = vol2->volatility(optionTenor: vars.optionTenors[tenorIndex],
696 strike: vars.strikes[strikeIndex], extrapolate: true);
697
698 // vol from flat vol surface (for comparison only)
699 Volatility flatVol = vars.flatTermVolSurface->volatility(optT: vars.optionTenors[tenorIndex],
700 strike: vars.strikes[strikeIndex], extrap: true);
701
702 Real error = std::fabs(x: strippedVol1-strippedVol2);
703 if (error>vars.tolerance)
704 BOOST_FAIL("\noption tenor: " << vars.optionTenors[tenorIndex] <<
705 "\nstrike: " << io::rate(vars.strikes[strikeIndex]) <<
706 "\nstripped vol1: " << io::rate(strippedVol1) <<
707 "\nstripped vol2: " << io::rate(strippedVol2) <<
708 "\nflat vol: " << io::rate(flatVol) <<
709 "\nerror: " << io::rate(error) <<
710 "\ntolerance: " << io::rate(vars.tolerance));
711 }
712 }
713
714}
715
716void OptionletStripperTest::testTermVolatilityStripping2() {
717
718 BOOST_TEST_MESSAGE(
719 "Testing forward/forward vol stripping from non-flat term vol "
720 "surface using OptionletStripper2 class...");
721
722 using namespace optionlet_stripper_test;
723
724 CommonVars vars;
725 Settings::instance().evaluationDate() = Date(30, April, 2015);
726
727 vars.setCapFloorTermVolCurve();
728 vars.setCapFloorTermVolSurface();
729
730 ext::shared_ptr<IborIndex> iborIndex(new Euribor6M(vars.yieldTermStructure));
731
732 // optionletstripper1
733 ext::shared_ptr<OptionletStripper1> optionletStripper1(new
734 OptionletStripper1(vars.capFloorVolSurface,
735 iborIndex,
736 Null<Rate>(),
737 vars.accuracy));
738
739 ext::shared_ptr<StrippedOptionletAdapter> strippedOptionletAdapter1 =
740 ext::make_shared<StrippedOptionletAdapter>(args&: optionletStripper1);
741
742 Handle<OptionletVolatilityStructure> vol1(strippedOptionletAdapter1);
743 vol1->enableExtrapolation();
744
745 // optionletstripper2
746 ext::shared_ptr<OptionletStripper> optionletStripper2(new
747 OptionletStripper2(optionletStripper1,
748 vars.capFloorVolCurve));
749
750 ext::shared_ptr<StrippedOptionletAdapter> strippedOptionletAdapter2(new
751 StrippedOptionletAdapter(optionletStripper2));
752
753 Handle<OptionletVolatilityStructure> vol2(strippedOptionletAdapter2);
754 vol2->enableExtrapolation();
755
756 // consistency check: diff(stripped vol1-stripped vol2)
757 for (Size strikeIndex=0; strikeIndex<vars.strikes.size(); ++strikeIndex) {
758 for (Size tenorIndex=0; tenorIndex<vars.optionTenors.size(); ++tenorIndex) {
759
760 Volatility strippedVol1 = vol1->volatility(optionTenor: vars.optionTenors[tenorIndex],
761 strike: vars.strikes[strikeIndex], extrapolate: true);
762
763 Volatility strippedVol2 = vol2->volatility(optionTenor: vars.optionTenors[tenorIndex],
764 strike: vars.strikes[strikeIndex], extrapolate: true);
765
766 // vol from flat vol surface (for comparison only)
767 Volatility flatVol = vars.capFloorVolSurface->volatility(optT: vars.optionTenors[tenorIndex],
768 strike: vars.strikes[strikeIndex], extrap: true);
769
770 Real error = std::fabs(x: strippedVol1-strippedVol2);
771 if (error>vars.tolerance)
772 BOOST_FAIL("\noption tenor: " << vars.optionTenors[tenorIndex] <<
773 "\nstrike: " << io::rate(vars.strikes[strikeIndex]) <<
774 "\nstripped vol1: " << io::rate(strippedVol1) <<
775 "\nstripped vol2: " << io::rate(strippedVol2) <<
776 "\nflat vol: " << io::rate(flatVol) <<
777 "\nerror: " << io::rate(error) <<
778 "\ntolerance: " << io::rate(vars.tolerance));
779 }
780 }
781}
782
783void OptionletStripperTest::testSwitchStrike() {
784 BOOST_TEST_MESSAGE("Testing switch strike level and recalibration of level "
785 "in case of curve relinking...");
786
787 using namespace optionlet_stripper_test;
788
789 bool usingAtParCoupons = IborCoupon::Settings::instance().usingAtParCoupons();
790
791 CommonVars vars;
792 Settings::instance().evaluationDate() = Date(28, October, 2013);
793 vars.setCapFloorTermVolSurface();
794
795 RelinkableHandle< YieldTermStructure > yieldTermStructure;
796 yieldTermStructure.linkTo(h: ext::make_shared< FlatForward >(
797 args: 0, args&: vars.calendar, args: 0.03, args&: vars.dayCounter));
798
799 ext::shared_ptr< IborIndex > iborIndex(new Euribor6M(yieldTermStructure));
800
801 ext::shared_ptr< OptionletStripper1 > optionletStripper1(
802 new OptionletStripper1(vars.capFloorVolSurface, iborIndex,
803 Null< Rate >(), vars.accuracy));
804
805 Real expected = usingAtParCoupons ? 0.02981223 : 0.02981258;
806
807 Real error = std::fabs(x: optionletStripper1->switchStrike() - expected);
808 if (error > vars.tolerance)
809 BOOST_FAIL("\nSwitchstrike not correctly computed: "
810 << "\nexpected switch strike: " << io::rate(expected)
811 << "\ncomputed switch strike: "
812 << io::rate(optionletStripper1->switchStrike())
813 << "\nerror: " << io::rate(error)
814 << "\ntolerance: " << io::rate(vars.tolerance));
815
816 yieldTermStructure.linkTo(h: ext::make_shared< FlatForward >(
817 args: 0, args&: vars.calendar, args: 0.05, args&: vars.dayCounter));
818
819 expected = usingAtParCoupons ? 0.0499371 : 0.0499381;
820
821 error = std::fabs(x: optionletStripper1->switchStrike() - expected);
822 if (error > vars.tolerance)
823 BOOST_FAIL("\nSwitchstrike not correctly computed: "
824 << "\nexpected switch strike: " << io::rate(expected)
825 << "\ncomputed switch strike: "
826 << io::rate(optionletStripper1->switchStrike())
827 << "\nerror: " << io::rate(error)
828 << "\ntolerance: " << io::rate(vars.tolerance));
829}
830
831test_suite* OptionletStripperTest::suite() {
832 auto* suite = BOOST_TEST_SUITE("OptionletStripper Tests");
833 suite->add(QUANTLIB_TEST_CASE(&OptionletStripperTest::testFlatTermVolatilityStripping1));
834 suite->add(QUANTLIB_TEST_CASE(&OptionletStripperTest::testTermVolatilityStripping1));
835 suite->add(QUANTLIB_TEST_CASE(&OptionletStripperTest::testFlatTermVolatilityStripping2));
836 suite->add(QUANTLIB_TEST_CASE(&OptionletStripperTest::testTermVolatilityStripping2));
837 suite->add(QUANTLIB_TEST_CASE(&OptionletStripperTest::testSwitchStrike));
838 suite->add(QUANTLIB_TEST_CASE(&OptionletStripperTest::testTermVolatilityStrippingNormalVol));
839 suite->add(QUANTLIB_TEST_CASE(&OptionletStripperTest::testTermVolatilityStrippingShiftedLogNormalVol));
840
841 return suite;
842}
843

source code of quantlib/test-suite/optionletstripper.cpp