Logo
Fully annotated reference manual - version 1.8.12
Loading...
Searching...
No Matches
Functions
sensitivityvsanalytic.cpp File Reference
#include <oret/toplevelfixture.hpp>
#include <test/oreatoplevelfixture.hpp>
#include "testmarket.hpp"
#include "testportfolio.hpp"
#include <orea/scenario/deltascenariofactory.hpp>
#include <qle/pricingengines/analyticeuropeanenginedeltagamma.hpp>
#include <qle/pricingengines/blackswaptionenginedeltagamma.hpp>
#include <qle/pricingengines/discountingswapenginedeltagamma.hpp>
#include <orea/cube/inmemorycube.hpp>
#include <orea/cube/npvcube.hpp>
#include <orea/engine/filteredsensitivitystream.hpp>
#include <orea/engine/observationmode.hpp>
#include <orea/engine/parametricvar.hpp>
#include <orea/engine/riskfilter.hpp>
#include <orea/engine/sensitivityaggregator.hpp>
#include <orea/engine/sensitivityanalysis.hpp>
#include <orea/engine/sensitivitycubestream.hpp>
#include <orea/engine/sensitivityfilestream.hpp>
#include <orea/engine/sensitivityinmemorystream.hpp>
#include <orea/engine/sensitivityrecord.hpp>
#include <orea/engine/sensitivitystream.hpp>
#include <orea/engine/stresstest.hpp>
#include <orea/engine/valuationcalculator.hpp>
#include <orea/engine/valuationengine.hpp>
#include <orea/scenario/scenariosimmarket.hpp>
#include <orea/scenario/scenariosimmarketparameters.hpp>
#include <orea/scenario/sensitivityscenariodata.hpp>
#include <orea/scenario/sensitivityscenariogenerator.hpp>
#include <ored/model/lgmdata.hpp>
#include <ored/portfolio/builders/capfloor.hpp>
#include <ored/portfolio/builders/fxforward.hpp>
#include <ored/portfolio/builders/fxoption.hpp>
#include <ored/portfolio/builders/swap.hpp>
#include <ored/portfolio/builders/swaption.hpp>
#include <ored/portfolio/fxoption.hpp>
#include <ored/portfolio/portfolio.hpp>
#include <ored/portfolio/swap.hpp>
#include <ored/portfolio/swaption.hpp>
#include <ored/utilities/log.hpp>
#include <ored/utilities/osutils.hpp>
#include <ql/math/randomnumbers/mt19937uniformrng.hpp>
#include <ql/time/calendars/target.hpp>
#include <ql/time/date.hpp>
#include <ql/time/daycounters/actualactual.hpp>

Go to the source code of this file.

Functions

 BOOST_AUTO_TEST_CASE (testSensitivities)
 

Function Documentation

◆ BOOST_AUTO_TEST_CASE()

BOOST_AUTO_TEST_CASE ( testSensitivities  )

Definition at line 406 of file sensitivityvsanalytic.cpp.

406 {
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}
static const string defaultConfiguration
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)
QuantLib::ext::shared_ptr< SensitivityScenarioData > setupSensitivityScenarioData5(bool parConversion)
QuantLib::ext::shared_ptr< analytics::ScenarioSimMarketParameters > setupSimMarketData5()
+ Here is the call graph for this function: