Logo
Fully annotated reference manual - version 1.8.12
Loading...
Searching...
No Matches
sensitivityvsanalytic.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 <oret/toplevelfixture.hpp>
21#include "testmarket.hpp"
22#include "testportfolio.hpp"
23
25#include <qle/pricingengines/analyticeuropeanenginedeltagamma.hpp>
26#include <qle/pricingengines/blackswaptionenginedeltagamma.hpp>
27#include <qle/pricingengines/discountingswapenginedeltagamma.hpp>
28
30#include <orea/cube/npvcube.hpp>
49
62
63#include <ql/math/randomnumbers/mt19937uniformrng.hpp>
64#include <ql/time/calendars/target.hpp>
65#include <ql/time/date.hpp>
66#include <ql/time/daycounters/actualactual.hpp>
67
68using namespace std;
69using namespace QuantLib;
70using namespace QuantExt;
71using namespace boost::unit_test_framework;
72using namespace ore;
73using namespace ore::data;
74using namespace ore::analytics;
75
77
78namespace {
79QuantLib::ext::shared_ptr<ore::data::Conventions> conv() {
80 QuantLib::ext::shared_ptr<ore::data::Conventions> conventions(new ore::data::Conventions());
81
82 QuantLib::ext::shared_ptr<ore::data::Convention> swapIndexConv(
83 new ore::data::SwapIndexConvention("EUR-CMS-2Y", "EUR-6M-SWAP-CONVENTIONS"));
84 conventions->add(swapIndexConv);
85
86 // QuantLib::ext::shared_ptr<ore::data::Convention> swapConv(
87 // new ore::data::IRSwapConvention("EUR-6M-SWAP-CONVENTIONS", "TARGET", "Annual", "MF", "30/360", "EUR-EURIBOR-6M"));
88 conventions->add(QuantLib::ext::make_shared<ore::data::IRSwapConvention>("EUR-6M-SWAP-CONVENTIONS", "TARGET", "A", "MF",
89 "30/360", "EUR-EURIBOR-6M"));
90 conventions->add(QuantLib::ext::make_shared<ore::data::IRSwapConvention>("USD-3M-SWAP-CONVENTIONS", "TARGET", "Q", "MF",
91 "30/360", "USD-LIBOR-3M"));
92 conventions->add(QuantLib::ext::make_shared<ore::data::IRSwapConvention>("USD-6M-SWAP-CONVENTIONS", "TARGET", "Q", "MF",
93 "30/360", "USD-LIBOR-6M"));
94 conventions->add(QuantLib::ext::make_shared<ore::data::IRSwapConvention>("GBP-6M-SWAP-CONVENTIONS", "TARGET", "A", "MF",
95 "30/360", "GBP-LIBOR-6M"));
96 conventions->add(QuantLib::ext::make_shared<ore::data::IRSwapConvention>("JPY-6M-SWAP-CONVENTIONS", "TARGET", "A", "MF",
97 "30/360", "JPY-LIBOR-6M"));
98 conventions->add(QuantLib::ext::make_shared<ore::data::IRSwapConvention>("CHF-6M-SWAP-CONVENTIONS", "TARGET", "A", "MF",
99 "30/360", "CHF-LIBOR-6M"));
100
101 conventions->add(QuantLib::ext::make_shared<ore::data::DepositConvention>("EUR-DEP-CONVENTIONS", "EUR-EURIBOR"));
102 conventions->add(QuantLib::ext::make_shared<ore::data::DepositConvention>("USD-DEP-CONVENTIONS", "USD-LIBOR"));
103 conventions->add(QuantLib::ext::make_shared<ore::data::DepositConvention>("GBP-DEP-CONVENTIONS", "GBP-LIBOR"));
104 conventions->add(QuantLib::ext::make_shared<ore::data::DepositConvention>("JPY-DEP-CONVENTIONS", "JPY-LIBOR"));
105 conventions->add(QuantLib::ext::make_shared<ore::data::DepositConvention>("CHF-DEP-CONVENTIONS", "CHF-LIBOR"));
106
107 conventions->add(QuantLib::ext::make_shared<FXConvention>("EUR-USD-FX", "0", "EUR", "USD", "10000", "EUR,USD"));
108 conventions->add(QuantLib::ext::make_shared<FXConvention>("EUR-GBP-FX", "0", "EUR", "GBP", "10000", "EUR,GBP"));
109 conventions->add(QuantLib::ext::make_shared<FXConvention>("EUR-CHF-FX", "0", "EUR", "CHF", "10000", "EUR,CHF"));
110 conventions->add(QuantLib::ext::make_shared<FXConvention>("EUR-JPY-FX", "0", "EUR", "JPY", "10000", "EUR,JPY"));
111 conventions->add(QuantLib::ext::make_shared<FXConvention>("EUR-SEK-FX", "0", "EUR", "SEK", "10000", "EUR,SEK"));
112 conventions->add(QuantLib::ext::make_shared<FXConvention>("EUR-CAD-FX", "0", "EUR", "CAD", "10000", "EUR,CAD"));
113
114 return conventions;
115}
116
117// QuantLib::ext::shared_ptr<analytics::ScenarioSimMarketParameters> setupSimMarketData2() {
118// QuantLib::ext::shared_ptr<analytics::ScenarioSimMarketParameters> simMarketData(
119// new analytics::ScenarioSimMarketParameters());
120// simMarketData->baseCcy() = "EUR";
121// simMarketData->ccys() = {"EUR", "GBP"};
122// simMarketData->yieldCurveTenors() = {1 * Months, 6 * Months, 1 * Years, 2 * Years, 3 * Years, 4 * Years,
123// 5 * Years, 6 * Years, 7 * Years, 8 * Years, 9 * Years, 10 * Years,
124// 12 * Years, 15 * Years, 20 * Years, 25 * Years, 30 * Years};
125// simMarketData->indices() = {"EUR-EURIBOR-6M", "GBP-LIBOR-6M"};
126// simMarketData->interpolation() = "LogLinear";
127// simMarketData->extrapolate() = true;
128
129// simMarketData->swapVolTerms() = {1 * Years, 2 * Years, 3 * Years, 4 * Years,
130// 5 * Years, 7 * Years, 10 * Years, 20 * Years};
131// simMarketData->swapVolExpiries() = {6 * Months, 1 * Years, 2 * Years, 3 * Years,
132// 5 * Years, 7 * Years, 10 * Years, 20 * Years};
133// simMarketData->swapVolCcys() = {"EUR", "GBP"};
134// simMarketData->swapVolDecayMode() = "ForwardVariance";
135// simMarketData->simulateSwapVols() = true;
136
137// simMarketData->fxVolExpiries() = {1 * Months, 3 * Months, 6 * Months, 2 * Years, 3 * Years, 4 * Years, 5 *
138// Years};
139// simMarketData->fxVolDecayMode() = "ConstantVariance";
140// simMarketData->simulateFXVols() = true;
141// simMarketData->fxVolCcyPairs() = {"EURGBP"};
142
143// simMarketData->fxCcyPairs() = {"EURGBP"};
144
145// simMarketData->simulateCapFloorVols() = false;
146
147// return simMarketData;
148// }
149
150QuantLib::ext::shared_ptr<analytics::ScenarioSimMarketParameters> setupSimMarketData5() {
151 QuantLib::ext::shared_ptr<analytics::ScenarioSimMarketParameters> simMarketData(
153
154 simMarketData->baseCcy() = "EUR";
155 simMarketData->setDiscountCurveNames({"EUR", "GBP", "USD", "CHF", "JPY"});
156 simMarketData->setYieldCurveTenors("", {1 * Months, 6 * Months, 1 * Years, 2 * Years, 3 * Years, 4 * Years,
157 5 * Years, 7 * Years, 10 * Years, 15 * Years, 20 * Years, 30 * Years});
158 simMarketData->setIndices(
159 {"EUR-EURIBOR-6M", "USD-LIBOR-3M", "USD-LIBOR-6M", "GBP-LIBOR-6M", "CHF-LIBOR-6M", "JPY-LIBOR-6M"});
160 simMarketData->interpolation() = "LogLinear";
161 simMarketData->extrapolation() = "FlatFwd";
162
163 simMarketData->setSwapVolTerms("", {1 * Years, 2 * Years, 3 * Years, 5 * Years, 7 * Years, 10 * Years, 20 * Years});
164 simMarketData->setSwapVolExpiries(
165 "", {6 * Months, 1 * Years, 2 * Years, 3 * Years, 5 * Years, 7 * Years, 10 * Years, 20 * Years});
166 simMarketData->setSwapVolKeys({"EUR", "GBP", "USD", "CHF", "JPY"});
167 simMarketData->swapVolDecayMode() = "ForwardVariance";
168 simMarketData->setSimulateSwapVols(true); // false;
169
170 simMarketData->setFxVolExpiries("",
171 vector<Period>{6 * Months, 1 * Years, 2 * Years, 3 * Years, 5 * Years, 7 * Years, 10 * Years, 20 * Years});
172 simMarketData->setFxVolDecayMode(string("ConstantVariance"));
173 simMarketData->setSimulateFXVols(true); // false;
174 simMarketData->setFxVolCcyPairs({"EURUSD", "EURGBP", "EURCHF", "EURJPY", "GBPCHF"});
175 simMarketData->setFxVolIsSurface(true);
176 simMarketData->setFxVolMoneyness(vector<Real>{0.1, 0.5, 1, 1.5, 2, 2.5, 2});
177
178 simMarketData->setFxCcyPairs({"EURUSD", "EURGBP", "EURCHF", "EURJPY"});
179
180 simMarketData->setSimulateCapFloorVols(true);
181 simMarketData->capFloorVolDecayMode() = "ForwardVariance";
182 simMarketData->setCapFloorVolKeys({"EUR", "USD"});
183 simMarketData->setCapFloorVolExpiries(
184 "", {6 * Months, 1 * Years, 2 * Years, 3 * Years, 5 * Years, 7 * Years, 10 * Years, 15 * Years, 20 * Years});
185 simMarketData->setCapFloorVolStrikes("", {0.00, 0.01, 0.02, 0.03, 0.04, 0.05, 0.06});
186
187 return simMarketData;
188}
189
190// QuantLib::ext::shared_ptr<SensitivityScenarioData> setupSensitivityScenarioData2() {
191// QuantLib::ext::shared_ptr<SensitivityScenarioData> sensiData = QuantLib::ext::make_shared<SensitivityScenarioData>();
192
193// sensiData->parConversion() = false;
194
195// SensitivityScenarioData::CurveShiftData cvsData;
196// cvsData.shiftTenors = {1 * Years, 2 * Years, 3 * Years, 5 * Years,
197// 7 * Years, 10 * Years, 15 * Years, 20 * Years}; // multiple tenors: triangular shifts
198// cvsData.shiftType = ShiftType::Absolute;
199// cvsData.shiftSize = 0.0001;
200// cvsData.parInstruments = {"DEP", "IRS", "IRS", "IRS", "IRS", "IRS", "IRS", "IRS", "IRS"};
201
202// SensitivityScenarioData::FxShiftData fxsData;
203// fxsData.shiftType = ShiftType::Relative;
204// fxsData.shiftSize = 0.01;
205
206// SensitivityScenarioData::FxVolShiftData fxvsData;
207// fxvsData.shiftType = ShiftType::Relative;
208// fxvsData.shiftSize = 1.0;
209// fxvsData.shiftExpiries = {2 * Years, 5 * Years};
210
211// SensitivityScenarioData::CapFloorVolShiftData cfvsData;
212// cfvsData.shiftType = ShiftType::Absolute;
213// cfvsData.shiftSize = 0.0001;
214// cfvsData.shiftExpiries = {1 * Years, 2 * Years, 3 * Years, 5 * Years, 10 * Years};
215// cfvsData.shiftStrikes = {0.05};
216
217// SensitivityScenarioData::SwaptionVolShiftData swvsData;
218// swvsData.shiftType = ShiftType::Relative;
219// swvsData.shiftSize = 0.01;
220// swvsData.shiftExpiries = {3 * Years, 5 * Years, 10 * Years};
221// swvsData.shiftTerms = {2 * Years, 5 * Years, 10 * Years};
222
223// sensiData->discountCurveShiftData()["EUR"] = cvsData;
224// sensiData->discountCurveShiftData()["EUR"].parInstrumentSingleCurve = true;
225// sensiData->discountCurveShiftData()["EUR"].parInstrumentConventions = {{"DEP", "EUR-DEP-CONVENTIONS"},
226// {"IRS", "EUR-6M-SWAP-CONVENTIONS"}};
227// sensiData->discountCurveShiftData()["GBP"] = cvsData;
228// sensiData->discountCurveShiftData()["GBP"].parInstrumentSingleCurve = true;
229// sensiData->discountCurveShiftData()["GBP"].parInstrumentConventions = {{"DEP", "GBP-DEP-CONVENTIONS"},
230// {"IRS", "GBP-6M-SWAP-CONVENTIONS"}};
231
232// sensiData->indexCurveShiftData()["EUR-EURIBOR-6M"] = cvsData;
233// sensiData->indexCurveShiftData()["EUR-EURIBOR-6M"].parInstrumentSingleCurve = false;
234// sensiData->indexCurveShiftData()["EUR-EURIBOR-6M"].parInstrumentConventions = {{"DEP", "EUR-DEP-CONVENTIONS"},
235// {"IRS",
236// "EUR-6M-SWAP-CONVENTIONS"}};
237// sensiData->indexCurveShiftData()["GBP-LIBOR-6M"] = cvsData;
238// sensiData->indexCurveShiftData()["GBP-LIBOR-6M"].parInstrumentSingleCurve = false;
239// sensiData->indexCurveShiftData()["GBP-LIBOR-6M"].parInstrumentConventions = {{"DEP", "GBP-DEP-CONVENTIONS"},
240// {"IRS", "GBP-6M-SWAP-CONVENTIONS"}};
241
242// sensiData->fxShiftData()["EURGBP"] = fxsData;
243
244// sensiData->fxVolShiftData()["EURGBP"] = fxvsData;
245
246// sensiData->swaptionVolShiftData()["EUR"] = swvsData;
247// sensiData->swaptionVolShiftData()["GBP"] = swvsData;
248
249// // sensiData->capFloorVolLabel() = "VOL_CAPFLOOR";
250// // sensiData->capFloorVolShiftData()["EUR"] = cfvsData;
251// // sensiData->capFloorVolShiftData()["EUR"].indexName = "EUR-EURIBOR-6M";
252// // sensiData->capFloorVolShiftData()["GBP"] = cfvsData;
253// // sensiData->capFloorVolShiftData()["GBP"].indexName = "GBP-LIBOR-6M";
254
255// return sensiData;
256// }
257
258QuantLib::ext::shared_ptr<ore::analytics::SensitivityScenarioData::CurveShiftParData> createCurveData() {
260
261 // identical to sim market tenor structure, we can only check this case, because the analytic engine
262 // assumes either linear in zero or linear in log discount interpolation, while the sensitivity analysis
263 // assumes a lienar in zero interpolation for rebucketing, but uses the linear in log discount interpolation
264 // of the sim market yield curves for the scenario calculation
265 cvsData.shiftTenors = {1 * Months, 6 * Months, 1 * Years, 2 * Years, 3 * Years, 4 * Years,
266 5 * Years, 7 * Years, 10 * Years, 15 * Years, 20 * Years, 30 * Years};
267 cvsData.shiftType = ShiftType::Absolute;
268 cvsData.shiftSize = 1E-5;
269 cvsData.parInstruments = {"DEP", "IRS", "IRS", "IRS", "IRS", "IRS", "IRS", "IRS", "IRS"};
270
271 return QuantLib::ext::make_shared<ore::analytics::SensitivityScenarioData::CurveShiftParData>(cvsData);
272}
273QuantLib::ext::shared_ptr<SensitivityScenarioData> setupSensitivityScenarioData5(bool parConversion) {
274 QuantLib::ext::shared_ptr<SensitivityScenarioData> sensiData =
275 QuantLib::ext::make_shared<ore::analytics::SensitivityScenarioData>(parConversion);
276
278 fxsData.shiftType = ShiftType::Absolute;
279 fxsData.shiftSize = 1E-5;
280
282 fxvsData.shiftType = ShiftType::Absolute;
283 fxvsData.shiftSize = 1E-5;
284 fxvsData.shiftExpiries = {5 * Years};
285
287 cfvsData.shiftType = ShiftType::Absolute;
288 cfvsData.shiftSize = 1E-5;
289 cfvsData.shiftExpiries = {1 * Years, 2 * Years, 3 * Years, 5 * Years, 10 * Years};
290 cfvsData.shiftStrikes = {0.01, 0.02, 0.03, 0.04, 0.05};
291
293 swvsData.shiftType = ShiftType::Absolute;
294 swvsData.shiftSize = 1E-5;
295 swvsData.shiftExpiries = {6 * Months, 1 * Years, 2 * Years, 3 * Years,
296 5 * Years, 7 * Years, 10 * Years, 20 * Years};
297 swvsData.shiftTerms = {1 * Years, 2 * Years, 3 * Years, 5 * Years, 7 * Years, 10 * Years, 20 * Years};
298
299 QuantLib::ext::shared_ptr<ore::analytics::SensitivityScenarioData::CurveShiftParData> cvsData = createCurveData();
300 cvsData->parInstrumentSingleCurve = true;
301 cvsData->parInstrumentConventions["DEP"] = "EUR-DEP-CONVENTIONS";
302 cvsData->parInstrumentConventions["IRS"] = "EUR-6M-SWAP-CONVENTIONS";
303 sensiData->discountCurveShiftData()["EUR"] = cvsData;
304
305 cvsData = createCurveData();
306 cvsData->parInstrumentSingleCurve = true;
307 cvsData->parInstrumentConventions["DEP"] = "USD-DEP-CONVENTIONS";
308 cvsData->parInstrumentConventions["IRS"] = "USD-3M-SWAP-CONVENTIONS";
309 sensiData->discountCurveShiftData()["USD"] = cvsData;
310
311 cvsData = createCurveData();
312 cvsData->parInstrumentSingleCurve = true;
313 cvsData->parInstrumentConventions["DEP"] = "GBP-DEP-CONVENTIONS";
314 cvsData->parInstrumentConventions["IRS"] = "GBP-6M-SWAP-CONVENTIONS";
315 sensiData->discountCurveShiftData()["GBP"] = cvsData;
316
317 cvsData = createCurveData();
318 cvsData->parInstrumentSingleCurve = true;
319 cvsData->parInstrumentConventions["DEP"] = "JPY-DEP-CONVENTIONS";
320 cvsData->parInstrumentConventions["IRS"] = "JPY-6M-SWAP-CONVENTIONS";
321 sensiData->discountCurveShiftData()["JPY"] = cvsData;
322
323 cvsData = createCurveData();
324 cvsData->parInstrumentSingleCurve = true;
325 cvsData->parInstrumentConventions["DEP"] = "CHF-DEP-CONVENTIONS";
326 cvsData->parInstrumentConventions["IRS"] = "CHF-6M-SWAP-CONVENTIONS";
327 sensiData->discountCurveShiftData()["CHF"] = cvsData;
328
329 cvsData = createCurveData();
330 cvsData->parInstrumentSingleCurve = false;
331 cvsData->parInstrumentConventions["DEP"] = "EUR-DEP-CONVENTIONS";
332 cvsData->parInstrumentConventions["IRS"] = "EUR-6M-SWAP-CONVENTIONS";
333 sensiData->indexCurveShiftData()["EUR-EURIBOR-6M"] = cvsData;
334
335 cvsData = createCurveData();
336 cvsData->parInstrumentSingleCurve = false;
337 cvsData->parInstrumentConventions["DEP"] = "USD-DEP-CONVENTIONS";
338 cvsData->parInstrumentConventions["IRS"] = "USD-3M-SWAP-CONVENTIONS";
339 sensiData->indexCurveShiftData()["USD-LIBOR-3M"] = cvsData;
340
341 cvsData = createCurveData();
342 cvsData->parInstrumentSingleCurve = false;
343 cvsData->parInstrumentConventions["DEP"] = "GBP-DEP-CONVENTIONS";
344 cvsData->parInstrumentConventions["IRS"] = "GBP-6M-SWAP-CONVENTIONS";
345 sensiData->indexCurveShiftData()["GBP-LIBOR-6M"] = cvsData;
346
347 cvsData = createCurveData();
348 cvsData->parInstrumentSingleCurve = false;
349 cvsData->parInstrumentConventions["DEP"] = "JPY-DEP-CONVENTIONS";
350 cvsData->parInstrumentConventions["IRS"] = "JPY-6M-SWAP-CONVENTIONS";
351 sensiData->indexCurveShiftData()["JPY-LIBOR-6M"] = cvsData;
352
353 cvsData = createCurveData();
354 cvsData->parInstrumentSingleCurve = true;
355 cvsData->parInstrumentConventions["DEP"] = "CHF-DEP-CONVENTIONS";
356 cvsData->parInstrumentConventions["IRS"] = "CHF-6M-SWAP-CONVENTIONS";
357 sensiData->indexCurveShiftData()["CHF-LIBOR-6M"] = cvsData;
358
359 sensiData->fxShiftData()["EURUSD"] = fxsData;
360 sensiData->fxShiftData()["EURGBP"] = fxsData;
361 sensiData->fxShiftData()["EURJPY"] = fxsData;
362 sensiData->fxShiftData()["EURCHF"] = fxsData;
363
364 sensiData->fxVolShiftData()["EURUSD"] = fxvsData;
365 sensiData->fxVolShiftData()["EURGBP"] = fxvsData;
366 sensiData->fxVolShiftData()["EURJPY"] = fxvsData;
367 sensiData->fxVolShiftData()["EURCHF"] = fxvsData;
368 sensiData->fxVolShiftData()["GBPCHF"] = fxvsData;
369
370 sensiData->swaptionVolShiftData()["EUR"] = swvsData;
371 sensiData->swaptionVolShiftData()["GBP"] = swvsData;
372 sensiData->swaptionVolShiftData()["USD"] = swvsData;
373 sensiData->swaptionVolShiftData()["JPY"] = swvsData;
374 sensiData->swaptionVolShiftData()["CHF"] = swvsData;
375
376 sensiData->capFloorVolShiftData()["EUR"] =
377 QuantLib::ext::make_shared<SensitivityScenarioData::CapFloorVolShiftData>(cfvsData);
378 sensiData->capFloorVolShiftData()["EUR"]->indexName = "EUR-EURIBOR-6M";
379 sensiData->capFloorVolShiftData()["USD"] =
380 QuantLib::ext::make_shared<SensitivityScenarioData::CapFloorVolShiftData>(cfvsData);
381 sensiData->capFloorVolShiftData()["USD"]->indexName = "USD-LIBOR-3M";
382
383 sensiData->crossGammaFilter() = {{"DiscountCurve/EUR", "DiscountCurve/EUR"},
384 {"DiscountCurve/USD", "DiscountCurve/USD"},
385 {"DiscountCurve/EUR", "IndexCurve/EUR"},
386 {"IndexCurve/EUR", "IndexCurve/EUR"},
387 {"DiscountCurve/EUR", "DiscountCurve/USD"}};
388
389 return sensiData;
390}
391
392bool check(const Real reference, const Real value) {
393 if (std::fabs(reference) >= 1E-2) {
394 return std::fabs((reference - value) / reference) < 5E-3;
395 } else {
396 return std::fabs(reference - value) < 1E-3;
397 }
398}
399
400} // anonymous namespace
401
402BOOST_FIXTURE_TEST_SUITE(OREAnalyticsTestSuite, ore::test::OreaTopLevelFixture)
403
404BOOST_AUTO_TEST_SUITE(SensitivityVsAnalyticSensiEnginesTest)
405
406BOOST_AUTO_TEST_CASE(testSensitivities) {
407
408 BOOST_TEST_MESSAGE("Checking sensitivity analysis results vs analytic sensi engine results...");
409
410 SavedSettings backup;
411
412 ObservationMode::Mode backupMode = ObservationMode::instance().mode();
413 ObservationMode::instance().setMode(ObservationMode::Mode::None);
414
415 Date today = Date(14, April, 2016); // Settings::instance().evaluationDate();
416 Settings::instance().evaluationDate() = today;
417
418 BOOST_TEST_MESSAGE("Today is " << today);
419
420 // Init market
421 QuantLib::ext::shared_ptr<Market> initMarket = QuantLib::ext::make_shared<testsuite::TestMarket>(today);
422
423 // build scenario sim market parameters
424 QuantLib::ext::shared_ptr<analytics::ScenarioSimMarketParameters> simMarketData = setupSimMarketData5();
425
426 // sensitivity config
427 QuantLib::ext::shared_ptr<SensitivityScenarioData> sensiData = setupSensitivityScenarioData5(false);
428
429 // build scenario sim market
430 InstrumentConventions::instance().setConventions(conv());
431 QuantLib::ext::shared_ptr<analytics::ScenarioSimMarket> simMarket =
432 QuantLib::ext::make_shared<analytics::ScenarioSimMarket>(initMarket, simMarketData);
433
434 // build scenario factory
435 QuantLib::ext::shared_ptr<Scenario> baseScenario = simMarket->baseScenario();
436 QuantLib::ext::shared_ptr<ScenarioFactory> scenarioFactory = QuantLib::ext::make_shared<DeltaScenarioFactory>(baseScenario);
437
438 // build scenario generator
439 QuantLib::ext::shared_ptr<SensitivityScenarioGenerator> scenarioGenerator =
440 QuantLib::ext::make_shared<SensitivityScenarioGenerator>(sensiData, baseScenario, simMarketData, simMarket,
441 scenarioFactory, false);
442 simMarket->scenarioGenerator() = scenarioGenerator;
443
444 // build porfolio
445 QuantLib::ext::shared_ptr<EngineData> data = QuantLib::ext::make_shared<EngineData>();
446 data->model("Swap") = "DiscountedCashflows";
447 data->engine("Swap") = "DiscountingSwapEngine";
448 data->model("CrossCurrencySwap") = "DiscountedCashflows";
449 data->engine("CrossCurrencySwap") = "DiscountingCrossCurrencySwapEngine";
450 data->model("FxOption") = "GarmanKohlhagen";
451 data->engine("FxOption") = "AnalyticEuropeanEngine";
452 QuantLib::ext::shared_ptr<EngineFactory> factory = QuantLib::ext::make_shared<EngineFactory>(data, simMarket);
453
454 // QuantLib::ext::shared_ptr<Portfolio> portfolio = buildSwapPortfolio(portfolioSize, factory);
455 QuantLib::ext::shared_ptr<Portfolio> portfolio(new Portfolio());
456 portfolio->add(testsuite::buildSwap("1_Swap_EUR", "EUR", true, 10.0, 0, 10, 0.03, 0.00, "1Y", "30/360",
457 "6M", "A360", "EUR-EURIBOR-6M"));
458 portfolio->add(testsuite::buildFxOption("7_FxOption_EUR_USD", "Long", "Call", 3, "EUR", 10.0, "USD", 11.0));
459 portfolio->build(factory);
460
461 BOOST_TEST_MESSAGE("Portfolio size after build: " << portfolio->size());
462
463 // analytic results
464
465 std::map<string, Real> analyticalResultsDelta, analyticalResultsGamma, analyticalResultsCrossGamma;
466 std::vector<Real> bucketTimes, bucketTimesVegaOpt, bucketTimesVegaUnd, bucketTimesFxOpt;
467 std::vector<string> bucketStr, numStr, bucketStrVega, numStrVega, bucketStrFxVega, numStrFxVega;
468 ActualActual dc(ActualActual::ISDA); // this is the dc used for the init / sim market curves
469 Size i = 0;
470 for (auto const& p : sensiData->discountCurveShiftData()["EUR"]->shiftTenors) {
471 bucketTimes.push_back(dc.yearFraction(today, today + p));
472 ostringstream bs;
473 bs << p;
474 bucketStr.push_back(bs.str());
475 ostringstream num;
476 num << i++;
477 numStr.push_back(num.str());
478 }
479 for (auto const& p : sensiData->swaptionVolShiftData()["EUR"].shiftExpiries) {
480 bucketTimesVegaOpt.push_back(dc.yearFraction(today, today + p));
481 }
482 for (auto const& p : sensiData->swaptionVolShiftData()["EUR"].shiftTerms) {
483 bucketTimesVegaUnd.push_back(dc.yearFraction(today, today + p));
484 }
485 i = 0;
486 for (auto const& p1 : sensiData->swaptionVolShiftData()["EUR"].shiftExpiries) {
487 for (auto const& p2 : sensiData->swaptionVolShiftData()["EUR"].shiftTerms) {
488 ostringstream bs, num;
489 bs << p1 << "/" << p2;
490 num << i++;
491 bucketStrVega.push_back(bs.str());
492 numStrVega.push_back(num.str());
493 }
494 }
495 i = 0;
496 for (auto const& p : sensiData->fxVolShiftData()["EURUSD"].shiftExpiries) {
497 bucketTimesFxOpt.push_back(dc.yearFraction(today, today + p));
498 ostringstream bs, num;
499 bs << p;
500 num << i++;
501 bucketStrFxVega.push_back(bs.str());
502 numStrFxVega.push_back(num.str());
503 }
504
505 Size n = bucketTimes.size();
506 auto analyticSwapEngine = QuantLib::ext::make_shared<DiscountingSwapEngineDeltaGamma>(simMarket->discountCurve("EUR"),
507 bucketTimes, true, true, true, false);
508 auto swap0 = portfolio->trades().begin()->second->instrument()->qlInstrument();
509 swap0->setPricingEngine(analyticSwapEngine);
510 auto deltaDsc0 = swap0->result<std::vector<Real>>("deltaDiscount");
511 auto deltaFwd0 = swap0->result<std::vector<Real>>("deltaForward");
512 auto gamma0 = swap0->result<Matrix>("gamma");
513
514 auto process =
515 QuantLib::ext::make_shared<GarmanKohlagenProcess>(simMarket->fxRate("EURUSD"), simMarket->discountCurve("EUR"),
516 simMarket->discountCurve("USD"), simMarket->fxVol("EURUSD"));
517 auto analyticFxEngine =
518 QuantLib::ext::make_shared<AnalyticEuropeanEngineDeltaGamma>(process, bucketTimes, bucketTimesFxOpt, true, true, false);
519 auto fxoption2 = (++portfolio->trades().begin())->second->instrument()->qlInstrument();
520 fxoption2->setPricingEngine(analyticFxEngine);
521 Real deltaSpot2 = fxoption2->result<Real>("deltaSpot");
522 Real gammaSpot2 = fxoption2->result<Real>("gammaSpot");
523 auto vega2 = fxoption2->result<std::vector<Real>>("vega");
524 auto deltaRate2 = fxoption2->result<std::vector<Real>>("deltaRate");
525 auto deltaDividend2 = fxoption2->result<std::vector<Real>>("deltaDividend");
526 auto gamma2 = fxoption2->result<Matrix>("gamma");
527 Real fxnpv = fxoption2->NPV();
528 Real fxspot = simMarket->fxRate("EURUSD")->value();
529
530 std::vector<string> dscKey, dscKey2, fwdKey;
531 for (Size i = 0; i < n; ++i) {
532 dscKey.push_back("DiscountCurve/EUR/" + numStr[i] + "/" + bucketStr[i]);
533 dscKey2.push_back("DiscountCurve/USD/" + numStr[i] + "/" + bucketStr[i]);
534 fwdKey.push_back("IndexCurve/EUR-EURIBOR-6M/" + numStr[i] + "/" + bucketStr[i]);
535 }
536
537 for (Size i = 0; i < n; ++i) {
538 analyticalResultsDelta["1_Swap_EUR " + dscKey[i]] = deltaDsc0[i];
539 analyticalResultsDelta["1_Swap_EUR " + fwdKey[i]] = deltaFwd0[i];
540 analyticalResultsDelta["7_FxOption_EUR_USD " + dscKey[i]] = deltaDividend2[i] * 10.0 / fxspot; // convert to EUR
541 analyticalResultsDelta["7_FxOption_EUR_USD " + dscKey2[i]] = deltaRate2[i] * 10.0 / fxspot; // convert to EUR
542 }
543
544 for (Size i = 0; i < n; ++i) {
545 analyticalResultsGamma["1_Swap_EUR " + dscKey[i]] = gamma0[i][i];
546 analyticalResultsGamma["1_Swap_EUR " + fwdKey[i]] = gamma0[n + i][n + i];
547 analyticalResultsGamma["7_FxOption_EUR_USD " + dscKey[i]] =
548 gamma2[n + i][n + i] * 10.0 / fxspot; // convert to EUR
549 analyticalResultsGamma["7_FxOption_EUR_USD " + dscKey2[i]] = gamma2[i][i] * 10.0 / fxspot; // convert to EUR
550 for (Size j = 0; j < n; ++j) {
551 if (i < j) {
552 analyticalResultsCrossGamma["1_Swap_EUR " + dscKey[i] + " " + dscKey[j]] = gamma0[i][j];
553 analyticalResultsCrossGamma["1_Swap_EUR " + fwdKey[i] + " " + fwdKey[j]] = gamma0[n + i][n + j];
554 analyticalResultsCrossGamma["7_FxOption_EUR_USD " + dscKey[i] + " " + dscKey[j]] =
555 gamma2[n + i][n + j] * 10.0 / fxspot; // convert to EUR
556 analyticalResultsCrossGamma["7_FxOption_EUR_USD " + dscKey2[i] + " " + dscKey2[j]] =
557 gamma2[i][j] * 10.0 / fxspot; // convert to EUR
558 }
559 analyticalResultsCrossGamma["1_Swap_EUR " + dscKey[i] + " " + fwdKey[j]] = gamma0[i][n + j];
560 analyticalResultsCrossGamma["7_FxOption_EUR_USD " + dscKey[i] + " " + dscKey2[j]] =
561 gamma2[n + i][j] * 10.0 / fxspot; // convert to EUR
562 }
563 }
564
565 // the sensitivity framework computes d/dS (npv/S), with S = EURUSD fx rate, npv = NPV in USD
566 // the analytical engine computes d/dS npv, the first expression is
567 // -npv/S^2+ d/dS npv / S
568 // furthermore the analytical engine produces results for an EUR notional of 1 instead of 10
569 analyticalResultsDelta["7_FxOption_EUR_USD FXSpot/EURUSD/0/spot"] =
570 10.0 * (deltaSpot2 / fxspot - fxnpv / (fxspot * fxspot));
571 // ... differentiate the above expression by S again gives
572 // 2npv/S^3-2 d/dS npv / S^2 + d^2/dS^2 npv / S
573 analyticalResultsGamma["7_FxOption_EUR_USD FXSpot/EURUSD/0/spot"] =
574 10.0 * (2.0 * fxnpv / (fxspot * fxspot * fxspot) - 2.0 * deltaSpot2 / (fxspot * fxspot) + gammaSpot2 / fxspot);
575
576 analyticalResultsDelta["7_FxOption_EUR_USD FXVolatility/EURUSD/0/5Y/ATM"] =
577 vega2.front() * 10.0 / fxspot; // we only have one vega bucket
578
579 // sensitivity analysis
580 QuantLib::ext::shared_ptr<SensitivityAnalysis> sa = QuantLib::ext::make_shared<SensitivityAnalysis>(
581 portfolio, initMarket, Market::defaultConfiguration, data, simMarketData, sensiData, false);
582 sa->generateSensitivities();
583 map<pair<string, string>, Real> deltaMap;
584 map<pair<string, string>, Real> gammaMap;
585
586 auto sensiCube = sa->sensiCube();
587 for (const auto& tradeId : portfolio->ids()) {
588 for (const auto& f : sensiCube->factors()) {
589 string des = sensiCube->factorDescription(f);
590 deltaMap[make_pair(tradeId, des)] = sensiCube->delta(tradeId, f);
591 gammaMap[make_pair(tradeId, des)] = sensiCube->gamma(tradeId, f);
592 }
593 }
594
595 std::vector<ore::analytics::SensitivityScenarioGenerator::ScenarioDescription> scenDesc =
596 sa->scenarioGenerator()->scenarioDescriptions();
597 Real shiftSize = 1E-5; // shift size
598
599 // check deltas
600 BOOST_TEST_MESSAGE("Checking deltas...");
601 Size foundDeltas = 0, zeroDeltas = 0;
602 for (auto const& x : deltaMap) {
603 string key = x.first.first + " " + x.first.second;
604 Real scaledResult = x.second / shiftSize;
605 if (analyticalResultsDelta.count(key) > 0) {
606 if (!check(analyticalResultsDelta.at(key), scaledResult))
607 BOOST_ERROR("Sensitivity analysis result " << key << " (" << scaledResult
608 << ") could not be verified against analytic result ("
609 << analyticalResultsDelta.at(key) << ")");
610 ++foundDeltas;
611 } else {
612 if (!close_enough(x.second, 0.0))
613 BOOST_ERROR("Sensitivity analysis result " << key << " (" << scaledResult << ") expected to be zero");
614 ++zeroDeltas;
615 }
616 }
617 if (foundDeltas != analyticalResultsDelta.size())
618 BOOST_ERROR("Mismatch between number of analytical results for delta ("
619 << analyticalResultsDelta.size() << ") and sensitivity results (" << foundDeltas << ")");
620 BOOST_TEST_MESSAGE("Checked " << foundDeltas << " deltas against analytical values (and " << zeroDeltas
621 << " deal-unrelated deltas for zero).");
622
623 // check gammas
624 BOOST_TEST_MESSAGE("Checking gammas...");
625 Size foundGammas = 0, zeroGammas = 0;
626 for (auto const& x : gammaMap) {
627 string key = x.first.first + " " + x.first.second;
628 Real scaledResult = x.second / (shiftSize * shiftSize);
629 if (analyticalResultsGamma.count(key) > 0) {
630 if (!check(analyticalResultsGamma.at(key), scaledResult))
631 BOOST_ERROR("Sensitivity analysis result " << key << " (" << scaledResult
632 << ") could not be verified against analytic result ("
633 << analyticalResultsGamma.at(key) << ")");
634 ++foundGammas;
635 } else {
636 // the sensi framework produces a Vomma, which we don't check (it isn't produced by the analytic sensi
637 // engine)
638 if (!close_enough(x.second, 0.0) && key != "5_Swaption_EUR SwaptionVolatility/EUR/47/10Y/10Y/ATM" &&
639 key != "7_FxOption_EUR_USD FXVolatility/EURUSD/0/5Y/ATM")
640 BOOST_ERROR("Sensitivity analysis result " << key << " (" << scaledResult << ") expected to be zero");
641 ++zeroGammas;
642 }
643 }
644 if (foundGammas != analyticalResultsGamma.size())
645 BOOST_ERROR("Mismatch between number of analytical results for gamma ("
646 << analyticalResultsGamma.size() << ") and sensitivity results (" << foundGammas << ")");
647 BOOST_TEST_MESSAGE("Checked " << foundGammas << " gammas against analytical values (and " << zeroGammas
648 << " deal-unrelated gammas for zero).");
649
650 // check cross gammas
651 BOOST_TEST_MESSAGE("Checking cross-gammas...");
652 Size foundCrossGammas = 0, zeroCrossGammas = 0;
653 for (const auto& [tradeId, trade] : portfolio->trades()) {
654 for (auto const& s : scenDesc) {
655 if (s.type() == ShiftScenarioGenerator::ScenarioDescription::Type::Cross) {
656 string key = tradeId + " " + s.factor1() + " " + s.factor2();
657 Real crossGamma = sa->sensiCube()->crossGamma(tradeId, make_pair(s.key1(), s.key2()));
658 Real scaledResult = crossGamma / (shiftSize * shiftSize);
659 // BOOST_TEST_MESSAGE(key << " " << scaledResult); // debug
660 if (analyticalResultsCrossGamma.count(key) > 0) {
661 if (!check(analyticalResultsCrossGamma.at(key), scaledResult))
662 BOOST_ERROR("Sensitivity analysis result "
663 << key << " (" << scaledResult
664 << ") could not be verified against analytic result ("
665 << analyticalResultsCrossGamma.at(key) << ")");
666 ++foundCrossGammas;
667 } else {
668 if (!check(crossGamma, 0.0))
669 BOOST_ERROR("Sensitivity analysis result " << key << " (" << scaledResult
670 << ") expected to be zero");
671 ++zeroCrossGammas;
672 }
673 }
674 }
675 }
676 if (foundCrossGammas != analyticalResultsCrossGamma.size())
677 BOOST_ERROR("Mismatch between number of analytical results for gamma ("
678 << analyticalResultsCrossGamma.size() << ") and sensitivity results (" << foundCrossGammas << ")");
679 BOOST_TEST_MESSAGE("Checked " << foundCrossGammas << " cross gammas against analytical values (and "
680 << zeroCrossGammas << " deal-unrelated cross gammas for zero).");
681
682 // debug: dump analytical cross gamma results
683 // for(auto const& x: analyticalResultsCrossGamma) {
684 // BOOST_TEST_MESSAGE(x.first << " " << x.second);
685 // }
686
687 ObservationMode::instance().setMode(backupMode);
688 IndexManager::instance().clearHistories();
689
690 BOOST_CHECK(true);
691}
692
693BOOST_AUTO_TEST_SUITE_END()
694
695BOOST_AUTO_TEST_SUITE_END()
Factory class for cloning scenario objects.
static const string defaultConfiguration
OREAnalytics Top level fixture.
factory class for cloning a cached scenario
Class that wraps a sensitivity stream and filters out negligible records.
A cube implementation that stores the cube in memory.
data
Filter close_enough(const RandomVariable &x, const RandomVariable &y)
QuantLib::ext::shared_ptr< Trade > buildFxOption(string id, string longShort, string putCall, Size expiry, string boughtCcy, Real boughtAmount, string soldCcy, Real soldAmount, Real premium, string premiumCcy, string premiumDate)
QuantLib::ext::shared_ptr< Trade > buildSwap(string id, string ccy, bool isPayer, Real notional, int start, Size term, Real rate, Real spread, string fixedFreq, string fixedDC, string floatFreq, string floatDC, string index, Calendar calendar, Natural spotDays, bool spotStartLag)
ore::analytics::SensitivityScenarioData::CurveShiftParData createCurveData()
The base NPV cube class.
Singleton class to hold global Observation Mode.
Fixture that can be used at top level of OREAnalytics test suites.
Perform parametric var calculation for a given portfolio.
risk class and type filter
A Market class that can be updated by Scenarios.
A class to hold Scenario parameters for scenarioSimMarket.
Class for aggregating SensitivityRecords.
Perform sensitivity analysis for a given portfolio.
Class for streaming SensitivityRecords from a SensitivityCube.
Class for streaming SensitivityRecords from file.
Class for streaming SensitivityRecords from in-memory container.
Struct for holding a sensitivity record.
A class to hold the parametrisation for building sensitivity scenarios.
Sensitivity scenario generation.
Base class for sensitivity record streamer.
BOOST_AUTO_TEST_CASE(testSensitivities)
perform a stress testing analysis for a given portfolio.
QuantLib::ext::shared_ptr< SensitivityScenarioData > setupSensitivityScenarioData5(bool parConversion)
QuantLib::ext::shared_ptr< analytics::ScenarioSimMarketParameters > setupSimMarketData5()
The counterparty cube calculator interface.
The cube valuation core.