diff --git a/test-suite/fittedbonddiscountcurve.cpp b/test-suite/fittedbonddiscountcurve.cpp index 6ff2f2acaaa..48a3b08d150 100644 --- a/test-suite/fittedbonddiscountcurve.cpp +++ b/test-suite/fittedbonddiscountcurve.cpp @@ -196,9 +196,73 @@ void FittedBondDiscountCurveTest::testFlatExtrapolation() { } +void FittedBondDiscountCurveTest::testConstraint() { + BOOST_TEST_MESSAGE("Testing fitted bond curve with constraints..."); + + // market quote for bonds below + const Real quote = 90.0; + + std::vector> curves; + + // some - unrealistic - values + std::vector> dates_rates = {{Date(3, Feb, 2020), -0.01}, + {Date(12, Jun, 2020), -0.02}, + {Date(24, Nov, 2020), -0.03}, + {Date(21, Feb, 2022), -0.04}}; + + // set up bond helpers + std::vector> bonds; + std::transform( + dates_rates.begin(), dates_rates.end(), std::back_inserter(bonds), + [](auto const& date_rate) { + return ext::make_shared( + 2, 100.0, + Schedule(Date(1, Feb, 2013), date_rate.first, 6 * Months, TARGET(), Following, + Following, DateGeneration::Forward, false, Date(3, Aug, 2013)), + std::vector(1, date_rate.second), ActualActual(ActualActual::ISDA)); + }); + + QuantLib::Date eval_date = Settings::instance().evaluationDate(); + std::vector> helpers; + std::transform(bonds.begin(), bonds.end(), std::back_inserter(helpers), + ["e](ext::shared_ptr bond) { + return ext::make_shared( + Handle(ext::make_shared(quote)), bond); + }); + + + // Build unconstrained curve. + NelsonSiegelFitting nelson_siegel; + auto orig_curve = ext::make_shared(eval_date, helpers, + Actual365Fixed{}, nelson_siegel); + + auto orig_sol = orig_curve->fitResults().solution(); + + // Build constrained curve: only positive values allowed. + NelsonSiegelFitting constrained_ns{Array{}, ext::shared_ptr{}, + Array{}, 0.0, + QL_MAX_REAL, PositiveConstraint{}}; + + // Initial guess must be within feasible region. + Array guess = {0.01, 0.01, 0.01, 0.01}; + auto constrained_curve = ext::make_shared( + eval_date, helpers, Actual365Fixed{}, constrained_ns, 1E-10, 10000, guess); + auto constrained_sol = constrained_curve->fitResults().solution(); + + // At least one param of the unconstrained curve should be negative. + BOOST_ASSERT( + std::any_of(orig_sol.begin(), orig_sol.end(), [](auto param) { return param < 0; })); + + // All params of the constrained curve should be positive. + BOOST_ASSERT(std::all_of(constrained_sol.begin(), constrained_sol.end(), + [](auto param) { return param > 0; })); +} + + test_suite* FittedBondDiscountCurveTest::suite() { auto* suite = BOOST_TEST_SUITE("Fitted bond discount curve tests"); suite->add(QUANTLIB_TEST_CASE(&FittedBondDiscountCurveTest::testEvaluation)); suite->add(QUANTLIB_TEST_CASE(&FittedBondDiscountCurveTest::testFlatExtrapolation)); + suite->add(QUANTLIB_TEST_CASE(&FittedBondDiscountCurveTest::testConstraint)); return suite; } diff --git a/test-suite/fittedbonddiscountcurve.hpp b/test-suite/fittedbonddiscountcurve.hpp index 62f12748ca5..e11a16509c5 100644 --- a/test-suite/fittedbonddiscountcurve.hpp +++ b/test-suite/fittedbonddiscountcurve.hpp @@ -29,6 +29,7 @@ class FittedBondDiscountCurveTest { public: static void testEvaluation(); static void testFlatExtrapolation(); + static void testConstraint(); static boost::unit_test_framework::test_suite* suite(); };