From 4de3dbbd8cc651760de29b7a961756a226f92aea Mon Sep 17 00:00:00 2001 From: jackgillett101 Date: Fri, 2 Jul 2021 22:59:56 +0800 Subject: [PATCH 1/4] Adding slow-running FDM calibration test --- test-suite/hestonslvmodel.cpp | 178 ++++++++++++++++++++++++++++++++++ test-suite/hestonslvmodel.hpp | 1 + 2 files changed, 179 insertions(+) diff --git a/test-suite/hestonslvmodel.cpp b/test-suite/hestonslvmodel.cpp index 68889fed5c6..ef4662f93b6 100644 --- a/test-suite/hestonslvmodel.cpp +++ b/test-suite/hestonslvmodel.cpp @@ -2577,6 +2577,183 @@ void HestonSLVModelTest::testDiffusionAndDriftSlvProcess() { } } +void HestonSLVModelTest::testBarrierPricingMixedModelsMonteCarloVsFdmPricing() { + BOOST_TEST_MESSAGE( + "Testing European and Barrier Pricing for Monte-Carlo and FDM " + "Pricing in Heston SLV models with a mixing factor..."); + + const Real epsilon = 0.01; + + SavedSettings backup; + const DayCounter dc = ActualActual(ActualActual::ISDA); + const Date todaysDate(1, Jul, 2021); + const Date maturityDate = todaysDate + Period(2, Years); + const Time maturity = dc.yearFraction(todaysDate, maturityDate); + Settings::instance().evaluationDate() = todaysDate; + + const Real s0 = 100; + const Handle spot(ext::make_shared(s0)); + const Rate r = 0.02; + const Rate q = 0.01; + const Real mixingFactors[] = {1.0, 0.64, 0.3}; + const std::vector& requiredDates = std::vector(); + + // Create two slightly different Heston models. The first will be our stochastic + // vol model, the second is used to create a similar implied vol surface which + // we fit a local vol model to + const Real kappa1 = 2.0; + const Real theta1 = 0.12; + const Real rho1 = -0.25; + const Real sigma1 = 0.8; + const Real v01 = 0.09; + + const Real kappa2 = 1.5; + const Real theta2 = 0.11; + const Real rho2 = -0.2; + const Real sigma2 = 0.9; + const Real v02 = 0.1; + + const Handle rTS(flatRate(r, dc)); + const Handle qTS(flatRate(q, dc)); + + const ext::shared_ptr hestonProcess + = ext::make_shared( + rTS, qTS, spot, v01, kappa1, theta1, sigma1, rho1); + + const ext::shared_ptr hestonModelPtr + = ext::make_shared(hestonProcess); + + const ext::shared_ptr hestonProcess2 + = ext::make_shared( + rTS, qTS, spot, v02, kappa2, theta2, sigma2, rho2); + + const ext::shared_ptr hestonModelPtr2 + = ext::make_shared(hestonProcess2); + + const ext::shared_ptr localVolPtr = + getFixedLocalVolFromHeston(hestonModelPtr2, + ext::make_shared(maturity, 20)); + + const Handle localVol = Handle(localVolPtr); + localVol->enableExtrapolation(); + const Handle hestonModel = Handle(hestonModelPtr); + const Handle hestonModel2 = Handle(hestonModelPtr2); + + // Create the options we will price - a vanilla and a barrier + const ext::shared_ptr exercise + = ext::make_shared(maturityDate); + + const Real strike = 100; + const ext::shared_ptr payoff = + ext::make_shared(Option::Call, strike); + + VanillaOption vanillaOption(payoff, exercise); + + const Real rebate = 0.0; + const Real barrier = 110.0; + BarrierOption barrierOption(Barrier::UpOut, barrier, rebate, payoff, exercise); + + // hestonModel2 is our simulated local vol model, so its vanilla prices + // should match the calibrated SLV model pricers + const ext::shared_ptr hestonVanillaEngine + = ext::make_shared(hestonModelPtr2); + vanillaOption.setPricingEngine(hestonVanillaEngine); + const Real localVolPrice = vanillaOption.NPV(); + + for (double mixingFactor : mixingFactors) { + + // Finite Difference calibration + const HestonSLVFokkerPlanckFdmParams logParams = { + 201, 401, 1000, 30, 2.0, 0, 2, + 0.1, 1e-4, 10000, + 1e-5, 1e-5, 0.0000025, 1.0, 0.1, 0.9, 1e-5, + FdmHestonGreensFct::Gaussian, + FdmSquareRootFwdOp::Log, + FdmSchemeDesc::ModifiedCraigSneyd() + }; + + const ext::shared_ptr leverageFctFDM = + HestonSLVFDMModel( + localVol, hestonModel, maturityDate, logParams, false, requiredDates, + mixingFactor).leverageFunction(); + + // Monte-Carlo calibration + const Size timeStepsPerYear = 365; + const Size nBins = 201; + const Size calibrationPaths = 65536; + + const ext::shared_ptr leverageFctMC = + HestonSLVMCModel( + localVol, hestonModel, + ext::shared_ptr(new MTBrownianGeneratorFactory(1234UL)), + maturityDate, timeStepsPerYear, nBins, calibrationPaths, requiredDates, + mixingFactor).leverageFunction(); + + // Create SLV pricing engines with both leverage functions + const ext::shared_ptr fdEngineWithMixingFactor + = ext::make_shared( + hestonModelPtr, 100, 100, 50, 0, + FdmSchemeDesc::Hundsdorfer(), leverageFctFDM, mixingFactor); + + const ext::shared_ptr mcEngineWithMixingFactor + = ext::make_shared( + hestonModelPtr, 100, 100, 50, 0, + FdmSchemeDesc::Hundsdorfer(), leverageFctMC, mixingFactor); + + const ext::shared_ptr fdBarrierEngineWithMixingFactor + = ext::make_shared( + hestonModelPtr, 100, 100, 50, 0, + FdmSchemeDesc::Hundsdorfer(), leverageFctFDM, mixingFactor); + + const ext::shared_ptr mcBarrierEngineWithMixingFactor + = ext::make_shared( + hestonModelPtr, 100, 100, 50, 0, + FdmSchemeDesc::Hundsdorfer(), leverageFctMC, mixingFactor); + + // Price the vanilla and barrier with both engines + vanillaOption.setPricingEngine(fdEngineWithMixingFactor); + const Real priceFDM = vanillaOption.NPV(); + + vanillaOption.setPricingEngine(mcEngineWithMixingFactor); + const Real priceMC = vanillaOption.NPV(); + + barrierOption.setPricingEngine(fdBarrierEngineWithMixingFactor); + const Real barrierPriceFDM = barrierOption.NPV(); + + barrierOption.setPricingEngine(mcBarrierEngineWithMixingFactor); + const Real barrierPriceMC = barrierOption.NPV(); + + // Check MC and FDM vanilla prices against local vol, and ensure that the barrier + // prices from MC and FDM are also consistent + if (relativeError(priceFDM, localVolPrice, localVolPrice) > epsilon) { + BOOST_ERROR("FDM price does not match with Local Vol" + << "\n Local Vol Price: " << localVolPrice + << "\n FDM Price: " << priceFDM + << "\n Relative Error: " << relativeError(priceFDM, localVolPrice, localVolPrice) + << "\n Allowed Error: " << epsilon + << "\n Mixing Factor: " << mixingFactor); + } + + if (relativeError(priceMC, localVolPrice, localVolPrice) > epsilon) { + BOOST_ERROR("MC price does not match with Local Vol" + << "\n Local Vol Price: " << localVolPrice + << "\n MC Price: " << priceMC + << "\n Relative Error: " << relativeError(priceMC, localVolPrice, localVolPrice) + << "\n Allowed Error: " << epsilon + << "\n Mixing Factor: " << mixingFactor); + } + + if (relativeError(barrierPriceFDM, barrierPriceMC, barrierPriceMC) > epsilon) { + BOOST_ERROR("FDM Barrier Price does not match MC Barrier Price" + << "\n FDM Barrier Price: " << barrierPriceFDM + << "\n MC Barrier Price: " << barrierPriceMC + << "\n Relative Error: " << relativeError(barrierPriceFDM, barrierPriceMC, barrierPriceMC) + << "\n Allowed Error: " << epsilon + << "\n Mixing Factor: " << mixingFactor); + } + } +} + test_suite* HestonSLVModelTest::experimental(SpeedLevel speed) { auto* suite = BOOST_TEST_SUITE("Heston Stochastic Local Volatility tests"); @@ -2600,6 +2777,7 @@ test_suite* HestonSLVModelTest::experimental(SpeedLevel speed) { suite->add(QUANTLIB_TEST_CASE(&HestonSLVModelTest::testMonteCarloCalibration)); suite->add(QUANTLIB_TEST_CASE(&HestonSLVModelTest::testBlackScholesFokkerPlanckFwdEquationLocalVol)); suite->add(QUANTLIB_TEST_CASE(&HestonSLVModelTest::testMoustacheGraph)); + suite->add(QUANTLIB_TEST_CASE(&HestonSLVModelTest::testBarrierPricingMixedModelsMonteCarloVsFdmPricing)); } // these tests take very long diff --git a/test-suite/hestonslvmodel.hpp b/test-suite/hestonslvmodel.hpp index 00e49715f50..d8ffe89c179 100644 --- a/test-suite/hestonslvmodel.hpp +++ b/test-suite/hestonslvmodel.hpp @@ -43,6 +43,7 @@ class HestonSLVModelTest { static void testBarrierPricingViaHestonLocalVol(); static void testBarrierPricingMixedModels(); static void testMonteCarloVsFdmPricing(); + static void testBarrierPricingMixedModelsMonteCarloVsFdmPricing(); static void testMonteCarloCalibration(); static void testMoustacheGraph(); static void testForwardSkewSLV(); From cd79c6bda8d1667773f0c122e90b421cf640f901 Mon Sep 17 00:00:00 2001 From: jackgillett101 Date: Fri, 2 Jul 2021 23:37:41 +0800 Subject: [PATCH 2/4] mixingFactor missing from one instance of the FDM calculator --- ql/experimental/models/hestonslvfdmmodel.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ql/experimental/models/hestonslvfdmmodel.cpp b/ql/experimental/models/hestonslvfdmmodel.cpp index c80f08802cf..dfd3bce9b29 100644 --- a/ql/experimental/models/hestonslvfdmmodel.cpp +++ b/ql/experimental/models/hestonslvfdmmodel.cpp @@ -453,7 +453,7 @@ namespace QuantLib { hestonFwdOp = ext::shared_ptr( new FdmHestonFwdOp(mesher, hestonProcess, - trafoType, leverageFct)); + trafoType, leverageFct, mixingFactor_)); } Array pn = p; From 70e8886bf752aa44ca86cf27b66ad1fee5c08d48 Mon Sep 17 00:00:00 2001 From: jackgillett101 Date: Sat, 3 Jul 2021 21:29:46 +0800 Subject: [PATCH 3/4] Using LD instead of PR to remove calculation noise cross-platform --- test-suite/hestonslvmodel.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/test-suite/hestonslvmodel.cpp b/test-suite/hestonslvmodel.cpp index ef4662f93b6..76e0180d790 100644 --- a/test-suite/hestonslvmodel.cpp +++ b/test-suite/hestonslvmodel.cpp @@ -2582,7 +2582,7 @@ void HestonSLVModelTest::testBarrierPricingMixedModelsMonteCarloVsFdmPricing() { "Testing European and Barrier Pricing for Monte-Carlo and FDM " "Pricing in Heston SLV models with a mixing factor..."); - const Real epsilon = 0.01; + const Real epsilon = 0.015; SavedSettings backup; const DayCounter dc = ActualActual(ActualActual::ISDA); @@ -2660,6 +2660,10 @@ void HestonSLVModelTest::testBarrierPricingMixedModelsMonteCarloVsFdmPricing() { vanillaOption.setPricingEngine(hestonVanillaEngine); const Real localVolPrice = vanillaOption.NPV(); + const ext::shared_ptr sobolGeneratorFactory( + ext::make_shared(SobolBrownianGenerator::Diagonal, 1234UL, + SobolRsg::JoeKuoD7)); + for (double mixingFactor : mixingFactors) { // Finite Difference calibration @@ -2685,7 +2689,7 @@ void HestonSLVModelTest::testBarrierPricingMixedModelsMonteCarloVsFdmPricing() { const ext::shared_ptr leverageFctMC = HestonSLVMCModel( localVol, hestonModel, - ext::shared_ptr(new MTBrownianGeneratorFactory(1234UL)), + sobolGeneratorFactory, maturityDate, timeStepsPerYear, nBins, calibrationPaths, requiredDates, mixingFactor).leverageFunction(); @@ -2777,7 +2781,7 @@ test_suite* HestonSLVModelTest::experimental(SpeedLevel speed) { suite->add(QUANTLIB_TEST_CASE(&HestonSLVModelTest::testMonteCarloCalibration)); suite->add(QUANTLIB_TEST_CASE(&HestonSLVModelTest::testBlackScholesFokkerPlanckFwdEquationLocalVol)); suite->add(QUANTLIB_TEST_CASE(&HestonSLVModelTest::testMoustacheGraph)); - suite->add(QUANTLIB_TEST_CASE(&HestonSLVModelTest::testBarrierPricingMixedModelsMonteCarloVsFdmPricing)); + suite->add(QUANTLIB_TEST_CASE(&HestonSLVModelTest::testBarrierPricingMixedModelsMonteCarloVsFdmPricing)); // ~250s } // these tests take very long From 4352233656c88651919c038e65f45418099fb5d7 Mon Sep 17 00:00:00 2001 From: jackgillett101 Date: Sat, 3 Jul 2021 23:24:30 +0800 Subject: [PATCH 4/4] Commenting out slow-running test --- test-suite/hestonslvmodel.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-suite/hestonslvmodel.cpp b/test-suite/hestonslvmodel.cpp index 76e0180d790..3a51e060db0 100644 --- a/test-suite/hestonslvmodel.cpp +++ b/test-suite/hestonslvmodel.cpp @@ -2781,13 +2781,13 @@ test_suite* HestonSLVModelTest::experimental(SpeedLevel speed) { suite->add(QUANTLIB_TEST_CASE(&HestonSLVModelTest::testMonteCarloCalibration)); suite->add(QUANTLIB_TEST_CASE(&HestonSLVModelTest::testBlackScholesFokkerPlanckFwdEquationLocalVol)); suite->add(QUANTLIB_TEST_CASE(&HestonSLVModelTest::testMoustacheGraph)); - suite->add(QUANTLIB_TEST_CASE(&HestonSLVModelTest::testBarrierPricingMixedModelsMonteCarloVsFdmPricing)); // ~250s } // these tests take very long // suite->add(QUANTLIB_TEST_CASE(&HestonSLVModelTest::testForwardSkewSLV)); // suite->add(QUANTLIB_TEST_CASE(&HestonSLVModelTest::testFDMCalibration)); // suite->add(QUANTLIB_TEST_CASE(&HestonSLVModelTest::testBarrierPricingMixedModels)); +// suite->add(QUANTLIB_TEST_CASE(&HestonSLVModelTest::testBarrierPricingMixedModelsMonteCarloVsFdmPricing)); // ~250s return suite; }