Skip to content

Commit

Permalink
Fix skipping expired additionalDates (#1838)
Browse files Browse the repository at this point in the history
  • Loading branch information
lballabio authored Dec 1, 2023
2 parents 7ae8d53 + 3a00f85 commit e0e8a08
Show file tree
Hide file tree
Showing 2 changed files with 26 additions and 23 deletions.
39 changes: 18 additions & 21 deletions ql/termstructures/globalbootstrap.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
#include <ql/termstructures/bootstraperror.hpp>
#include <ql/termstructures/bootstraphelper.hpp>
#include <ql/utilities/dataformatters.hpp>
#include <algorithm>
#include <utility>

namespace QuantLib {
Expand Down Expand Up @@ -75,8 +76,6 @@ template <class Curve> class GlobalBootstrap {
mutable bool initialized_ = false, validCurve_ = false;
mutable Size firstHelper_, numberHelpers_;
mutable Size firstAdditionalHelper_, numberAdditionalHelpers_;
mutable Size firstAdditionalDate_, numberAdditionalDates_;
mutable std::vector<Real> lowerBounds_, upperBounds_;
};

// template definitions
Expand Down Expand Up @@ -111,7 +110,7 @@ template <class Curve> void GlobalBootstrap<Curve>::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()) {
Expand All @@ -133,16 +132,18 @@ template <class Curve> void GlobalBootstrap<Curve>::initialize() const {
std::vector<Date> 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
Expand All @@ -154,8 +155,7 @@ template <class Curve> void GlobalBootstrap<Curve>::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");
Expand All @@ -166,13 +166,10 @@ template <class Curve> void GlobalBootstrap<Curve>::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
Expand Down Expand Up @@ -231,9 +228,9 @@ template <class Curve> void GlobalBootstrap<Curve>::calculate() const {
}

// determine bounds, we use an unconstrained optimisation transforming the free variables to [lowerBound,upperBound]
std::vector<Real> lowerBounds(numberHelpers_ + numberAdditionalDates_),
upperBounds(numberHelpers_ + numberAdditionalDates_);
for (Size i = 0; i < numberHelpers_ + numberAdditionalDates_; ++i) {
const Size numberBounds = ts_->times_.size() - 1;
std::vector<Real> 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);
Expand Down Expand Up @@ -295,8 +292,8 @@ template <class Curve> void GlobalBootstrap<Curve>::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);
}
Expand Down
10 changes: 8 additions & 2 deletions test-suite/piecewiseyieldcurve.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1276,10 +1276,16 @@ namespace piecewise_yield_curve_test {
// functor returning additional dates used in the bootstrap
struct additionalDates {
std::vector<Date> 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<Date> 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;
}
};
Expand Down

0 comments on commit e0e8a08

Please sign in to comment.