From c1241da7e2938daa45e36fdaf005d70716405008 Mon Sep 17 00:00:00 2001 From: Luigi Ballabio Date: Wed, 17 Jul 2024 11:02:32 +0200 Subject: [PATCH] Allow forward price as additional result and in implied vol calculation --- ql/instruments/swaption.cpp | 13 +++++++++---- ql/instruments/swaption.hpp | 4 +++- test-suite/swaption.cpp | 28 ++++++++++++++++++++-------- 3 files changed, 32 insertions(+), 13 deletions(-) diff --git a/ql/instruments/swaption.cpp b/ql/instruments/swaption.cpp index 016758d3a02..696d981932e 100644 --- a/ql/instruments/swaption.cpp +++ b/ql/instruments/swaption.cpp @@ -180,20 +180,25 @@ namespace QuantLib { } Volatility Swaption::impliedVolatility(Real targetValue, - const Handle& d, + const Handle& discountCurve, Volatility guess, Real accuracy, Natural maxEvaluations, Volatility minVol, Volatility maxVol, VolatilityType type, - Real displacement) const { + Real displacement, + PriceType priceType) const { QL_REQUIRE(!isExpired(), "instrument expired"); QL_REQUIRE(exercise_->type() == Exercise::European, "not a European option"); - ImpliedSwaptionVolHelper f(*this, d, targetValue, displacement, type); - //Brent solver; + if (priceType == Forward) { + // convert to spot + targetValue *= discountCurve->discount(exercise_->date(0)); + } + + ImpliedSwaptionVolHelper f(*this, discountCurve, targetValue, displacement, type); NewtonSafe solver; solver.setMaxEvaluations(maxEvaluations); return solver.solve(f, accuracy, guess, minVol, maxVol); diff --git a/ql/instruments/swaption.hpp b/ql/instruments/swaption.hpp index a9727894645..4360f1abaca 100644 --- a/ql/instruments/swaption.hpp +++ b/ql/instruments/swaption.hpp @@ -87,6 +87,7 @@ namespace QuantLib { */ class Swaption : public Option { public: + enum PriceType { Spot, Forward }; class arguments; class engine; Swaption(ext::shared_ptr swap, @@ -131,7 +132,8 @@ namespace QuantLib { Volatility minVol = 1.0e-7, Volatility maxVol = 4.0, VolatilityType type = ShiftedLognormal, - Real displacement = 0.0) const; + Real displacement = 0.0, + PriceType priceType = Spot) const; private: // arguments ext::shared_ptr swap_; diff --git a/test-suite/swaption.cpp b/test-suite/swaption.cpp index d3aa8eeabe0..a027692771d 100644 --- a/test-suite/swaption.cpp +++ b/test-suite/swaption.cpp @@ -831,6 +831,7 @@ BOOST_AUTO_TEST_CASE(testImpliedVolatility, *precondition(if_speed(Faster))) { Settlement::Type types[] = { Settlement::Physical, Settlement::Cash }; Settlement::Method methods[] = { Settlement::PhysicalOTC, Settlement::ParYieldCurve }; + Swaption::PriceType priceTypes[] = { Swaption::Spot, Swaption::Forward }; // test data Rate strikes[] = { 0.02, 0.03, 0.04, 0.05, 0.06, 0.07 }; Volatility vols[] = { 0.01, 0.05, 0.10, 0.20, 0.30, 0.70, 0.90 }; @@ -851,12 +852,13 @@ BOOST_AUTO_TEST_CASE(testImpliedVolatility, *precondition(if_speed(Faster))) { .withFloatingLegSpread(0.0) .withType(k); for (Size h=0; h swaption = vars.makeSwaption(swap, exerciseDate, vol, types[h], methods[h], BlackSwaptionEngine::DiscountCurve); // Black price - Real value = swaption->NPV(); + Real value = priceType == Swaption::Spot? swaption->NPV() : swaption->result("forwardPrice"); Volatility implVol = 0.0; try { implVol = @@ -868,11 +870,12 @@ BOOST_AUTO_TEST_CASE(testImpliedVolatility, *precondition(if_speed(Faster))) { 1.0e-7, 4.0, ShiftedLognormal, - 0.0); + 0.0, + priceType); } catch (std::exception& e) { // couldn't bracket? swaption->setPricingEngine(vars.makeEngine(0.0, BlackSwaptionEngine::DiscountCurve)); - Real value2 = swaption->NPV(); + Real value2 = priceType == Swaption::Spot? swaption->NPV() : swaption->result("forwardPrice"); if (std::fabs(value-value2) < tolerance) { // ok, just skip: continue; @@ -885,12 +888,13 @@ BOOST_AUTO_TEST_CASE(testImpliedVolatility, *precondition(if_speed(Faster))) { << "\natm level: " << io::rate(swap->fairRate()) << "\nvol: " << io::volatility(vol) << "\nprice: " << value << "\n" + << "\ntype: " << (priceType == Swaption::Spot? "spot" : "forward") << "\n" << e.what()); } if (std::fabs(implVol - vol) > tolerance) { // the difference might not matter swaption->setPricingEngine(vars.makeEngine(implVol, BlackSwaptionEngine::DiscountCurve)); - Real value2 = swaption->NPV(); + Real value2 = priceType == Swaption::Spot? swaption->NPV() : swaption->result("forwardPrice"); if (std::fabs(value-value2) > tolerance) { BOOST_ERROR("implied vol failure: " << exercise << "x" << length << " " << k @@ -899,11 +903,13 @@ BOOST_AUTO_TEST_CASE(testImpliedVolatility, *precondition(if_speed(Faster))) { << "\natm level: " << io::rate(swap->fairRate()) << "\nvol: " << io::volatility(vol) << "\nprice: " << value + << "\ntype: " << (priceType == Swaption::Spot? "spot" : "forward") << "\n" << "\nimplied vol: " << io::volatility(implVol) << "\nimplied price: " << value2); } } } + } } } } @@ -923,6 +929,7 @@ BOOST_AUTO_TEST_CASE(testImpliedVolatilityOis, *precondition(if_speed(Fast))) { Settlement::Type types[] = { Settlement::Physical, Settlement::Cash }; Settlement::Method methods[] = { Settlement::PhysicalOTC, Settlement::ParYieldCurve }; + Swaption::PriceType priceTypes[] = { Swaption::Spot, Swaption::Forward }; // test data Rate strikes[] = { 0.02, 0.03, 0.04, 0.05, 0.06, 0.07 }; Volatility vols[] = { 0.01, 0.05, 0.10, 0.20, 0.30, 0.70, 0.90 }; @@ -942,12 +949,13 @@ BOOST_AUTO_TEST_CASE(testImpliedVolatilityOis, *precondition(if_speed(Fast))) { .withFixedLegDayCount(vars.fixedDayCount) .withType(k); for (Size h=0; h swaption = vars.makeOISwaption(swap, exerciseDate, vol, types[h], methods[h], BlackSwaptionEngine::DiscountCurve); // Black price - Real value = swaption->NPV(); + Real value = priceType == Swaption::Spot? swaption->NPV() : swaption->result("forwardPrice"); Volatility implVol = 0.0; try { implVol = @@ -959,11 +967,12 @@ BOOST_AUTO_TEST_CASE(testImpliedVolatilityOis, *precondition(if_speed(Fast))) { 1.0e-7, 4.0, ShiftedLognormal, - 0.0); + 0.0, + priceType); } catch (std::exception& e) { // couldn't bracket? swaption->setPricingEngine(vars.makeEngine(0.0, BlackSwaptionEngine::DiscountCurve)); - Real value2 = swaption->NPV(); + Real value2 = priceType == Swaption::Spot? swaption->NPV() : swaption->result("forwardPrice"); if (std::fabs(value-value2) < tolerance) { // ok, just skip: continue; @@ -976,12 +985,13 @@ BOOST_AUTO_TEST_CASE(testImpliedVolatilityOis, *precondition(if_speed(Fast))) { << "\natm level: " << io::rate(swap->fairRate()) << "\nvol: " << io::volatility(vol) << "\nprice: " << value << "\n" + << "\ntype: " << (priceType == Swaption::Spot? "spot" : "forward") << "\n" << e.what()); } if (std::fabs(implVol - vol) > tolerance) { // the difference might not matter swaption->setPricingEngine(vars.makeEngine(implVol, BlackSwaptionEngine::DiscountCurve)); - Real value2 = swaption->NPV(); + Real value2 = priceType == Swaption::Spot? swaption->NPV() : swaption->result("forwardPrice"); if (std::fabs(value-value2) > tolerance) { BOOST_ERROR("implied vol failure: " << exercise << "x" << length << " " << k @@ -990,11 +1000,13 @@ BOOST_AUTO_TEST_CASE(testImpliedVolatilityOis, *precondition(if_speed(Fast))) { << "\natm level: " << io::rate(swap->fairRate()) << "\nvol: " << io::volatility(vol) << "\nprice: " << value + << "\ntype: " << (priceType == Swaption::Spot? "spot" : "forward") << "\n" << "\nimplied vol: " << io::volatility(implVol) << "\nimplied price: " << value2); } } } + } } } }