Skip to content

Commit

Permalink
Fix lower/upper bounds for global bootstrap
Browse files Browse the repository at this point in the history
Currently global bootstrap uses minValueAfter/maxValueAfter methods from
Traits to come up with lower/upper bounds. This is problematic. These
methods depend on the previous point in the curve. In the iterative
bootstrap these methods are called after the previous point is already
known exactly. However, in the global bootstrap all points are still
filled with Traits::initialValue. This makes the bounds too tight and
leads to bad results for curves with higher interest rates.

Instead add separate minValueGlobal/maxValueGlobal methods that do not
rely on previous data.
  • Loading branch information
eltoder committed Nov 27, 2024
1 parent ce96c8e commit c987e2d
Show file tree
Hide file tree
Showing 2 changed files with 58 additions and 9 deletions.
15 changes: 8 additions & 7 deletions ql/termstructures/globalbootstrap.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,9 @@ template <class Curve> class GlobalBootstrap {
The additional helpers are treated like the usual rate helpers, but no standard pillar dates are added for them.
WARNING: This class is known to work with Traits Discount, ZeroYield, Forward (i.e. the usual traits for IR curves
in QL), it might fail for other traits - check the usage of Traits::updateGuess(), Traits::guess(),
Traits::minValueAfter(), Traits::maxValueAfter() in this class against them.
WARNING: This class is known to work with Traits Discount, ZeroYield, Forward, i.e. the usual traits for IR curves
in QL. It requires Traits::minValueGlobal() and Traits::maxValueGlobal() to be implemented. Also, check the usage
of Traits::updateGuess(), Traits::guess() in this class.
*/
GlobalBootstrap(std::vector<ext::shared_ptr<typename Traits::helper> > additionalHelpers,
std::function<std::vector<Date>()> additionalDates,
Expand Down Expand Up @@ -246,9 +246,8 @@ template <class Curve> void GlobalBootstrap<Curve>::calculate() const {
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);
lowerBounds[i] = Traits::minValueGlobal(i + 1, ts_, validCurve_);
upperBounds[i] = Traits::maxValueGlobal(i + 1, ts_, validCurve_);
}

// setup cost function
Expand Down Expand Up @@ -284,7 +283,9 @@ template <class Curve> void GlobalBootstrap<Curve>::calculate() const {
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] = transformInverse(Traits::guess(i + 1, ts_, validCurve_, 0), i);
// update ts_->data_ since Traits::guess() usually depends on previous values
Traits::updateGuess(ts_->data_, Traits::guess(i + 1, ts_, validCurve_, 0), i + 1);
guess[i] = transformInverse(ts_->data_[i + 1], i);
}

// setup problem
Expand Down
52 changes: 50 additions & 2 deletions ql/termstructures/yield/bootstraptraits.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ namespace QuantLib {
namespace detail {
const Real avgRate = 0.05;
const Real maxRate = 1.0;
const Real maxDF = 10.0;
}

//! Discount-curve traits
Expand Down Expand Up @@ -101,6 +102,18 @@ namespace QuantLib {
return c->data()[i-1] * std::exp(detail::maxRate * dt);
}

// possible constraints for global optimization
template <class C>
static Real minValueGlobal(Size i, const C* c, bool validData)
{
return 0;
}
template <class C>
static Real maxValueGlobal(Size i, const C* c, bool validData)
{
return detail::maxDF;
}

// root-finding update
static void updateGuess(std::vector<Real>& data,
Real discount,
Expand Down Expand Up @@ -180,6 +193,18 @@ namespace QuantLib {
return detail::maxRate;
}

// possible constraints for global optimization
template <class C>
static Real minValueGlobal(Size i, const C* c, bool validData)
{
return -detail::maxRate;
}
template <class C>
static Real maxValueGlobal(Size i, const C* c, bool validData)
{
return detail::maxRate;
}

// root-finding update
static void updateGuess(std::vector<Real>& data,
Real rate,
Expand Down Expand Up @@ -261,6 +286,18 @@ namespace QuantLib {
return detail::maxRate;
}

// possible constraints for global optimization
template <class C>
static Real minValueGlobal(Size i, const C* c, bool validData)
{
return -detail::maxRate;
}
template <class C>
static Real maxValueGlobal(Size i, const C* c, bool validData)
{
return detail::maxRate;
}

// root-finding update
static void updateGuess(std::vector<Real>& data,
Real forward,
Expand Down Expand Up @@ -327,8 +364,7 @@ namespace QuantLib {
// We choose as min a value very unlikely to be exceeded.
result = -detail::maxRate;
}
Real t = c->timeFromReference(c->dates()[i]);
return std::max(result, -1.0 / t + 1E-8);
return std::max(result, -1.0 / c->times()[i] + 1E-8);
}
template <class C>
static Real maxValueAfter(Size,
Expand All @@ -345,6 +381,18 @@ namespace QuantLib {
return detail::maxRate;
}

// possible constraints for global optimization
template <class C>
static Real minValueGlobal(Size i, const C* c, bool validData)
{
return std::max(-detail::maxRate, -1.0 / c->times()[i] + 1E-8);
}
template <class C>
static Real maxValueGlobal(Size i, const C* c, bool validData)
{
return detail::maxRate;
}

// root-finding update
static void updateGuess(std::vector<Real>& data,
Real rate,
Expand Down

0 comments on commit c987e2d

Please sign in to comment.