From 7d4de7722b4d007be8e26bd292eba9fd8a69f3e2 Mon Sep 17 00:00:00 2001 From: Kai Lin Date: Mon, 19 Aug 2024 12:51:43 +1000 Subject: [PATCH 1/5] Allow constraints in FittedBondDiscountCurve::FittingMethod (#1954) --- .../yield/fittedbonddiscountcurve.cpp | 11 +++- .../yield/fittedbonddiscountcurve.hpp | 13 ++++- test-suite/fittedbonddiscountcurve.cpp | 57 +++++++++++++++++++ 3 files changed, 77 insertions(+), 4 deletions(-) diff --git a/ql/termstructures/yield/fittedbonddiscountcurve.cpp b/ql/termstructures/yield/fittedbonddiscountcurve.cpp index d7f466137a5..5cd7b43895e 100644 --- a/ql/termstructures/yield/fittedbonddiscountcurve.cpp +++ b/ql/termstructures/yield/fittedbonddiscountcurve.cpp @@ -130,9 +130,11 @@ namespace QuantLib { ext::shared_ptr optimizationMethod, Array l2, const Real minCutoffTime, - const Real maxCutoffTime) + const Real maxCutoffTime, + ext::shared_ptr constraint) : constrainAtZero_(constrainAtZero), weights_(weights), l2_(std::move(l2)), calculateWeights_(weights.empty()), optimizationMethod_(std::move(optimizationMethod)), + constraint_(std::move(constraint)), minCutoffTime_(minCutoffTime), maxCutoffTime_(maxCutoffTime) {} void FittedBondDiscountCurve::FittingMethod::init() { @@ -188,7 +190,6 @@ namespace QuantLib { void FittedBondDiscountCurve::FittingMethod::calculate() { FittingCost& costFunction = *costFunction_; - Constraint constraint = NoConstraint(); // start with the guess solution, if it exists Array x(size(), 0.0); @@ -221,7 +222,11 @@ namespace QuantLib { if(!optimization){ optimization = ext::make_shared(curve_->simplexLambda_); } - Problem problem(costFunction, constraint, x); + ext::shared_ptr constraint = constraint_; + if (!constraint) { + constraint = ext::make_shared(); + } + Problem problem(costFunction, *constraint, x); Real rootEpsilon = curve_->accuracy_; Real functionEpsilon = curve_->accuracy_; diff --git a/ql/termstructures/yield/fittedbonddiscountcurve.hpp b/ql/termstructures/yield/fittedbonddiscountcurve.hpp index dd601d0868a..d02ed36de17 100644 --- a/ql/termstructures/yield/fittedbonddiscountcurve.hpp +++ b/ql/termstructures/yield/fittedbonddiscountcurve.hpp @@ -31,6 +31,7 @@ #include #include #include +#include namespace QuantLib { @@ -209,6 +210,8 @@ namespace QuantLib { Array l2() const; //! return optimization method being used ext::shared_ptr optimizationMethod() const; + //! return optimization contraint + ext::shared_ptr constraint() const; //! open discountFunction to public DiscountFactor discount(const Array& x, Time t) const; protected: @@ -219,7 +222,8 @@ namespace QuantLib { ext::shared_ptr(), Array l2 = Array(), Real minCutoffTime = 0.0, - Real maxCutoffTime = QL_MAX_REAL); + Real maxCutoffTime = QL_MAX_REAL, + ext::shared_ptr constraint = ext::shared_ptr()); //! rerun every time instruments/referenceDate changes virtual void init(); //! discount function called by FittedBondDiscountCurve @@ -257,6 +261,8 @@ namespace QuantLib { EndCriteria::Type errorCode_ = EndCriteria::None; // optimization method to be used, if none provided use Simplex ext::shared_ptr optimizationMethod_; + // optimization constraint, if none provided use NoConstraint + ext::shared_ptr constraint_; // flat extrapolation of instantaneous forward before / after cutoff Real minCutoffTime_, maxCutoffTime_; }; @@ -329,6 +335,11 @@ namespace QuantLib { return optimizationMethod_; } + inline ext::shared_ptr + FittedBondDiscountCurve::FittingMethod::constraint() const { + return constraint_; + } + inline DiscountFactor FittedBondDiscountCurve::FittingMethod::discount(const Array& x, Time t) const { if (t < minCutoffTime_) { // flat fwd extrapolation before min cutoff time diff --git a/test-suite/fittedbonddiscountcurve.cpp b/test-suite/fittedbonddiscountcurve.cpp index 61a20a98cf8..c20534358d1 100644 --- a/test-suite/fittedbonddiscountcurve.cpp +++ b/test-suite/fittedbonddiscountcurve.cpp @@ -272,6 +272,63 @@ BOOST_AUTO_TEST_CASE(testGuessSize) { ExpectedErrorMessage("wrong size for guess")); } + + + +BOOST_AUTO_TEST_CASE(testConstraint) { + + BOOST_TEST_MESSAGE("Testing that fitted bond curves check the guess size when given..."); + + class FlatZero : public FittedBondDiscountCurve::FittingMethod { + public: + FlatZero(bool forcePositive) + : FittedBondDiscountCurve::FittingMethod(true, // constrainAtZero + Array(), // weights + ext::shared_ptr(), + Array(), // l2 + 0.0, // minCutoffTime + QL_MAX_REAL, //maxCutoffTime + forcePositive ? + ext::shared_ptr(ext::make_shared()) : + ext::shared_ptr(ext::make_shared())){}; + + std::unique_ptr clone() const override { + return std::unique_ptr(new FlatZero(*this)); + } + + private: + Size size() const override { return 1; } + + DiscountFactor discountFunction(const Array& x, Time t) const override { + Real zeroRate = x[0]; + DiscountFactor d = std::exp(-zeroRate * t); + return d; + } + }; + + Date today = Settings::instance().evaluationDate(); + auto bond1 = ext::make_shared(3, TARGET(), 100.0, today + Period(1, Years)); + auto bond2 = ext::make_shared(3, TARGET(), 100.0, today + Period(2, Years)); + + std::vector> helpers(2); + helpers[0] = ext::make_shared(makeQuoteHandle(101.0), bond1); + helpers[1] = ext::make_shared(makeQuoteHandle(102.0), bond2); + + Real accuracy = 1e-10; // default value + Size maxIterations = 10000; // default value + Array guess = {0.01}; // something positive so that initial value is in feasible region + + FlatZero unconstrainedMethod(false); + FittedBondDiscountCurve unconstrainedCurve(0, TARGET(), helpers, Actual365Fixed(), unconstrainedMethod, + accuracy, maxIterations, guess); + BOOST_CHECK_LT(unconstrainedCurve.fitResults().solution()[0], 0.0); + + FlatZero positiveMethod(true); + FittedBondDiscountCurve positiveCurve(0, TARGET(), helpers, Actual365Fixed(), positiveMethod, + accuracy, maxIterations, guess); + BOOST_CHECK_GT(positiveCurve.fitResults().solution()[0], 0.0); +} + BOOST_AUTO_TEST_SUITE_END() BOOST_AUTO_TEST_SUITE_END() From 66b74d88f721a92920dc98003519468c014643ce Mon Sep 17 00:00:00 2001 From: Luigi Ballabio Date: Tue, 20 Aug 2024 11:31:13 +0200 Subject: [PATCH 2/5] No need of shared_ptr for Constraint class --- ql/termstructures/yield/fittedbonddiscountcurve.cpp | 13 ++++++------- ql/termstructures/yield/fittedbonddiscountcurve.hpp | 9 ++++----- test-suite/fittedbonddiscountcurve.cpp | 4 +--- 3 files changed, 11 insertions(+), 15 deletions(-) diff --git a/ql/termstructures/yield/fittedbonddiscountcurve.cpp b/ql/termstructures/yield/fittedbonddiscountcurve.cpp index 5cd7b43895e..2d0aa4b279f 100644 --- a/ql/termstructures/yield/fittedbonddiscountcurve.cpp +++ b/ql/termstructures/yield/fittedbonddiscountcurve.cpp @@ -131,11 +131,14 @@ namespace QuantLib { Array l2, const Real minCutoffTime, const Real maxCutoffTime, - ext::shared_ptr constraint) + Constraint constraint) : constrainAtZero_(constrainAtZero), weights_(weights), l2_(std::move(l2)), calculateWeights_(weights.empty()), optimizationMethod_(std::move(optimizationMethod)), constraint_(std::move(constraint)), - minCutoffTime_(minCutoffTime), maxCutoffTime_(maxCutoffTime) {} + minCutoffTime_(minCutoffTime), maxCutoffTime_(maxCutoffTime) { + if (constraint_.empty()) + constraint_ = NoConstraint(); + } void FittedBondDiscountCurve::FittingMethod::init() { // yield conventions @@ -222,11 +225,7 @@ namespace QuantLib { if(!optimization){ optimization = ext::make_shared(curve_->simplexLambda_); } - ext::shared_ptr constraint = constraint_; - if (!constraint) { - constraint = ext::make_shared(); - } - Problem problem(costFunction, *constraint, x); + Problem problem(costFunction, constraint_, x); Real rootEpsilon = curve_->accuracy_; Real functionEpsilon = curve_->accuracy_; diff --git a/ql/termstructures/yield/fittedbonddiscountcurve.hpp b/ql/termstructures/yield/fittedbonddiscountcurve.hpp index d02ed36de17..6db9eba8d43 100644 --- a/ql/termstructures/yield/fittedbonddiscountcurve.hpp +++ b/ql/termstructures/yield/fittedbonddiscountcurve.hpp @@ -211,7 +211,7 @@ namespace QuantLib { //! return optimization method being used ext::shared_ptr optimizationMethod() const; //! return optimization contraint - ext::shared_ptr constraint() const; + const Constraint& constraint() const; //! open discountFunction to public DiscountFactor discount(const Array& x, Time t) const; protected: @@ -223,7 +223,7 @@ namespace QuantLib { Array l2 = Array(), Real minCutoffTime = 0.0, Real maxCutoffTime = QL_MAX_REAL, - ext::shared_ptr constraint = ext::shared_ptr()); + Constraint constraint = NoConstraint()); //! rerun every time instruments/referenceDate changes virtual void init(); //! discount function called by FittedBondDiscountCurve @@ -262,7 +262,7 @@ namespace QuantLib { // optimization method to be used, if none provided use Simplex ext::shared_ptr optimizationMethod_; // optimization constraint, if none provided use NoConstraint - ext::shared_ptr constraint_; + Constraint constraint_; // flat extrapolation of instantaneous forward before / after cutoff Real minCutoffTime_, maxCutoffTime_; }; @@ -335,8 +335,7 @@ namespace QuantLib { return optimizationMethod_; } - inline ext::shared_ptr - FittedBondDiscountCurve::FittingMethod::constraint() const { + inline const Constraint& FittedBondDiscountCurve::FittingMethod::constraint() const { return constraint_; } diff --git a/test-suite/fittedbonddiscountcurve.cpp b/test-suite/fittedbonddiscountcurve.cpp index c20534358d1..bdd61a72225 100644 --- a/test-suite/fittedbonddiscountcurve.cpp +++ b/test-suite/fittedbonddiscountcurve.cpp @@ -288,9 +288,7 @@ BOOST_AUTO_TEST_CASE(testConstraint) { Array(), // l2 0.0, // minCutoffTime QL_MAX_REAL, //maxCutoffTime - forcePositive ? - ext::shared_ptr(ext::make_shared()) : - ext::shared_ptr(ext::make_shared())){}; + forcePositive ? Constraint(PositiveConstraint()) : Constraint(NoConstraint())){}; std::unique_ptr clone() const override { return std::unique_ptr(new FlatZero(*this)); From 3773b64950529de0e2c7a3d05b804797d93351a7 Mon Sep 17 00:00:00 2001 From: Luigi Ballabio Date: Tue, 20 Aug 2024 11:37:36 +0200 Subject: [PATCH 3/5] Clean up example --- test-suite/fittedbonddiscountcurve.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/test-suite/fittedbonddiscountcurve.cpp b/test-suite/fittedbonddiscountcurve.cpp index bdd61a72225..aa56fbee269 100644 --- a/test-suite/fittedbonddiscountcurve.cpp +++ b/test-suite/fittedbonddiscountcurve.cpp @@ -277,18 +277,18 @@ BOOST_AUTO_TEST_CASE(testGuessSize) { BOOST_AUTO_TEST_CASE(testConstraint) { - BOOST_TEST_MESSAGE("Testing that fitted bond curves check the guess size when given..."); - + BOOST_TEST_MESSAGE("Testing that fitted bond curves respect passed constraint..."); + class FlatZero : public FittedBondDiscountCurve::FittingMethod { public: - FlatZero(bool forcePositive) + FlatZero(Constraint constraint = NoConstraint()) : FittedBondDiscountCurve::FittingMethod(true, // constrainAtZero Array(), // weights ext::shared_ptr(), Array(), // l2 0.0, // minCutoffTime QL_MAX_REAL, //maxCutoffTime - forcePositive ? Constraint(PositiveConstraint()) : Constraint(NoConstraint())){}; + std::move(constraint)) {} std::unique_ptr clone() const override { return std::unique_ptr(new FlatZero(*this)); @@ -316,12 +316,12 @@ BOOST_AUTO_TEST_CASE(testConstraint) { Size maxIterations = 10000; // default value Array guess = {0.01}; // something positive so that initial value is in feasible region - FlatZero unconstrainedMethod(false); + FlatZero unconstrainedMethod; FittedBondDiscountCurve unconstrainedCurve(0, TARGET(), helpers, Actual365Fixed(), unconstrainedMethod, accuracy, maxIterations, guess); BOOST_CHECK_LT(unconstrainedCurve.fitResults().solution()[0], 0.0); - FlatZero positiveMethod(true); + FlatZero positiveMethod{PositiveConstraint()}; FittedBondDiscountCurve positiveCurve(0, TARGET(), helpers, Actual365Fixed(), positiveMethod, accuracy, maxIterations, guess); BOOST_CHECK_GT(positiveCurve.fitResults().solution()[0], 0.0); From 68251e7e0fcf462ca66f1302394ca942277d7c3f Mon Sep 17 00:00:00 2001 From: Luigi Ballabio Date: Tue, 20 Aug 2024 11:54:57 +0200 Subject: [PATCH 4/5] Add additional constraint to existing fitting methods --- .../yield/nonlinearfittingmethods.cpp | 141 +++++++++--------- .../yield/nonlinearfittingmethods.hpp | 48 +++--- 2 files changed, 95 insertions(+), 94 deletions(-) diff --git a/ql/termstructures/yield/nonlinearfittingmethods.cpp b/ql/termstructures/yield/nonlinearfittingmethods.cpp index f9f1b217cc9..711a472d667 100644 --- a/ql/termstructures/yield/nonlinearfittingmethods.cpp +++ b/ql/termstructures/yield/nonlinearfittingmethods.cpp @@ -33,34 +33,33 @@ namespace QuantLib { const Real minCutoffTime, const Real maxCutoffTime, const Size numCoeffs, - const Real fixedKappa) - : FittedBondDiscountCurve::FittingMethod( - constrainAtZero, weights, optimizationMethod, l2, minCutoffTime, maxCutoffTime), - numCoeffs_(numCoeffs), fixedKappa_(fixedKappa) - { + const Real fixedKappa, + Constraint constraint) + : FittedBondDiscountCurve::FittingMethod(constrainAtZero, weights, optimizationMethod, l2, + minCutoffTime, maxCutoffTime, std::move(constraint)), + numCoeffs_(numCoeffs), fixedKappa_(fixedKappa) { QL_REQUIRE(ExponentialSplinesFitting::size() > 0, "At least 1 unconstrained coefficient required"); } - ExponentialSplinesFitting::ExponentialSplinesFitting(bool constrainAtZero, + ExponentialSplinesFitting::ExponentialSplinesFitting( + bool constrainAtZero, const Array& weights, const Array& l2, const Real minCutoffTime, const Real maxCutoffTime, - const Size numCoeffs, const Real fixedKappa) - : FittedBondDiscountCurve::FittingMethod(constrainAtZero, weights, ext::shared_ptr(), l2, - minCutoffTime, maxCutoffTime), - numCoeffs_(numCoeffs),fixedKappa_(fixedKappa) - { - QL_REQUIRE(ExponentialSplinesFitting::size() > 0, "At least 1 unconstrained coefficient required"); - } + const Size numCoeffs, const Real fixedKappa, + Constraint constraint) + : ExponentialSplinesFitting(constrainAtZero, weights, {}, l2, + minCutoffTime, maxCutoffTime, + numCoeffs, fixedKappa, std::move(constraint)) {} - ExponentialSplinesFitting::ExponentialSplinesFitting(bool constrainAtZero, - const Size numCoeffs, - const Real fixedKappa, - const Array& weights ) - : FittedBondDiscountCurve::FittingMethod(constrainAtZero, weights, ext::shared_ptr(), Array(),0.0,QL_MAX_REAL), - numCoeffs_(numCoeffs), fixedKappa_(fixedKappa) - { - QL_REQUIRE(ExponentialSplinesFitting::size() > 0, "At least 1 unconstrained coefficient required"); - } + ExponentialSplinesFitting::ExponentialSplinesFitting( + bool constrainAtZero, + const Size numCoeffs, + const Real fixedKappa, + const Array& weights, + Constraint constraint) + : ExponentialSplinesFitting(constrainAtZero, weights, {}, Array(), + 0.0, QL_MAX_REAL, + numCoeffs, fixedKappa, std::move(constraint)) {} std::unique_ptr ExponentialSplinesFitting::clone() const { @@ -107,15 +106,19 @@ namespace QuantLib { const ext::shared_ptr& optimizationMethod, const Array& l2, const Real minCutoffTime, - const Real maxCutoffTime) - : FittedBondDiscountCurve::FittingMethod( - true, weights, optimizationMethod, l2, minCutoffTime, maxCutoffTime) {} + const Real maxCutoffTime, + Constraint constraint) + : FittedBondDiscountCurve::FittingMethod(true, weights, optimizationMethod, l2, + minCutoffTime, maxCutoffTime, std::move(constraint)) {} - NelsonSiegelFitting::NelsonSiegelFitting(const Array& weights, - const Array& l2, - const Real minCutoffTime, const Real maxCutoffTime) - : FittedBondDiscountCurve::FittingMethod(true, weights, ext::shared_ptr(), l2, - minCutoffTime, maxCutoffTime) {} + NelsonSiegelFitting::NelsonSiegelFitting( + const Array& weights, + const Array& l2, + const Real minCutoffTime, + const Real maxCutoffTime, + Constraint constraint) + : NelsonSiegelFitting(weights, {}, l2, + minCutoffTime, maxCutoffTime, std::move(constraint)) {} std::unique_ptr NelsonSiegelFitting::clone() const { @@ -143,14 +146,18 @@ namespace QuantLib { const ext::shared_ptr& optimizationMethod, const Array& l2, const Real minCutoffTime, - const Real maxCutoffTime) - : FittedBondDiscountCurve::FittingMethod( - true, weights, optimizationMethod, l2, minCutoffTime, maxCutoffTime) {} + const Real maxCutoffTime, + Constraint constraint) + : FittedBondDiscountCurve::FittingMethod(true, weights, optimizationMethod, l2, + minCutoffTime, maxCutoffTime, std::move(constraint)) {} SvenssonFitting::SvenssonFitting(const Array& weights, - const Array& l2, const Real minCutoffTime, const Real maxCutoffTime) - : FittedBondDiscountCurve::FittingMethod(true, weights, ext::shared_ptr(), l2, - minCutoffTime, maxCutoffTime) {} + const Array& l2, + const Real minCutoffTime, + const Real maxCutoffTime, + Constraint constraint) + : SvenssonFitting(weights, {}, l2, + minCutoffTime, maxCutoffTime, std::move(constraint)) {} std::unique_ptr SvenssonFitting::clone() const { @@ -184,9 +191,10 @@ namespace QuantLib { const ext::shared_ptr& optimizationMethod, const Array& l2, const Real minCutoffTime, - const Real maxCutoffTime) - : FittedBondDiscountCurve::FittingMethod( - constrainAtZero, weights, optimizationMethod, l2, minCutoffTime, maxCutoffTime), + const Real maxCutoffTime, + Constraint constraint) + : FittedBondDiscountCurve::FittingMethod(constrainAtZero, weights, optimizationMethod, l2, + minCutoffTime, maxCutoffTime, std::move(constraint)), splines_(3, knots.size() - 5, knots) { QL_REQUIRE(knots.size() >= 8, @@ -208,34 +216,16 @@ namespace QuantLib { } } - CubicBSplinesFitting::CubicBSplinesFitting(const std::vector