From 3a00f85968454a4dcd89fdd3ff385f0114c5b549 Mon Sep 17 00:00:00 2001 From: Eugene Toder Date: Mon, 20 Nov 2023 20:09:14 -0500 Subject: [PATCH] Fix skipping expired additionalDates * Do not assume additionalDates are sorted. * Fix appending additionalDates to dates (2*firstAdditionalDate_ were skipped leading to an abort later). * Simplify the code. --- ql/termstructures/globalbootstrap.hpp | 39 +++++++++++++-------------- test-suite/piecewiseyieldcurve.cpp | 10 +++++-- 2 files changed, 26 insertions(+), 23 deletions(-) diff --git a/ql/termstructures/globalbootstrap.hpp b/ql/termstructures/globalbootstrap.hpp index ed966215bf6..77af4532ba3 100644 --- a/ql/termstructures/globalbootstrap.hpp +++ b/ql/termstructures/globalbootstrap.hpp @@ -31,6 +31,7 @@ #include #include #include +#include #include namespace QuantLib { @@ -75,8 +76,6 @@ template class GlobalBootstrap { mutable bool initialized_ = false, validCurve_ = false; mutable Size firstHelper_, numberHelpers_; mutable Size firstAdditionalHelper_, numberAdditionalHelpers_; - mutable Size firstAdditionalDate_, numberAdditionalDates_; - mutable std::vector lowerBounds_, upperBounds_; }; // template definitions @@ -111,7 +110,7 @@ template void GlobalBootstrap::initialize() const { std::sort(additionalHelpers_.begin(), additionalHelpers_.end(), detail::BootstrapHelperSorter()); // skip expired helpers - Date firstDate = Traits::initialDate(ts_); + const Date firstDate = Traits::initialDate(ts_); firstHelper_ = 0; if (!ts_->instruments_.empty()) { @@ -133,16 +132,18 @@ template void GlobalBootstrap::initialize() const { std::vector additionalDates; if (additionalDates_) additionalDates = additionalDates_(); - firstAdditionalDate_ = 0; if (!additionalDates.empty()) { - while (firstAdditionalDate_ < additionalDates.size() && additionalDates[firstAdditionalDate_] <= firstDate) - ++firstAdditionalDate_; + additionalDates.erase( + std::remove_if(additionalDates.begin(), additionalDates.end(), + [=](const Date& date) { return date <= firstDate; }), + additionalDates.end() + ); } - numberAdditionalDates_ = additionalDates.size() - firstAdditionalDate_; + const Size numberAdditionalDates = additionalDates.size(); - QL_REQUIRE(numberHelpers_ + numberAdditionalDates_ >= Interpolator::requiredPoints - 1, - "not enough alive instruments (" << numberHelpers_ << ") + additional dates (" << numberAdditionalDates_ - << ") = " << numberHelpers_ + numberAdditionalDates_ << " provided, " + QL_REQUIRE(numberHelpers_ + numberAdditionalDates >= Interpolator::requiredPoints - 1, + "not enough alive instruments (" << numberHelpers_ << ") + additional dates (" << numberAdditionalDates + << ") = " << numberHelpers_ + numberAdditionalDates << " provided, " << Interpolator::requiredPoints - 1 << " required"); // calculate dates and times @@ -154,8 +155,7 @@ template void GlobalBootstrap::initialize() const { dates.push_back(firstDate); for (Size j = 0; j < numberHelpers_; ++j) dates.push_back(ts_->instruments_[firstHelper_ + j]->pillarDate()); - for (Size j = firstAdditionalDate_; j < numberAdditionalDates_; ++j) - dates.push_back(additionalDates[firstAdditionalDate_ + j]); + dates.insert(dates.end(), additionalDates.begin(), additionalDates.end()); std::sort(dates.begin(), dates.end()); auto it = std::unique(dates.begin(), dates.end()); QL_REQUIRE(it == dates.end(), "duplicate dates among alive instruments and additional dates"); @@ -166,13 +166,10 @@ template void GlobalBootstrap::initialize() const { times.push_back(ts_->timeFromReference(date)); // determine maxDate - Date maxDate = firstDate; + Date maxDate = dates.back(); for (Size j = 0; j < numberHelpers_; ++j) { maxDate = std::max(ts_->instruments_[firstHelper_ + j]->latestRelevantDate(), maxDate); } - for (Size j = 0; j < numberAdditionalDates_; ++j) { - maxDate = std::max(additionalDates[firstAdditionalDate_ + j], maxDate); - } ts_->maxDate_ = maxDate; // set initial guess only if the current curve cannot be used as guess @@ -231,9 +228,9 @@ template void GlobalBootstrap::calculate() const { } // determine bounds, we use an unconstrained optimisation transforming the free variables to [lowerBound,upperBound] - std::vector lowerBounds(numberHelpers_ + numberAdditionalDates_), - upperBounds(numberHelpers_ + numberAdditionalDates_); - for (Size i = 0; i < numberHelpers_ + numberAdditionalDates_; ++i) { + const Size numberBounds = ts_->times_.size() - 1; + std::vector lowerBounds(numberBounds), upperBounds(numberBounds); + for (Size i = 0; i < numberBounds; ++i) { // just pass zero as the first alive helper, it's not used in the standard QL traits anyway lowerBounds[i] = Traits::minValueAfter(i + 1, ts_, validCurve_, 0); upperBounds[i] = Traits::maxValueAfter(i + 1, ts_, validCurve_, 0); @@ -295,8 +292,8 @@ template void GlobalBootstrap::calculate() const { TargetFunction cost(firstHelper_, numberHelpers_, additionalErrors_, ts_, lowerBounds, upperBounds); // setup guess - Array guess(numberHelpers_ + numberAdditionalDates_); - for (Size i = 0; i < guess.size(); ++i) { + Array guess(numberBounds); + for (Size i = 0; i < numberBounds; ++i) { // just pass zero as the first alive helper, it's not used in the standard QL traits anyway guess[i] = cost.transformInverse(Traits::guess(i + 1, ts_, validCurve_, 0), i); } diff --git a/test-suite/piecewiseyieldcurve.cpp b/test-suite/piecewiseyieldcurve.cpp index 41d789219de..2c893252ec4 100644 --- a/test-suite/piecewiseyieldcurve.cpp +++ b/test-suite/piecewiseyieldcurve.cpp @@ -1276,10 +1276,16 @@ namespace piecewise_yield_curve_test { // functor returning additional dates used in the bootstrap struct additionalDates { std::vector operator()() { - Date settl = TARGET().advance(Settings::instance().evaluationDate(), 2 * Days); + Date today = Settings::instance().evaluationDate(); + Calendar cal = TARGET(); + Date settl = cal.advance(today, 2 * Days); std::vector dates; for (Size i = 0; i < 5; ++i) - dates.push_back(TARGET().advance(settl, (1 + i) * Months)); + dates.push_back(cal.advance(settl, (1 + i) * Months)); + // Add dates before the referenceDate and not in sorted order. + // These should be skipped by GlobalBootstrap::initialize(). + dates.insert(dates.begin(), today - 1); + dates.push_back(today - 2); return dates; } };