1/* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2
3/*
4 Copyright (C) 2012 Peter Caspers
5
6 This file is part of QuantLib, a free-software/open-source library
7 for financial quantitative analysts and developers - http://quantlib.org/
8
9 QuantLib is free software: you can redistribute it and/or modify it
10 under the terms of the QuantLib license. You should have received a
11 copy of the license along with this program; if not, please email
12 <quantlib-dev@lists.sf.net>. The license is also available online at
13 <http://quantlib.org/license.shtml>.
14
15 This program is distributed in the hope that it will be useful, but WITHOUT
16 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
17 FOR A PARTICULAR PURPOSE. See the license for more details.
18*/
19
20#include "markovfunctional.hpp"
21#include "utilities.hpp"
22#include <ql/processes/mfstateprocess.hpp>
23#include <ql/models/shortrate/onefactormodels/markovfunctional.hpp>
24#include <ql/pricingengines/swaption/gaussian1dswaptionengine.hpp>
25#include <ql/pricingengines/capfloor/gaussian1dcapfloorengine.hpp>
26#include <ql/termstructures/yield/flatforward.hpp>
27#include <ql/termstructures/yield/piecewiseyieldcurve.hpp>
28#include <ql/termstructures/volatility/swaption/swaptionconstantvol.hpp>
29#include <ql/termstructures/volatility/optionlet/constantoptionletvol.hpp>
30#include <ql/termstructures/volatility/swaption/swaptionvolmatrix.hpp>
31#include <ql/termstructures/volatility/swaption/sabrswaptionvolatilitycube.hpp>
32#include <ql/termstructures/volatility/swaption/interpolatedswaptionvolatilitycube.hpp>
33#include <ql/termstructures/volatility/capfloor/capfloortermvolsurface.hpp>
34#include <ql/termstructures/volatility/optionlet/optionletstripper1.hpp>
35#include <ql/termstructures/volatility/optionlet/strippedoptionletadapter.hpp>
36#include <ql/termstructures/volatility/interpolatedsmilesection.hpp>
37#include <ql/termstructures/volatility/kahalesmilesection.hpp>
38#include <ql/time/calendars/target.hpp>
39#include <ql/indexes/swap/euriborswap.hpp>
40#include <ql/indexes/ibor/euribor.hpp>
41#include <ql/termstructures/yield/ratehelpers.hpp>
42#include <ql/time/daycounters/actual360.hpp>
43#include <ql/time/daycounters/thirty360.hpp>
44#include <ql/time/daycounters/actualactual.hpp>
45#include <ql/instruments/makeswaption.hpp>
46#include <ql/instruments/makevanillaswap.hpp>
47#include <ql/instruments/makecapfloor.hpp>
48#include <ql/cashflows/cashflowvectors.hpp>
49#include <ql/pricingengines/swaption/blackswaptionengine.hpp>
50#include <ql/pricingengines/capfloor/blackcapfloorengine.hpp>
51#include <ql/models/shortrate/calibrationhelpers/swaptionhelper.hpp>
52#include <ql/models/shortrate/calibrationhelpers/caphelper.hpp>
53
54using namespace QuantLib;
55using namespace boost::unit_test_framework;
56
57using std::fabs;
58
59void MarkovFunctionalTest::testMfStateProcess() {
60
61 const Real tolerance = 1E-10;
62 BOOST_TEST_MESSAGE("Testing Markov functional state process...");
63
64 Array times1(0), vols1(1, 1.0);
65 MfStateProcess sp1(0.00, times1, vols1);
66 Real var11 = sp1.variance(t0: 0.0, x0: 0.0, dt: 1.0);
67 Real var12 = sp1.variance(t0: 0.0, x0: 0.0, dt: 2.0);
68 if (std::fabs(x: var11 - 1.0) > tolerance)
69 BOOST_ERROR("process 1 has not variance 1.0 for dt = 1.0 but "
70 << var11);
71 if (std::fabs(x: var12 - 2.0) > tolerance)
72 BOOST_ERROR("process 1 has not variance 1.0 for dt = 1.0 but "
73 << var12);
74
75 Array times2(2), vols2(3);
76 times2[0] = 1.0;
77 times2[1] = 2.0;
78 vols2[0] = 1.0;
79 vols2[1] = 2.0;
80 vols2[2] = 3.0;
81 MfStateProcess sp2(0.00, times2, vols2);
82 Real dif21 = sp2.diffusion(t: 0.0, x: 0.0);
83 Real dif22 = sp2.diffusion(t: 0.99, x: 0.0);
84 Real dif23 = sp2.diffusion(t: 1.0, x: 0.0);
85 Real dif24 = sp2.diffusion(t: 1.9, x: 0.0);
86 Real dif25 = sp2.diffusion(t: 2.0, x: 0.0);
87 Real dif26 = sp2.diffusion(t: 3.0, x: 0.0);
88 Real dif27 = sp2.diffusion(t: 5.0, x: 0.0);
89 if (std::fabs(x: dif21 - 1.0) > tolerance)
90 BOOST_ERROR("process 2 has wrong drift at 0.0, should be 1.0 but is "
91 << dif21);
92 if (std::fabs(x: dif22 - 1.0) > tolerance)
93 BOOST_ERROR("process 2 has wrong drift at 0.99, should be 1.0 but is "
94 << dif22);
95 if (std::fabs(x: dif23 - 2.0) > tolerance)
96 BOOST_ERROR("process 2 has wrong drift at 1.0, should be 2.0 but is "
97 << dif23);
98 if (std::fabs(x: dif24 - 2.0) > tolerance)
99 BOOST_ERROR("process 2 has wrong drift at 1.9, should be 2.0 but is "
100 << dif24);
101 if (std::fabs(x: dif25 - 3.0) > tolerance)
102 BOOST_ERROR("process 2 has wrong drift at 2.0, should be 3.0 but is "
103 << dif25);
104 if (std::fabs(x: dif26 - 3.0) > tolerance)
105 BOOST_ERROR("process 2 has wrong drift at 3.0, should be 3.0 but is "
106 << dif26);
107 if (std::fabs(x: dif27 - 3.0) > tolerance)
108 BOOST_ERROR("process 2 has wrong drift at 5.0, should be 3.0 but is "
109 << dif27);
110 Real var21 = sp2.variance(t0: 0.0, x0: 0.0, dt: 0.0);
111 Real var22 = sp2.variance(t0: 0.0, x0: 0.0, dt: 0.5);
112 Real var23 = sp2.variance(t0: 0.0, x0: 0.0, dt: 1.0);
113 Real var24 = sp2.variance(t0: 0.0, x0: 0.0, dt: 1.5);
114 Real var25 = sp2.variance(t0: 0.0, x0: 0.0, dt: 3.0);
115 Real var26 = sp2.variance(t0: 0.0, x0: 0.0, dt: 5.0);
116 Real var27 = sp2.variance(t0: 1.2, x0: 0.0, dt: 1.0);
117 if (std::fabs(x: var21 - 0.0) > tolerance)
118 BOOST_ERROR("process 2 has wrong variance at 0.0, should be 0.0 but is "
119 << var21);
120 if (std::fabs(x: var22 - 0.5) > tolerance)
121 BOOST_ERROR("process 2 has wrong variance at 0.5, should be 0.5 but is "
122 << var22);
123 if (std::fabs(x: var23 - 1.0) > tolerance)
124 BOOST_ERROR("process 2 has wrong variance at 1.0, should be 1.0 but is "
125 << var23);
126 if (std::fabs(x: var24 - 3.0) > tolerance)
127 BOOST_ERROR("process 2 has wrong variance at 1.5, should be 3.0 but is "
128 << var24);
129 if (std::fabs(x: var25 - 14.0) > tolerance)
130 BOOST_ERROR(
131 "process 2 has wrong variance at 3.0, should be 14.0 but is "
132 << var25);
133 if (std::fabs(x: var26 - 32.0) > tolerance)
134 BOOST_ERROR(
135 "process 2 has wrong variance at 5.0, should be 32.0 but is "
136 << var26);
137 if (std::fabs(x: var27 - 5.0) > tolerance)
138 BOOST_ERROR("process 2 has wrong variance between 1.2 and 2.2, should "
139 "be 5.0 but is "
140 << var27);
141
142 MfStateProcess sp3(0.01, times2, vols2);
143 Real var31 = sp3.variance(t0: 0.0, x0: 0.0, dt: 0.0);
144 Real var32 = sp3.variance(t0: 0.0, x0: 0.0, dt: 0.5);
145 Real var33 = sp3.variance(t0: 0.0, x0: 0.0, dt: 1.0);
146 Real var34 = sp3.variance(t0: 0.0, x0: 0.0, dt: 1.5);
147 Real var35 = sp3.variance(t0: 0.0, x0: 0.0, dt: 3.0);
148 Real var36 = sp3.variance(t0: 0.0, x0: 0.0, dt: 5.0);
149 Real var37 = sp3.variance(t0: 1.2, x0: 0.0, dt: 1.0);
150 if (std::fabs(x: var31 - 0.0) > tolerance)
151 BOOST_ERROR("process 3 has wrong variance at 0.0, should be 0.0 but is "
152 << std::setprecision(12) << var31);
153 if (std::fabs(x: var32 - 0.502508354208) > tolerance)
154 BOOST_ERROR("process 3 has wrong variance at 0.5, should be 0.5 but it "
155 << std::setprecision(12) << var32);
156 if (std::fabs(x: var33 - 1.01006700134) > tolerance)
157 BOOST_ERROR("process 3 has wrong variance at 1.0, should be 1.0 but it "
158 << std::setprecision(12) << var33);
159 if (std::fabs(x: var34 - 3.06070578669) > tolerance)
160 BOOST_ERROR("process 3 has wrong variance at 1.5, should be 3.0 but it "
161 << std::setprecision(12) << var34);
162 if (std::fabs(x: var35 - 14.5935513933) > tolerance)
163 BOOST_ERROR(
164 "process 3 has wrong variance at 3.0, should be 14.0 but it "
165 << std::setprecision(12) << var35);
166 if (std::fabs(x: var36 - 34.0940185819) > tolerance)
167 BOOST_ERROR(
168 "process 3 has wrong variance at 5.0, should be 32.0 but it "
169 << std::setprecision(12) << var36);
170 if (std::fabs(x: var37 - 5.18130257358) > tolerance)
171 BOOST_ERROR("process 3 has wrong variance between 1.2 and 2.2, should "
172 "be 5.0 but it "
173 << std::setprecision(12) << var37);
174}
175
176namespace {
177
178 // Flat yield term structure at 3%
179
180 Handle<YieldTermStructure> flatYts() {
181
182 return Handle<YieldTermStructure>(ext::shared_ptr<YieldTermStructure>(
183 new FlatForward(0, TARGET(), 0.03, Actual365Fixed())));
184 }
185
186 // Flat swaption volatility structure at 20%
187
188 Handle<SwaptionVolatilityStructure> flatSwaptionVts() {
189
190 return Handle<SwaptionVolatilityStructure>(
191 ext::shared_ptr<SwaptionVolatilityStructure>(
192 new ConstantSwaptionVolatility(0, TARGET(), ModifiedFollowing,
193 0.20, Actual365Fixed())));
194 }
195
196 // Flat cap volatility structure at 20%
197
198 Handle<OptionletVolatilityStructure> flatOptionletVts() {
199
200 return Handle<OptionletVolatilityStructure>(
201 ext::shared_ptr<OptionletVolatilityStructure>(
202 new ConstantOptionletVolatility(0, TARGET(), ModifiedFollowing,
203 0.20, Actual365Fixed())));
204 }
205
206 // Yield term structure as of 14.11.2012 (6m discounting)
207
208 Handle<YieldTermStructure> md0Yts() {
209
210 ext::shared_ptr<IborIndex> euribor6mEmpty(new Euribor(6 * Months));
211
212 std::vector<ext::shared_ptr<Quote> > q6m;
213 std::vector<ext::shared_ptr<RateHelper> > r6m;
214
215 double q6mh[] = { 0.0001, 0.0001, 0.0001, 0.0003, 0.00055, 0.0009,
216 0.0014, 0.0019, 0.0025, 0.0031, 0.00325, 0.00313,
217 0.0031, 0.00307, 0.00309, 0.00339, 0.00316, 0.00326,
218 0.00335, 0.00343, 0.00358, 0.00351, 0.00388, 0.00404,
219 0.00425, 0.00442, 0.00462, 0.00386, 0.00491, 0.00647,
220 0.00837, 0.01033, 0.01218, 0.01382, 0.01527, 0.01654,
221 0.0177, 0.01872, 0.01959, 0.0203, 0.02088, 0.02132,
222 0.02164, 0.02186, 0.02202, 0.02213, 0.02222, 0.02229,
223 0.02234, 0.02238, 0.02241, 0.02243, 0.02244, 0.02245,
224 0.02247, 0.0225, 0.02284, 0.02336, 0.02407, 0.0245 };
225
226 Period q6mh1[] = { 1 * Days, 1 * Days, 1 * Days, 1 * Weeks,
227 1 * Months, 2 * Months, 3 * Months, 4 * Months,
228 5 * Months, 6 * Months };
229
230 Period q6mh2[] = {
231 7 * Months, 8 * Months, 9 * Months, 10 * Months, 11 * Months,
232 1 * Years, 13 * Months, 14 * Months, 15 * Months, 16 * Months,
233 17 * Months, 18 * Months, 19 * Months, 20 * Months, 21 * Months,
234 22 * Months, 23 * Months, 2 * Years, 3 * Years, 4 * Years,
235 5 * Years, 6 * Years, 7 * Years, 8 * Years, 9 * Years,
236 10 * Years, 11 * Years, 12 * Years, 13 * Years, 14 * Years,
237 15 * Years, 16 * Years, 17 * Years, 18 * Years, 19 * Years,
238 20 * Years, 21 * Years, 22 * Years, 23 * Years, 24 * Years,
239 25 * Years, 26 * Years, 27 * Years, 28 * Years, 29 * Years,
240 30 * Years, 35 * Years, 40 * Years, 50 * Years, 60 * Years
241 };
242
243 q6m.reserve(n: 10 + 15 + 35);
244 for (Real i : q6mh) {
245 q6m.push_back(x: ext::shared_ptr<Quote>(new SimpleQuote(i)));
246 }
247
248 r6m.reserve(n: 10);
249 for (int i = 0; i < 10; i++) {
250 r6m.push_back(x: ext::make_shared<DepositRateHelper>(
251 args: Handle<Quote>(q6m[i]), args&: q6mh1[i],
252 args: i < 2 ? i : 2, args: TARGET(),
253 args: ModifiedFollowing, args: false, args: Actual360()));
254 }
255
256 for (int i = 0; i < 18; i++) {
257 if (i + 1 != 6 && i + 1 != 12 && i + 1 != 18) {
258 r6m.push_back(x: ext::make_shared<FraRateHelper>(
259 args: Handle<Quote>(q6m[10 + i]), args: i + 1,
260 args: i + 7, args: 2, args: TARGET(), args: ModifiedFollowing,
261 args: false, args: Actual360()));
262 }
263 }
264
265 for (int i = 0; i < 15 + 35; i++) {
266 if (i + 7 == 12 || i + 7 == 18 || i + 7 >= 24) {
267 r6m.push_back(x: ext::make_shared<SwapRateHelper>(
268 args: Handle<Quote>(q6m[10 + i]), args&: q6mh2[i],
269 args: TARGET(), args: Annual, args: ModifiedFollowing,
270 args: Actual360(), args&: euribor6mEmpty));
271 }
272 }
273
274 Handle<YieldTermStructure> res(ext::shared_ptr<YieldTermStructure>(
275 new PiecewiseYieldCurve<Discount, LogLinear>(0, TARGET(), r6m,
276 Actual365Fixed())));
277 res->enableExtrapolation();
278
279 return res;
280 }
281
282 // Swaption volatility cube as of 14.11.2012, 1y underlying vols are not
283 // converted here from 3m to 6m
284
285 Handle<SwaptionVolatilityStructure> md0SwaptionVts() {
286
287 std::vector<Period> optionTenors = {
288 1 * Months, 2 * Months, 3 * Months, 6 * Months,
289 9 * Months, 1 * Years, 18 * Months, 2 * Years,
290 3 * Years, 4 * Years, 5 * Years, 6 * Years,
291 7 * Years, 8 * Years, 9 * Years, 10 * Years,
292 15 * Years, 20 * Years, 25 * Years, 30 * Years };
293
294 std::vector<Period> swapTenors = {
295 1 * Years, 2 * Years, 3 * Years, 4 * Years,
296 5 * Years, 6 * Years, 7 * Years, 8 * Years,
297 9 * Years, 10 * Years, 15 * Years, 20 * Years,
298 25 * Years, 30 * Years };
299
300 double qSwAtmh[] = {
301 1.81, 0.897, 0.819, 0.692, 0.551, 0.47, 0.416, 0.379, 0.357,
302 0.335, 0.283, 0.279, 0.283, 0.287, 1.717, 0.886, 0.79, 0.69,
303 0.562, 0.481, 0.425, 0.386, 0.359, 0.339, 0.29, 0.287, 0.292,
304 0.296, 1.762, 0.903, 0.804, 0.693, 0.582, 0.5, 0.448, 0.411,
305 0.387, 0.365, 0.31, 0.307, 0.312, 0.317, 1.662, 0.882, 0.764,
306 0.67, 0.586, 0.513, 0.468, 0.432, 0.408, 0.388, 0.331, 0.325,
307 0.33, 0.334, 1.53, 0.854, 0.728, 0.643, 0.565, 0.503, 0.464,
308 0.435, 0.415, 0.393, 0.337, 0.33, 0.333, 0.338, 1.344, 0.786,
309 0.683, 0.609, 0.54, 0.488, 0.453, 0.429, 0.411, 0.39, 0.335,
310 0.329, 0.332, 0.336, 1.1, 0.711, 0.617, 0.548, 0.497, 0.456,
311 0.43, 0.408, 0.392, 0.374, 0.328, 0.323, 0.326, 0.33, 0.956,
312 0.638, 0.553, 0.496, 0.459, 0.427, 0.403, 0.385, 0.371, 0.359,
313 0.321, 0.318, 0.323, 0.327, 0.671, 0.505, 0.45, 0.42, 0.397,
314 0.375, 0.36, 0.347, 0.337, 0.329, 0.305, 0.303, 0.309, 0.313,
315 0.497, 0.406, 0.378, 0.36, 0.348, 0.334, 0.323, 0.315, 0.309,
316 0.304, 0.289, 0.289, 0.294, 0.297, 0.404, 0.352, 0.334, 0.322,
317 0.313, 0.304, 0.296, 0.291, 0.288, 0.286, 0.278, 0.277, 0.281,
318 0.282, 0.345, 0.312, 0.302, 0.294, 0.286, 0.28, 0.276, 0.274,
319 0.273, 0.273, 0.267, 0.265, 0.268, 0.269, 0.305, 0.285, 0.277,
320 0.271, 0.266, 0.262, 0.26, 0.259, 0.26, 0.262, 0.259, 0.256,
321 0.257, 0.256, 0.282, 0.265, 0.259, 0.254, 0.251, 0.25, 0.25,
322 0.251, 0.253, 0.256, 0.253, 0.25, 0.249, 0.246, 0.263, 0.248,
323 0.244, 0.241, 0.24, 0.24, 0.242, 0.245, 0.249, 0.252, 0.249,
324 0.245, 0.243, 0.238, 0.242, 0.234, 0.232, 0.232, 0.233, 0.235,
325 0.239, 0.243, 0.247, 0.249, 0.246, 0.242, 0.237, 0.231, 0.233,
326 0.234, 0.241, 0.246, 0.249, 0.253, 0.257, 0.261, 0.263, 0.264,
327 0.251, 0.236, 0.222, 0.214, 0.262, 0.26, 0.262, 0.263, 0.263,
328 0.266, 0.268, 0.269, 0.269, 0.265, 0.237, 0.214, 0.202, 0.196,
329 0.26, 0.26, 0.261, 0.261, 0.258, 0.255, 0.252, 0.248, 0.245,
330 0.24, 0.207, 0.187, 0.182, 0.176, 0.236, 0.223, 0.221, 0.218,
331 0.214, 0.21, 0.207, 0.204, 0.202, 0.2, 0.175, 0.167, 0.163,
332 0.158
333 };
334
335 std::vector<std::vector<Handle<Quote> > > qSwAtm;
336 for (int i = 0; i < 20; i++) {
337 std::vector<Handle<Quote> > qSwAtmTmp;
338 qSwAtmTmp.reserve(n: 14);
339 for (int j = 0; j < 14; j++) {
340 qSwAtmTmp.emplace_back(
341 args: ext::shared_ptr<Quote>(new SimpleQuote(qSwAtmh[i * 14 + j])));
342 }
343 qSwAtm.push_back(x: qSwAtmTmp);
344 }
345
346 Handle<SwaptionVolatilityStructure> swaptionVolAtm(
347 ext::shared_ptr<SwaptionVolatilityStructure>(
348 new SwaptionVolatilityMatrix(TARGET(), ModifiedFollowing,
349 optionTenors, swapTenors, qSwAtm,
350 Actual365Fixed())));
351
352 std::vector<Period> optionTenorsSmile = { 3 * Months, 1 * Years, 5 * Years,
353 10 * Years, 20 * Years, 30 * Years };
354 std::vector<Period> swapTenorsSmile = { 2 * Years, 5 * Years, 10 * Years,
355 20 * Years, 30 * Years };
356 std::vector<Real> strikeSpreads = { -0.02, -0.01, -0.0050, -0.0025, 0.0,
357 0.0025, 0.0050, 0.01, 0.02 };
358
359 std::vector<std::vector<Handle<Quote> > > qSwSmile;
360
361 double qSwSmileh[] = {
362 2.2562, 2.2562, 2.2562, 0.1851, 0.0, -0.0389, -0.0507,
363 -0.0571, -0.06, 14.9619, 14.9619, 0.1249, 0.0328, 0.0,
364 -0.0075, -0.005, 0.0078, 0.0328, 0.2296, 0.2296, 0.0717,
365 0.0267, 0.0, -0.0115, -0.0126, -0.0002, 0.0345, 0.6665,
366 0.1607, 0.0593, 0.0245, 0.0, -0.0145, -0.0204, -0.0164,
367 0.0102, 0.6509, 0.1649, 0.0632, 0.027, 0.0, -0.018,
368 -0.0278, -0.0303, -0.0105, 0.6303, 0.6303, 0.6303, 0.1169,
369 0.0, -0.0469, -0.0699, -0.091, -0.1065, 0.4437, 0.4437,
370 0.0944, 0.0348, 0.0, -0.0206, -0.0327, -0.0439, -0.0472,
371 2.1557, 0.1501, 0.0531, 0.0225, 0.0, -0.0161, -0.0272,
372 -0.0391, -0.0429, 0.4365, 0.1077, 0.0414, 0.0181, 0.0,
373 -0.0137, -0.0237, -0.0354, -0.0401, 0.4415, 0.1117, 0.0437,
374 0.0193, 0.0, -0.015, -0.0264, -0.0407, -0.0491, 0.4301,
375 0.0776, 0.0283, 0.0122, 0.0, -0.0094, -0.0165, -0.0262,
376 -0.035, 0.2496, 0.0637, 0.0246, 0.0109, 0.0, -0.0086,
377 -0.0153, -0.0247, -0.0334, 0.1912, 0.0569, 0.023, 0.0104,
378 0.0, -0.0085, -0.0155, -0.0256, -0.0361, 0.2095, 0.06,
379 0.0239, 0.0107, 0.0, -0.0087, -0.0156, -0.0254, -0.0348,
380 0.2357, 0.0669, 0.0267, 0.012, 0.0, -0.0097, -0.0174,
381 -0.0282, -0.0383, 0.1291, 0.0397, 0.0158, 0.007, 0.0,
382 -0.0056, -0.01, -0.0158, -0.0203, 0.1281, 0.0397, 0.0159,
383 0.0071, 0.0, -0.0057, -0.0102, -0.0164, -0.0215, 0.1547,
384 0.0468, 0.0189, 0.0085, 0.0, -0.0069, -0.0125, -0.0205,
385 -0.0283, 0.1851, 0.0522, 0.0208, 0.0093, 0.0, -0.0075,
386 -0.0135, -0.0221, -0.0304, 0.1782, 0.0506, 0.02, 0.0089,
387 0.0, -0.0071, -0.0128, -0.0206, -0.0276, 0.2665, 0.0654,
388 0.0255, 0.0113, 0.0, -0.0091, -0.0163, -0.0265, -0.0367,
389 0.2873, 0.0686, 0.0269, 0.0121, 0.0, -0.0098, -0.0179,
390 -0.0298, -0.043, 0.2993, 0.0688, 0.0273, 0.0123, 0.0,
391 -0.0103, -0.0189, -0.0324, -0.0494, 0.1869, 0.0501, 0.0202,
392 0.0091, 0.0, -0.0076, -0.014, -0.0239, -0.0358, 0.1573,
393 0.0441, 0.0178, 0.008, 0.0, -0.0066, -0.0121, -0.0202,
394 -0.0294, 0.196, 0.0525, 0.0204, 0.009, 0.0, -0.0071,
395 -0.0125, -0.0197, -0.0253, 0.1795, 0.0497, 0.0197, 0.0088,
396 0.0, -0.0071, -0.0128, -0.0208, -0.0286, 0.1401, 0.0415,
397 0.0171, 0.0078, 0.0, -0.0066, -0.0122, -0.0209, -0.0318,
398 0.112, 0.0344, 0.0142, 0.0065, 0.0, -0.0055, -0.01,
399 -0.0171, -0.0256, 0.1077, 0.0328, 0.0134, 0.0061, 0.0,
400 -0.005, -0.0091, -0.0152, -0.0216,
401 };
402
403 for (int i = 0; i < 30; i++) {
404 std::vector<Handle<Quote> > qSwSmileTmp;
405 qSwSmileTmp.reserve(n: 9);
406 for (int j = 0; j < 9; j++) {
407 qSwSmileTmp.emplace_back(
408 args: ext::shared_ptr<Quote>(new SimpleQuote(qSwSmileh[i * 9 + j])));
409 }
410 qSwSmile.push_back(x: qSwSmileTmp);
411 }
412
413 double qSwSmileh1[] = {
414 0.01, 0.2, 0.8, -0.2, 0.01, 0.2, 0.8, -0.2, 0.01, 0.2, 0.8, -0.2,
415 0.01, 0.2, 0.8, -0.2, 0.01, 0.2, 0.8, -0.2, 0.01, 0.2, 0.8, -0.2,
416 0.01, 0.2, 0.8, -0.2, 0.01, 0.2, 0.8, -0.2, 0.01, 0.2, 0.8, -0.2,
417 0.01, 0.2, 0.8, -0.2, 0.01, 0.2, 0.8, -0.2, 0.01, 0.2, 0.8, -0.2,
418 0.01, 0.2, 0.8, -0.2, 0.01, 0.2, 0.8, -0.2, 0.01, 0.2, 0.8, -0.2,
419 0.01, 0.2, 0.8, -0.2, 0.01, 0.2, 0.8, -0.2, 0.01, 0.2, 0.8, -0.2,
420 0.01, 0.2, 0.8, -0.2, 0.01, 0.2, 0.8, -0.2, 0.01, 0.2, 0.8, -0.2,
421 0.01, 0.2, 0.8, -0.2, 0.01, 0.2, 0.8, -0.2, 0.01, 0.2, 0.8, -0.2,
422 0.01, 0.2, 0.8, -0.2, 0.01, 0.2, 0.8, -0.2, 0.01, 0.2, 0.8, -0.2,
423 0.01, 0.2, 0.8, -0.2, 0.01, 0.2, 0.8, -0.2, 0.01, 0.2, 0.8, -0.2
424 };
425
426 std::vector<bool> parameterFixed = {false, false, false, false};
427
428 std::vector<std::vector<Handle<Quote> > > parameterGuess;
429 for (int i = 0; i < 30; i++) {
430 std::vector<Handle<Quote> > parameterGuessTmp;
431 parameterGuessTmp.reserve(n: 4);
432 for (int j = 0; j < 4; j++) {
433 parameterGuessTmp.emplace_back(
434 args: ext::shared_ptr<Quote>(new SimpleQuote(qSwSmileh1[i * 4 + j])));
435 }
436 parameterGuess.push_back(x: parameterGuessTmp);
437 }
438
439 ext::shared_ptr<EndCriteria> ec(
440 new EndCriteria(50000, 250, 1E-6, 1E-6, 1E-6));
441
442 ext::shared_ptr<SwapIndex> swapIndex(new EuriborSwapIsdaFixA(
443 30 * Years, Handle<YieldTermStructure>(md0Yts())));
444 ext::shared_ptr<SwapIndex> shortSwapIndex(new EuriborSwapIsdaFixA(
445 1 * Years,
446 Handle<YieldTermStructure>(md0Yts()))); // We assume that we have 6m
447 // vols (which we actually
448 // don't have for 1y
449 // underlying, but this is
450 // just a test...)
451
452 // return Handle<SwaptionVolatilityStructure>(new
453 // InterpolatedSwaptionVolatilityCube(swaptionVolAtm,optionTenorsSmile,swapTenorsSmile,strikeSpreads,qSwSmile,swapIndex,shortSwapIndex,false));
454 // // bilinear interpolation gives nasty digitals
455 Handle<SwaptionVolatilityStructure> res(
456 ext::shared_ptr<SwaptionVolatilityStructure>(new SabrSwaptionVolatilityCube(
457 swaptionVolAtm, optionTenorsSmile, swapTenorsSmile,
458 strikeSpreads, qSwSmile, swapIndex, shortSwapIndex, true,
459 parameterGuess, parameterFixed, true, ec,
460 0.0050))); // put a big error tolerance here ... we just want a
461 // smooth cube for testing
462 res->enableExtrapolation();
463 return res;
464 }
465
466 // Cap volatility surface as of 14.11.2012. Par vols up to 2y are converted
467 // to 6m to get a consistent caplet surface.
468
469 Handle<OptionletVolatilityStructure> md0OptionletVts() {
470
471 // with the thread safe observer it takes very long to destruct
472 // the cap floor instruments created in OptionletStripper1
473#ifdef QL_ENABLE_THREAD_SAFE_OBSERVER_PATTERN
474 return flatOptionletVts();
475#endif
476
477 Size nOptTen = 16;
478 Size nStrikes = 12; // leave out last strike 10% because it causes an
479 // exception in bootstrapping
480
481 std::vector<Period> optionTenors = {
482 1 * Years, 18 * Months, 2 * Years, 3 * Years,
483 4 * Years, 5 * Years, 6 * Years, 7 * Years,
484 8 * Years, 9 * Years, 10 * Years, 12 * Years,
485 15 * Years, 20 * Years, 25 * Years, 30 * Years };
486
487 std::vector<Real> strikes = {
488 0.0025, 0.0050, 0.0100, 0.0150, 0.0200, 0.0225,
489 0.0250, 0.0300, 0.0350, 0.0400, 0.0500, 0.0600 };
490
491 Matrix vols(nOptTen, nStrikes);
492 Real volsa[13][16] = { { 1.3378, 1.3032, 1.2514, 1.081, 1.019, 0.961,
493 0.907, 0.862, 0.822, 0.788, 0.758, 0.709,
494 0.66, 0.619, 0.597, 0.579 }, // strike1
495 { 1.1882, 1.1057, 0.9823, 0.879, 0.828, 0.779,
496 0.736, 0.7, 0.67, 0.644, 0.621, 0.582,
497 0.544, 0.513, 0.496, 0.482 }, // strike2
498 { 1.1646, 1.0356, 0.857, 0.742, 0.682, 0.626,
499 0.585, 0.553, 0.527, 0.506, 0.488, 0.459,
500 0.43, 0.408, 0.396, 0.386 }, // ...
501 { 1.1932, 1.0364, 0.8291, 0.691, 0.618, 0.553,
502 0.509, 0.477, 0.452, 0.433, 0.417, 0.391,
503 0.367, 0.35, 0.342, 0.335 },
504 { 1.2233, 1.0489, 0.8268, 0.666, 0.582, 0.51,
505 0.463, 0.43, 0.405, 0.387, 0.372, 0.348,
506 0.326, 0.312, 0.306, 0.301 },
507 { 1.2369, 1.0555, 0.8283, 0.659, 0.57, 0.495,
508 0.447, 0.414, 0.388, 0.37, 0.355, 0.331,
509 0.31, 0.298, 0.293, 0.289 },
510 { 1.2498, 1.0622, 0.8307, 0.653, 0.56, 0.483,
511 0.434, 0.4, 0.374, 0.356, 0.341, 0.318,
512 0.297, 0.286, 0.282, 0.279 },
513 { 1.2719, 1.0747, 0.8368, 0.646, 0.546, 0.465,
514 0.415, 0.38, 0.353, 0.335, 0.32, 0.296,
515 0.277, 0.268, 0.265, 0.263 },
516 { 1.2905, 1.0858, 0.8438, 0.643, 0.536, 0.453,
517 0.403, 0.367, 0.339, 0.32, 0.305, 0.281,
518 0.262, 0.255, 0.254, 0.252 },
519 { 1.3063, 1.0953, 0.8508, 0.642, 0.53, 0.445,
520 0.395, 0.358, 0.329, 0.31, 0.294, 0.271,
521 0.252, 0.246, 0.246, 0.244 },
522 { 1.332, 1.1108, 0.8631, 0.642, 0.521, 0.436,
523 0.386, 0.348, 0.319, 0.298, 0.282, 0.258,
524 0.24, 0.237, 0.237, 0.236 },
525 { 1.3513, 1.1226, 0.8732, 0.645, 0.517, 0.43,
526 0.381, 0.344, 0.314, 0.293, 0.277, 0.252,
527 0.235, 0.233, 0.234, 0.233 },
528 { 1.395, 1.1491, 0.9003, 0.661, 0.511, 0.425,
529 0.38, 0.344, 0.314, 0.292, 0.275, 0.251,
530 0.236, 0.236, 0.238, 0.235 } };
531
532 for (Size i = 0; i < nStrikes; i++) {
533 for (Size j = 0; j < nOptTen; j++) {
534 vols[j][i] = volsa[i][j];
535 }
536 }
537
538 ext::shared_ptr<IborIndex> iborIndex(
539 new Euribor(6 * Months, md0Yts()));
540 ext::shared_ptr<CapFloorTermVolSurface> cf(new CapFloorTermVolSurface(
541 0, TARGET(), ModifiedFollowing, optionTenors, strikes, vols));
542 ext::shared_ptr<OptionletStripper> stripper(
543 new OptionletStripper1(cf, iborIndex));
544
545 return Handle<OptionletVolatilityStructure>(
546 ext::shared_ptr<OptionletVolatilityStructure>(
547 new StrippedOptionletAdapter(stripper)));
548 }
549
550 // Calibration Basket 1: CMS10y Swaptions, 5 yearly fixings
551
552 std::vector<Date> expiriesCalBasket1() {
553
554 std::vector<Date> res;
555 Date referenceDate_ = Settings::instance().evaluationDate();
556
557 for (int i = 1; i <= 5; i++)
558 res.push_back(x: TARGET().advance(date: referenceDate_, period: i * Years));
559
560 return res;
561 }
562
563 std::vector<Period> tenorsCalBasket1() {
564
565 std::vector<Period> res(5, 10 * Years);
566
567 return res;
568 }
569
570 // Calibration Basket 2: 6m caplets, 5 years
571
572 std::vector<Date> expiriesCalBasket2() {
573
574 Date referenceDate_ = Settings::instance().evaluationDate();
575
576 std::vector<Date> res = {
577 TARGET().advance(date: referenceDate_, period: 6 * Months),
578 TARGET().advance(date: referenceDate_, period: 12 * Months),
579 TARGET().advance(date: referenceDate_, period: 18 * Months),
580 TARGET().advance(date: referenceDate_, period: 24 * Months),
581 TARGET().advance(date: referenceDate_, period: 30 * Months),
582 TARGET().advance(date: referenceDate_, period: 36 * Months),
583 TARGET().advance(date: referenceDate_, period: 42 * Months),
584 TARGET().advance(date: referenceDate_, period: 48 * Months),
585 TARGET().advance(date: referenceDate_, period: 54 * Months),
586 TARGET().advance(date: referenceDate_, period: 60 * Months)
587 };
588
589 return res;
590 }
591
592 // Calibration Basket 3: Coterminal Swaptions 10y
593
594 std::vector<Date> expiriesCalBasket3() {
595
596 Date referenceDate_ = Settings::instance().evaluationDate();
597
598 std::vector<Date> res = {
599 TARGET().advance(date: referenceDate_, period: 1 * Years),
600 TARGET().advance(date: referenceDate_, period: 2 * Years),
601 TARGET().advance(date: referenceDate_, period: 3 * Years),
602 TARGET().advance(date: referenceDate_, period: 4 * Years),
603 TARGET().advance(date: referenceDate_, period: 5 * Years),
604 TARGET().advance(date: referenceDate_, period: 6 * Years),
605 TARGET().advance(date: referenceDate_, period: 7 * Years),
606 TARGET().advance(date: referenceDate_, period: 8 * Years),
607 TARGET().advance(date: referenceDate_, period: 9 * Years)
608 };
609
610 return res;
611 }
612
613 std::vector<Period> tenorsCalBasket3() {
614
615 std::vector<Period> res = {9 * Years, 8 * Years, 7 * Years, 6 * Years, 5 * Years,
616 4 * Years, 3 * Years, 2 * Years, 1 * Years};
617 return res;
618 }
619
620 std::vector<Real> impliedStdDevs(const Real atm, const std::vector<Real> &strikes,
621 const std::vector<Real> &prices) {
622
623 std::vector<Real> result;
624
625 for (Size i = 0; i < prices.size(); i++) {
626 result.push_back(x: blackFormulaImpliedStdDev(optionType: Option::Call, strike: strikes[i],
627 forward: atm, blackPrice: prices[i], discount: 1.0, displacement: 0.0,
628 guess: 0.2, accuracy: 1E-8, maxIterations: 1000));
629 }
630
631 return result;
632 }
633}
634
635void MarkovFunctionalTest::testKahaleSmileSection() {
636
637 BOOST_TEST_MESSAGE("Testing Kahale smile section...");
638
639 const Real tol = 1E-8;
640
641 // arbitrage free sample smile data
642
643 const Real atm = 0.05;
644 const Real t = 1.0;
645
646 const Real strikes0[] = { 0.01, 0.02, 0.03, 0.04, 0.05,
647 0.06, 0.07, 0.08, 0.09, 0.10 };
648
649 const std::vector<Real> strikes(strikes0, strikes0 + 10);
650 std::vector<Real> money;
651 std::vector<Real> calls0;
652
653 for (Real strike : strikes) {
654 money.push_back(x: strike / atm);
655 calls0.push_back(x: blackFormula(optionType: Option::Call, strike, forward: atm, stdDev: 0.50 * std::sqrt(x: t), discount: 1.0, displacement: 0.0));
656 }
657
658 std::vector<Real> stdDevs0 = impliedStdDevs(atm, strikes, prices: calls0);
659 ext::shared_ptr<SmileSection> sec1(
660 new InterpolatedSmileSection<Linear>(t, strikes, stdDevs0, atm));
661
662 // test arbitrage free smile reproduction
663
664 ext::shared_ptr<KahaleSmileSection> ksec11(
665 new KahaleSmileSection(sec1, atm, false, false, false, money));
666
667 if (std::fabs(x: ksec11->leftCoreStrike() - 0.01) > tol)
668 BOOST_ERROR("smile11 left af strike is " << ksec11->leftCoreStrike()
669 << " expected 0.01");
670
671 if (std::fabs(x: ksec11->rightCoreStrike() - 0.10) > tol)
672 BOOST_ERROR("smile11 right af strike is " << ksec11->rightCoreStrike()
673 << " expected 0.10");
674
675 Real k = strikes[0];
676 while (k <= strikes.back() + tol) {
677 Real pric0 = sec1->optionPrice(strike: k);
678 Real pric1 = ksec11->optionPrice(strike: k);
679 if (std::fabs(x: pric0 - pric1) > tol)
680 BOOST_ERROR("smile11 is not reprocduced at strike "
681 << k << " input smile call price is " << pric0
682 << " kahale smile call price is " << pric1);
683 k += 0.0001;
684 }
685
686 // test interpolation
687
688 ext::shared_ptr<KahaleSmileSection> ksec12(
689 new KahaleSmileSection(sec1, atm, true, false, false, money));
690
691 // sanity check for left point extrapolation may mark 0.01 as bad as well as
692 // good depending
693 // on platform and compiler due to numerical differences, so we have to
694 // admit two possible results
695 if (std::fabs(x: ksec12->leftCoreStrike() - 0.02) > tol &&
696 std::fabs(x: ksec12->leftCoreStrike() - 0.01) > tol)
697 BOOST_ERROR("smile12 left af strike is " << ksec12->leftCoreStrike()
698 << "expected 0.01 or 0.02");
699
700 if (std::fabs(x: ksec12->rightCoreStrike() - 0.10) > tol)
701 BOOST_ERROR("smile12 right af strike is " << ksec12->rightCoreStrike()
702 << "expected 0.10");
703
704 for (Size i = 1; i < strikes.size(); i++) {
705 Real pric0 = sec1->optionPrice(strike: strikes[i]);
706 Real pric1 = ksec12->optionPrice(strike: strikes[i]);
707 if (std::fabs(x: pric0 - pric1) > tol)
708 BOOST_ERROR("smile12 is not reproduced on grid at strike "
709 << strikes[i] << " input smile call price is " << pric0
710 << " kahale smile call price is " << pric1);
711 }
712
713 // test global no arbitrageability
714
715 k = 0.0010;
716 Real dig00 = 1.0, dig10 = 1.0;
717 while (k <= 2.0 * strikes.back() + tol) {
718 Real dig0 = ksec11->digitalOptionPrice(strike: k);
719 Real dig1 = ksec12->digitalOptionPrice(strike: k);
720 if (!(dig0 <= dig00 + tol && dig0 >= 0.0))
721 BOOST_ERROR("arbitrage in digitals11 (" << dig00 << "," << dig0
722 << ") at strike " << k);
723 if (!(dig1 <= dig10 + tol && dig1 >= 0.0))
724 BOOST_ERROR("arbitrage in digitals12 (" << dig10 << "," << dig1
725 << ") at strike " << k);
726 dig00 = dig0;
727 dig10 = dig1;
728 k += 0.0001;
729 }
730
731 // test exponential extrapolation
732
733 ext::shared_ptr<KahaleSmileSection> ksec13(
734 new KahaleSmileSection(sec1, atm, false, true, false, money));
735
736 k = strikes.back();
737 Real dig0 = ksec13->digitalOptionPrice(strike: k - 0.0010);
738 while (k <= 10.0 * strikes.back() + tol) {
739 Real dig = ksec13->digitalOptionPrice(strike: k);
740 if (!(dig <= dig0 + tol && dig >= 0.0))
741 BOOST_ERROR("arbitrage in digitals13 (" << dig0 << "," << dig
742 << ") at strike " << k);
743 k += 0.0001;
744 }
745
746 // test arbitrageable smile (leftmost point)
747
748 std::vector<Real> calls1(calls0);
749 calls1[0] = (atm - strikes[0]) +
750 0.0010; // introduce arbitrage by changing call price
751 std::vector<Real> stdDevs1 = impliedStdDevs(atm, strikes, prices: calls1);
752 ext::shared_ptr<SmileSection> sec2(
753 new InterpolatedSmileSection<Linear>(t, strikes, stdDevs1, atm));
754
755 ext::shared_ptr<KahaleSmileSection> ksec21(
756 new KahaleSmileSection(sec2, atm, false, false, false, money));
757 ext::shared_ptr<KahaleSmileSection> ksec22(
758 new KahaleSmileSection(sec2, atm, true, false, true, money));
759
760 if (std::fabs(x: ksec21->leftCoreStrike() - 0.02) > tol)
761 BOOST_ERROR("smile21 left af strike is " << ksec21->leftCoreStrike()
762 << " expected 0.02");
763 if (std::fabs(x: ksec22->leftCoreStrike() - 0.02) > tol)
764 BOOST_ERROR("smile22 left af strike is " << ksec22->leftCoreStrike()
765 << " expected 0.02");
766
767 if (std::fabs(x: ksec21->rightCoreStrike() - 0.10) > tol)
768 BOOST_ERROR("smile21 right af strike is " << ksec21->rightCoreStrike()
769 << " expected 0.10");
770 if (std::fabs(x: ksec22->rightCoreStrike() - 0.10) > tol)
771 BOOST_ERROR("smile22 right af strike is " << ksec22->rightCoreStrike()
772 << " expected 0.10");
773
774 k = 0.0010;
775 dig00 = dig10 = 1.0;
776 while (k <= 2.0 * strikes.back() + tol) {
777 Real dig0 = ksec21->digitalOptionPrice(strike: k);
778 Real dig1 = ksec22->digitalOptionPrice(strike: k);
779 if (!(dig0 <= dig00 + tol && dig0 >= 0.0))
780 BOOST_ERROR("arbitrage in digitals21 (" << dig00 << "," << dig0
781 << ") at strike " << k);
782 if (!(dig1 <= dig10 + tol && dig1 >= 0.0))
783 BOOST_ERROR("arbitrage in digitals22 (" << dig10 << "," << dig1
784 << ") at strike " << k);
785 dig00 = dig0;
786 dig10 = dig1;
787 k += 0.0001;
788 }
789
790 // test arbitrageable smile (second but rightmost point)
791
792 std::vector<Real> calls2(calls0);
793 calls2[8] = 0.9 * calls2[9] +
794 0.1 * calls2[8]; // introduce arbitrage by changing call price
795 std::vector<Real> stdDevs2 = impliedStdDevs(atm, strikes, prices: calls2);
796 ext::shared_ptr<SmileSection> sec3(
797 new InterpolatedSmileSection<Linear>(t, strikes, stdDevs2, atm));
798
799 ext::shared_ptr<KahaleSmileSection> ksec31(
800 new KahaleSmileSection(sec3, atm, false, false, false, money));
801 ext::shared_ptr<KahaleSmileSection> ksec32(
802 new KahaleSmileSection(sec3, atm, true, false, true, money));
803
804 if (std::fabs(x: ksec31->leftCoreStrike() - 0.01) > tol)
805 BOOST_ERROR("smile31 left af strike is " << ksec31->leftCoreStrike()
806 << " expected 0.01");
807
808 // sanity check for left point extrapolation may mark 0.01 as bad as well as
809 // good depending
810 // on platform and compiler due to numerical differences, so we have to
811 // admit two possible results
812 if (std::fabs(x: ksec32->leftCoreStrike() - 0.02) > tol &&
813 std::fabs(x: ksec32->leftCoreStrike() - 0.01) > tol)
814 BOOST_ERROR("smile32 left af strike is " << ksec32->leftCoreStrike()
815 << " expected 0.01 or 0.02");
816
817 if (std::fabs(x: ksec31->rightCoreStrike() - 0.08) > tol)
818 BOOST_ERROR("smile31 right af strike is " << ksec31->rightCoreStrike()
819 << " expected 0.08");
820 if (std::fabs(x: ksec32->rightCoreStrike() - 0.10) > tol)
821 BOOST_ERROR("smile32 right af strike is " << ksec32->rightCoreStrike()
822 << " expected 0.10");
823 k = 0.0010;
824 dig00 = dig10 = 1.0;
825 while (k <= 2.0 * strikes.back() + tol) {
826 Real dig0 = ksec31->digitalOptionPrice(strike: k);
827 Real dig1 = ksec32->digitalOptionPrice(strike: k);
828 if (!(dig0 <= dig00 + tol && dig0 >= 0.0))
829 BOOST_ERROR("arbitrage in digitals31 (" << dig00 << "," << dig0
830 << ") at strike " << k);
831 if (!(dig1 <= dig10 + tol && dig1 >= 0.0))
832 BOOST_ERROR("arbitrage in digitals32 (" << dig10 << "," << dig1
833 << ") at strike " << k);
834 dig00 = dig0;
835 dig10 = dig1;
836 k += 0.0001;
837 }
838}
839
840void MarkovFunctionalTest::testCalibrationOneInstrumentSet() {
841
842 const Real tol0 = 0.0001; // 1bp tolerance for model zero rates vs. market
843 // zero rates (note that model zero rates are
844 // implied by the calibration of the numeraire to
845 // the smile)
846 const Real tol1 =
847 0.0001; // 1bp tolerance for model call put premia vs. market premia
848
849 BOOST_TEST_MESSAGE(
850 "Testing Markov functional calibration to one instrument set...");
851
852 Date savedEvalDate = Settings::instance().evaluationDate();
853 Date referenceDate(14, November, 2012);
854 Settings::instance().evaluationDate() = referenceDate;
855
856 Handle<YieldTermStructure> flatYts_ = flatYts();
857 Handle<YieldTermStructure> md0Yts_ = md0Yts();
858 Handle<SwaptionVolatilityStructure> flatSwaptionVts_ = flatSwaptionVts();
859 Handle<SwaptionVolatilityStructure> md0SwaptionVts_ = md0SwaptionVts();
860 Handle<OptionletVolatilityStructure> flatOptionletVts_ = flatOptionletVts();
861 Handle<OptionletVolatilityStructure> md0OptionletVts_ = md0OptionletVts();
862
863 ext::shared_ptr<SwapIndex> swapIndexBase(
864 new EuriborSwapIsdaFixA(1 * Years));
865 ext::shared_ptr<IborIndex> iborIndex(new Euribor(6 * Months));
866
867 std::vector<Date> volStepDates;
868 std::vector<Real> vols = {1.0};
869
870 // use a grid with fewer points for smile arbitrage
871 // testing and model outputs than the default grid
872 // for the testing here
873 std::vector<Real> money = {0.1, 0.25, 0.50, 0.75, 1.0, 1.25, 1.50, 2.0, 5.0};
874
875 // Calibration Basket 1 / flat yts, vts
876
877 ext::shared_ptr<MarkovFunctional> mf1(new MarkovFunctional(
878 flatYts_, 0.01, volStepDates, vols, flatSwaptionVts_,
879 expiriesCalBasket1(), tenorsCalBasket1(), swapIndexBase,
880 MarkovFunctional::ModelSettings()
881 .withYGridPoints(n: 64) // we use the default values more or less, this
882 // is just to demonstrate how to set the model
883 // parameters
884 .withYStdDevs(s: 7.0)
885 .withGaussHermitePoints(n: 32)
886 .withDigitalGap(d: 1e-5)
887 .withMarketRateAccuracy(a: 1e-7)
888 .withLowerRateBound(l: 0.0)
889 .withUpperRateBound(u: 2.0)
890 .withAdjustments(
891 a: MarkovFunctional::ModelSettings::KahaleSmile |
892 MarkovFunctional::ModelSettings::SmileExponentialExtrapolation)
893 .withSmileMoneynessCheckpoints(m: money)));
894
895 MarkovFunctional::ModelOutputs outputs1 =
896 mf1->modelOutputs(); // this costs a lot of time, so only use it if you
897 // want to check the calibration
898 // BOOST_TEST_MESSAGE(outputs1);
899
900 for (Size i = 0; i < outputs1.expiries_.size(); i++) {
901 if (fabs(x: outputs1.marketZerorate_[i] - outputs1.modelZerorate_[i]) >
902 tol0)
903 BOOST_ERROR("Basket 1 / flat termstructures : Market zero rate ("
904 << outputs1.marketZerorate_[i]
905 << ") and model zero rate ("
906 << outputs1.modelZerorate_[i] << ") do not agree.");
907 }
908
909 for (Size i = 0; i < outputs1.expiries_.size(); i++) {
910 for (Size j = 0; j < outputs1.smileStrikes_[i].size(); j++) {
911 if (fabs(x: outputs1.marketCallPremium_[i][j] -
912 outputs1.modelCallPremium_[i][j]) > tol1)
913 BOOST_ERROR(
914 "Basket 1 / flat termstructures : Market call premium ("
915 << outputs1.marketCallPremium_[i][j]
916 << ") does not match model premium ("
917 << outputs1.modelCallPremium_[i][j] << ")");
918 if (fabs(x: outputs1.marketPutPremium_[i][j] -
919 outputs1.modelPutPremium_[i][j]) > tol1)
920 BOOST_ERROR(
921 "Basket 1 / flat termstructures : Market put premium ("
922 << outputs1.marketPutPremium_[i][j]
923 << ") does not match model premium ("
924 << outputs1.modelPutPremium_[i][j] << ")");
925 }
926 }
927
928 // Calibration Basket 2 / flat yts, vts
929
930 ext::shared_ptr<MarkovFunctional> mf2(new MarkovFunctional(
931 flatYts_, 0.01, volStepDates, vols, flatOptionletVts_,
932 expiriesCalBasket2(), iborIndex,
933 MarkovFunctional::ModelSettings()
934 .withYGridPoints(n: 64)
935 .withYStdDevs(s: 7.0)
936 .withGaussHermitePoints(n: 32)
937 .withDigitalGap(d: 1e-5)
938 .withMarketRateAccuracy(a: 1e-7)
939 .withLowerRateBound(l: 0.0)
940 .withUpperRateBound(u: 2.0)
941 .withAdjustments(a: MarkovFunctional::ModelSettings::AdjustNone)
942 .withSmileMoneynessCheckpoints(m: money)));
943
944 MarkovFunctional::ModelOutputs outputs2 = mf2->modelOutputs();
945 // BOOST_TEST_MESSAGE(outputs2);
946
947 for (Size i = 0; i < outputs2.expiries_.size(); i++) {
948 if (fabs(x: outputs2.marketZerorate_[i] - outputs2.modelZerorate_[i]) >
949 tol0)
950 BOOST_ERROR("Basket 2 / flat termstructures : Market zero rate ("
951 << outputs2.marketZerorate_[i]
952 << ") and model zero rate ("
953 << outputs2.modelZerorate_[i] << ") do not agree.");
954 }
955
956 for (Size i = 0; i < outputs2.expiries_.size(); i++) {
957 for (Size j = 0; j < outputs2.smileStrikes_[i].size(); j++) {
958 if (fabs(x: outputs2.marketCallPremium_[i][j] -
959 outputs2.modelCallPremium_[i][j]) > tol1)
960 BOOST_ERROR(
961 "Basket 2 / flat termstructures : Market call premium ("
962 << outputs2.marketCallPremium_[i][j]
963 << ") does not match model premium ("
964 << outputs2.modelCallPremium_[i][j] << ")");
965 if (fabs(x: outputs2.marketPutPremium_[i][j] -
966 outputs2.modelPutPremium_[i][j]) > tol1)
967 BOOST_ERROR(
968 "Basket 2/ flat termstructures : Market put premium ("
969 << outputs2.marketPutPremium_[i][j]
970 << ") does not match model premium ("
971 << outputs2.modelPutPremium_[i][j] << ")");
972 }
973 }
974
975 // Calibration Basket 1 / real yts, vts
976
977 ext::shared_ptr<MarkovFunctional> mf3(new MarkovFunctional(
978 md0Yts_, 0.01, volStepDates, vols, md0SwaptionVts_,
979 expiriesCalBasket1(), tenorsCalBasket1(), swapIndexBase,
980 MarkovFunctional::ModelSettings()
981 .withYGridPoints(n: 128) // use more points to increase accuracy
982 .withYStdDevs(s: 7.0)
983 .withGaussHermitePoints(n: 64)
984 .withDigitalGap(d: 1e-5)
985 .withMarketRateAccuracy(a: 1e-7)
986 .withLowerRateBound(l: 0.0)
987 .withUpperRateBound(u: 2.0)
988 .withSmileMoneynessCheckpoints(m: money)));
989
990 MarkovFunctional::ModelOutputs outputs3 = mf3->modelOutputs();
991 // BOOST_TEST_MESSAGE(outputs3);
992 // outputSurfaces(mf3,md0Yts_);
993
994 for (Size i = 0; i < outputs3.expiries_.size(); i++) {
995 if (fabs(x: outputs3.marketZerorate_[i] - outputs3.modelZerorate_[i]) >
996 tol0)
997 BOOST_ERROR("Basket 1 / real termstructures: Market zero rate ("
998 << outputs3.marketZerorate_[i]
999 << ") and model zero rate ("
1000 << outputs3.modelZerorate_[i] << ") do not agree.");
1001 }
1002
1003 for (Size i = 0; i < outputs3.expiries_.size(); i++) {
1004 for (Size j = 0; j < outputs3.smileStrikes_[i].size(); j++) {
1005 if (fabs(x: outputs3.marketCallPremium_[i][j] -
1006 outputs3.modelCallPremium_[i][j]) > tol1)
1007 BOOST_ERROR(
1008 "Basket 1 / real termstructures: Market call premium ("
1009 << outputs3.marketCallPremium_[i][j]
1010 << ") does not match model premium ("
1011 << outputs3.modelCallPremium_[i][j] << ")");
1012 if (fabs(x: outputs3.marketPutPremium_[i][j] -
1013 outputs3.modelPutPremium_[i][j]) > tol1)
1014 BOOST_ERROR(
1015 "Basket 1 / real termstructures: Market put premium ("
1016 << outputs3.marketPutPremium_[i][j]
1017 << ") does not match model premium ("
1018 << outputs3.modelPutPremium_[i][j] << ")");
1019 }
1020 }
1021
1022 // Calibration Basket 2 / real yts, vts
1023
1024 ext::shared_ptr<MarkovFunctional> mf4(
1025 new MarkovFunctional(md0Yts_, 0.01, volStepDates, vols,
1026 md0OptionletVts_, expiriesCalBasket2(), iborIndex,
1027 MarkovFunctional::ModelSettings()
1028 .withYGridPoints(n: 64)
1029 .withYStdDevs(s: 7.0)
1030 .withGaussHermitePoints(n: 32)
1031 .withDigitalGap(d: 1e-5)
1032 .withMarketRateAccuracy(a: 1e-7)
1033 .withLowerRateBound(l: 0.0)
1034 .withUpperRateBound(u: 2.0)
1035 .withSmileMoneynessCheckpoints(m: money)));
1036
1037 MarkovFunctional::ModelOutputs outputs4 = mf4->modelOutputs();
1038 // BOOST_TEST_MESSAGE(outputs4);
1039
1040 for (Size i = 0; i < outputs4.expiries_.size(); i++) {
1041 if (fabs(x: outputs4.marketZerorate_[i] - outputs4.modelZerorate_[i]) >
1042 tol0)
1043 BOOST_ERROR("Basket 2 / real termstructures : Market zero rate ("
1044 << outputs4.marketZerorate_[i]
1045 << ") and model zero rate ("
1046 << outputs4.modelZerorate_[i] << ") do not agree.");
1047 }
1048
1049 for (Size i = 0; i < outputs4.expiries_.size(); i++) {
1050 for (Size j = 0; j < outputs4.smileStrikes_[i].size(); j++) {
1051 if (fabs(x: outputs4.marketCallPremium_[i][j] -
1052 outputs4.modelCallPremium_[i][j]) > tol1)
1053 BOOST_ERROR(
1054 "Basket 2 / real termstructures : Market call premium ("
1055 << outputs4.marketCallPremium_[i][j]
1056 << ") does not match model premium ("
1057 << outputs4.modelCallPremium_[i][j] << ")");
1058 if (fabs(x: outputs4.marketPutPremium_[i][j] -
1059 outputs4.modelPutPremium_[i][j]) > tol1)
1060 BOOST_ERROR(
1061 "Basket 2/ real termstructures : Market put premium ("
1062 << outputs4.marketPutPremium_[i][j]
1063 << ") does not match model premium ("
1064 << outputs4.modelPutPremium_[i][j] << ")");
1065 }
1066 }
1067
1068 Settings::instance().evaluationDate() = savedEvalDate;
1069}
1070
1071void MarkovFunctionalTest::testVanillaEngines() {
1072
1073 const Real tol1 = 0.0001; // 1bp tolerance for model engine call put premia
1074 // vs. black premia
1075 // note that we use the real market conventions here (i.e. 2 fixing days),
1076 // different from the calibration approach where 0 fixing days must be used.
1077 // therefore higher errors compared to the calibration results are expected.
1078
1079 BOOST_TEST_MESSAGE("Testing Markov functional vanilla engines...");
1080
1081 Date savedEvalDate = Settings::instance().evaluationDate();
1082 Date referenceDate(14, November, 2012);
1083 Settings::instance().evaluationDate() = referenceDate;
1084
1085 Handle<YieldTermStructure> flatYts_ = flatYts();
1086 Handle<YieldTermStructure> md0Yts_ = md0Yts();
1087 Handle<SwaptionVolatilityStructure> flatSwaptionVts_ = flatSwaptionVts();
1088 Handle<SwaptionVolatilityStructure> md0SwaptionVts_ = md0SwaptionVts();
1089 Handle<OptionletVolatilityStructure> flatOptionletVts_ = flatOptionletVts();
1090 Handle<OptionletVolatilityStructure> md0OptionletVts_ = md0OptionletVts();
1091
1092 ext::shared_ptr<SwapIndex> swapIndexBase(
1093 new EuriborSwapIsdaFixA(1 * Years));
1094
1095 std::vector<Date> volStepDates;
1096 std::vector<Real> vols = {1.0};
1097
1098 // use a grid with few points for the check here
1099 std::vector<Real> money = {0.1, 0.25, 0.50, 0.75, 1.0, 1.25, 1.50, 2.0, 5.0};
1100
1101 // Calibration Basket 1 / flat yts, vts
1102
1103 ext::shared_ptr<IborIndex> iborIndex1(new Euribor(6 * Months, flatYts_));
1104
1105 ext::shared_ptr<MarkovFunctional> mf1(new MarkovFunctional(
1106 flatYts_, 0.01, volStepDates, vols, flatSwaptionVts_,
1107 expiriesCalBasket1(), tenorsCalBasket1(), swapIndexBase,
1108 MarkovFunctional::ModelSettings()
1109 .withYGridPoints(n: 64)
1110 .withYStdDevs(s: 7.0)
1111 .withGaussHermitePoints(n: 32)
1112 .withDigitalGap(d: 1e-5)
1113 .withMarketRateAccuracy(a: 1e-7)
1114 .withLowerRateBound(l: 0.0)
1115 .withUpperRateBound(u: 2.0)
1116 .withSmileMoneynessCheckpoints(m: money)));
1117
1118 MarkovFunctional::ModelOutputs outputs1 = mf1->modelOutputs();
1119 // BOOST_TEST_MESSAGE(outputs1);
1120
1121 ext::shared_ptr<Gaussian1dSwaptionEngine> mfSwaptionEngine1(
1122 new Gaussian1dSwaptionEngine(mf1, 64, 7.0));
1123 ext::shared_ptr<BlackSwaptionEngine> blackSwaptionEngine1(
1124 new BlackSwaptionEngine(flatYts_, flatSwaptionVts_));
1125
1126 for (Size i = 0; i < outputs1.expiries_.size(); i++) {
1127 for (Size j = 0; j < outputs1.smileStrikes_[0].size(); j++) {
1128 ext::shared_ptr<VanillaSwap> underlyingCall =
1129 MakeVanillaSwap(outputs1.tenors_[i], iborIndex1,
1130 outputs1.smileStrikes_[i][j])
1131 .withEffectiveDate(
1132 TARGET().advance(outputs1.expiries_[i], n: 2, unit: Days))
1133 .receiveFixed(flag: false);
1134 ext::shared_ptr<VanillaSwap> underlyingPut =
1135 MakeVanillaSwap(outputs1.tenors_[i], iborIndex1,
1136 outputs1.smileStrikes_[i][j])
1137 .withEffectiveDate(
1138 TARGET().advance(outputs1.expiries_[i], n: 2, unit: Days))
1139 .receiveFixed(flag: true);
1140 ext::shared_ptr<Exercise> exercise(
1141 new EuropeanExercise(outputs1.expiries_[i]));
1142 Swaption swaptionC(underlyingCall, exercise);
1143 Swaption swaptionP(underlyingPut, exercise);
1144 swaptionC.setPricingEngine(blackSwaptionEngine1);
1145 swaptionP.setPricingEngine(blackSwaptionEngine1);
1146 Real blackPriceCall = swaptionC.NPV();
1147 Real blackPricePut = swaptionP.NPV();
1148 swaptionC.setPricingEngine(mfSwaptionEngine1);
1149 swaptionP.setPricingEngine(mfSwaptionEngine1);
1150 Real mfPriceCall = swaptionC.NPV();
1151 Real mfPricePut = swaptionP.NPV();
1152 if (fabs(x: blackPriceCall - mfPriceCall) > tol1)
1153 BOOST_ERROR(
1154 "Basket 1 / flat termstructures: Call premium market ("
1155 << blackPriceCall << ") does not match model premium ("
1156 << mfPriceCall << ")");
1157 if (fabs(x: blackPricePut - mfPricePut) > tol1)
1158 BOOST_ERROR(
1159 "Basket 1 / flat termstructures: Put premium market ("
1160 << blackPricePut << ") does not match model premium ("
1161 << mfPricePut << ")");
1162 }
1163 }
1164
1165 // Calibration Basket 2 / flat yts, vts
1166
1167 ext::shared_ptr<IborIndex> iborIndex2(new Euribor(6 * Months, flatYts_));
1168
1169 ext::shared_ptr<MarkovFunctional> mf2(new MarkovFunctional(
1170 flatYts_, 0.01, volStepDates, vols, flatOptionletVts_,
1171 expiriesCalBasket2(), iborIndex2,
1172 MarkovFunctional::ModelSettings()
1173 .withYGridPoints(n: 64)
1174 .withYStdDevs(s: 7.0)
1175 .withGaussHermitePoints(n: 16)
1176 .withDigitalGap(d: 1e-5)
1177 .withMarketRateAccuracy(a: 1e-7)
1178 .withLowerRateBound(l: 0.0)
1179 .withUpperRateBound(u: 2.0)
1180 .withSmileMoneynessCheckpoints(m: money)));
1181
1182 MarkovFunctional::ModelOutputs outputs2 = mf2->modelOutputs();
1183 // BOOST_TEST_MESSAGE(outputs2);
1184
1185 ext::shared_ptr<BlackCapFloorEngine> blackCapFloorEngine2(
1186 new BlackCapFloorEngine(flatYts_, flatOptionletVts_));
1187 ext::shared_ptr<Gaussian1dCapFloorEngine> mfCapFloorEngine2(
1188 new Gaussian1dCapFloorEngine(mf2, 64, 7.0));
1189 std::vector<CapFloor> c2 = {
1190 MakeCapFloor(CapFloor::Cap, 5 * Years, iborIndex2, 0.01),
1191 MakeCapFloor(CapFloor::Cap, 5 * Years, iborIndex2, 0.02),
1192 MakeCapFloor(CapFloor::Cap, 5 * Years, iborIndex2, 0.03),
1193 MakeCapFloor(CapFloor::Cap, 5 * Years, iborIndex2, 0.04),
1194 MakeCapFloor(CapFloor::Cap, 5 * Years, iborIndex2, 0.05),
1195 MakeCapFloor(CapFloor::Cap, 5 * Years, iborIndex2, 0.07),
1196 MakeCapFloor(CapFloor::Cap, 5 * Years, iborIndex2, 0.10),
1197 MakeCapFloor(CapFloor::Floor, 5 * Years, iborIndex2, 0.01),
1198 MakeCapFloor(CapFloor::Floor, 5 * Years, iborIndex2, 0.02),
1199 MakeCapFloor(CapFloor::Floor, 5 * Years, iborIndex2, 0.03),
1200 MakeCapFloor(CapFloor::Floor, 5 * Years, iborIndex2, 0.04),
1201 MakeCapFloor(CapFloor::Floor, 5 * Years, iborIndex2, 0.05),
1202 MakeCapFloor(CapFloor::Floor, 5 * Years, iborIndex2, 0.07),
1203 MakeCapFloor(CapFloor::Floor, 5 * Years, iborIndex2, 0.10)
1204 };
1205
1206 for (auto& i : c2) {
1207 i.setPricingEngine(blackCapFloorEngine2);
1208 Real blackPrice = i.NPV();
1209 i.setPricingEngine(mfCapFloorEngine2);
1210 Real mfPrice = i.NPV();
1211 if (fabs(x: blackPrice - mfPrice) > tol1)
1212 BOOST_ERROR(
1213 "Basket 2 / flat termstructures: Cap/Floor premium market ("
1214 << blackPrice << ") does not match model premium (" << mfPrice
1215 << ")");
1216 }
1217
1218 // Calibration Basket 1 / real yts, vts
1219
1220 ext::shared_ptr<IborIndex> iborIndex3(new Euribor(6 * Months, md0Yts_));
1221
1222 ext::shared_ptr<MarkovFunctional> mf3(new MarkovFunctional(
1223 md0Yts_, 0.01, volStepDates, vols, md0SwaptionVts_,
1224 expiriesCalBasket1(), tenorsCalBasket1(), swapIndexBase,
1225 MarkovFunctional::ModelSettings()
1226 .withYGridPoints(n: 64)
1227 .withYStdDevs(s: 7.0)
1228 .withGaussHermitePoints(n: 32)
1229 .withDigitalGap(d: 1e-5)
1230 .withMarketRateAccuracy(a: 1e-7)
1231 .withLowerRateBound(l: 0.0)
1232 .withUpperRateBound(u: 2.0)
1233 .withSmileMoneynessCheckpoints(m: money)));
1234
1235 ext::shared_ptr<Gaussian1dSwaptionEngine> mfSwaptionEngine3(
1236 new Gaussian1dSwaptionEngine(mf3, 64, 7.0));
1237 ext::shared_ptr<BlackSwaptionEngine> blackSwaptionEngine3(
1238 new BlackSwaptionEngine(md0Yts_, md0SwaptionVts_));
1239
1240 MarkovFunctional::ModelOutputs outputs3 = mf3->modelOutputs();
1241 // BOOST_TEST_MESSAGE(outputs3);
1242
1243 for (Size i = 0; i < outputs3.expiries_.size(); i++) {
1244 for (Size j = 0; j < outputs3.smileStrikes_[0].size(); j++) {
1245 ext::shared_ptr<VanillaSwap> underlyingCall =
1246 MakeVanillaSwap(outputs3.tenors_[i], iborIndex3,
1247 outputs3.smileStrikes_[i][j])
1248 .withEffectiveDate(
1249 TARGET().advance(outputs3.expiries_[i], n: 2, unit: Days))
1250 .receiveFixed(flag: false);
1251 ext::shared_ptr<VanillaSwap> underlyingPut =
1252 MakeVanillaSwap(outputs3.tenors_[i], iborIndex3,
1253 outputs3.smileStrikes_[i][j])
1254 .withEffectiveDate(
1255 TARGET().advance(outputs3.expiries_[i], n: 2, unit: Days))
1256 .receiveFixed(flag: true);
1257 ext::shared_ptr<Exercise> exercise(
1258 new EuropeanExercise(outputs3.expiries_[i]));
1259 Swaption swaptionC(underlyingCall, exercise);
1260 Swaption swaptionP(underlyingPut, exercise);
1261 swaptionC.setPricingEngine(blackSwaptionEngine3);
1262 swaptionP.setPricingEngine(blackSwaptionEngine3);
1263 Real blackPriceCall = swaptionC.NPV();
1264 Real blackPricePut = swaptionP.NPV();
1265 swaptionC.setPricingEngine(mfSwaptionEngine3);
1266 swaptionP.setPricingEngine(mfSwaptionEngine3);
1267 Real mfPriceCall = swaptionC.NPV();
1268 Real mfPricePut = swaptionP.NPV();
1269 Real smileCorrectionCall =
1270 (outputs3.marketCallPremium_[i][j] -
1271 outputs3.marketRawCallPremium_[i][j]); // we can not expect to
1272 // match the black
1273 // scholes price where
1274 // the smile is adjusted
1275 Real smileCorrectionPut = (outputs3.marketPutPremium_[i][j] -
1276 outputs3.marketRawPutPremium_[i][j]);
1277 if (fabs(x: blackPriceCall - mfPriceCall + smileCorrectionCall) > tol1)
1278 BOOST_ERROR(
1279 "Basket 1 / real termstructures: Call premium market ("
1280 << blackPriceCall << ") does not match model premium ("
1281 << mfPriceCall << ")");
1282 if (fabs(x: blackPricePut - mfPricePut + smileCorrectionPut) > tol1)
1283 BOOST_ERROR(
1284 "Basket 1 / real termstructures: Put premium market ("
1285 << blackPricePut << ") does not match model premium ("
1286 << mfPricePut << ")");
1287 }
1288 }
1289
1290 // Calibration Basket 2 / real yts, vts
1291
1292 ext::shared_ptr<IborIndex> iborIndex4(new Euribor(6 * Months, md0Yts_));
1293
1294 ext::shared_ptr<MarkovFunctional> mf4(new MarkovFunctional(
1295 md0Yts_, 0.01, volStepDates, vols, md0OptionletVts_,
1296 expiriesCalBasket2(), iborIndex4,
1297 MarkovFunctional::ModelSettings()
1298 .withYGridPoints(n: 64)
1299 .withYStdDevs(s: 7.0)
1300 .withGaussHermitePoints(n: 32)
1301 .withDigitalGap(d: 1e-5)
1302 .withMarketRateAccuracy(a: 1e-7)
1303 .withLowerRateBound(l: 0.0)
1304 .withUpperRateBound(u: 2.0)
1305 .withSmileMoneynessCheckpoints(m: money)
1306 //.withAdjustments(MarkovFunctional::ModelSettings::KahaleSmile
1307 // |
1308 // MarkovFunctional::ModelSettings::KahaleInterpolation
1309 //| MarkovFunctional::ModelSettings::KahaleExponentialExtrapolation
1310 //)
1311 ));
1312
1313 MarkovFunctional::ModelOutputs outputs4 = mf4->modelOutputs();
1314 // BOOST_TEST_MESSAGE(outputs4);
1315
1316 ext::shared_ptr<BlackCapFloorEngine> blackCapFloorEngine4(
1317 new BlackCapFloorEngine(md0Yts_, md0OptionletVts_));
1318 ext::shared_ptr<Gaussian1dCapFloorEngine> mfCapFloorEngine4(
1319 new Gaussian1dCapFloorEngine(mf4, 64, 7.0));
1320
1321 std::vector<CapFloor> c4 = {
1322 MakeCapFloor(CapFloor::Cap, 5 * Years, iborIndex4, 0.01),
1323 MakeCapFloor(CapFloor::Cap, 5 * Years, iborIndex4, 0.02),
1324 MakeCapFloor(CapFloor::Cap, 5 * Years, iborIndex4, 0.03),
1325 MakeCapFloor(CapFloor::Cap, 5 * Years, iborIndex4, 0.04),
1326 MakeCapFloor(CapFloor::Cap, 5 * Years, iborIndex4, 0.05),
1327 MakeCapFloor(CapFloor::Cap, 5 * Years, iborIndex4, 0.06),
1328 // //exclude because caplet stripper fails for this strike
1329 // MakeCapFloor(CapFloor::Cap,5*Years,iborIndex4,0.10),
1330 MakeCapFloor(CapFloor::Floor, 5 * Years, iborIndex4, 0.01),
1331 MakeCapFloor(CapFloor::Floor, 5 * Years, iborIndex4, 0.02),
1332 MakeCapFloor(CapFloor::Floor, 5 * Years, iborIndex4, 0.03),
1333 MakeCapFloor(CapFloor::Floor, 5 * Years, iborIndex4, 0.04),
1334 MakeCapFloor(CapFloor::Floor, 5 * Years, iborIndex4, 0.05),
1335 MakeCapFloor(CapFloor::Floor, 5 * Years, iborIndex4, 0.06)
1336 // //exclude because caplet stripper fails for this strike
1337 // MakeCapFloor(CapFloor::Floor,5*Years,iborIndex4,0.10)
1338 };
1339
1340 for (auto& i : c4) {
1341 i.setPricingEngine(blackCapFloorEngine4);
1342 Real blackPrice = i.NPV();
1343 i.setPricingEngine(mfCapFloorEngine4);
1344 Real mfPrice = i.NPV();
1345 if (fabs(x: blackPrice - mfPrice) > tol1)
1346 BOOST_ERROR(
1347 "Basket 2 / real termstructures: Cap/Floor premium market ("
1348 << blackPrice << ") does not match model premium (" << mfPrice
1349 << ")");
1350 }
1351
1352 Settings::instance().evaluationDate() = savedEvalDate;
1353}
1354
1355void MarkovFunctionalTest::testCalibrationTwoInstrumentSets() {
1356
1357 const Real tol1 = 0.1; // 0.1 times vega tolerance for model vs. market in
1358 // second instrument set
1359 BOOST_TEST_MESSAGE(
1360 "Testing Markov functional calibration to two instrument sets...");
1361
1362 Date savedEvalDate = Settings::instance().evaluationDate();
1363 Date referenceDate(14, November, 2012);
1364 Settings::instance().evaluationDate() = referenceDate;
1365
1366 Handle<YieldTermStructure> flatYts_ = flatYts();
1367 Handle<YieldTermStructure> md0Yts_ = md0Yts();
1368 Handle<SwaptionVolatilityStructure> flatSwaptionVts_ = flatSwaptionVts();
1369 Handle<SwaptionVolatilityStructure> md0SwaptionVts_ = md0SwaptionVts();
1370 Handle<OptionletVolatilityStructure> flatOptionletVts_ = flatOptionletVts();
1371 Handle<OptionletVolatilityStructure> md0OptionletVts_ = md0OptionletVts();
1372
1373 ext::shared_ptr<SwapIndex> swapIndexBase(
1374 new EuriborSwapIsdaFixA(1 * Years));
1375
1376 std::vector<Date> volStepDates = {
1377 TARGET().advance(date: referenceDate, period: 1 * Years),
1378 TARGET().advance(date: referenceDate, period: 2 * Years),
1379 TARGET().advance(date: referenceDate, period: 3 * Years),
1380 TARGET().advance(date: referenceDate, period: 4 * Years)
1381 };
1382 std::vector<Real> vols = {1.0, 1.0, 1.0, 1.0, 1.0};
1383 std::vector<Real> money = {0.1, 0.25, 0.50, 0.75, 1.0, 1.25, 1.50, 2.0, 5.0};
1384
1385 LevenbergMarquardt om;
1386 // ConjugateGradient om;
1387 EndCriteria ec(1000, 500, 1e-2, 1e-2, 1e-2);
1388
1389 // Calibration Basket 1 / flat yts, vts / Secondary calibration set consists
1390 // of coterminal swaptions
1391
1392 ext::shared_ptr<IborIndex> iborIndex1(new Euribor(6 * Months, flatYts_));
1393
1394 std::vector<ext::shared_ptr<BlackCalibrationHelper> > calibrationHelper1;
1395 std::vector<Real> calibrationHelperVols1 = {0.20, 0.20, 0.20, 0.20};
1396
1397 calibrationHelper1.push_back(x: ext::shared_ptr<BlackCalibrationHelper>(
1398 new SwaptionHelper(1 * Years, 4 * Years,
1399 Handle<Quote>(ext::shared_ptr<Quote>(
1400 new SimpleQuote(calibrationHelperVols1[0]))),
1401 iborIndex1, 1 * Years, Thirty360(Thirty360::BondBasis), Actual360(),
1402 flatYts_)));
1403 calibrationHelper1.push_back(x: ext::shared_ptr<BlackCalibrationHelper>(
1404 new SwaptionHelper(2 * Years, 3 * Years,
1405 Handle<Quote>(ext::shared_ptr<Quote>(
1406 new SimpleQuote(calibrationHelperVols1[1]))),
1407 iborIndex1, 1 * Years, Thirty360(Thirty360::BondBasis), Actual360(),
1408 flatYts_)));
1409 calibrationHelper1.push_back(x: ext::shared_ptr<BlackCalibrationHelper>(
1410 new SwaptionHelper(3 * Years, 2 * Years,
1411 Handle<Quote>(ext::shared_ptr<Quote>(
1412 new SimpleQuote(calibrationHelperVols1[2]))),
1413 iborIndex1, 1 * Years, Thirty360(Thirty360::BondBasis), Actual360(),
1414 flatYts_)));
1415 calibrationHelper1.push_back(x: ext::shared_ptr<BlackCalibrationHelper>(
1416 new SwaptionHelper(4 * Years, 1 * Years,
1417 Handle<Quote>(ext::shared_ptr<Quote>(
1418 new SimpleQuote(calibrationHelperVols1[3]))),
1419 iborIndex1, 1 * Years, Thirty360(Thirty360::BondBasis), Actual360(),
1420 flatYts_)));
1421
1422 ext::shared_ptr<MarkovFunctional> mf1(new MarkovFunctional(
1423 flatYts_, 0.01, volStepDates, vols, flatSwaptionVts_,
1424 expiriesCalBasket1(), tenorsCalBasket1(), swapIndexBase,
1425 MarkovFunctional::ModelSettings()
1426 .withYGridPoints(n: 64)
1427 .withYStdDevs(s: 7.0)
1428 .withGaussHermitePoints(n: 32)
1429 .withDigitalGap(d: 1e-5)
1430 .withMarketRateAccuracy(a: 1e-7)
1431 .withLowerRateBound(l: 0.0)
1432 .withUpperRateBound(u: 2.0)
1433 .withSmileMoneynessCheckpoints(m: money)));
1434
1435 ext::shared_ptr<Gaussian1dSwaptionEngine> mfSwaptionEngine1(
1436 new Gaussian1dSwaptionEngine(mf1, 64, 7.0));
1437 calibrationHelper1[0]->setPricingEngine(mfSwaptionEngine1);
1438 calibrationHelper1[1]->setPricingEngine(mfSwaptionEngine1);
1439 calibrationHelper1[2]->setPricingEngine(mfSwaptionEngine1);
1440 calibrationHelper1[3]->setPricingEngine(mfSwaptionEngine1);
1441
1442 mf1->calibrate(helpers: calibrationHelper1, method&: om, endCriteria: ec);
1443
1444 // std::cout << "Calibrated parameters 1: ";
1445 // Array params1 = mf1->params();
1446 // for(Size i=0;i<params1.size();i++) std::cout << params1[i] << ";";
1447 // std::cout << std::endl;
1448
1449 std::vector<Swaption> ch1;
1450 ch1.push_back(
1451 x: MakeSwaption(ext::shared_ptr<SwapIndex>(
1452 new EuriborSwapIsdaFixA(4 * Years, flatYts_)),
1453 1 * Years));
1454 ch1.push_back(
1455 x: MakeSwaption(ext::shared_ptr<SwapIndex>(
1456 new EuriborSwapIsdaFixA(3 * Years, flatYts_)),
1457 2 * Years));
1458 ch1.push_back(
1459 x: MakeSwaption(ext::shared_ptr<SwapIndex>(
1460 new EuriborSwapIsdaFixA(2 * Years, flatYts_)),
1461 3 * Years));
1462 ch1.push_back(
1463 x: MakeSwaption(ext::shared_ptr<SwapIndex>(
1464 new EuriborSwapIsdaFixA(1 * Years, flatYts_)),
1465 4 * Years));
1466
1467 for (Size i = 0; i < ch1.size(); i++) {
1468 ext::shared_ptr<BlackSwaptionEngine> blackEngine(
1469 new BlackSwaptionEngine(flatYts_, calibrationHelperVols1[i]));
1470 ch1[i].setPricingEngine(blackEngine);
1471 Real blackPrice = ch1[i].NPV();
1472 Real blackVega = ch1[i].result<Real>(tag: "vega");
1473 ch1[i].setPricingEngine(mfSwaptionEngine1);
1474 Real mfPrice = ch1[i].NPV();
1475 if (fabs(x: blackPrice - mfPrice) / blackVega > tol1)
1476 BOOST_TEST_MESSAGE("Basket 1 / flat yts, vts: Secondary instrument set "
1477 "calibration failed for instrument #"
1478 << i << " black premium is " << blackPrice
1479 << " while model premium is " << mfPrice
1480 << " (market vega is " << blackVega << ")");
1481 }
1482
1483 // MarkovFunctional::ModelOutputs outputs1 = mf1->modelOutputs();
1484 // BOOST_TEST_MESSAGE(outputs1);
1485
1486 // Calibration Basket 1 / real yts, vts / Secondary calibration set consists
1487 // of coterminal swaptions
1488
1489 ext::shared_ptr<IborIndex> iborIndex2(new Euribor(6 * Months, md0Yts_));
1490
1491 ext::shared_ptr<MarkovFunctional> mf2(new MarkovFunctional(
1492 md0Yts_, 0.01, volStepDates, vols, md0SwaptionVts_,
1493 expiriesCalBasket1(), tenorsCalBasket1(), swapIndexBase,
1494 MarkovFunctional::ModelSettings()
1495 .withYGridPoints(n: 64)
1496 .withYStdDevs(s: 7.0)
1497 .withGaussHermitePoints(n: 32)
1498 .withDigitalGap(d: 1e-5)
1499 .withMarketRateAccuracy(a: 1e-7)
1500 .withLowerRateBound(l: 0.0)
1501 .withUpperRateBound(u: 2.0)
1502 .withSmileMoneynessCheckpoints(m: money)));
1503
1504 std::vector<ext::shared_ptr<BlackCalibrationHelper> > calibrationHelper2;
1505 std::vector<Real> calibrationHelperVols2;
1506 calibrationHelperVols2.push_back(x: md0SwaptionVts_->volatility(
1507 optionTenor: 1 * Years, swapTenor: 4 * Years,
1508 strike: ext::dynamic_pointer_cast<SwaptionVolatilityCube>(
1509 r: md0SwaptionVts_.currentLink())->atmStrike(optionTenor: 1 * Years, swapTenor: 4 * Years)));
1510 calibrationHelperVols2.push_back(x: md0SwaptionVts_->volatility(
1511 optionTenor: 2 * Years, swapTenor: 3 * Years,
1512 strike: ext::dynamic_pointer_cast<SwaptionVolatilityCube>(
1513 r: md0SwaptionVts_.currentLink())->atmStrike(optionTenor: 2 * Years, swapTenor: 3 * Years)));
1514 calibrationHelperVols2.push_back(x: md0SwaptionVts_->volatility(
1515 optionTenor: 3 * Years, swapTenor: 2 * Years,
1516 strike: ext::dynamic_pointer_cast<SwaptionVolatilityCube>(
1517 r: md0SwaptionVts_.currentLink())->atmStrike(optionTenor: 3 * Years, swapTenor: 2 * Years)));
1518 calibrationHelperVols2.push_back(x: md0SwaptionVts_->volatility(
1519 optionTenor: 4 * Years, swapTenor: 1 * Years,
1520 strike: ext::dynamic_pointer_cast<SwaptionVolatilityCube>(
1521 r: md0SwaptionVts_.currentLink())->atmStrike(optionTenor: 4 * Years, swapTenor: 1 * Years)));
1522
1523 calibrationHelper2.push_back(x: ext::shared_ptr<BlackCalibrationHelper>(
1524 new SwaptionHelper(1 * Years, 4 * Years,
1525 Handle<Quote>(ext::shared_ptr<Quote>(
1526 new SimpleQuote(calibrationHelperVols2[0]))),
1527 iborIndex2, 1 * Years, Thirty360(Thirty360::BondBasis), Actual360(),
1528 md0Yts_)));
1529 calibrationHelper2.push_back(x: ext::shared_ptr<BlackCalibrationHelper>(
1530 new SwaptionHelper(2 * Years, 3 * Years,
1531 Handle<Quote>(ext::shared_ptr<Quote>(
1532 new SimpleQuote(calibrationHelperVols2[1]))),
1533 iborIndex2, 1 * Years, Thirty360(Thirty360::BondBasis), Actual360(),
1534 md0Yts_)));
1535 calibrationHelper2.push_back(x: ext::shared_ptr<BlackCalibrationHelper>(
1536 new SwaptionHelper(3 * Years, 2 * Years,
1537 Handle<Quote>(ext::shared_ptr<Quote>(
1538 new SimpleQuote(calibrationHelperVols2[2]))),
1539 iborIndex2, 1 * Years, Thirty360(Thirty360::BondBasis), Actual360(),
1540 md0Yts_)));
1541 calibrationHelper2.push_back(x: ext::shared_ptr<BlackCalibrationHelper>(
1542 new SwaptionHelper(4 * Years, 1 * Years,
1543 Handle<Quote>(ext::shared_ptr<Quote>(
1544 new SimpleQuote(calibrationHelperVols2[3]))),
1545 iborIndex2, 1 * Years, Thirty360(Thirty360::BondBasis), Actual360(),
1546 md0Yts_)));
1547
1548 ext::shared_ptr<Gaussian1dSwaptionEngine> mfSwaptionEngine2(
1549 new Gaussian1dSwaptionEngine(mf2, 64, 7.0));
1550 calibrationHelper2[0]->setPricingEngine(mfSwaptionEngine2);
1551 calibrationHelper2[1]->setPricingEngine(mfSwaptionEngine2);
1552 calibrationHelper2[2]->setPricingEngine(mfSwaptionEngine2);
1553 calibrationHelper2[3]->setPricingEngine(mfSwaptionEngine2);
1554
1555 mf2->calibrate(helpers: calibrationHelper2, method&: om, endCriteria: ec);
1556
1557 // std::cout << "Calibrated parameters 2: ";
1558 // Array params2 = mf2->params();
1559 // for(Size i=0;i<params2.size();i++) std::cout << params2[i] << ";";
1560 // std::cout << std::endl;
1561
1562 std::vector<Swaption> ch2;
1563 ch2.push_back(x: MakeSwaption(ext::shared_ptr<SwapIndex>(
1564 new EuriborSwapIsdaFixA(4 * Years, md0Yts_)),
1565 1 * Years));
1566 ch2.push_back(x: MakeSwaption(ext::shared_ptr<SwapIndex>(
1567 new EuriborSwapIsdaFixA(3 * Years, md0Yts_)),
1568 2 * Years));
1569 ch2.push_back(x: MakeSwaption(ext::shared_ptr<SwapIndex>(
1570 new EuriborSwapIsdaFixA(2 * Years, md0Yts_)),
1571 3 * Years));
1572 ch2.push_back(x: MakeSwaption(ext::shared_ptr<SwapIndex>(
1573 new EuriborSwapIsdaFixA(1 * Years, md0Yts_)),
1574 4 * Years));
1575
1576 for (Size i = 0; i < ch2.size(); i++) {
1577 ext::shared_ptr<BlackSwaptionEngine> blackEngine(
1578 new BlackSwaptionEngine(md0Yts_, calibrationHelperVols2[i]));
1579 ch2[i].setPricingEngine(blackEngine);
1580 Real blackPrice = ch2[i].NPV();
1581 Real blackVega = ch2[i].result<Real>(tag: "vega");
1582 ch2[i].setPricingEngine(mfSwaptionEngine2);
1583 Real mfPrice = ch2[i].NPV();
1584 if (fabs(x: blackPrice - mfPrice) / blackVega > tol1)
1585 BOOST_TEST_MESSAGE("Basket 1 / real yts, vts: Secondary instrument set "
1586 "calibration failed for instrument #"
1587 << i << " black premium is " << blackPrice
1588 << " while model premium is " << mfPrice
1589 << " (market vega is " << blackVega << ")");
1590 }
1591
1592 // MarkovFunctional::ModelOutputs outputs2 = mf2->modelOutputs();
1593 // BOOST_TEST_MESSAGE(outputs2);
1594
1595 Settings::instance().evaluationDate() = savedEvalDate;
1596}
1597
1598void MarkovFunctionalTest::testBermudanSwaption() {
1599
1600 Real tol0 = 0.0001; // 1bp tolerance against cached values
1601
1602 BOOST_TEST_MESSAGE("Testing Markov functional Bermudan swaption engine...");
1603
1604 Date savedEvalDate = Settings::instance().evaluationDate();
1605 Date referenceDate(14, November, 2012);
1606 Settings::instance().evaluationDate() = referenceDate;
1607
1608 Handle<YieldTermStructure> flatYts_ = flatYts();
1609 Handle<YieldTermStructure> md0Yts_ = md0Yts();
1610 Handle<SwaptionVolatilityStructure> flatSwaptionVts_ = flatSwaptionVts();
1611 Handle<SwaptionVolatilityStructure> md0SwaptionVts_ = md0SwaptionVts();
1612 Handle<OptionletVolatilityStructure> flatOptionletVts_ = flatOptionletVts();
1613 Handle<OptionletVolatilityStructure> md0OptionletVts_ = md0OptionletVts();
1614
1615 ext::shared_ptr<SwapIndex> swapIndexBase(
1616 new EuriborSwapIsdaFixA(1 * Years));
1617
1618 std::vector<Date> volStepDates;
1619 std::vector<Real> vols = {1.0};
1620
1621 ext::shared_ptr<IborIndex> iborIndex1(new Euribor(6 * Months, md0Yts_));
1622
1623 ext::shared_ptr<MarkovFunctional> mf1(
1624 new MarkovFunctional(md0Yts_, 0.01, volStepDates, vols, md0SwaptionVts_,
1625 expiriesCalBasket3(), tenorsCalBasket3(),
1626 swapIndexBase, MarkovFunctional::ModelSettings()
1627 .withYGridPoints(n: 32)
1628 .withYStdDevs(s: 7.0)
1629 .withGaussHermitePoints(n: 16)
1630 .withMarketRateAccuracy(a: 1e-7)
1631 .withDigitalGap(d: 1e-5)
1632 .withLowerRateBound(l: 0.0)
1633 .withUpperRateBound(u: 2.0)));
1634
1635 ext::shared_ptr<PricingEngine> mfSwaptionEngine1(
1636 new Gaussian1dSwaptionEngine(mf1, 64, 7.0));
1637
1638 ext::shared_ptr<VanillaSwap> underlyingCall =
1639 MakeVanillaSwap(10 * Years, iborIndex1, 0.03)
1640 .withEffectiveDate(TARGET().advance(referenceDate, n: 2, unit: Days))
1641 //.withNominal(100000000.0)
1642 .receiveFixed(flag: false);
1643
1644 std::vector<ext::shared_ptr<Exercise> > europeanExercises;
1645 std::vector<Date> expiries = expiriesCalBasket3();
1646 std::vector<Swaption> europeanSwaptions;
1647 for (Size i = 0; i < expiries.size(); i++) {
1648 europeanExercises.push_back(
1649 x: ext::shared_ptr<Exercise>(new EuropeanExercise(expiries[i])));
1650 europeanSwaptions.emplace_back(args&: underlyingCall, args&: europeanExercises[i]);
1651 europeanSwaptions.back().setPricingEngine(mfSwaptionEngine1);
1652 }
1653
1654 ext::shared_ptr<Exercise> bermudanExercise(
1655 new BermudanExercise(expiries));
1656 Swaption bermudanSwaption(underlyingCall, bermudanExercise);
1657 bermudanSwaption.setPricingEngine(mfSwaptionEngine1);
1658
1659 Real cachedValues[] = { 0.0030757, 0.0107344, 0.0179862,
1660 0.0225881, 0.0243215, 0.0229148,
1661 0.0191415, 0.0139035, 0.0076354 };
1662 Real cachedValue = 0.0327776;
1663
1664 for (Size i = 0; i < expiries.size(); i++) {
1665 Real npv = europeanSwaptions[i].NPV();
1666 if (fabs(x: npv - cachedValues[i]) > tol0)
1667 BOOST_ERROR("European swaption value ("
1668 << npv << ") deviates from cached value ("
1669 << cachedValues[i] << ")");
1670 }
1671
1672 Real npv = bermudanSwaption.NPV();
1673 if (fabs(x: npv - cachedValue) > tol0)
1674 BOOST_ERROR("Bermudan swaption value ("
1675 << npv << ") deviates from cached value (" << cachedValue
1676 << ")");
1677
1678 Settings::instance().evaluationDate() = savedEvalDate;
1679}
1680
1681test_suite *MarkovFunctionalTest::suite(SpeedLevel speed) {
1682 auto* suite = BOOST_TEST_SUITE("Markov functional model tests");
1683
1684 suite->add(QUANTLIB_TEST_CASE(&MarkovFunctionalTest::testMfStateProcess));
1685 suite->add(QUANTLIB_TEST_CASE(&MarkovFunctionalTest::testKahaleSmileSection));
1686 suite->add(QUANTLIB_TEST_CASE(&MarkovFunctionalTest::testBermudanSwaption));
1687
1688 if (speed <= Fast) {
1689 suite->add(QUANTLIB_TEST_CASE(&MarkovFunctionalTest::testCalibrationTwoInstrumentSets));
1690 }
1691
1692 if (speed == Slow) {
1693 suite->add(QUANTLIB_TEST_CASE(&MarkovFunctionalTest::testCalibrationOneInstrumentSet));
1694 suite->add(QUANTLIB_TEST_CASE(&MarkovFunctionalTest::testVanillaEngines));
1695 }
1696
1697 return suite;
1698}
1699

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