QuantLib: a free/open-source library for quantitative finance
Fully annotated sources - version 1.32
Loading...
Searching...
No Matches
globalbootstrap.hpp
1/* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2
3/*
4 Copyright (C) 2019 SoftSolutions! S.r.l.
5
6 This file is part of QuantLib, a free-software/open-source library
7 for financial quantitative analysts and developers - http://quantlib.org/
8
9 QuantLib is free software: you can redistribute it and/or modify it
10 under the terms of the QuantLib license. You should have received a
11 copy of the license along with this program; if not, please email
12 <quantlib-dev@lists.sf.net>. The license is also available online at
13 <http://quantlib.org/license.shtml>.
14
15 This program is distributed in the hope that it will be useful, but WITHOUT
16 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
17 FOR A PARTICULAR PURPOSE. See the license for more details.
18*/
19
25#ifndef quantlib_global_bootstrap_hpp
26#define quantlib_global_bootstrap_hpp
27
28#include <ql/functional.hpp>
29#include <ql/math/interpolations/linearinterpolation.hpp>
30#include <ql/math/optimization/levenbergmarquardt.hpp>
31#include <ql/termstructures/bootstraperror.hpp>
32#include <ql/termstructures/bootstraphelper.hpp>
33#include <ql/utilities/dataformatters.hpp>
34#include <utility>
35
36namespace QuantLib {
37
39template <class Curve> class GlobalBootstrap {
40 typedef typename Curve::traits_type Traits; // ZeroYield, Discount, ForwardRate
41 typedef typename Curve::interpolator_type Interpolator; // Linear, LogLinear, ...
42
43 public:
44 GlobalBootstrap(Real accuracy = Null<Real>());
61 GlobalBootstrap(std::vector<ext::shared_ptr<typename Traits::helper> > additionalHelpers,
62 ext::function<std::vector<Date>()> additionalDates,
63 ext::function<Array()> additionalErrors,
64 Real accuracy = Null<Real>());
65 void setup(Curve *ts);
66 void calculate() const;
67
68 private:
69 void initialize() const;
70 Curve *ts_;
72 mutable std::vector<ext::shared_ptr<typename Traits::helper> > additionalHelpers_;
73 ext::function<std::vector<Date>()> additionalDates_;
74 ext::function<Array()> additionalErrors_;
75 mutable bool initialized_ = false, validCurve_ = false;
79 mutable std::vector<Real> lowerBounds_, upperBounds_;
80};
81
82// template definitions
83
84template <class Curve>
85GlobalBootstrap<Curve>::GlobalBootstrap(Real accuracy) : ts_(0), accuracy_(accuracy) {}
86
87template <class Curve>
89 std::vector<ext::shared_ptr<typename Traits::helper> > additionalHelpers,
90 ext::function<std::vector<Date>()> additionalDates,
91 ext::function<Array()> additionalErrors,
92 Real accuracy)
93: ts_(nullptr), accuracy_(accuracy), additionalHelpers_(std::move(additionalHelpers)),
94 additionalDates_(std::move(additionalDates)), additionalErrors_(std::move(additionalErrors)) {}
95
96template <class Curve> void GlobalBootstrap<Curve>::setup(Curve *ts) {
97 ts_ = ts;
98 for (Size j = 0; j < ts_->instruments_.size(); ++j)
99 ts_->registerWithObservables(ts_->instruments_[j]);
100 for (Size j = 0; j < additionalHelpers_.size(); ++j)
101 ts_->registerWithObservables(additionalHelpers_[j]);
102
103 // do not initialize yet: instruments could be invalid here
104 // but valid later when bootstrapping is actually required
105}
106
107template <class Curve> void GlobalBootstrap<Curve>::initialize() const {
108
109 // ensure helpers are sorted
110 std::sort(ts_->instruments_.begin(), ts_->instruments_.end(), detail::BootstrapHelperSorter());
111 std::sort(additionalHelpers_.begin(), additionalHelpers_.end(), detail::BootstrapHelperSorter());
112
113 // skip expired helpers
114 Date firstDate = Traits::initialDate(ts_);
115
116 firstHelper_ = 0;
117 if (!ts_->instruments_.empty()) {
118 while (firstHelper_ < ts_->instruments_.size() && ts_->instruments_[firstHelper_]->pillarDate() <= firstDate)
119 ++firstHelper_;
120 }
121 numberHelpers_ = ts_->instruments_.size() - firstHelper_;
122
123 // skip expired additional helpers
124 firstAdditionalHelper_ = 0;
125 if (!additionalHelpers_.empty()) {
126 while (firstAdditionalHelper_ < additionalHelpers_.size() &&
127 additionalHelpers_[firstAdditionalHelper_]->pillarDate() <= firstDate)
128 ++firstAdditionalHelper_;
129 }
130 numberAdditionalHelpers_ = additionalHelpers_.size() - firstAdditionalHelper_;
131
132 // skip expired additional dates
133 std::vector<Date> additionalDates;
134 if (additionalDates_)
135 additionalDates = additionalDates_();
136 firstAdditionalDate_ = 0;
137 if (!additionalDates.empty()) {
138 while (firstAdditionalDate_ < additionalDates.size() && additionalDates[firstAdditionalDate_] <= firstDate)
139 ++firstAdditionalDate_;
140 }
141 numberAdditionalDates_ = additionalDates.size() - firstAdditionalDate_;
142
143 QL_REQUIRE(numberHelpers_ + numberAdditionalDates_ >= Interpolator::requiredPoints - 1,
144 "not enough alive instruments (" << numberHelpers_ << ") + additional dates (" << numberAdditionalDates_
145 << ") = " << numberHelpers_ + numberAdditionalDates_ << " provided, "
146 << Interpolator::requiredPoints - 1 << " required");
147
148 // calculate dates and times
149 std::vector<Date> &dates = ts_->dates_;
150 std::vector<Time> &times = ts_->times_;
151
152 // first populate the dates vector and make sure they are sorted and there are no duplicates
153 dates.clear();
154 dates.push_back(firstDate);
155 for (Size j = 0; j < numberHelpers_; ++j)
156 dates.push_back(ts_->instruments_[firstHelper_ + j]->pillarDate());
157 for (Size j = firstAdditionalDate_; j < numberAdditionalDates_; ++j)
158 dates.push_back(additionalDates[firstAdditionalDate_ + j]);
159 std::sort(dates.begin(), dates.end());
160 auto it = std::unique(dates.begin(), dates.end());
161 QL_REQUIRE(it == dates.end(), "duplicate dates among alive instruments and additional dates");
162
163 // build times vector
164 times.clear();
165 for (auto& date : dates)
166 times.push_back(ts_->timeFromReference(date));
167
168 // determine maxDate
169 Date maxDate = firstDate;
170 for (Size j = 0; j < numberHelpers_; ++j) {
171 maxDate = std::max(ts_->instruments_[firstHelper_ + j]->latestRelevantDate(), maxDate);
172 }
173 for (Size j = 0; j < numberAdditionalDates_; ++j) {
174 maxDate = std::max(additionalDates[firstAdditionalDate_ + j], maxDate);
175 }
176 ts_->maxDate_ = maxDate;
177
178 // set initial guess only if the current curve cannot be used as guess
179 if (!validCurve_ || ts_->data_.size() != dates.size()) {
180 // ts_->data_[0] is the only relevant item,
181 // but reasonable numbers might be needed for the whole data vector
182 // because, e.g., of interpolation's early checks
183 ts_->data_ = std::vector<Real>(dates.size(), Traits::initialValue(ts_));
184 }
185 initialized_ = true;
186}
187
188template <class Curve> void GlobalBootstrap<Curve>::calculate() const {
189
190 // we might have to call initialize even if the curve is initialized
191 // and not moving, just because helpers might be date relative and change
192 // with evaluation date change.
193 // anyway it makes little sense to use date relative helpers with a
194 // non-moving curve if the evaluation date changes
195 if (!initialized_ || ts_->moving_)
196 initialize();
197
198 // setup helpers
199 for (Size j = 0; j < numberHelpers_; ++j) {
200 const ext::shared_ptr<typename Traits::helper> &helper = ts_->instruments_[firstHelper_ + j];
201 // check for valid quote
202 QL_REQUIRE(helper->quote()->isValid(), io::ordinal(j + 1)
203 << " instrument (maturity: " << helper->maturityDate()
204 << ", pillar: " << helper->pillarDate() << ") has an invalid quote");
205 // don't try this at home!
206 // This call creates helpers, and removes "const".
207 // There is a significant interaction with observability.
208 helper->setTermStructure(const_cast<Curve *>(ts_));
209 }
210
211 // setup additional helpers
212 for (Size j = 0; j < numberAdditionalHelpers_; ++j) {
213 const ext::shared_ptr<typename Traits::helper> &helper = additionalHelpers_[firstAdditionalHelper_ + j];
214 QL_REQUIRE(helper->quote()->isValid(), io::ordinal(j + 1)
215 << " additional instrument (maturity: " << helper->maturityDate()
216 << ") has an invalid quote");
217 helper->setTermStructure(const_cast<Curve *>(ts_));
218 }
219
220 Real accuracy = accuracy_ != Null<Real>() ? accuracy_ : ts_->accuracy_;
221
222 // setup optimizer and EndCriteria
223 Real optEps = accuracy;
224 LevenbergMarquardt optimizer(optEps, optEps, optEps); // FIXME hardcoded tolerances
225 EndCriteria ec(1000, 10, optEps, optEps, optEps); // FIXME hardcoded values here as well
226
227 // setup interpolation
228 if (!validCurve_) {
229 ts_->interpolation_ =
230 ts_->interpolator_.interpolate(ts_->times_.begin(), ts_->times_.end(), ts_->data_.begin());
231 }
232
233 // determine bounds, we use an unconstrained optimisation transforming the free variables to [lowerBound,upperBound]
234 std::vector<Real> lowerBounds(numberHelpers_ + numberAdditionalDates_),
235 upperBounds(numberHelpers_ + numberAdditionalDates_);
236 for (Size i = 0; i < numberHelpers_ + numberAdditionalDates_; ++i) {
237 // just pass zero as the first alive helper, it's not used in the standard QL traits anyway
238 lowerBounds[i] = Traits::minValueAfter(i + 1, ts_, validCurve_, 0);
239 upperBounds[i] = Traits::maxValueAfter(i + 1, ts_, validCurve_, 0);
240 }
241
242 // setup cost function
243 class TargetFunction : public CostFunction {
244 public:
245 TargetFunction(const Size firstHelper,
246 const Size numberHelpers,
247 ext::function<Array()> additionalErrors,
248 Curve* ts,
249 std::vector<Real> lowerBounds,
250 std::vector<Real> upperBounds)
251 : firstHelper_(firstHelper), numberHelpers_(numberHelpers),
252 additionalErrors_(std::move(additionalErrors)), ts_(ts),
253 lowerBounds_(std::move(lowerBounds)), upperBounds_(std::move(upperBounds)) {}
254
255 Real transformDirect(const Real x, const Size i) const {
256 return (std::atan(x) + M_PI_2) / M_PI * (upperBounds_[i] - lowerBounds_[i]) + lowerBounds_[i];
257 }
258
259 Real transformInverse(const Real y, const Size i) const {
260 return std::tan((y - lowerBounds_[i]) * M_PI / (upperBounds_[i] - lowerBounds_[i]) - M_PI_2);
261 }
262
263 Real value(const Array& x) const override {
264 Array v = values(x);
265 std::transform(v.begin(), v.end(), v.begin(), [](Real x) -> Real { return x*x; });
266 return std::sqrt(std::accumulate(v.begin(), v.end(), Real(0.0)) / static_cast<Real>(v.size()));
267 }
268
269 Array values(const Array& x) const override {
270 for (Size i = 0; i < x.size(); ++i) {
271 Traits::updateGuess(ts_->data_, transformDirect(x[i], i), i + 1);
272 }
273 ts_->interpolation_.update();
274 std::vector<Real> result(numberHelpers_);
275 for (Size i = 0; i < numberHelpers_; ++i) {
276 result[i] = ts_->instruments_[firstHelper_ + i]->quote()->value() -
277 ts_->instruments_[firstHelper_ + i]->impliedQuote();
278 }
279 if (additionalErrors_) {
280 Array tmp = additionalErrors_();
281 result.resize(numberHelpers_ + tmp.size());
282 for (Size i = 0; i < tmp.size(); ++i) {
283 result[numberHelpers_ + i] = tmp[i];
284 }
285 }
286 return Array(result.begin(), result.end());
287 }
288
289 private:
290 Size firstHelper_, numberHelpers_;
291 ext::function<Array()> additionalErrors_;
292 Curve *ts_;
293 const std::vector<Real> lowerBounds_, upperBounds_;
294 };
295 TargetFunction cost(firstHelper_, numberHelpers_, additionalErrors_, ts_, lowerBounds, upperBounds);
296
297 // setup guess
298 Array guess(numberHelpers_ + numberAdditionalDates_);
299 for (Size i = 0; i < guess.size(); ++i) {
300 // just pass zero as the first alive helper, it's not used in the standard QL traits anyway
301 guess[i] = cost.transformInverse(Traits::guess(i + 1, ts_, validCurve_, 0), i);
302 }
303
304 // setup problem
305 NoConstraint noConstraint;
306 Problem problem(cost, noConstraint, guess);
307
308 // run optimization
309 optimizer.minimize(problem, ec);
310
311 // evaluate target function on best value found to ensure that data_ contains the optimal value
312 Real finalTargetError = cost.value(problem.currentValue());
313
314 // check final error
315 QL_REQUIRE(finalTargetError <= accuracy,
316 "global bootstrap failed, error is " << finalTargetError << ", accuracy is " << accuracy);
317
318 // set valid flag
319 validCurve_ = true;
320}
321
322} // namespace QuantLib
323
324#endif
1-D array used in linear algebra.
Definition: array.hpp:52
Size size() const
dimension of the array
Definition: array.hpp:495
Cost function abstract class for optimization problem.
Concrete date class.
Definition: date.hpp:125
Criteria to end optimization process:
Definition: endcriteria.hpp:40
Global boostrapper, with additional restrictions.
ext::function< Array()> additionalErrors_
ext::function< std::vector< Date >()> additionalDates_
GlobalBootstrap(Real accuracy=Null< Real >())
Curve::interpolator_type Interpolator
std::vector< Real > lowerBounds_
std::vector< Real > upperBounds_
Curve::traits_type Traits
std::vector< ext::shared_ptr< typename Traits::helper > > additionalHelpers_
Levenberg-Marquardt optimization method.
EndCriteria::Type minimize(Problem &P, const EndCriteria &endCriteria) override
minimize the optimization problem P
No constraint.
Definition: constraint.hpp:79
template class providing a null value for a given type.
Definition: null.hpp:76
Constrained optimization problem.
Definition: problem.hpp:42
const Array & currentValue() const
current value of the local minimum
Definition: problem.hpp:81
detail::ordinal_holder ordinal(Size)
outputs naturals as 1st, 2nd, 3rd...
QL_REAL Real
real number
Definition: types.hpp:50
std::size_t Size
size of a container
Definition: types.hpp:58
Definition: any.hpp:35
STL namespace.