1 | /* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ |
2 | |
3 | /* |
4 | Copyright (C) 2006, 2008 Ferdinando Ametrano |
5 | Copyright (C) 2006 François du Vignaud |
6 | Copyright (C) 2007 Cristina Duminuco |
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 "swaptionvolatilitymatrix.hpp" |
23 | #include "swaptionvolstructuresutilities.hpp" |
24 | #include "utilities.hpp" |
25 | #include <ql/utilities/dataformatters.hpp> |
26 | #include <ql/indexes/swap/euriborswap.hpp> |
27 | #include <ql/instruments/makeswaption.hpp> |
28 | #include <ql/pricingengines/swaption/blackswaptionengine.hpp> |
29 | #include <ql/termstructures/yield/flatforward.hpp> |
30 | #include <ql/math/comparison.hpp> |
31 | #include <string> |
32 | |
33 | using namespace QuantLib; |
34 | using namespace boost::unit_test_framework; |
35 | |
36 | namespace swaption_volatility_matrix_test { |
37 | |
38 | struct CommonVars { |
39 | // global data |
40 | Date referenceDate; |
41 | SwaptionMarketConventions conventions; |
42 | AtmVolatility atm; |
43 | RelinkableHandle<YieldTermStructure> termStructure; |
44 | RelinkableHandle<SwaptionVolatilityStructure> atmVolMatrix; |
45 | Real tolerance; |
46 | |
47 | // setup |
48 | CommonVars() { |
49 | conventions.setConventions(); |
50 | atm.setMarketData(); |
51 | Settings::instance().evaluationDate() = |
52 | conventions.calendar.adjust(Date::todaysDate()); |
53 | atmVolMatrix = RelinkableHandle<SwaptionVolatilityStructure>( |
54 | ext::shared_ptr<SwaptionVolatilityStructure>(new |
55 | SwaptionVolatilityMatrix(conventions.calendar, |
56 | conventions.optionBdc, |
57 | atm.tenors.options, |
58 | atm.tenors.swaps, |
59 | atm.volsHandle, |
60 | conventions.dayCounter))); |
61 | termStructure.linkTo( |
62 | h: ext::shared_ptr<YieldTermStructure>(new |
63 | FlatForward(0, conventions.calendar, |
64 | 0.05, Actual365Fixed()))); |
65 | } |
66 | |
67 | // utilities |
68 | void makeObservabilityTest( |
69 | const std::string& description, |
70 | const ext::shared_ptr<SwaptionVolatilityStructure>& vol, |
71 | bool mktDataFloating, |
72 | bool referenceDateFloating) { |
73 | Rate dummyStrike = .02; |
74 | Date referenceDate = Settings::instance().evaluationDate(); |
75 | Volatility initialVol = vol->volatility( |
76 | optionDate: referenceDate + atm.tenors.options[0], |
77 | swapTenor: atm.tenors.swaps[0], strike: dummyStrike, extrapolate: false); |
78 | // testing evaluation date change ... |
79 | Settings::instance().evaluationDate() = |
80 | referenceDate - Period(1, Years); |
81 | Volatility newVol = vol->volatility( |
82 | optionDate: referenceDate + atm.tenors.options[0], |
83 | swapTenor: atm.tenors.swaps[0], strike: dummyStrike, extrapolate: false); |
84 | Settings::instance().evaluationDate() = referenceDate; |
85 | if (referenceDateFloating && (initialVol == newVol)) |
86 | BOOST_ERROR(description << |
87 | " the volatility should change when the reference date is changed !" ); |
88 | if (!referenceDateFloating && (initialVol != newVol)) |
89 | BOOST_ERROR(description << |
90 | " the volatility should not change when the reference date is changed !" ); |
91 | |
92 | // test market data change... |
93 | if (mktDataFloating){ |
94 | Volatility initialVolatility = atm.volsHandle[0][0]->value(); |
95 | ext::dynamic_pointer_cast<SimpleQuote>( |
96 | r: atm.volsHandle[0][0].currentLink())->setValue(10); |
97 | newVol = vol->volatility( |
98 | optionDate: referenceDate + atm.tenors.options[0], |
99 | swapTenor: atm.tenors.swaps[0], strike: dummyStrike, extrapolate: false); |
100 | ext::dynamic_pointer_cast<SimpleQuote>( |
101 | r: atm.volsHandle[0][0].currentLink()) |
102 | ->setValue(initialVolatility); |
103 | if (initialVol == newVol) |
104 | BOOST_ERROR(description << " the volatility should change when" |
105 | " the market data is changed !" ); |
106 | } |
107 | } |
108 | |
109 | void makeCoherenceTest( |
110 | const std::string& description, |
111 | const ext::shared_ptr<SwaptionVolatilityDiscrete>& vol) { |
112 | |
113 | for (Size i=0; i<atm.tenors.options.size(); ++i) { |
114 | Date optionDate = |
115 | vol->optionDateFromTenor(p: atm.tenors.options[i]); |
116 | if (optionDate!=vol->optionDates()[i]) |
117 | BOOST_FAIL( |
118 | "optionDateFromTenor failure for " << |
119 | description << ":" |
120 | "\n option tenor: " << atm.tenors.options[i] << |
121 | "\nactual option date : " << optionDate << |
122 | "\n exp. option date : " << vol->optionDates()[i]); |
123 | Time optionTime = vol->timeFromReference(d: optionDate); |
124 | if (!close(x: optionTime,y: vol->optionTimes()[i])) |
125 | BOOST_FAIL( |
126 | "timeFromReference failure for " << |
127 | description << ":" |
128 | "\n option tenor: " << atm.tenors.options[i] << |
129 | "\n option date : " << optionDate << |
130 | "\nactual option time : " << optionTime << |
131 | "\n exp. option time : " << vol->optionTimes()[i]); |
132 | } |
133 | |
134 | ext::shared_ptr<BlackSwaptionEngine> engine(new |
135 | BlackSwaptionEngine(termStructure, |
136 | Handle<SwaptionVolatilityStructure>(vol))); |
137 | |
138 | for (Size j=0; j<atm.tenors.swaps.size(); j++) { |
139 | Time swapLength = vol->swapLength(swapTenor: atm.tenors.swaps[j]); |
140 | if (!close(x: swapLength,y: years(atm.tenors.swaps[j]))) |
141 | BOOST_FAIL("convertSwapTenor failure for " << |
142 | description << ":" |
143 | "\n swap tenor : " << atm.tenors.swaps[j] << |
144 | "\n actual swap length: " << swapLength << |
145 | "\n exp. swap length: " << years(atm.tenors.swaps[j])); |
146 | |
147 | ext::shared_ptr<SwapIndex> swapIndex(new |
148 | EuriborSwapIsdaFixA(atm.tenors.swaps[j], termStructure)); |
149 | |
150 | for (Size i=0; i<atm.tenors.options.size(); ++i) { |
151 | Real error, tolerance = 1.0e-16; |
152 | Volatility actVol, expVol = atm.vols[i][j]; |
153 | |
154 | actVol = vol->volatility(optionTenor: atm.tenors.options[i], |
155 | swapTenor: atm.tenors.swaps[j], strike: 0.05, extrapolate: true); |
156 | error = std::abs(x: expVol-actVol); |
157 | if (error>tolerance) |
158 | BOOST_FAIL( |
159 | "recovery of atm vols failed for " << |
160 | description << ":" |
161 | "\noption tenor = " << atm.tenors.options[i] << |
162 | "\n swap length = " << atm.tenors.swaps[j] << |
163 | "\nexpected vol = " << io::volatility(expVol) << |
164 | "\n actual vol = " << io::volatility(actVol) << |
165 | "\n error = " << io::volatility(error) << |
166 | "\n tolerance = " << tolerance); |
167 | |
168 | Date optionDate = |
169 | vol->optionDateFromTenor(p: atm.tenors.options[i]); |
170 | actVol = vol->volatility(optionDate, |
171 | swapTenor: atm.tenors.swaps[j], strike: 0.05, extrapolate: true); |
172 | error = std::abs(x: expVol-actVol); |
173 | if (error>tolerance) |
174 | BOOST_FAIL( |
175 | "recovery of atm vols failed for " << |
176 | description << ":" |
177 | "\noption tenor: " << atm.tenors.options[i] << |
178 | "\noption date : " << optionDate << |
179 | "\n swap tenor: " << atm.tenors.swaps[j] << |
180 | "\n exp. vol: " << io::volatility(expVol) << |
181 | "\n actual vol: " << io::volatility(actVol) << |
182 | "\n error: " << io::volatility(error) << |
183 | "\n tolerance: " << tolerance); |
184 | |
185 | Time optionTime = vol->timeFromReference(d: optionDate); |
186 | actVol = vol->volatility(optionTime, swapLength, |
187 | strike: 0.05, extrapolate: true); |
188 | error = std::abs(x: expVol-actVol); |
189 | if (error>tolerance) |
190 | BOOST_FAIL( |
191 | "recovery of atm vols failed for " << |
192 | description << ":" |
193 | "\noption tenor: " << atm.tenors.options[i] << |
194 | "\noption time : " << optionTime << |
195 | "\n swap tenor: " << atm.tenors.swaps[j] << |
196 | "\n swap length: " << swapLength << |
197 | "\n exp. vol: " << io::volatility(expVol) << |
198 | "\n actual vol: " << io::volatility(actVol) << |
199 | "\n error: " << io::volatility(error) << |
200 | "\n tolerance: " << tolerance); |
201 | |
202 | // ATM swaption |
203 | Swaption swaption = |
204 | MakeSwaption(swapIndex, atm.tenors.options[i]) |
205 | .withPricingEngine(engine); |
206 | |
207 | Date exerciseDate = swaption.exercise()->dates().front(); |
208 | if (exerciseDate!=vol->optionDates()[i]) |
209 | BOOST_FAIL( |
210 | "\noptionDateFromTenor mismatch for " << |
211 | description << ":" |
212 | "\n option tenor: " << atm.tenors.options[i] << |
213 | "\nactual option date: " << exerciseDate << |
214 | "\n exp. option date: " << vol->optionDates()[i]); |
215 | |
216 | Date start = swaption.underlyingSwap()->startDate(); |
217 | Date end = swaption.underlyingSwap()->maturityDate(); |
218 | Time swapLength2 = vol->swapLength(start, end); |
219 | if (!close(x: swapLength2,y: swapLength)) |
220 | BOOST_FAIL("\nswapLength failure for " << |
221 | description << ":" |
222 | "\n exp. swap length: " << swapLength << |
223 | "\n actual swap length: " << swapLength2 << |
224 | "\n swap tenor : " << atm.tenors.swaps[j] << |
225 | "\n swap index tenor : " << swapIndex->tenor() << |
226 | "\n option date: " << exerciseDate << |
227 | "\n start date: " << start << |
228 | "\n maturity date: " << end |
229 | ); |
230 | |
231 | Real npv = swaption.NPV(); |
232 | actVol = swaption.impliedVolatility(price: npv, discountCurve: termStructure, |
233 | guess: expVol*0.98, accuracy: 1e-6, |
234 | maxEvaluations: 100, minVol: 10.0e-7, maxVol: 4.0, |
235 | type: ShiftedLognormal, displacement: 0.0); |
236 | error = std::abs(x: expVol-actVol); |
237 | Real tolerance2 = 0.000001; |
238 | if (error>tolerance2) |
239 | BOOST_FAIL( |
240 | "recovery of atm vols through BlackSwaptionEngine failed for " << |
241 | description << ":" |
242 | "\noption tenor: " << atm.tenors.options[i] << |
243 | "\noption time : " << optionTime << |
244 | "\n swap tenor: " << atm.tenors.swaps[j] << |
245 | "\n swap length: " << swapLength << |
246 | "\n exp. vol: " << io::volatility(expVol) << |
247 | "\n actual vol: " << io::volatility(actVol) << |
248 | "\n error: " << io::volatility(error) << |
249 | "\n tolerance: " << tolerance2); |
250 | } |
251 | } |
252 | } |
253 | }; |
254 | |
255 | } |
256 | |
257 | |
258 | void SwaptionVolatilityMatrixTest::testSwaptionVolMatrixObservability() { |
259 | |
260 | BOOST_TEST_MESSAGE("Testing swaption volatility matrix observability..." ); |
261 | |
262 | using namespace swaption_volatility_matrix_test; |
263 | |
264 | CommonVars vars; |
265 | |
266 | ext::shared_ptr<SwaptionVolatilityMatrix> vol; |
267 | std::string description; |
268 | |
269 | //floating reference date, floating market data |
270 | description = "floating reference date, floating market data" ; |
271 | vol = ext::make_shared<SwaptionVolatilityMatrix>(args&: vars.conventions.calendar, |
272 | args&: vars.conventions.optionBdc, |
273 | args&: vars.atm.tenors.options, |
274 | args&: vars.atm.tenors.swaps, |
275 | args&: vars.atm.volsHandle, |
276 | args&: vars.conventions.dayCounter); |
277 | vars.makeObservabilityTest(description, vol, mktDataFloating: true, referenceDateFloating: true); |
278 | |
279 | //fixed reference date, floating market data |
280 | description = "fixed reference date, floating market data" ; |
281 | vol = ext::make_shared<SwaptionVolatilityMatrix>(args&: Settings::instance().evaluationDate(), |
282 | args&: vars.conventions.calendar, |
283 | args&: vars.conventions.optionBdc, |
284 | args&: vars.atm.tenors.options, |
285 | args&: vars.atm.tenors.swaps, |
286 | args&: vars.atm.volsHandle, |
287 | args&: vars.conventions.dayCounter); |
288 | vars.makeObservabilityTest(description, vol, mktDataFloating: true, referenceDateFloating: false); |
289 | |
290 | // floating reference date, fixed market data |
291 | description = "floating reference date, fixed market data" ; |
292 | vol = ext::make_shared<SwaptionVolatilityMatrix>(args&: vars.conventions.calendar, |
293 | args&: vars.conventions.optionBdc, |
294 | args&: vars.atm.tenors.options, |
295 | args&: vars.atm.tenors.swaps, |
296 | args&: vars.atm.volsHandle, |
297 | args&: vars.conventions.dayCounter); |
298 | vars.makeObservabilityTest(description, vol, mktDataFloating: false, referenceDateFloating: true); |
299 | |
300 | // fixed reference date, fixed market data |
301 | description = "fixed reference date, fixed market data" ; |
302 | vol = ext::make_shared<SwaptionVolatilityMatrix>(args&: Settings::instance().evaluationDate(), |
303 | args&: vars.conventions.calendar, |
304 | args&: vars.conventions.optionBdc, |
305 | args&: vars.atm.tenors.options, |
306 | args&: vars.atm.tenors.swaps, |
307 | args&: vars.atm.volsHandle, |
308 | args&: vars.conventions.dayCounter); |
309 | vars.makeObservabilityTest(description, vol, mktDataFloating: false, referenceDateFloating: false); |
310 | |
311 | // fixed reference date and fixed market data, option dates |
312 | //SwaptionVolatilityMatrix(const Date& referenceDate, |
313 | // const std::vector<Date>& exerciseDates, |
314 | // const std::vector<Period>& swapTenors, |
315 | // const Matrix& volatilities, |
316 | // const DayCounter& dayCounter); |
317 | } |
318 | |
319 | |
320 | void SwaptionVolatilityMatrixTest::testSwaptionVolMatrixCoherence() { |
321 | |
322 | BOOST_TEST_MESSAGE("Testing swaption volatility matrix..." ); |
323 | |
324 | using namespace swaption_volatility_matrix_test; |
325 | |
326 | CommonVars vars; |
327 | |
328 | ext::shared_ptr<SwaptionVolatilityMatrix> vol; |
329 | std::string description; |
330 | |
331 | //floating reference date, floating market data |
332 | description = "floating reference date, floating market data" ; |
333 | vol = ext::make_shared<SwaptionVolatilityMatrix>(args&: vars.conventions.calendar, |
334 | args&: vars.conventions.optionBdc, |
335 | args&: vars.atm.tenors.options, |
336 | args&: vars.atm.tenors.swaps, |
337 | args&: vars.atm.volsHandle, |
338 | args&: vars.conventions.dayCounter); |
339 | vars.makeCoherenceTest(description, vol); |
340 | |
341 | //fixed reference date, floating market data |
342 | description = "fixed reference date, floating market data" ; |
343 | vol = ext::make_shared<SwaptionVolatilityMatrix>(args&: Settings::instance().evaluationDate(), |
344 | args&: vars.conventions.calendar, |
345 | args&: vars.conventions.optionBdc, |
346 | args&: vars.atm.tenors.options, |
347 | args&: vars.atm.tenors.swaps, |
348 | args&: vars.atm.volsHandle, |
349 | args&: vars.conventions.dayCounter); |
350 | vars.makeCoherenceTest(description, vol); |
351 | |
352 | // floating reference date, fixed market data |
353 | description = "floating reference date, fixed market data" ; |
354 | vol = ext::make_shared<SwaptionVolatilityMatrix>(args&: vars.conventions.calendar, |
355 | args&: vars.conventions.optionBdc, |
356 | args&: vars.atm.tenors.options, |
357 | args&: vars.atm.tenors.swaps, |
358 | args&: vars.atm.volsHandle, |
359 | args&: vars.conventions.dayCounter); |
360 | vars.makeCoherenceTest(description, vol); |
361 | |
362 | // fixed reference date, fixed market data |
363 | description = "fixed reference date, fixed market data" ; |
364 | vol = ext::make_shared<SwaptionVolatilityMatrix>(args&: Settings::instance().evaluationDate(), |
365 | args&: vars.conventions.calendar, |
366 | args&: vars.conventions.optionBdc, |
367 | args&: vars.atm.tenors.options, |
368 | args&: vars.atm.tenors.swaps, |
369 | args&: vars.atm.volsHandle, |
370 | args&: vars.conventions.dayCounter); |
371 | vars.makeCoherenceTest(description, vol); |
372 | } |
373 | |
374 | test_suite* SwaptionVolatilityMatrixTest::suite() { |
375 | auto* suite = BOOST_TEST_SUITE("Swaption Volatility Matrix tests" ); |
376 | |
377 | suite->add(QUANTLIB_TEST_CASE( |
378 | &SwaptionVolatilityMatrixTest::testSwaptionVolMatrixCoherence)); |
379 | |
380 | suite->add(QUANTLIB_TEST_CASE( |
381 | &SwaptionVolatilityMatrixTest::testSwaptionVolMatrixObservability)); |
382 | |
383 | return suite; |
384 | } |
385 | |