Skip to content

Commit

Permalink
Move transformation used in GlobalBootstrap into Traits
Browse files Browse the repository at this point in the history
Also, use exp instead of atan transformation for discount factors. This
is an improvement on #2120 based on these observations:

* Rates-based traits don't need any constraints in the optimization, so
  we don't need any transformation with them. This removes unnecessary
  computations.

* Discount factors are only bounded on one side (have to be positive),
  so instead of estimating a max value to use atan, we can simply use
  exp. This is faster and uses fewer magic numbers.

  Another issue with atan is that it changes the gradients too much when
  the value is far from the middle of the range. This pushes
  optimization away from the correct solution when rates are low. This
  makes it hard to find value of maxDF that works for both very low and
  very high rates.
  • Loading branch information
eltoder committed Jan 8, 2025
1 parent fec2aa0 commit e709cb6
Show file tree
Hide file tree
Showing 2 changed files with 26 additions and 40 deletions.
24 changes: 5 additions & 19 deletions ql/termstructures/globalbootstrap.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ 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 requires Traits::minValueGlobal() and Traits::maxValueGlobal() to be implemented. Also, check the usage
in QL. It requires Traits::transformDirect() and Traits::transformInverse() 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,
Expand Down Expand Up @@ -200,6 +200,7 @@ template <class Curve> void GlobalBootstrap<Curve>::initialize() const {
// but reasonable numbers might be needed for the whole data vector
// because, e.g., of interpolation's early checks
ts_->data_ = std::vector<Real>(dates.size(), Traits::initialValue(ts_));
validCurve_ = false;
}
initialized_ = true;
}
Expand Down Expand Up @@ -242,26 +243,10 @@ template <class Curve> void GlobalBootstrap<Curve>::calculate() const {
ts_->interpolator_.interpolate(ts_->times_.begin(), ts_->times_.end(), ts_->data_.begin());
}

// determine bounds, we use an unconstrained optimisation transforming the free variables to [lowerBound,upperBound]
const Size numberBounds = ts_->times_.size() - 1;
std::vector<Real> lowerBounds(numberBounds), upperBounds(numberBounds);
for (Size i = 0; i < numberBounds; ++i) {
lowerBounds[i] = Traits::minValueGlobal(i + 1, ts_, validCurve_);
upperBounds[i] = Traits::maxValueGlobal(i + 1, ts_, validCurve_);
}

// setup cost function
auto transformDirect = [&](const Real x, const Size i) {
return (std::atan(x) + M_PI_2) / M_PI * (upperBounds[i] - lowerBounds[i]) + lowerBounds[i];
};

auto transformInverse = [&](const Real y, const Size i) {
return std::tan((y - lowerBounds[i]) * M_PI / (upperBounds[i] - lowerBounds[i]) - M_PI_2);
};

SimpleCostFunction cost([&](const Array& x) {
for (Size i = 0; i < x.size(); ++i) {
Traits::updateGuess(ts_->data_, transformDirect(x[i], i), i + 1);
Traits::updateGuess(ts_->data_, Traits::transformDirect(x[i], i + 1, ts_), i + 1);
}
ts_->interpolation_.update();
std::vector<Real> result(numberHelpers_);
Expand All @@ -280,12 +265,13 @@ template <class Curve> void GlobalBootstrap<Curve>::calculate() const {
});

// setup guess
const Size numberBounds = ts_->times_.size() - 1;
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
// 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);
guess[i] = Traits::transformInverse(ts_->data_[i + 1], i + 1, ts_);
}

// setup problem
Expand Down
42 changes: 21 additions & 21 deletions ql/termstructures/yield/bootstraptraits.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ 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 @@ -102,16 +101,16 @@ namespace QuantLib {
return c->data()[i-1] * std::exp(detail::maxRate * dt);
}

// possible constraints for global optimization
// transformation to add constraints to an unconstrained optimization
template <class C>
static Real minValueGlobal(Size i, const C* c, bool validData)
static Real transformDirect(Real x, Size i, const C* c)
{
return 0;
return std::exp(x);
}
template <class C>
static Real maxValueGlobal(Size i, const C* c, bool validData)
static Real transformInverse(Real x, Size i, const C* c)
{
return detail::maxDF;
return std::log(x);
}

// root-finding update
Expand Down Expand Up @@ -193,16 +192,16 @@ namespace QuantLib {
return detail::maxRate;
}

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

// root-finding update
Expand Down Expand Up @@ -286,18 +285,19 @@ namespace QuantLib {
return detail::maxRate;
}

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


// root-finding update
static void updateGuess(std::vector<Real>& data,
Real forward,
Expand Down Expand Up @@ -381,16 +381,16 @@ namespace QuantLib {
return detail::maxRate;
}

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

// root-finding update
Expand Down

0 comments on commit e709cb6

Please sign in to comment.