Logo
Fully annotated reference manual - version 1.8.12
Loading...
Searching...
No Matches
analyticeuropeanenginedeltagamma.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/exercise.hpp>
25#include <ql/pricingengines/vanilla/analyticeuropeanengine.hpp>
26#include <ql/quotes/simplequote.hpp>
27#include <ql/termstructures/volatility/equityfx/blackconstantvol.hpp>
28#include <ql/termstructures/yield/flatforward.hpp>
29#include <ql/termstructures/yield/piecewisezerospreadedtermstructure.hpp>
30#include <ql/time/calendars/nullcalendar.hpp>
31
32#include <boost/make_shared.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 rateDiscount = Handle<YieldTermStructure>(QuantLib::ext::make_shared<FlatForward>(
45 0, NullCalendar(), Handle<Quote>(QuantLib::ext::make_shared<SimpleQuote>(0.02)), Actual365Fixed()));
46 divDiscount = Handle<YieldTermStructure>(QuantLib::ext::make_shared<FlatForward>(
47 0, NullCalendar(), 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 pillarDates.push_back(refDate + 15 * Years);
56 pillarDates.push_back(refDate + 20 * Years);
57 std::vector<Handle<Quote>> tmpRateSpr, tmpDivSpr;
58 for (Size i = 0; i < pillarDates.size(); ++i) {
59 QuantLib::ext::shared_ptr<SimpleQuote> qr = QuantLib::ext::make_shared<SimpleQuote>(0.0);
60 QuantLib::ext::shared_ptr<SimpleQuote> qd = QuantLib::ext::make_shared<SimpleQuote>(0.0);
61 rateSpreads.push_back(qr);
62 divSpreads.push_back(qd);
63 tmpRateSpr.push_back(Handle<Quote>(qr));
64 tmpDivSpr.push_back(Handle<Quote>(qd));
65 pillarTimes.push_back(rateDiscount->timeFromReference(pillarDates[i]));
66 }
67 rateCurve =
68 Handle<YieldTermStructure>(QuantLib::ext::make_shared<InterpolatedPiecewiseZeroSpreadedTermStructure<Linear>>(
69 rateDiscount, tmpRateSpr, pillarDates));
70 divCurve =
71 Handle<YieldTermStructure>(QuantLib::ext::make_shared<InterpolatedPiecewiseZeroSpreadedTermStructure<Linear>>(
72 divDiscount, tmpDivSpr, pillarDates));
73 rateCurve->enableExtrapolation();
74 divCurve->enableExtrapolation();
75 vol = QuantLib::ext::make_shared<SimpleQuote>(0.20);
76 volTs = Handle<BlackVolTermStructure>(
77 QuantLib::ext::make_shared<BlackConstantVol>(0, NullCalendar(), Handle<Quote>(vol), Actual365Fixed()));
78 spot = QuantLib::ext::make_shared<SimpleQuote>(100.0);
79 process = QuantLib::ext::make_shared<GeneralizedBlackScholesProcess>(Handle<Quote>(spot), divCurve, rateCurve, volTs);
80 }
81 Date refDate;
82 Handle<YieldTermStructure> rateDiscount, divDiscount, rateCurve, divCurve;
83 std::vector<Date> pillarDates;
84 std::vector<QuantLib::ext::shared_ptr<SimpleQuote>> rateSpreads, divSpreads;
85 std::vector<Real> pillarTimes;
86 QuantLib::ext::shared_ptr<SimpleQuote> vol, spot;
87 Handle<BlackVolTermStructure> volTs;
88 QuantLib::ext::shared_ptr<GeneralizedBlackScholesProcess> process;
89}; // TestData
90
91bool check(const Real reference, const Real value) {
92 if (std::fabs(reference) >= 1E-4) {
93 return std::fabs((reference - value) / reference) < 1E-3;
94 } else {
95 return std::fabs(reference - value) < 1E-5;
96 }
97}
98
99} // anonymous namespace
100
101BOOST_FIXTURE_TEST_SUITE(QuantExtTestSuite, qle::test::TopLevelFixture)
102
103BOOST_AUTO_TEST_SUITE(AnalyticEuropeanEngineDeltaGammaTest)
104
105BOOST_AUTO_TEST_CASE(testNpvDeltasGammaVegas) {
106
107 BOOST_TEST_MESSAGE("Testing npv calculation in AnalyticEuropeanEngineDeltaGamma against QuantLib engine...");
108
109 TestData d;
110
111 Size n = d.pillarTimes.size();
112
113 Real strike = d.spot->value();
114 Date expiryDate = d.refDate + 6 * Years;
115 QuantLib::ext::shared_ptr<StrikedTypePayoff> payoff = QuantLib::ext::make_shared<PlainVanillaPayoff>(Option::Put, strike);
116 QuantLib::ext::shared_ptr<Exercise> exercise = QuantLib::ext::make_shared<EuropeanExercise>(expiryDate);
117 QuantLib::ext::shared_ptr<VanillaOption> option = QuantLib::ext::make_shared<VanillaOption>(payoff, exercise);
118
119 QuantLib::ext::shared_ptr<PricingEngine> engine0 = QuantLib::ext::make_shared<AnalyticEuropeanEngine>(d.process);
120 QuantLib::ext::shared_ptr<PricingEngine> engine1 =
121 QuantLib::ext::make_shared<AnalyticEuropeanEngineDeltaGamma>(d.process, d.pillarTimes, d.pillarTimes, true, true);
122
123 option->setPricingEngine(engine0);
124 Real npv0 = option->NPV();
125
126 option->setPricingEngine(engine1);
127 Real npv = option->NPV();
128
129 Real tol = 1E-8;
130 if (std::fabs(npv0 - npv) > tol)
131 BOOST_ERROR("npv (" << npv << ") can not be verified, expected " << npv0);
132
133 // get results from sensi engine
134 Real deltaSpot = option->result<Real>("deltaSpot");
135 Real gammaSpot = option->result<Real>("gammaSpot");
136 // Real theta = option->result<Real>("theta"); TODO in the pricer
137 std::vector<Real> vega = option->result<std::vector<Real>>("vega");
138 std::vector<Real> deltaRate = option->result<std::vector<Real>>("deltaRate");
139 std::vector<Real> deltaDividend = option->result<std::vector<Real>>("deltaDividend");
140 Matrix gamma = option->result<Matrix>("gamma");
141 std::vector<Real> gammaSpotRate = option->result<std::vector<Real>>("gammaSpotRate");
142 std::vector<Real> gammaSpotDiv = option->result<std::vector<Real>>("gammaSpotDiv");
143
144 // use QL engine for bump and revalue results
145 option->setPricingEngine(engine0);
146
147 // check dimension of result vectors
148
149 BOOST_TEST_MESSAGE("Checking additional results for correct dimensions in AnalyticEuropeanEngineDeltaGamma...");
150
151 if (vega.size() != n)
152 BOOST_ERROR("vega size (" << vega.size() << ") mismatch, expected " << n);
153 if (deltaRate.size() != n)
154 BOOST_ERROR("delta rate size (" << deltaRate.size() << ") mismatch, expected " << n);
155 if (deltaDividend.size() != n)
156 BOOST_ERROR("delta dividend size (" << deltaDividend.size() << ") mismatch, expected " << n);
157 if (gamma.rows() != 2 * n || gamma.columns() != 2 * n)
158 BOOST_ERROR("gamma size (" << gamma.rows() << "x" << gamma.columns() << ") mismatch, expected " << 2 * n << "x"
159 << 2 * n);
160 if (gammaSpotRate.size() != n)
161 BOOST_ERROR("gamma spot rate size (" << gammaSpotRate.size() << ") mismatch, expected " << n);
162 if (gammaSpotDiv.size() != n)
163 BOOST_ERROR("gamma spot div size (" << gammaSpotDiv.size() << ") mismatch, expected " << n);
164
165 // check results against bump and revalue calculations
166
167 BOOST_TEST_MESSAGE(
168 "Checking additional results against bump and revalue results in AnalyticEuropeanEngineDeltaGamma...");
169
170 Real h1 = 1E-4;
171 Real h2 = 1E-6;
172
173 d.spot->setValue(d.spot->value() + h1);
174 Real npvp = option->NPV();
175 d.spot->setValue(d.spot->value() - 2.0 * h1);
176 Real npvm = option->NPV();
177 d.spot->setValue(d.spot->value() + h1);
178
179 Real refDelta = (npvp - npvm) / (2.0 * h1);
180 Real refGamma = (npvp - 2.0 * npv + npvm) / (h1 * h1);
181
182 if (!check(refDelta, deltaSpot))
183 BOOST_ERROR("could not verify delta (reference value=" << refDelta << ", result=" << deltaSpot
184 << ", difference=" << deltaSpot - refDelta << ")");
185 if (!check(refGamma, gammaSpot))
186 BOOST_ERROR("could not verify gamma (reference value=" << refGamma << ", result=" << gammaSpot
187 << ", difference=" << gammaSpot - refGamma << ")");
188
189 // theta is a TODO in the pricer
190 // Settings::instance().evaluationDate() = d.refDate + 1;
191 // Real npvtp = option->NPV();
192 // Real dt = Actual365Fixed().yearFraction(d.refDate, d.refDate + 1);
193 // Settings::instance().evaluationDate() = d.refDate;
194 // Real refTheta = (npvtp - npv) / dt;
195
196 Real vegaSum = 0.0;
197 for (Size i = 0; i < vega.size(); ++i) {
198 vegaSum += vega[i];
199 }
200
201 d.vol->setValue(d.vol->value() + h2);
202 Real npvvp = option->NPV();
203 d.vol->setValue(d.vol->value() - h2);
204 Real refVega = (npvvp - npv) / h2;
205
206 if (!check(refVega, vegaSum))
207 BOOST_ERROR("could not verify vega (reference value=" << refVega << ", result=" << vegaSum
208 << ", difference=" << vegaSum - refVega << ")");
209
210 for (Size i = 0; i < n; ++i) {
211 d.rateSpreads[i]->setValue(h2);
212 Real refDeltaRate = (option->NPV() - npv) / h2;
213 d.rateSpreads[i]->setValue(0.0);
214 d.divSpreads[i]->setValue(h2);
215 Real refDeltaDiv = (option->NPV() - npv) / h2;
216 d.divSpreads[i]->setValue(0.0);
217 if (!check(refDeltaRate, deltaRate[i]))
218 BOOST_ERROR("delta on pillar " << d.pillarTimes[i] << " (rate curve) could not be verified, analytical: "
219 << deltaRate[i] << ", bump and revalue: " << refDeltaRate);
220 if (!check(refDeltaDiv, deltaDividend[i]))
221 BOOST_ERROR("delta on pillar " << d.pillarTimes[i]
222 << " (dividend curve) could not be verified, analytical: "
223 << deltaDividend[i] << ", bump and revalue: " << refDeltaDiv);
224 }
225
226 Matrix refGammaRateDiv(n * 2, n * 2, 0.0);
227
228 // rate-rate
229 for (Size i = 0; i < n; ++i) {
230 // j < i
231 for (Size j = 0; j < i; ++j) {
232 d.rateSpreads[i]->setValue(h1);
233 d.rateSpreads[j]->setValue(h1);
234 Real npvpp = option->NPV();
235 d.rateSpreads[j]->setValue(0.0);
236 Real npvp0 = option->NPV();
237 d.rateSpreads[i]->setValue(0.0);
238 d.rateSpreads[j]->setValue(h1);
239 Real npv0p = option->NPV();
240 d.rateSpreads[j]->setValue(0.0);
241 Real gamma = (npvpp - npvp0 - npv0p + npv) / (h1 * h1);
242 refGammaRateDiv[i][j] = refGammaRateDiv[j][i] = gamma;
243 }
244 // j == i
245 d.rateSpreads[i]->setValue(2.0 * h1);
246 Real npvpp = option->NPV();
247 d.rateSpreads[i]->setValue(h1);
248 Real npvp = option->NPV();
249 d.rateSpreads[i]->setValue(0.0);
250 Real gamma = (npvpp - 2.0 * npvp + npv) / (h1 * h1);
251 refGammaRateDiv[i][i] = gamma;
252 }
253
254 // rate-div
255 for (Size i = 0; i < n; ++i) {
256 // j <= i
257 for (Size j = 0; j < n; ++j) {
258 d.rateSpreads[i]->setValue(h1);
259 d.divSpreads[j]->setValue(h1);
260 Real npvpp = option->NPV();
261 d.divSpreads[j]->setValue(0.0);
262 Real npvp0 = option->NPV();
263 d.rateSpreads[i]->setValue(0.0);
264 d.divSpreads[j]->setValue(h1);
265 Real npv0p = option->NPV();
266 d.divSpreads[j]->setValue(0.0);
267 Real gamma = (npvpp - npvp0 - npv0p + npv) / (h1 * h1);
268 refGammaRateDiv[i][n + j] = refGammaRateDiv[n + j][i] = gamma;
269 }
270 }
271
272 // div-div
273 for (Size i = 0; i < n; ++i) {
274 // j < i
275 for (Size j = 0; j < i; ++j) {
276 d.divSpreads[i]->setValue(h1);
277 d.divSpreads[j]->setValue(h1);
278 Real npvpp = option->NPV();
279 d.divSpreads[j]->setValue(0.0);
280 Real npvp0 = option->NPV();
281 d.divSpreads[i]->setValue(0.0);
282 d.divSpreads[j]->setValue(h1);
283 Real npv0p = option->NPV();
284 d.divSpreads[j]->setValue(0.0);
285 Real gamma = (npvpp - npvp0 - npv0p + npv) / (h1 * h1);
286 refGammaRateDiv[n + i][n + j] = refGammaRateDiv[n + j][n + i] = gamma;
287 }
288 // j == i
289 d.divSpreads[i]->setValue(2.0 * h1);
290 Real npvpp = option->NPV();
291 d.divSpreads[i]->setValue(h1);
292 Real npvp = option->NPV();
293 d.divSpreads[i]->setValue(0.0);
294 Real gamma = (npvpp - 2.0 * npvp + npv0) / (h1 * h1);
295 refGammaRateDiv[n + i][n + i] = gamma;
296 }
297
298 for (Size i = 0; i < 2 * n; ++i) {
299 for (Size j = 0; j < 2 * n; ++j) {
300 if (!check(refGammaRateDiv[i][j], gamma[i][j]))
301 BOOST_ERROR("gamma entry (" << i << "," << j << ") is " << gamma[i][j]
302 << ", bump and revalue result is " << refGammaRateDiv[i][j]);
303 }
304 }
305
306 // spot-rate
307 for (Size i = 0; i < n; ++i) {
308 d.spot->setValue(d.spot->value() + h1);
309 d.rateSpreads[i]->setValue(h1);
310 Real npvpp = option->NPV();
311 d.rateSpreads[i]->setValue(0.0);
312 Real npvp0 = option->NPV();
313 d.spot->setValue(d.spot->value() - h1);
314 d.rateSpreads[i]->setValue(h1);
315 Real npv0p = option->NPV();
316 d.rateSpreads[i]->setValue(0.0);
317 Real refGamma = (npvpp - npvp0 - npv0p + npv) / (h1 * h1);
318 if (!check(refGamma, gammaSpotRate[i]))
319 BOOST_ERROR("spot-rate gamma pillar " << i << " can not be verified, result is " << gammaSpotRate[i]
320 << ", bump and revalue is " << refGamma);
321 }
322
323 // spot-div
324 for (Size i = 0; i < n; ++i) {
325 d.spot->setValue(d.spot->value() + h1);
326 d.divSpreads[i]->setValue(h1);
327 Real npvpp = option->NPV();
328 d.divSpreads[i]->setValue(0.0);
329 Real npvp0 = option->NPV();
330 d.spot->setValue(d.spot->value() - h1);
331 d.divSpreads[i]->setValue(h1);
332 Real npv0p = option->NPV();
333 d.divSpreads[i]->setValue(0.0);
334 Real refGamma = (npvpp - npvp0 - npv0p + npv) / (h1 * h1);
335 if (!check(refGamma, gammaSpotDiv[i]))
336 BOOST_ERROR("spot-div gamma pillar " << i << " can not be verified, result is " << gammaSpotDiv[i]
337 << ", bump and revalue is " << refGamma);
338 }
339
340 BOOST_CHECK(true);
341
342} // testNpvDeltasGammaVegas
343
344BOOST_AUTO_TEST_SUITE_END()
345
346BOOST_AUTO_TEST_SUITE_END()
Analytic European engine providing sensitivities.
BOOST_AUTO_TEST_CASE(testNpvDeltasGammaVegas)
Fixture that can be used at top level.