QuantLib: a free/open-source library for quantitative finance
fully annotated source code - version 1.34
Loading...
Searching...
No Matches
globalbootstrap.hpp
Go to the documentation of this file.
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
20/*! \file globalbootstrap.hpp
21 \brief global bootstrap, with additional restrictions
22*/
23
24#ifndef quantlib_global_bootstrap_hpp
25#define quantlib_global_bootstrap_hpp
26
27#include <ql/functional.hpp>
33#include <algorithm>
34#include <utility>
35
36namespace QuantLib {
37
38//! Global boostrapper, with additional restrictions
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>());
45 /*! The set of (alive) additional dates is added to the interpolation grid. The set of additional dates must only
46 depend on the current global evaluation date. The additionalErrors functor must yield at least as many values
47 such that
48
49 number of (usual, alive) rate helpers + number of (alive) additional values >= number of data points - 1
50
51 (note that the data points contain t=0). These values are treated as additional error terms in the optimization,
52 the usual rate helpers return marketQuote - impliedQuote here. All error terms are equally weighted in the
53 optimisation.
54
55 The additional helpers are treated like the usual rate helpers, but no standard pillar dates are added for them.
56
57 WARNING: This class is known to work with Traits Discount, ZeroYield, Forward (i.e. the usual traits for IR curves
58 in QL), it might fail for other traits - check the usage of Traits::updateGuess(), Traits::guess(),
59 Traits::minValueAfter(), Traits::maxValueAfter() in this class against them.
60 */
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;
78};
79
80// template definitions
81
82template <class Curve>
83GlobalBootstrap<Curve>::GlobalBootstrap(Real accuracy) : ts_(0), accuracy_(accuracy) {}
84
85template <class Curve>
87 std::vector<ext::shared_ptr<typename Traits::helper> > additionalHelpers,
88 ext::function<std::vector<Date>()> additionalDates,
89 ext::function<Array()> additionalErrors,
90 Real accuracy)
91: ts_(nullptr), accuracy_(accuracy), additionalHelpers_(std::move(additionalHelpers)),
92 additionalDates_(std::move(additionalDates)), additionalErrors_(std::move(additionalErrors)) {}
93
94template <class Curve> void GlobalBootstrap<Curve>::setup(Curve *ts) {
95 ts_ = ts;
96 for (Size j = 0; j < ts_->instruments_.size(); ++j)
97 ts_->registerWithObservables(ts_->instruments_[j]);
98 for (Size j = 0; j < additionalHelpers_.size(); ++j)
99 ts_->registerWithObservables(additionalHelpers_[j]);
100
101 // do not initialize yet: instruments could be invalid here
102 // but valid later when bootstrapping is actually required
103}
104
105template <class Curve> void GlobalBootstrap<Curve>::initialize() const {
106
107 // ensure helpers are sorted
108 std::sort(ts_->instruments_.begin(), ts_->instruments_.end(), detail::BootstrapHelperSorter());
109 std::sort(additionalHelpers_.begin(), additionalHelpers_.end(), detail::BootstrapHelperSorter());
110
111 // skip expired helpers
112 const Date firstDate = Traits::initialDate(ts_);
113
114 firstHelper_ = 0;
115 if (!ts_->instruments_.empty()) {
116 while (firstHelper_ < ts_->instruments_.size() && ts_->instruments_[firstHelper_]->pillarDate() <= firstDate)
117 ++firstHelper_;
118 }
119 numberHelpers_ = ts_->instruments_.size() - firstHelper_;
120
121 // skip expired additional helpers
122 firstAdditionalHelper_ = 0;
123 if (!additionalHelpers_.empty()) {
124 while (firstAdditionalHelper_ < additionalHelpers_.size() &&
125 additionalHelpers_[firstAdditionalHelper_]->pillarDate() <= firstDate)
126 ++firstAdditionalHelper_;
127 }
128 numberAdditionalHelpers_ = additionalHelpers_.size() - firstAdditionalHelper_;
129
130 // skip expired additional dates
131 std::vector<Date> additionalDates;
132 if (additionalDates_)
133 additionalDates = additionalDates_();
134 if (!additionalDates.empty()) {
135 additionalDates.erase(
136 std::remove_if(additionalDates.begin(), additionalDates.end(),
137 [=](const Date& date) { return date <= firstDate; }),
138 additionalDates.end()
139 );
140 }
141 const Size numberAdditionalDates = additionalDates.size();
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 dates.insert(dates.end(), additionalDates.begin(), additionalDates.end());
158 std::sort(dates.begin(), dates.end());
159 auto it = std::unique(dates.begin(), dates.end());
160 QL_REQUIRE(it == dates.end(), "duplicate dates among alive instruments and additional dates");
161
162 // build times vector
163 times.clear();
164 for (auto& date : dates)
165 times.push_back(ts_->timeFromReference(date));
166
167 // determine maxDate
168 Date maxDate = dates.back();
169 for (Size j = 0; j < numberHelpers_; ++j) {
170 maxDate = std::max(ts_->instruments_[firstHelper_ + j]->latestRelevantDate(), maxDate);
171 }
172 ts_->maxDate_ = maxDate;
173
174 // set initial guess only if the current curve cannot be used as guess
175 if (!validCurve_ || ts_->data_.size() != dates.size()) {
176 // ts_->data_[0] is the only relevant item,
177 // but reasonable numbers might be needed for the whole data vector
178 // because, e.g., of interpolation's early checks
179 ts_->data_ = std::vector<Real>(dates.size(), Traits::initialValue(ts_));
180 }
181 initialized_ = true;
182}
183
184template <class Curve> void GlobalBootstrap<Curve>::calculate() const {
185
186 // we might have to call initialize even if the curve is initialized
187 // and not moving, just because helpers might be date relative and change
188 // with evaluation date change.
189 // anyway it makes little sense to use date relative helpers with a
190 // non-moving curve if the evaluation date changes
191 if (!initialized_ || ts_->moving_)
192 initialize();
193
194 // setup helpers
195 for (Size j = 0; j < numberHelpers_; ++j) {
196 const ext::shared_ptr<typename Traits::helper> &helper = ts_->instruments_[firstHelper_ + j];
197 // check for valid quote
198 QL_REQUIRE(helper->quote()->isValid(), io::ordinal(j + 1)
199 << " instrument (maturity: " << helper->maturityDate()
200 << ", pillar: " << helper->pillarDate() << ") has an invalid quote");
201 // don't try this at home!
202 // This call creates helpers, and removes "const".
203 // There is a significant interaction with observability.
204 helper->setTermStructure(const_cast<Curve *>(ts_));
205 }
206
207 // setup additional helpers
208 for (Size j = 0; j < numberAdditionalHelpers_; ++j) {
209 const ext::shared_ptr<typename Traits::helper> &helper = additionalHelpers_[firstAdditionalHelper_ + j];
210 QL_REQUIRE(helper->quote()->isValid(), io::ordinal(j + 1)
211 << " additional instrument (maturity: " << helper->maturityDate()
212 << ") has an invalid quote");
213 helper->setTermStructure(const_cast<Curve *>(ts_));
214 }
215
216 Real accuracy = accuracy_ != Null<Real>() ? accuracy_ : ts_->accuracy_;
217
218 // setup optimizer and EndCriteria
219 Real optEps = accuracy;
220 LevenbergMarquardt optimizer(optEps, optEps, optEps); // FIXME hardcoded tolerances
221 EndCriteria ec(1000, 10, optEps, optEps, optEps); // FIXME hardcoded values here as well
222
223 // setup interpolation
224 if (!validCurve_) {
225 ts_->interpolation_ =
226 ts_->interpolator_.interpolate(ts_->times_.begin(), ts_->times_.end(), ts_->data_.begin());
227 }
228
229 // determine bounds, we use an unconstrained optimisation transforming the free variables to [lowerBound,upperBound]
230 const Size numberBounds = ts_->times_.size() - 1;
231 std::vector<Real> lowerBounds(numberBounds), upperBounds(numberBounds);
232 for (Size i = 0; i < numberBounds; ++i) {
233 // just pass zero as the first alive helper, it's not used in the standard QL traits anyway
234 lowerBounds[i] = Traits::minValueAfter(i + 1, ts_, validCurve_, 0);
235 upperBounds[i] = Traits::maxValueAfter(i + 1, ts_, validCurve_, 0);
236 }
237
238 // setup cost function
239 class TargetFunction : public CostFunction {
240 public:
241 TargetFunction(const Size firstHelper,
242 const Size numberHelpers,
243 ext::function<Array()> additionalErrors,
244 Curve* ts,
245 std::vector<Real> lowerBounds,
246 std::vector<Real> upperBounds)
247 : firstHelper_(firstHelper), numberHelpers_(numberHelpers),
248 additionalErrors_(std::move(additionalErrors)), ts_(ts),
249 lowerBounds_(std::move(lowerBounds)), upperBounds_(std::move(upperBounds)) {}
250
251 Real transformDirect(const Real x, const Size i) const {
252 return (std::atan(x) + M_PI_2) / M_PI * (upperBounds_[i] - lowerBounds_[i]) + lowerBounds_[i];
253 }
254
255 Real transformInverse(const Real y, const Size i) const {
256 return std::tan((y - lowerBounds_[i]) * M_PI / (upperBounds_[i] - lowerBounds_[i]) - M_PI_2);
257 }
258
259 Real value(const Array& x) const override {
260 Array v = values(x);
261 std::transform(v.begin(), v.end(), v.begin(), [](Real x) -> Real { return x*x; });
262 return std::sqrt(std::accumulate(v.begin(), v.end(), Real(0.0)) / static_cast<Real>(v.size()));
263 }
264
265 Array values(const Array& x) const override {
266 for (Size i = 0; i < x.size(); ++i) {
267 Traits::updateGuess(ts_->data_, transformDirect(x[i], i), i + 1);
268 }
269 ts_->interpolation_.update();
270 std::vector<Real> result(numberHelpers_);
271 for (Size i = 0; i < numberHelpers_; ++i) {
272 result[i] = ts_->instruments_[firstHelper_ + i]->quote()->value() -
273 ts_->instruments_[firstHelper_ + i]->impliedQuote();
274 }
275 if (additionalErrors_) {
276 Array tmp = additionalErrors_();
277 result.resize(numberHelpers_ + tmp.size());
278 for (Size i = 0; i < tmp.size(); ++i) {
279 result[numberHelpers_ + i] = tmp[i];
280 }
281 }
282 return Array(result.begin(), result.end());
283 }
284
285 private:
286 Size firstHelper_, numberHelpers_;
287 ext::function<Array()> additionalErrors_;
288 Curve *ts_;
289 const std::vector<Real> lowerBounds_, upperBounds_;
290 };
291 TargetFunction cost(firstHelper_, numberHelpers_, additionalErrors_, ts_, lowerBounds, upperBounds);
292
293 // setup guess
294 Array guess(numberBounds);
295 for (Size i = 0; i < numberBounds; ++i) {
296 // just pass zero as the first alive helper, it's not used in the standard QL traits anyway
297 guess[i] = cost.transformInverse(Traits::guess(i + 1, ts_, validCurve_, 0), i);
298 }
299
300 // setup problem
301 NoConstraint noConstraint;
302 Problem problem(cost, noConstraint, guess);
303
304 // run optimization
305 optimizer.minimize(problem, ec);
306
307 // evaluate target function on best value found to ensure that data_ contains the optimal value
308 Real finalTargetError = cost.value(problem.currentValue());
309
310 // check final error
311 QL_REQUIRE(finalTargetError <= accuracy,
312 "global bootstrap failed, error is " << finalTargetError << ", accuracy is " << accuracy);
313
314 // set valid flag
315 validCurve_ = true;
316}
317
318} // namespace QuantLib
319
320#endif
boostrap error.
base helper class used for bootstrapping
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
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
output manipulators
#define QL_REQUIRE(condition, message)
throw an error if the given pre-condition is not verified
Definition: errors.hpp:117
Maps function, bind and cref to either the boost or std implementation.
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
Levenberg-Marquardt optimization method.
linear interpolation between discrete points
#define M_PI_2
#define M_PI
Definition: any.hpp:35
STL namespace.
ext::shared_ptr< BlackVolTermStructure > v