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 7, 2025
1 parent fec2aa0 commit 5c41ac0
Show file tree
Hide file tree
Showing 2 changed files with 130 additions and 14 deletions.
41 changes: 29 additions & 12 deletions ql/termstructures/globalbootstrap.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ 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>&)>
ErrorsFn;

public:
GlobalBootstrap(Real accuracy = Null<Real>(),
Expand All @@ -60,6 +62,12 @@ template <class Curve> class GlobalBootstrap {
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,
ErrorsFn additionalErrors,
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()> additionalErrors,
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_;
ErrorsFn additionalErrors_;
mutable bool initialized_ = false, validCurve_ = false;
mutable Size firstHelper_, numberHelpers_;
mutable Size firstAdditionalHelper_, numberAdditionalHelpers_;
Expand All @@ -97,14 +105,26 @@ 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,
ErrorsFn additionalErrors,
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)) {}

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,
Real accuracy,
ext::shared_ptr<OptimizationMethod> optimizer,
ext::shared_ptr<EndCriteria> endCriteria)
: GlobalBootstrap(std::move(additionalHelpers), std::move(additionalDates),
additionalErrors ? std::bind(additionalErrors) : ErrorsFn(),
accuracy, std::move(optimizer), std::move(endCriteria)) {}

template <class Curve> void GlobalBootstrap<Curve>::setup(Curve *ts) {
ts_ = ts;
for (Size j = 0; j < ts_->instruments_.size(); ++j)
Expand Down Expand Up @@ -264,19 +284,16 @@ template <class Curve> void GlobalBootstrap<Curve>::calculate() const {
Traits::updateGuess(ts_->data_, transformDirect(x[i], i), 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();
}
Array result(numberHelpers_);
std::transform(ts_->instruments_.begin() + firstHelper_, ts_->instruments_.end(),
result.begin(),
[](const auto& helper) { return helper->quoteError(); });
if (additionalErrors_) {
Array tmp = additionalErrors_();
Array tmp = additionalErrors_(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
103 changes: 101 additions & 2 deletions test-suite/piecewiseyieldcurve.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1363,8 +1363,107 @@ BOOST_AUTO_TEST_CASE(testGlobalBootstrap, *precondition(usingAtParCoupons())) {
// check expected zero rates
for (Size i = 0; i < std::size(refZeroRate); ++i) {
// 0.01 basis points tolerance
QL_CHECK_SMALL(std::fabs(refZeroRate[i] - curve->zeroRate(refDate[i], Actual360(), Continuous).rate()),
1E-6);
QL_CHECK_SMALL(refZeroRate[i] - curve->zeroRate(refDate[i], Actual360(), Continuous).rate(),
1E-6);
}
}

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

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

// 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 < 19; ++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);
}
}

Expand Down

0 comments on commit 5c41ac0

Please sign in to comment.