Logo
Fully annotated reference manual - version 1.8.12
Loading...
Searching...
No Matches
swaptionvolatilityconverter.cpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2016 Quaternion Risk Management Ltd
3 All rights reserved.
4
5 This file is part of ORE, a free-software/open-source library
6 for transparent pricing and risk analysis - http://opensourcerisk.org
7
8 ORE is free software: you can redistribute it and/or modify it
9 under the terms of the Modified BSD License. You should have received a
10 copy of the license along with this program.
11 The license is also available online at <http://opensourcerisk.org>
12
13 This program is distributed on the basis that it will form a useful
14 contribution to risk analytics and model standardisation, but WITHOUT
15 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
16 FITNESS FOR A PARTICULAR PURPOSE. See the license for more details.
17*/
18
20#include "toplevelfixture.hpp"
22#include <boost/test/unit_test.hpp>
23
26
27#include <ql/indexes/swap/euriborswap.hpp>
28#include <ql/pricingengines/blackformula.hpp>
29
30#include <boost/assign/list_of.hpp>
31
32using namespace QuantExt;
33using namespace QuantLib;
34using namespace boost::unit_test_framework;
35using namespace boost::assign;
36
37namespace {
38// Variables to be used in the test
39struct CommonVars {
40 // Constructor
41 CommonVars() : referenceDate(5, Feb, 2016) {
42 // Reference date
43 Settings::instance().evaluationDate() = referenceDate;
44
45 // Link ibor index to correct forward curve
46 QuantLib::ext::shared_ptr<IborIndex> iborIndex = conventions.floatIndex->clone(yieldCurves.forward6M);
47
48 // Create underlying swap conventions
49 swapConventions = QuantLib::ext::make_shared<SwapConventions>(conventions.settlementDays, conventions.fixedTenor,
50 conventions.fixedCalendar, conventions.fixedConvention,
51 conventions.fixedDayCounter, iborIndex);
52
53 // Set up the various swaption matrices
54 atmNormalVolMatrix = QuantLib::ext::make_shared<SwaptionVolatilityMatrix>(
55 referenceDate, conventions.fixedCalendar, conventions.fixedConvention, atmVols.optionTenors,
56 atmVols.swapTenors, atmVols.nVols, Actual365Fixed(), true, Normal);
57
58 atmLogNormalVolMatrix = QuantLib::ext::make_shared<SwaptionVolatilityMatrix>(
59 referenceDate, conventions.fixedCalendar, conventions.fixedConvention, atmVols.optionTenors,
60 atmVols.swapTenors, atmVols.lnVols, Actual365Fixed(), true, ShiftedLognormal);
61
62 // QuantLib::ext::make_shared can only handle 9 parameters
63 atmShiftedLogNormalVolMatrix_1 = QuantLib::ext::shared_ptr<SwaptionVolatilityMatrix>(new SwaptionVolatilityMatrix(
64 referenceDate, conventions.fixedCalendar, conventions.fixedConvention, atmVols.optionTenors,
65 atmVols.swapTenors, atmVols.slnVols_1, Actual365Fixed(), true, ShiftedLognormal, atmVols.shifts_1));
66
67 atmShiftedLogNormalVolMatrix_2 = QuantLib::ext::shared_ptr<SwaptionVolatilityMatrix>(new SwaptionVolatilityMatrix(
68 referenceDate, conventions.fixedCalendar, conventions.fixedConvention, atmVols.optionTenors,
69 atmVols.swapTenors, atmVols.slnVols_2, Actual365Fixed(), true, ShiftedLognormal, atmVols.shifts_2));
70 }
71
72 // Members
73 Date referenceDate;
74 SwaptionConventionsEUR conventions;
76 YieldCurveEUR yieldCurves;
77 QuantLib::ext::shared_ptr<SwapConventions> swapConventions;
78 QuantLib::ext::shared_ptr<SwaptionVolatilityStructure> atmNormalVolMatrix;
79 QuantLib::ext::shared_ptr<SwaptionVolatilityStructure> atmLogNormalVolMatrix;
80 QuantLib::ext::shared_ptr<SwaptionVolatilityStructure> atmShiftedLogNormalVolMatrix_1;
81 QuantLib::ext::shared_ptr<SwaptionVolatilityStructure> atmShiftedLogNormalVolMatrix_2;
82 SavedSettings backup;
83};
84} // namespace
85
86BOOST_FIXTURE_TEST_SUITE(QuantExtTestSuite, qle::test::TopLevelFixture)
87
88BOOST_AUTO_TEST_SUITE(SwaptionVolatilityConverterTest)
89
90BOOST_AUTO_TEST_CASE(testNormalToLognormal) {
91 BOOST_TEST_MESSAGE("Testing conversion of swaption vols from normal to lognormal...");
92
93 CommonVars vars;
94
95 // Tolerance used in boost check
96 Real tolerance = 0.00001;
97
98 // Set up the converter (Normal -> Lognormal with no shifts)
99 SwaptionVolatilityConverter converter(vars.referenceDate, vars.atmNormalVolMatrix, vars.yieldCurves.discountEonia,
100 vars.yieldCurves.discountEonia, vars.swapConventions, vars.swapConventions,
101 1 * Years, 30 * Years, ShiftedLognormal);
102
103 // Get back converted volatility structure and test result on pillar points
104 QuantLib::ext::shared_ptr<SwaptionVolatilityStructure> convertedsvs = converter.convert();
105 Volatility targetVol = 0.0;
106 Volatility outVol = 0.0;
107 Real dummyStrike = 0.0;
108 for (Size i = 0; i < vars.atmVols.optionTenors.size(); ++i) {
109 for (Size j = 0; j < vars.atmVols.swapTenors.size(); ++j) {
110 Period optionTenor = vars.atmVols.optionTenors[i];
111 Period swapTenor = vars.atmVols.swapTenors[j];
112 targetVol = vars.atmVols.lnVols[i][j];
113 outVol = convertedsvs->volatility(optionTenor, swapTenor, dummyStrike);
114 BOOST_CHECK_SMALL(outVol - targetVol, tolerance);
115 }
116 }
117}
118
119BOOST_AUTO_TEST_CASE(testLognormalToNormal) {
120 BOOST_TEST_MESSAGE("Testing conversion of swaption vols from lognormal to normal...");
121
122 CommonVars vars;
123
124 // Tolerance used in boost check
125 Real tolerance = 0.00001;
126
127 // Set up the converter (Lognormal with no shifts -> Normal)
128 SwaptionVolatilityConverter converter(vars.referenceDate, vars.atmLogNormalVolMatrix,
129 vars.yieldCurves.discountEonia, vars.yieldCurves.discountEonia,
130 vars.swapConventions, vars.swapConventions, 1 * Years, 30 * Years, Normal);
131
132 // Get back converted volatility structure and test result on pillar points
133 QuantLib::ext::shared_ptr<SwaptionVolatilityStructure> convertedsvs = converter.convert();
134 Volatility targetVol = 0.0;
135 Volatility outVol = 0.0;
136 Real dummyStrike = 0.0;
137 for (Size i = 0; i < vars.atmVols.optionTenors.size(); ++i) {
138 for (Size j = 0; j < vars.atmVols.swapTenors.size(); ++j) {
139 Period optionTenor = vars.atmVols.optionTenors[i];
140 Period swapTenor = vars.atmVols.swapTenors[j];
141 targetVol = vars.atmVols.nVols[i][j];
142 outVol = convertedsvs->volatility(optionTenor, swapTenor, dummyStrike);
143 BOOST_CHECK_SMALL(outVol - targetVol, tolerance);
144 }
145 }
146}
147
148BOOST_AUTO_TEST_CASE(testNormalToShiftedLognormal) {
149 BOOST_TEST_MESSAGE("Testing conversion of swaption vols from normal to shifted lognormal...");
150
151 CommonVars vars;
152
153 // Tolerance used in boost check
154 Real tolerance = 0.00001;
155
156 // Set up the converter (Normal -> Shifted Lognormal with shift set 1)
157 SwaptionVolatilityConverter converter(vars.referenceDate, vars.atmNormalVolMatrix, vars.yieldCurves.discountEonia,
158 vars.yieldCurves.discountEonia, vars.swapConventions, vars.swapConventions,
159 1 * Years, 30 * Years, ShiftedLognormal, vars.atmVols.shifts_1);
160
161 // Get back converted volatility structure and test result on pillar points
162 QuantLib::ext::shared_ptr<SwaptionVolatilityStructure> convertedsvs = converter.convert();
163 Volatility targetVol = 0.0;
164 Volatility outVol = 0.0;
165 Real dummyStrike = 0.0;
166 for (Size i = 0; i < vars.atmVols.optionTenors.size(); ++i) {
167 for (Size j = 0; j < vars.atmVols.swapTenors.size(); ++j) {
168 Period optionTenor = vars.atmVols.optionTenors[i];
169 Period swapTenor = vars.atmVols.swapTenors[j];
170 targetVol = vars.atmVols.slnVols_1[i][j];
171 outVol = convertedsvs->volatility(optionTenor, swapTenor, dummyStrike);
172 BOOST_CHECK_SMALL(outVol - targetVol, tolerance);
173 }
174 }
175}
176
177BOOST_AUTO_TEST_CASE(testShiftedLognormalToShiftedLognormal) {
178 BOOST_TEST_MESSAGE("Testing conversion of swaption vols from shifted lognormal to shifted lognormal...");
179
180 CommonVars vars;
181
182 // Tolerance used in boost check
183 Real tolerance = 0.00001;
184
185 // Set up the converter (Normal -> Shifted Lognormal with shift set 1)
186 SwaptionVolatilityConverter converter(vars.referenceDate, vars.atmShiftedLogNormalVolMatrix_1,
187 vars.yieldCurves.discountEonia, vars.yieldCurves.discountEonia,
188 vars.swapConventions, vars.swapConventions, 1 * Years, 30 * Years,
189 ShiftedLognormal, vars.atmVols.shifts_2);
190
191 // Get back converted volatility structure and test result on pillar points
192 QuantLib::ext::shared_ptr<SwaptionVolatilityStructure> convertedsvs = converter.convert();
193 Volatility targetVol = 0.0;
194 Volatility outVol = 0.0;
195 Real dummyStrike = 0.0;
196 for (Size i = 0; i < vars.atmVols.optionTenors.size(); ++i) {
197 for (Size j = 0; j < vars.atmVols.swapTenors.size(); ++j) {
198 Period optionTenor = vars.atmVols.optionTenors[i];
199 Period swapTenor = vars.atmVols.swapTenors[j];
200 targetVol = vars.atmVols.slnVols_2[i][j];
201 outVol = convertedsvs->volatility(optionTenor, swapTenor, dummyStrike);
202 BOOST_CHECK_SMALL(outVol - targetVol, tolerance);
203 }
204 }
205}
206
207BOOST_AUTO_TEST_CASE(testShiftedLognormalToNormal) {
208 BOOST_TEST_MESSAGE("Testing conversion of swaption vols from shifted lognormal to normal...");
209
210 CommonVars vars;
211
212 // Tolerance used in boost check
213 Real tolerance = 0.00001;
214
215 // Set up the converter (Shifted Lognormal with shift set 2 -> Normal)
216 SwaptionVolatilityConverter converter(vars.referenceDate, vars.atmShiftedLogNormalVolMatrix_2,
217 vars.yieldCurves.discountEonia, vars.yieldCurves.discountEonia,
218 vars.swapConventions, vars.swapConventions, 1 * Years, 30 * Years, Normal);
219
220 // Get back converted volatility structure and test result on pillar points
221 QuantLib::ext::shared_ptr<SwaptionVolatilityStructure> convertedsvs = converter.convert();
222 Volatility targetVol = 0.0;
223 Volatility outVol = 0.0;
224 Real dummyStrike = 0.0;
225 for (Size i = 0; i < vars.atmVols.optionTenors.size(); ++i) {
226 for (Size j = 0; j < vars.atmVols.swapTenors.size(); ++j) {
227 Period optionTenor = vars.atmVols.optionTenors[i];
228 Period swapTenor = vars.atmVols.swapTenors[j];
229 targetVol = vars.atmVols.nVols[i][j];
230 outVol = convertedsvs->volatility(optionTenor, swapTenor, dummyStrike);
231 BOOST_CHECK_SMALL(outVol - targetVol, tolerance);
232 }
233 }
234}
235
236BOOST_AUTO_TEST_CASE(testFailureImplyingVol) {
237 BOOST_TEST_MESSAGE("Testing failure to imply lognormal vol from normal vol...");
238
239 CommonVars vars;
240
241 // Normal volatility matrix where we cannot imply lognormal vol at 3M x 1Y point
242 vector<Period> optionTenors = list_of(Period(3, Months))(Period(1, Years));
243 vector<Period> swapTenors = list_of(Period(1, Years))(Period(5, Years));
244 Matrix normalVols(2, 2);
245 normalVols[0][0] = 0.003340;
246 normalVols[0][1] = 0.004973;
247 normalVols[1][0] = 0.003543;
248 normalVols[1][1] = 0.005270;
249
250 QuantLib::ext::shared_ptr<SwaptionVolatilityStructure> volMatrix = QuantLib::ext::make_shared<SwaptionVolatilityMatrix>(
251 vars.referenceDate, vars.conventions.fixedCalendar, vars.conventions.fixedConvention, optionTenors, swapTenors,
252 normalVols, Actual365Fixed(), true, Normal);
253
254 // Set up the converter (Normal -> Lognormal)
255 SwaptionVolatilityConverter converter(vars.referenceDate, volMatrix, vars.yieldCurves.discountEonia,
256 vars.yieldCurves.discountEonia, vars.swapConventions, vars.swapConventions,
257 1 * Years, 30 * Years, ShiftedLognormal);
258
259 // We expect the conversion to fail
260 BOOST_CHECK_THROW(converter.convert(), QuantLib::Error);
261}
262
263BOOST_AUTO_TEST_CASE(testNormalShiftsIgnored) {
264 BOOST_TEST_MESSAGE("Testing shifts supplied to normal converter ignored...");
265
266 CommonVars vars;
267
268 // Tolerance used in boost check
269 Real tolerance = 0.00001;
270
271 // Set up the converter (Lognormal with no shifts -> Normal)
272 // We supply target shifts but they are ignored since target type is Normal
274 vars.referenceDate, vars.atmLogNormalVolMatrix, vars.yieldCurves.discountEonia, vars.yieldCurves.discountEonia,
275 vars.swapConventions, vars.swapConventions, 1 * Years, 30 * Years, Normal, vars.atmVols.shifts_1);
276
277 // Get back converted volatility structure and test result on pillar points
278 QuantLib::ext::shared_ptr<SwaptionVolatilityStructure> convertedsvs = converter.convert();
279 Volatility targetVol = 0.0;
280 Volatility outVol = 0.0;
281 Real dummyStrike = 0.0;
282 for (Size i = 0; i < vars.atmVols.optionTenors.size(); ++i) {
283 for (Size j = 0; j < vars.atmVols.swapTenors.size(); ++j) {
284 Period optionTenor = vars.atmVols.optionTenors[i];
285 Period swapTenor = vars.atmVols.swapTenors[j];
286 targetVol = vars.atmVols.nVols[i][j];
287 outVol = convertedsvs->volatility(optionTenor, swapTenor, dummyStrike);
288 BOOST_CHECK_SMALL(outVol - targetVol, tolerance);
289 }
290 }
291}
292
293BOOST_AUTO_TEST_CASE(testConstructionFromSwapIndex) {
294 BOOST_TEST_MESSAGE("Testing construction of SwaptionVolatilityConverter from SwapIndex...");
295
296 CommonVars vars;
297
298 // Tolerance used in boost check
299 Real tolerance = 0.00001;
300
301 // Set up a SwapIndex
302 QuantLib::ext::shared_ptr<SwapIndex> swapIndex =
303 QuantLib::ext::make_shared<EuriborSwapIsdaFixA>(2 * Years, vars.yieldCurves.forward6M, vars.yieldCurves.discountEonia);
304
305 // Set up the converter using swap index (Shifted Lognormal with shift set 2 -> Normal)
306 SwaptionVolatilityConverter converter(vars.referenceDate, vars.atmShiftedLogNormalVolMatrix_2, swapIndex, swapIndex,
307 Normal);
308
309 // Test that the results are still ok
310 QuantLib::ext::shared_ptr<SwaptionVolatilityStructure> convertedsvs = converter.convert();
311 Volatility targetVol = 0.0;
312 Volatility outVol = 0.0;
313 Real dummyStrike = 0.0;
314 for (Size i = 0; i < vars.atmVols.optionTenors.size(); ++i) {
315 for (Size j = 0; j < vars.atmVols.swapTenors.size(); ++j) {
316 Period optionTenor = vars.atmVols.optionTenors[i];
317 Period swapTenor = vars.atmVols.swapTenors[j];
318 targetVol = vars.atmVols.nVols[i][j];
319 outVol = convertedsvs->volatility(optionTenor, swapTenor, dummyStrike);
320 BOOST_CHECK_SMALL(outVol - targetVol, tolerance);
321 }
322 }
323}
324
325BOOST_AUTO_TEST_CASE(testConstructionFromSwapIndexNoDiscount) {
326 BOOST_TEST_MESSAGE("Testing construction from SwapIndex with no exogenous discount curve...");
327
328 CommonVars vars;
329
330 // Set up a SwapIndex
331 QuantLib::ext::shared_ptr<SwapIndex> swapIndex =
332 QuantLib::ext::make_shared<EuriborSwapIsdaFixA>(2 * Years, vars.yieldCurves.forward6M);
333
334 // Set up the converter using swap index (Shifted Lognormal with shift set 2 -> Normal)
335 SwaptionVolatilityConverter converter(vars.referenceDate, vars.atmShiftedLogNormalVolMatrix_2, swapIndex, swapIndex,
336 Normal);
337
338 // Test that calling convert() still works
339 BOOST_CHECK_NO_THROW(converter.convert());
340}
341
343 BOOST_TEST_MESSAGE("Testing lognormal to normal conversion for cube...");
344
345 CommonVars vars;
346
347 // Set up Swap Indices
348 QuantLib::ext::shared_ptr<SwapIndex> shortSwapIndex =
349 QuantLib::ext::make_shared<EuriborSwapIsdaFixA>(1 * Years, vars.yieldCurves.forward3M, vars.yieldCurves.discountEonia);
350 QuantLib::ext::shared_ptr<SwapIndex> swapIndex =
351 QuantLib::ext::make_shared<EuriborSwapIsdaFixA>(30 * Years, vars.yieldCurves.forward6M, vars.yieldCurves.discountEonia);
352
353 // Set up a lognormal cube
354 QuantLib::ext::shared_ptr<SwaptionVolatilityCube> cube = QuantLib::ext::make_shared<QuantExt::SwaptionVolCube2>(
355 Handle<SwaptionVolatilityStructure>(vars.atmLogNormalVolMatrix), vars.atmVols.optionTenors,
356 vars.atmVols.swapTenors, vars.atmVols.strikeSpreads, vars.atmVols.lnVolSpreads, swapIndex, shortSwapIndex,
357 false, true);
358
359 // Convert the cube to normal
360 SwaptionVolatilityConverter converter(vars.referenceDate, cube, swapIndex, shortSwapIndex, Normal);
361 QuantLib::ext::shared_ptr<SwaptionVolatilityStructure> convertedsvs = converter.convert();
362
363 // Price swaptions in the lognormal and normal cube and compare their premiums
364 for (Size i = 0; i < vars.atmVols.optionTenors.size(); ++i) {
365 for (Size j = 0; j < vars.atmVols.swapTenors.size(); ++j) {
366 for (Size k = 0; k < vars.atmVols.strikeSpreads.size(); ++k) {
367 Period optionTenor = vars.atmVols.optionTenors[i];
368 Period swapTenor = vars.atmVols.swapTenors[j];
369 Real atmStrike = cube->atmStrike(optionTenor, swapTenor);
370 Real strikeSpread = vars.atmVols.strikeSpreads[k];
371 Real strike = atmStrike + strikeSpread;
372 if (strike > 0.0) {
373 Real inVol = cube->volatility(optionTenor, swapTenor, atmStrike + strikeSpread);
374 Real outVol = convertedsvs->volatility(optionTenor, swapTenor, atmStrike + strikeSpread);
375 Option::Type type = strikeSpread < 0.0 ? Option::Put : Option::Call;
376 Real tte = cube->optionTimes()[i];
377 Real inPrem = blackFormula(type, strike, atmStrike, inVol * std::sqrt(tte));
378 Real outPrem = bachelierBlackFormula(type, strike, atmStrike, outVol * std::sqrt(tte));
379 BOOST_CHECK_CLOSE(inPrem, outPrem, 0.01);
380 }
381 }
382 }
383 }
384}
385
386BOOST_AUTO_TEST_SUITE_END()
387
388BOOST_AUTO_TEST_SUITE_END()
Class that converts a supplied SwaptionVolatilityStructure to one of another type with possibly diffe...
QuantLib::ext::shared_ptr< SwaptionVolatilityStructure > convert() const
Method that returns the converted SwaptionVolatilityStructure
structs containing swaption market data that can be used in tests
Convert swaption volatilities from one type to another.
Swaption volatility cube, fit-later-interpolate-early approach.
BOOST_AUTO_TEST_CASE(testNormalToLognormal)
Fixture that can be used at top level.
structs containing yield curve market data that can be used in tests