Skip to content

Commit

Permalink
Support error functions with arguments
Browse files Browse the repository at this point in the history
Pass curve data (times and values) to GlobalBootstrap's error functions
so that they can compute errors based on the curve's shape. For example,
one can penalize gradient to make the curve smoother.
  • Loading branch information
eltoder committed Jan 28, 2025
1 parent 947cab4 commit 4a5b84f
Show file tree
Hide file tree
Showing 2 changed files with 135 additions and 16 deletions.
53 changes: 37 additions & 16 deletions ql/termstructures/globalbootstrap.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,15 @@ namespace QuantLib {
template <class Curve> class GlobalBootstrap {
typedef typename Curve::traits_type Traits; // ZeroYield, Discount, ForwardRate
typedef typename Curve::interpolator_type Interpolator; // Linear, LogLinear, ...
typedef std::function<Array(const std::vector<Time>&, const std::vector<Real>&)>
AdditionalPenalties;

public:
GlobalBootstrap(Real accuracy = Null<Real>(),
ext::shared_ptr<OptimizationMethod> optimizer = nullptr,
ext::shared_ptr<EndCriteria> endCriteria = nullptr);
/*! The set of (alive) additional dates is added to the interpolation grid. The set of additional dates must only
depend on the current global evaluation date. The additionalErrors functor must yield at least as many values
depend on the current global evaluation date. The additionalPenalties functor must yield at least as many values
such that
number of (usual, alive) rate helpers + number of (alive) additional values >= number of data points - 1
Expand All @@ -62,7 +64,13 @@ template <class Curve> class GlobalBootstrap {
*/
GlobalBootstrap(std::vector<ext::shared_ptr<typename Traits::helper> > additionalHelpers,
std::function<std::vector<Date>()> additionalDates,
std::function<Array()> additionalErrors,
AdditionalPenalties additionalPenalties,
Real accuracy = Null<Real>(),
ext::shared_ptr<OptimizationMethod> optimizer = nullptr,
ext::shared_ptr<EndCriteria> endCriteria = nullptr);
GlobalBootstrap(std::vector<ext::shared_ptr<typename Traits::helper> > additionalHelpers,
std::function<std::vector<Date>()> additionalDates,
std::function<Array()> additionalPenalties,
Real accuracy = Null<Real>(),
ext::shared_ptr<OptimizationMethod> optimizer = nullptr,
ext::shared_ptr<EndCriteria> endCriteria = nullptr);
Expand All @@ -77,7 +85,7 @@ template <class Curve> class GlobalBootstrap {
ext::shared_ptr<EndCriteria> endCriteria_;
mutable std::vector<ext::shared_ptr<typename Traits::helper> > additionalHelpers_;
std::function<std::vector<Date>()> additionalDates_;
std::function<Array()> additionalErrors_;
AdditionalPenalties additionalPenalties_;
mutable bool initialized_ = false, validCurve_ = false;
mutable Size firstHelper_, numberHelpers_;
mutable Size firstAdditionalHelper_, numberAdditionalHelpers_;
Expand All @@ -97,13 +105,29 @@ template <class Curve>
GlobalBootstrap<Curve>::GlobalBootstrap(
std::vector<ext::shared_ptr<typename Traits::helper> > additionalHelpers,
std::function<std::vector<Date>()> additionalDates,
std::function<Array()> additionalErrors,
AdditionalPenalties additionalPenalties,
Real accuracy,
ext::shared_ptr<OptimizationMethod> optimizer,
ext::shared_ptr<EndCriteria> endCriteria)
: ts_(nullptr), accuracy_(accuracy), optimizer_(std::move(optimizer)),
endCriteria_(std::move(endCriteria)), additionalHelpers_(std::move(additionalHelpers)),
additionalDates_(std::move(additionalDates)), additionalErrors_(std::move(additionalErrors)) {}
additionalDates_(std::move(additionalDates)), additionalPenalties_(std::move(additionalPenalties)) {}

template <class Curve>
GlobalBootstrap<Curve>::GlobalBootstrap(
std::vector<ext::shared_ptr<typename Traits::helper> > additionalHelpers,
std::function<std::vector<Date>()> additionalDates,
std::function<Array()> additionalPenalties,
Real accuracy,
ext::shared_ptr<OptimizationMethod> optimizer,
ext::shared_ptr<EndCriteria> endCriteria)
: GlobalBootstrap(std::move(additionalHelpers), std::move(additionalDates),
additionalPenalties
? [f=std::move(additionalPenalties)](const std::vector<Time>&, const std::vector<Real>&) {
return f();
}
: AdditionalPenalties(),
accuracy, std::move(optimizer), std::move(endCriteria)) {}

template <class Curve> void GlobalBootstrap<Curve>::setup(Curve *ts) {
ts_ = ts;
Expand Down Expand Up @@ -249,19 +273,16 @@ template <class Curve> void GlobalBootstrap<Curve>::calculate() const {
Traits::updateGuess(ts_->data_, Traits::transformDirect(x[i], i + 1, ts_), i + 1);
}
ts_->interpolation_.update();
std::vector<Real> result(numberHelpers_);
for (Size i = 0; i < numberHelpers_; ++i) {
result[i] = ts_->instruments_[firstHelper_ + i]->quote()->value() -
ts_->instruments_[firstHelper_ + i]->impliedQuote();
}
if (additionalErrors_) {
Array tmp = additionalErrors_();
Array result(numberHelpers_);
std::transform(ts_->instruments_.begin() + firstHelper_, ts_->instruments_.end(),
result.begin(),
[](const auto& helper) { return helper->quoteError(); });
if (additionalPenalties_) {
Array tmp = additionalPenalties_(ts_->times_, ts_->data_);
result.resize(numberHelpers_ + tmp.size());
for (Size i = 0; i < tmp.size(); ++i) {
result[numberHelpers_ + i] = tmp[i];
}
std::copy(tmp.begin(), tmp.end(), result.begin() + numberHelpers_);
}
return Array(result.begin(), result.end());
return result;
});

// setup guess
Expand Down
98 changes: 98 additions & 0 deletions test-suite/piecewiseyieldcurve.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1368,6 +1368,104 @@ BOOST_AUTO_TEST_CASE(testGlobalBootstrap, *precondition(usingAtParCoupons())) {
}
}

BOOST_AUTO_TEST_CASE(testGlobalBootstrapPenalty, *precondition(usingAtParCoupons())) {

Settings::instance().evaluationDate() = Date(26, Sep, 2019);

// market rates
Real refMktRate[] = {-0.373, -0.388, -0.402, -0.418, -0.431, -0.441, -0.45,
-0.457, -0.463, -0.469, -0.461, -0.463, -0.479, -0.4511,
-0.45418, -0.439, -0.4124, -0.37703, -0.3335, -0.28168, -0.22725,
-0.1745, -0.12425, -0.07746, 0.0385, 0.1435, 0.17525, 0.17275,
0.1515, 0.1225, 0.095, 0.0644};

// expected outputs
Date refDate[] = {
Date(31, Mar, 2020), Date(30, Apr, 2020), Date(29, May, 2020), Date(30, Jun, 2020),
Date(31, Jul, 2020), Date(31, Aug, 2020), Date(30, Sep, 2020), Date(30, Oct, 2020),
Date(30, Nov, 2020), Date(31, Dec, 2020), Date(29, Jan, 2021), Date(26, Feb, 2021),
Date(31, Mar, 2021), Date(30, Sep, 2021), Date(30, Sep, 2022), Date(29, Sep, 2023),
Date(30, Sep, 2024), Date(30, Sep, 2025), Date(30, Sep, 2026), Date(30, Sep, 2027),
Date(29, Sep, 2028), Date(28, Sep, 2029), Date(30, Sep, 2030), Date(30, Sep, 2031),
Date(29, Sep, 2034), Date(30, Sep, 2039), Date(30, Sep, 2044), Date(30, Sep, 2049),
Date(30, Sep, 2054), Date(30, Sep, 2059), Date(30, Sep, 2064), Date(30, Sep, 2069)};

Real refZeroRateNP[] = {
-0.00373354, -0.00386194, -0.00395205, -0.00403303, -0.00408033, -0.00410875, -0.00411935,
-0.00419161, -0.00424817, -0.00429923, -0.00428029, -0.00429178, -0.00434401, -0.00445243,
-0.00448506, -0.0043369, -0.00407401, -0.00372752, -0.0033005, -0.00279139, -0.00225477,
-0.00173422, -0.00123688, -0.00077236, 0.00038550, 0.00144208, 0.00175947, 0.00172834,
0.00150757, 0.00121131, 0.00093384, 0.00062891};

Real refZeroRateGP[] = {
-0.00377892, -0.00386127, -0.00394737, -0.00402914, -0.00409541, -0.00413252, -0.00415463,
-0.00419484, -0.00424238, -0.00427875, -0.00429712, -0.00431898, -0.00436027, -0.00445297,
-0.00448502, -0.00433694, -0.00407406, -0.00372755, -0.00330018, -0.00279133, -0.00225491,
-0.00173429, -0.00123643, -0.00077298, 0.00038547, 0.00144206, 0.00175948, 0.00172834,
0.00150756, 0.00121135, 0.00093379, 0.00062895};

// build ql helpers
std::vector<ext::shared_ptr<RateHelper>> helpers;
ext::shared_ptr<IborIndex> index = ext::make_shared<Euribor>(6 * Months);

helpers.push_back(ext::make_shared<DepositRateHelper>(
refMktRate[0] / 100.0, 6 * Months, 2, TARGET(), ModifiedFollowing, true, Actual360()));

for (Size i = 0; i < 12; ++i) {
helpers.push_back(
ext::make_shared<FraRateHelper>(refMktRate[1 + i] / 100.0, (i + 1) * Months, index));
}

Size swapTenors[] = {2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 15, 20, 25, 30, 35, 40, 45, 50};
for (Size i = 0; i < std::size(swapTenors); ++i) {
helpers.push_back(ext::make_shared<SwapRateHelper>(
refMktRate[13 + i] / 100.0, swapTenors[i] * Years, TARGET(), Annual, ModifiedFollowing,
Thirty360(Thirty360::BondBasis), index));
}

// build the curve without penalties first
typedef PiecewiseYieldCurve<ForwardRate, BackwardFlat, GlobalBootstrap> Curve;
auto curve = ext::make_shared<Curve>(
2, TARGET(), helpers, Actual365Fixed(), std::vector<Handle<Quote>>(), std::vector<Date>(),
BackwardFlat(),
Curve::bootstrap_type({}, nullptr, std::function<Array()>(), 1.0e-12));

// check expected pillar dates
for (Size i = 0; i < std::size(refDate); ++i) {
BOOST_CHECK_EQUAL(refDate[i], helpers[i]->pillarDate());
}

// check expected zero rates
for (Size i = 0; i < std::size(refZeroRateNP); ++i) {
// 0.01 basis points tolerance
QL_CHECK_SMALL(
refZeroRateNP[i] - curve->zeroRate(refDate[i], Actual360(), Continuous).rate(),
1E-6);
}

// build the curve with gradient penalties
auto gradientPenalty = [](const std::vector<Time>& times, const std::vector<Real>& data) {
Array errors(times.size() - 1);
for (Size i = 0; i < times.size() - 1; ++i) {
errors[i] = 0.01 * (data[i+1] - data[i]) / (times[i+1] - times[i]);
}
return errors;
};

curve = ext::make_shared<Curve>(
2, TARGET(), helpers, Actual365Fixed(), std::vector<Handle<Quote>>(), std::vector<Date>(),
BackwardFlat(),
Curve::bootstrap_type({}, nullptr, gradientPenalty, 1.0e-12));

// check expected zero rates
for (Size i = 0; i < std::size(refZeroRateGP); ++i) {
// 0.01 basis points tolerance
QL_CHECK_SMALL(
refZeroRateGP[i] - curve->zeroRate(refDate[i], Actual360(), Continuous).rate(),
1E-6);
}
}

/* This test attempts to build an ARS collateralised in USD curve as of 25 Sep 2019. Using the default
IterativeBootstrap with no retries, the yield curve building fails. Allowing retries, it expands the min and max
bounds and passes.
Expand Down

0 comments on commit 4a5b84f

Please sign in to comment.