Skip to content

Commit

Permalink
Add possibility to reset guess in fitted bond curves (#1992)
Browse files Browse the repository at this point in the history
  • Loading branch information
lballabio authored Jun 20, 2024
2 parents 8770770 + c79c8e0 commit 0a34fa0
Show file tree
Hide file tree
Showing 3 changed files with 61 additions and 2 deletions.
8 changes: 8 additions & 0 deletions ql/termstructures/yield/fittedbonddiscountcurve.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,13 @@ namespace QuantLib {
}


void FittedBondDiscountCurve::resetGuess(const Array& guess) {
QL_REQUIRE(guess.empty() || guess.size() == fittingMethod_->size(), "guess is of wrong size");
guessSolution_ = guess;
update();
}


void FittedBondDiscountCurve::performCalculations() const {

QL_REQUIRE(!bondHelpers_.empty(), "no bondHelpers given");
Expand Down Expand Up @@ -186,6 +193,7 @@ namespace QuantLib {
// start with the guess solution, if it exists
Array x(size(), 0.0);
if (!curve_->guessSolution_.empty()) {
QL_REQUIRE(curve_->guessSolution_.size() == size(), "wrong size for guess");
x = curve_->guessSolution_;
}

Expand Down
6 changes: 6 additions & 0 deletions ql/termstructures/yield/fittedbonddiscountcurve.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,12 @@ namespace QuantLib {
const FittingMethod& fitResults() const;
//@}

//! \name Other utilities
//@{
/*! This allows to try out multiple guesses and avoid local minima */
void resetGuess(const Array& guess);
//@}

//! \name Observer interface
//@{
void update() override;
Expand Down
49 changes: 47 additions & 2 deletions test-suite/fittedbonddiscountcurve.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -189,12 +189,29 @@ BOOST_AUTO_TEST_CASE(testFlatExtrapolation) {
// Real modelYield1 = bonds[i]->yield(modelPrices1[i], Actual365Fixed(), Continuous, NoFrequency);
Real modelYield2 =
bonds[i]->yield(modelPrices2[i], Actual365Fixed(), Continuous, NoFrequency);
// Real curveYield1 = curve1->zeroRate(t, Continuous).rate();
Real curveYield1 = curve1->zeroRate(t, Continuous).rate();
Real curveYield2 = curve2->zeroRate(t, Continuous).rate();

if (curveYield1 < 1.0) {
BOOST_ERROR("Expecting huge yield; the test premise might be outdated");
}
QL_CHECK_CLOSE(modelYield2, curveYield2, 1.0); // 1.0 percent relative tolerance
}


// resetting the guess changes the calibration

curve1->resetGuess({ 0.02, 0.0, 0.0, 0.0 });

BOOST_CHECK_EQUAL(curve1->fitResults().errorCode(), EndCriteria::StationaryPoint);

for (Size i = 0; i < helpers.size(); ++i) {
Real t = curve1->timeFromReference(helpers[i]->bond()->maturityDate());
Real modelYield1 = bonds[i]->yield(modelPrices1[i], Actual365Fixed(), Continuous, NoFrequency);
Real curveYield1 = curve1->zeroRate(t, Continuous).rate();

QL_CHECK_CLOSE(modelYield1, curveYield1, 6); // somewhat better, within a dozen bps
}

}

BOOST_AUTO_TEST_CASE(testRequiredGuess) {
Expand Down Expand Up @@ -227,6 +244,34 @@ BOOST_AUTO_TEST_CASE(testRequiredGuess) {
ExpectedErrorMessage("L2 penalty requires a guess"));
}

BOOST_AUTO_TEST_CASE(testGuessSize) {

BOOST_TEST_MESSAGE("Testing that fitted bond curves check the guess size when given...");

Date today = Settings::instance().evaluationDate();
auto bond1 = ext::make_shared<ZeroCouponBond>(3, TARGET(), 100.0, today + Period(1, Years));
auto bond2 = ext::make_shared<ZeroCouponBond>(3, TARGET(), 100.0, today + Period(2, Years));
auto bond3 = ext::make_shared<ZeroCouponBond>(3, TARGET(), 100.0, today + Period(5, Years));
auto bond4 = ext::make_shared<ZeroCouponBond>(3, TARGET(), 100.0, today + Period(10, Years));

std::vector<ext::shared_ptr<BondHelper> > helpers(4);
helpers[0] = ext::make_shared<BondHelper>(makeQuoteHandle(99.0), bond1);
helpers[1] = ext::make_shared<BondHelper>(makeQuoteHandle(98.0), bond2);
helpers[2] = ext::make_shared<BondHelper>(makeQuoteHandle(95.0), bond3);
helpers[3] = ext::make_shared<BondHelper>(makeQuoteHandle(90.0), bond4);

NelsonSiegelFitting fittingMethod;

Real accuracy = 1e-10;
Size maxIterations = 10000;
Array guess = { 0.01, 0.0, 0.0 }; // too few
FittedBondDiscountCurve curve(0, TARGET(), helpers, Actual365Fixed(),
fittingMethod, accuracy, maxIterations, guess);

BOOST_CHECK_EXCEPTION(curve.discount(3.0), Error,
ExpectedErrorMessage("wrong size for guess"));
}

BOOST_AUTO_TEST_SUITE_END()

BOOST_AUTO_TEST_SUITE_END()

0 comments on commit 0a34fa0

Please sign in to comment.