19#include <oret/toplevelfixture.hpp>
25#include <qle/pricingengines/analyticeuropeanenginedeltagamma.hpp>
26#include <qle/pricingengines/blackswaptionenginedeltagamma.hpp>
27#include <qle/pricingengines/discountingswapenginedeltagamma.hpp>
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>
71using namespace boost::unit_test_framework;
79QuantLib::ext::shared_ptr<ore::data::Conventions> conv() {
82 QuantLib::ext::shared_ptr<ore::data::Convention> swapIndexConv(
84 conventions->add(swapIndexConv);
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"));
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"));
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"));
151 QuantLib::ext::shared_ptr<analytics::ScenarioSimMarketParameters> simMarketData(
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";
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);
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);
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});
178 simMarketData->setFxCcyPairs({
"EURUSD",
"EURGBP",
"EURCHF",
"EURJPY"});
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});
187 return simMarketData;
258QuantLib::ext::shared_ptr<ore::analytics::SensitivityScenarioData::CurveShiftParData>
createCurveData() {
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};
269 cvsData.
parInstruments = {
"DEP",
"IRS",
"IRS",
"IRS",
"IRS",
"IRS",
"IRS",
"IRS",
"IRS"};
271 return QuantLib::ext::make_shared<ore::analytics::SensitivityScenarioData::CurveShiftParData>(cvsData);
274 QuantLib::ext::shared_ptr<SensitivityScenarioData> sensiData =
275 QuantLib::ext::make_shared<ore::analytics::SensitivityScenarioData>(parConversion);
282 fxvsData.
shiftType = ShiftType::Absolute;
287 cfvsData.
shiftType = ShiftType::Absolute;
289 cfvsData.
shiftExpiries = {1 * Years, 2 * Years, 3 * Years, 5 * Years, 10 * Years};
293 swvsData.
shiftType = ShiftType::Absolute;
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};
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;
306 cvsData->parInstrumentSingleCurve =
true;
307 cvsData->parInstrumentConventions[
"DEP"] =
"USD-DEP-CONVENTIONS";
308 cvsData->parInstrumentConventions[
"IRS"] =
"USD-3M-SWAP-CONVENTIONS";
309 sensiData->discountCurveShiftData()[
"USD"] = cvsData;
312 cvsData->parInstrumentSingleCurve =
true;
313 cvsData->parInstrumentConventions[
"DEP"] =
"GBP-DEP-CONVENTIONS";
314 cvsData->parInstrumentConventions[
"IRS"] =
"GBP-6M-SWAP-CONVENTIONS";
315 sensiData->discountCurveShiftData()[
"GBP"] = cvsData;
318 cvsData->parInstrumentSingleCurve =
true;
319 cvsData->parInstrumentConventions[
"DEP"] =
"JPY-DEP-CONVENTIONS";
320 cvsData->parInstrumentConventions[
"IRS"] =
"JPY-6M-SWAP-CONVENTIONS";
321 sensiData->discountCurveShiftData()[
"JPY"] = cvsData;
324 cvsData->parInstrumentSingleCurve =
true;
325 cvsData->parInstrumentConventions[
"DEP"] =
"CHF-DEP-CONVENTIONS";
326 cvsData->parInstrumentConventions[
"IRS"] =
"CHF-6M-SWAP-CONVENTIONS";
327 sensiData->discountCurveShiftData()[
"CHF"] = cvsData;
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;
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;
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;
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;
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;
359 sensiData->fxShiftData()[
"EURUSD"] = fxsData;
360 sensiData->fxShiftData()[
"EURGBP"] = fxsData;
361 sensiData->fxShiftData()[
"EURJPY"] = fxsData;
362 sensiData->fxShiftData()[
"EURCHF"] = fxsData;
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;
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;
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";
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"}};
392bool check(
const Real reference,
const Real value) {
393 if (std::fabs(reference) >= 1E-2) {
394 return std::fabs((reference - value) / reference) < 5E-3;
396 return std::fabs(reference - value) < 1E-3;
404BOOST_AUTO_TEST_SUITE(SensitivityVsAnalyticSensiEnginesTest)
408 BOOST_TEST_MESSAGE(
"Checking sensitivity analysis results vs analytic sensi engine results...");
410 SavedSettings backup;
413 ObservationMode::instance().setMode(ObservationMode::Mode::None);
415 Date today = Date(14, April, 2016);
416 Settings::instance().evaluationDate() = today;
418 BOOST_TEST_MESSAGE(
"Today is " << today);
421 QuantLib::ext::shared_ptr<Market> initMarket = QuantLib::ext::make_shared<testsuite::TestMarket>(today);
424 QuantLib::ext::shared_ptr<analytics::ScenarioSimMarketParameters> simMarketData =
setupSimMarketData5();
430 InstrumentConventions::instance().setConventions(conv());
431 QuantLib::ext::shared_ptr<analytics::ScenarioSimMarket> simMarket =
432 QuantLib::ext::make_shared<analytics::ScenarioSimMarket>(initMarket, simMarketData);
435 QuantLib::ext::shared_ptr<Scenario> baseScenario = simMarket->baseScenario();
436 QuantLib::ext::shared_ptr<ScenarioFactory> scenarioFactory = QuantLib::ext::make_shared<DeltaScenarioFactory>(baseScenario);
439 QuantLib::ext::shared_ptr<SensitivityScenarioGenerator> scenarioGenerator =
440 QuantLib::ext::make_shared<SensitivityScenarioGenerator>(sensiData, baseScenario, simMarketData, simMarket,
441 scenarioFactory,
false);
442 simMarket->scenarioGenerator() = scenarioGenerator;
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);
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"));
459 portfolio->build(factory);
461 BOOST_TEST_MESSAGE(
"Portfolio size after build: " << portfolio->size());
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);
470 for (
auto const& p : sensiData->discountCurveShiftData()[
"EUR"]->shiftTenors) {
471 bucketTimes.push_back(dc.yearFraction(today, today + p));
474 bucketStr.push_back(bs.str());
477 numStr.push_back(num.str());
479 for (
auto const& p : sensiData->swaptionVolShiftData()[
"EUR"].shiftExpiries) {
480 bucketTimesVegaOpt.push_back(dc.yearFraction(today, today + p));
482 for (
auto const& p : sensiData->swaptionVolShiftData()[
"EUR"].shiftTerms) {
483 bucketTimesVegaUnd.push_back(dc.yearFraction(today, today + p));
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;
491 bucketStrVega.push_back(bs.str());
492 numStrVega.push_back(num.str());
496 for (
auto const& p : sensiData->fxVolShiftData()[
"EURUSD"].shiftExpiries) {
497 bucketTimesFxOpt.push_back(dc.yearFraction(today, today + p));
498 ostringstream bs, num;
501 bucketStrFxVega.push_back(bs.str());
502 numStrFxVega.push_back(num.str());
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");
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();
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]);
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;
541 analyticalResultsDelta[
"7_FxOption_EUR_USD " + dscKey2[i]] = deltaRate2[i] * 10.0 / fxspot;
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;
549 analyticalResultsGamma[
"7_FxOption_EUR_USD " + dscKey2[i]] = gamma2[i][i] * 10.0 / fxspot;
550 for (Size j = 0; j < n; ++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;
556 analyticalResultsCrossGamma[
"7_FxOption_EUR_USD " + dscKey2[i] +
" " + dscKey2[j]] =
557 gamma2[i][j] * 10.0 / fxspot;
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;
569 analyticalResultsDelta[
"7_FxOption_EUR_USD FXSpot/EURUSD/0/spot"] =
570 10.0 * (deltaSpot2 / fxspot - fxnpv / (fxspot * fxspot));
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);
576 analyticalResultsDelta[
"7_FxOption_EUR_USD FXVolatility/EURUSD/0/5Y/ATM"] =
577 vega2.front() * 10.0 / fxspot;
580 QuantLib::ext::shared_ptr<SensitivityAnalysis> sa = QuantLib::ext::make_shared<SensitivityAnalysis>(
582 sa->generateSensitivities();
583 map<pair<string, string>, Real> deltaMap;
584 map<pair<string, string>, Real> gammaMap;
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);
595 std::vector<ore::analytics::SensitivityScenarioGenerator::ScenarioDescription> scenDesc =
596 sa->scenarioGenerator()->scenarioDescriptions();
597 Real shiftSize = 1E-5;
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) <<
")");
613 BOOST_ERROR(
"Sensitivity analysis result " << key <<
" (" << scaledResult <<
") expected to be zero");
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).");
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) <<
")");
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");
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).");
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);
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) <<
")");
668 if (!
check(crossGamma, 0.0))
669 BOOST_ERROR(
"Sensitivity analysis result " << key <<
" (" << scaledResult
670 <<
") expected to be zero");
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).");
687 ObservationMode::instance().setMode(backupMode);
688 IndexManager::instance().clearHistories();
693BOOST_AUTO_TEST_SUITE_END()
695BOOST_AUTO_TEST_SUITE_END()
Factory class for cloning scenario objects.
ScenarioSimMarket description.
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.
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()
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.
vector< Period > shiftTenors
vector< string > parInstruments
vector< Period > shiftTerms
vector< Real > shiftStrikes
vector< Period > shiftExpiries
QuantLib::ext::shared_ptr< SensitivityScenarioData > setupSensitivityScenarioData5(bool parConversion)
QuantLib::ext::shared_ptr< analytics::ScenarioSimMarketParameters > setupSimMarketData5()
The counterparty cube calculator interface.