Logo
Fully annotated reference manual - version 1.8.12
Loading...
Searching...
No Matches
discountingswapenginedeltagamma.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
19#include "toplevelfixture.hpp"
20#include <boost/test/unit_test.hpp>
21
23
24#include <ql/indexes/ibor/euribor.hpp>
25#include <ql/instruments/makevanillaswap.hpp>
26#include <ql/pricingengines/swap/discountingswapengine.hpp>
27#include <ql/quotes/simplequote.hpp>
28#include <ql/termstructures/yield/flatforward.hpp>
29#include <ql/termstructures/yield/piecewisezerospreadedtermstructure.hpp>
30
31#include <boost/make_shared.hpp>
32#include <boost/timer/timer.hpp>
33
34using namespace QuantLib;
35using namespace QuantExt;
36
37using namespace boost::unit_test_framework;
38using std::vector;
39
40namespace {
41struct TestData {
42 TestData() : refDate(Date(22, Aug, 2016)) {
43 Settings::instance().evaluationDate() = refDate;
44 baseDiscount = Handle<YieldTermStructure>(QuantLib::ext::make_shared<FlatForward>(
45 refDate, Handle<Quote>(QuantLib::ext::make_shared<SimpleQuote>(0.02)), Actual365Fixed()));
46 baseForward = Handle<YieldTermStructure>(QuantLib::ext::make_shared<FlatForward>(
47 refDate, Handle<Quote>(QuantLib::ext::make_shared<SimpleQuote>(0.03)), Actual365Fixed()));
48 pillarDates.push_back(refDate + 1 * Years);
49 pillarDates.push_back(refDate + 2 * Years);
50 pillarDates.push_back(refDate + 3 * Years);
51 pillarDates.push_back(refDate + 4 * Years);
52 pillarDates.push_back(refDate + 5 * Years);
53 pillarDates.push_back(refDate + 7 * Years);
54 pillarDates.push_back(refDate + 10 * Years);
55 std::vector<Handle<Quote>> tmpDiscSpr, tmpFwdSpr;
56 for (Size i = 0; i < pillarDates.size(); ++i) {
57 QuantLib::ext::shared_ptr<SimpleQuote> qd = QuantLib::ext::make_shared<SimpleQuote>(0.0);
58 QuantLib::ext::shared_ptr<SimpleQuote> qf = QuantLib::ext::make_shared<SimpleQuote>(0.0);
59 discountSpreads.push_back(qd);
60 forwardSpreads.push_back(qf);
61 tmpDiscSpr.push_back(Handle<Quote>(qd));
62 tmpFwdSpr.push_back(Handle<Quote>(qf));
63 pillarTimes.push_back(baseDiscount->timeFromReference(pillarDates[i]));
64 }
65 discountCurve =
66 Handle<YieldTermStructure>(QuantLib::ext::make_shared<InterpolatedPiecewiseZeroSpreadedTermStructure<Linear>>(
67 baseDiscount, tmpDiscSpr, pillarDates));
68 forwardCurve =
69 Handle<YieldTermStructure>(QuantLib::ext::make_shared<InterpolatedPiecewiseZeroSpreadedTermStructure<Linear>>(
70 baseForward, tmpFwdSpr, pillarDates));
71 discountCurve->enableExtrapolation();
72 forwardCurve->enableExtrapolation();
73 forwardIndex = QuantLib::ext::make_shared<Euribor>(6 * Months, forwardCurve);
74 }
75 Date refDate;
76 Handle<YieldTermStructure> baseDiscount, baseForward, discountCurve, forwardCurve;
77 QuantLib::ext::shared_ptr<IborIndex> forwardIndex;
78 std::vector<Date> pillarDates;
79 std::vector<QuantLib::ext::shared_ptr<SimpleQuote>> discountSpreads, forwardSpreads;
80 std::vector<Real> pillarTimes;
81}; // TestData
82
83bool check(const Real reference, const Real value) {
84 if (std::fabs(reference) >= 1E-4) {
85 return std::fabs((reference - value) / reference) < 1E-3;
86 } else {
87 return std::fabs(reference - value) < 5E-5;
88 }
89}
90
91class Timer {
92 boost::timer::cpu_timer timer_;
93 double elapsed_;
94
95public:
96 void start() { timer_ = boost::timer::cpu_timer(); }
97 void stop() { elapsed_ = timer_.elapsed().wall * 1e-9; }
98 double elapsed() const { return elapsed_; }
99};
100
101void performTest(const TestData& d, const QuantLib::ext::shared_ptr<PricingEngine>& engine0,
102 const QuantLib::ext::shared_ptr<PricingEngine>& engine, const bool receiveFixed, const Real spread,
103 const std::string& config) {
104 BOOST_TEST_MESSAGE("Testing npv and bps calculation in DiscountingSwapEngineDeltaGamma against QuantLib engine ("
105 << config << ")...");
106
107 VanillaSwap swap = MakeVanillaSwap(13 * Years, d.forwardIndex, 0.04, 0 * Days)
108 .receiveFixed(receiveFixed)
109 .withNominal(10.0)
110 .withFloatingLegSpread(spread);
111
112 swap.setPricingEngine(engine0);
113 Timer timer;
114 timer.start();
115 Real npvRef = swap.NPV();
116 timer.stop();
117 // BOOST_TEST_MESSAGE("ql engine took " << timer.elapsed() * 1E6 << " mus");
118 Real bps1Ref = swap.legBPS(0) * 10000.0;
119 Real bps2Ref = swap.legBPS(1) * 10000.0;
120
121 swap.setPricingEngine(engine);
122 timer.start();
123 Real npv = swap.NPV();
124 timer.stop();
125 // BOOST_TEST_MESSAGE("dg engine took " << timer.elapsed() * 1E6 << " mus");
126 Real bps1 = swap.legBPS(0);
127 Real bps2 = swap.legBPS(1);
128
129 const Real tol0 = 1E-10;
130 if (std::fabs(npv - npvRef) > tol0)
131 BOOST_ERROR("npv (" << npv << ") is inconsistent to expected value (" << npvRef << "), difference is "
132 << npv - npvRef << ", tolerance is " << tol0);
133 if (std::fabs(bps1 - bps1Ref) > tol0)
134 BOOST_ERROR("bps leg #1 (" << bps1 << ") is inconsistent to expected value (" << bps1Ref << "), difference is "
135 << bps1 - bps1Ref << ", tolerance is " << tol0);
136 if (std::fabs(bps2 - bps2Ref) > tol0)
137 BOOST_ERROR("bps leg #2 (" << bps2 << ") is inconsistent to expected value (" << bps2Ref << "), difference is "
138 << bps2 - bps2Ref << ", tolerance is " << tol0);
139
140 BOOST_TEST_MESSAGE("Testing delta calculation in DiscountingSwapEngineDeltaGamma against bump and revalue results ("
141 << config << ")...");
142
143 std::vector<Real> resultDeltaDsc = swap.result<std::vector<Real>>("deltaDiscount");
144 std::vector<Real> resultDeltaFwd = swap.result<std::vector<Real>>("deltaForward");
145 Matrix resultGamma = swap.result<Matrix>("gamma");
146 std::vector<std::vector<Real>> resultDeltaBPS = swap.result<std::vector<std::vector<Real>>>("deltaBPS");
147 std::vector<Matrix> resultGammaBPS = swap.result<std::vector<Matrix>>("gammaBPS");
148
149 // bump and revalue checks
150
151 // use QL engine to compute reference bump and revalue results
152 swap.setPricingEngine(engine0);
153
154 const Size n = d.pillarDates.size();
155
156 // check results for correct dimensions
157 if (resultDeltaDsc.size() != n)
158 BOOST_ERROR("deltaDiscount result vector has a wrong dimension (" << resultDeltaDsc.size() << "), expected "
159 << n);
160 if (resultDeltaFwd.size() != n)
161 BOOST_ERROR("deltaForward result vector has a wrong dimension (" << resultDeltaFwd.size() << "), expected "
162 << n);
163 for (Size l = 0; l < 2; ++l) {
164 if (resultDeltaBPS[l].size() != n)
165 BOOST_ERROR("deltaBPS result vector for leg #" << l << " has a wrong dimension (" << resultDeltaBPS.size()
166 << "), expected " << n);
167 if (resultGammaBPS[l].rows() != n || resultGammaBPS[l].columns() != n)
168 BOOST_ERROR("gamma result matrix has wrong dimensions ("
169 << resultGammaBPS[l].rows() << "x" << resultGammaBPS[l].columns() << "), expected " << 2 * n
170 << "x" << 2 * n);
171 }
172
173 if (resultGamma.rows() != 2 * n || resultGamma.columns() != 2 * n)
174 BOOST_ERROR("gamma result matrix has wrong dimensions (" << resultGamma.rows() << "x" << resultGamma.columns()
175 << "), expected " << 2 * n << "x" << 2 * n);
176
177 // delta (npv)
178
179 const Real bump = 1E-7;
180 Real npv0 = swap.NPV();
181 std::vector<Real> legBPS0;
182 legBPS0.push_back(swap.legBPS(0) * 1E4);
183 legBPS0.push_back(swap.legBPS(1) * 1E4);
184
185 for (Size i = 0; i < n; ++i) {
186 d.discountSpreads[i]->setValue(bump);
187 Real deltaDsc = (swap.NPV() - npv0) / bump;
188 d.discountSpreads[i]->setValue(0.0);
189 d.forwardSpreads[i]->setValue(bump);
190 Real deltaFwd = (swap.NPV() - npv0) / bump;
191 d.forwardSpreads[i]->setValue(0.0);
192 if (!check(deltaDsc, resultDeltaDsc[i]))
193 BOOST_ERROR("delta on pillar " << d.pillarTimes[i]
194 << " (discount curve) could not be verified, analytical: "
195 << resultDeltaDsc[i] << ", bump and revalue: " << deltaDsc);
196 if (!check(deltaFwd, resultDeltaFwd[i]))
197 BOOST_ERROR("delta on pillar " << d.pillarTimes[i] << " (forward curve) could not be verified, analytical: "
198 << resultDeltaFwd[i] << ", bump and revalue: " << deltaFwd);
199 }
200
201 // delta (bps, per leg)
202
203 for (Size l = 0; l < 2; ++l) {
204 for (Size i = 0; i < n; ++i) {
205 d.discountSpreads[i]->setValue(bump);
206 Real deltaDsc = (swap.legBPS(l) * 1E4 - legBPS0[l]) / bump;
207 d.discountSpreads[i]->setValue(0.0);
208 if (!check(deltaDsc / 1E4, resultDeltaBPS[l][i] / 1E4))
209 BOOST_ERROR("bps-delta for leg #" << (l + 1) << " on pillar " << d.pillarTimes[i]
210 << " (discount curve) could not be verified, analytical: "
211 << resultDeltaBPS[l][i] << ", bump and revalue: " << deltaDsc);
212 }
213 }
214
215 // gamma (npv)
216
217 const Real bump2 = 1E-5;
218 Matrix bumpGamma(n * 2, n * 2, 0.0);
219
220 // dsc-dsc
221 for (Size i = 0; i < n; ++i) {
222 // j < i
223 for (Size j = 0; j < i; ++j) {
224 d.discountSpreads[i]->setValue(bump2);
225 d.discountSpreads[j]->setValue(bump2);
226 Real npvpp = swap.NPV();
227 d.discountSpreads[j]->setValue(0.0);
228 Real npvp0 = swap.NPV();
229 d.discountSpreads[i]->setValue(0.0);
230 d.discountSpreads[j]->setValue(bump2);
231 Real npv0p = swap.NPV();
232 d.discountSpreads[j]->setValue(0.0);
233 Real gamma = (npvpp - npvp0 - npv0p + npv0) / (bump2 * bump2);
234 bumpGamma[i][j] = bumpGamma[j][i] = gamma;
235 }
236 // j == i
237 d.discountSpreads[i]->setValue(2.0 * bump2);
238 Real npvpp = swap.NPV();
239 d.discountSpreads[i]->setValue(bump2);
240 Real npvp = swap.NPV();
241 d.discountSpreads[i]->setValue(0.0);
242 Real gamma = (npvpp - 2.0 * npvp + npv0) / (bump2 * bump2);
243 bumpGamma[i][i] = gamma;
244 }
245
246 // dsc-fwd
247 for (Size i = 0; i < n; ++i) {
248 // j <= i
249 for (Size j = 0; j < n; ++j) {
250 d.discountSpreads[i]->setValue(bump2);
251 d.forwardSpreads[j]->setValue(bump2);
252 Real npvpp = swap.NPV();
253 d.forwardSpreads[j]->setValue(0.0);
254 Real npvp0 = swap.NPV();
255 d.discountSpreads[i]->setValue(0.0);
256 d.forwardSpreads[j]->setValue(bump2);
257 Real npv0p = swap.NPV();
258 d.forwardSpreads[j]->setValue(0.0);
259 Real gamma = (npvpp - npvp0 - npv0p + npv0) / (bump2 * bump2);
260 bumpGamma[i][n + j] = bumpGamma[n + j][i] = gamma;
261 }
262 }
263
264 // fwd-fwd
265 for (Size i = 0; i < n; ++i) {
266 // j < i
267 for (Size j = 0; j < i; ++j) {
268 d.forwardSpreads[i]->setValue(bump2);
269 d.forwardSpreads[j]->setValue(bump2);
270 Real npvpp = swap.NPV();
271 d.forwardSpreads[j]->setValue(0.0);
272 Real npvp0 = swap.NPV();
273 d.forwardSpreads[i]->setValue(0.0);
274 d.forwardSpreads[j]->setValue(bump2);
275 Real npv0p = swap.NPV();
276 d.forwardSpreads[j]->setValue(0.0);
277 Real gamma = (npvpp - npvp0 - npv0p + npv0) / (bump2 * bump2);
278 bumpGamma[n + i][n + j] = bumpGamma[n + j][n + i] = gamma;
279 }
280 // j == i
281 d.forwardSpreads[i]->setValue(2.0 * bump2);
282 Real npvpp = swap.NPV();
283 d.forwardSpreads[i]->setValue(bump2);
284 Real npvp = swap.NPV();
285 d.forwardSpreads[i]->setValue(0.0);
286 Real gamma = (npvpp - 2.0 * npvp + npv0) / (bump2 * bump2);
287 bumpGamma[n + i][n + i] = gamma;
288 }
289
290 for (Size i = 0; i < 2 * n; ++i) {
291 for (Size j = 0; j < 2 * n; ++j) {
292 if (!check(bumpGamma[i][j], resultGamma[i][j]))
293 BOOST_ERROR("gamma entry (" << i << "," << j << ") is " << resultGamma[i][j]
294 << ", bump and revalue result is " << bumpGamma[i][j]);
295 }
296 }
297
298 // gamma (bps, per leg)
299
300 for (Size l = 0; l < 2; ++l) {
301 Matrix bumpGammaBPS(n, n, 0.0);
302 for (Size i = 0; i < n; ++i) {
303 // j < i
304 for (Size j = 0; j < i; ++j) {
305 d.discountSpreads[i]->setValue(bump2);
306 d.discountSpreads[j]->setValue(bump2);
307 Real bpspp = swap.legBPS(l) * 1E4;
308 d.discountSpreads[j]->setValue(0.0);
309 Real bpsp0 = swap.legBPS(l) * 1E4;
310 d.discountSpreads[i]->setValue(0.0);
311 d.discountSpreads[j]->setValue(bump2);
312 Real bps0p = swap.legBPS(l) * 1E4;
313 d.discountSpreads[j]->setValue(0.0);
314 Real gamma = (bpspp - bpsp0 - bps0p + legBPS0[l]) / (bump2 * bump2);
315 bumpGammaBPS[i][j] = bumpGammaBPS[j][i] = gamma;
316 }
317 // j == i
318 d.discountSpreads[i]->setValue(2.0 * bump2);
319 Real bpspp = swap.legBPS(l) * 1E4;
320 d.discountSpreads[i]->setValue(bump2);
321 Real bpsp = swap.legBPS(l) * 1E4;
322 d.discountSpreads[i]->setValue(0.0);
323 Real gamma = (bpspp - 2.0 * bpsp + legBPS0[l]) / (bump2 * bump2);
324 bumpGammaBPS[i][i] = gamma;
325 }
326 for (Size i = 0; i < n; ++i) {
327 for (Size j = 0; j < n; ++j) {
328 if (!check(bumpGammaBPS[i][j] / 1E4, resultGammaBPS[l][i][j] / 1E4))
329 BOOST_ERROR("bps-gamma for leg #"
330 << (l + 1) << " at (" << i << "," << j << ")"
331 << " could not be verified, analytical: " << resultGammaBPS[l][i][j]
332 << ", bump and revalue: " << bumpGammaBPS[i][j]);
333 }
334 }
335 }
336
337 BOOST_TEST_MESSAGE(
338 "Testing sum of deltas and gammas in DiscountingSwapEngineDeltaGamma against parallel bump of all yields ("
339 << config << ")...");
340
341 // totals (parallel shift over all curves)
342 // this tests, if we have identified all non-zero first and second order partial derivatives
343
344 for (Size i = 0; i < n; ++i) {
345 d.discountSpreads[i]->setValue(bump);
346 d.forwardSpreads[i]->setValue(bump);
347 }
348
349 Real totalDeltaBump = (swap.NPV() - npv0) / bump;
350
351 for (Size i = 0; i < n; ++i) {
352 d.discountSpreads[i]->setValue(2.0 * bump2);
353 d.forwardSpreads[i]->setValue(2.0 * bump2);
354 }
355 Real npvPP = swap.NPV();
356 for (Size i = 0; i < n; ++i) {
357 d.discountSpreads[i]->setValue(bump2);
358 d.forwardSpreads[i]->setValue(bump2);
359 }
360 Real npvP = swap.NPV();
361 for (Size i = 0; i < n; ++i) {
362 d.discountSpreads[i]->setValue(0.0);
363 d.forwardSpreads[i]->setValue(0.0);
364 }
365
366 Real totalGammaBump = (npvPP - 2.0 * npvP + npv0) / (bump2 * bump2);
367
368 Real totalDelta = 0.0, totalGamma = 0.0;
369 for (Size i = 0; i < n; ++i) {
370 totalDelta += resultDeltaDsc[i];
371 totalDelta += resultDeltaFwd[i];
372 }
373
374 for (Size i = 0; i < 2 * n; ++i) {
375 for (Size j = 0; j < 2 * n; ++j) {
376 totalGamma += resultGamma[i][j];
377 }
378 }
379
380 if (!check(totalDeltaBump, totalDelta))
381 BOOST_ERROR("total delta (" << totalDelta << ") can not be verified against bump and revalue result ("
382 << totalDeltaBump << ")");
383
384 if (!check(totalGammaBump, totalGamma))
385 BOOST_ERROR("total gamma (" << totalGamma << ") can not be verified against bump and revalue result ("
386 << totalGammaBump << ")");
387
388} // performTest
389
390} // anonymous namespace
391
392BOOST_FIXTURE_TEST_SUITE(QuantExtTestSuite, qle::test::TopLevelFixture)
393
394BOOST_AUTO_TEST_SUITE(DiscountingSwapEngineDeltaGammaTest)
395
396BOOST_AUTO_TEST_CASE(testNpvDeltasGammas) {
397
398 TestData d;
399
400 QuantLib::ext::shared_ptr<PricingEngine> engine0 = QuantLib::ext::make_shared<DiscountingSwapEngine>(d.discountCurve);
401 QuantLib::ext::shared_ptr<PricingEngine> engine =
402 QuantLib::ext::make_shared<DiscountingSwapEngineDeltaGamma>(d.discountCurve, d.pillarTimes, true, true, true);
403
404 performTest(d, engine0, engine, false, 0.0, "payer, zero spread");
405 performTest(d, engine0, engine, true, 0.0, "receiver, zero spread");
406 performTest(d, engine0, engine, false, 0.01, "payer, positive spread");
407 performTest(d, engine0, engine, true, 0.01, "receiver, positive spread");
408 performTest(d, engine0, engine, false, -0.01, "payer, negative spread");
409 performTest(d, engine0, engine, true, -0.01, "receiver, negative spread");
410
411 BOOST_CHECK(true);
412}
413
414BOOST_AUTO_TEST_SUITE_END()
415
416BOOST_AUTO_TEST_SUITE_END()
Swap engine providing analytical deltas and gammas for vanilla swaps.
BOOST_AUTO_TEST_CASE(testNpvDeltasGammas)
Fixture that can be used at top level.