QuantLib: a free/open-source library for quantitative finance
fully annotated source code - version 1.38
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
39 public:
40 virtual ~AdditionalBootstrapVariables() = default;
41 // Initialize variables to initial guesses and return them.
42 virtual Array initialize(bool validData) = 0;
43 // Update variables to given values.
44 virtual void update(const Array& x) = 0;
45};
46
47/*! Global boostrapper, with additional restrictions
48
49 The additionalDates functor must return a set of additional dates to add to the
50 interpolation grid. These dates must only depend on the global evaluation date.
51
52 The additionalPenalties functor must yield at least as many values such that
53
54 number of (usual, alive) rate helpers + number of additional values >= number of data points - 1
55
56 (note that the data points contain t=0). These values are treated as additional
57 error terms in the optimization. The usual rate helpers return quoteError here.
58 All error terms are equally weighted.
59
60 The additionalHelpers are registered with the curve like the usual rate helpers,
61 but no pillar dates or error terms are added for them. Pillars and error terms
62 have to be added by additionalDates and additionalPenalties.
63
64 The additionalVariables interface manages a set of additional variables to add
65 to the optimization. This is useful to optimize model parameters used by rate
66 helpers, for example, convexity adjustments for futures. See SimpleQuoteVariables
67 for a concrete implementation of this interface.
68
69 WARNING: This class is known to work with Traits Discount, ZeroYield, Forward,
70 i.e. the usual IR curves traits in QL. It requires Traits::transformDirect()
71 and Traits::transformInverse() to be implemented. Also, check the usage of
72 Traits::updateGuess(), Traits::guess() in this class.
73*/
74template <class Curve> class GlobalBootstrap {
75 typedef typename Curve::traits_type Traits; // ZeroYield, Discount, ForwardRate
76 typedef typename Curve::interpolator_type Interpolator; // Linear, LogLinear, ...
77 typedef std::function<Array(const std::vector<Time>&, const std::vector<Real>&)>
79
80 public:
81 GlobalBootstrap(Real accuracy = Null<Real>(),
82 ext::shared_ptr<OptimizationMethod> optimizer = nullptr,
83 ext::shared_ptr<EndCriteria> endCriteria = nullptr);
84 GlobalBootstrap(std::vector<ext::shared_ptr<typename Traits::helper> > additionalHelpers,
85 std::function<std::vector<Date>()> additionalDates,
86 AdditionalPenalties additionalPenalties,
87 Real accuracy = Null<Real>(),
88 ext::shared_ptr<OptimizationMethod> optimizer = nullptr,
89 ext::shared_ptr<EndCriteria> endCriteria = nullptr,
90 ext::shared_ptr<AdditionalBootstrapVariables> additionalVariables = nullptr);
91 GlobalBootstrap(std::vector<ext::shared_ptr<typename Traits::helper> > additionalHelpers,
92 std::function<std::vector<Date>()> additionalDates,
93 std::function<Array()> additionalPenalties,
94 Real accuracy = Null<Real>(),
95 ext::shared_ptr<OptimizationMethod> optimizer = nullptr,
96 ext::shared_ptr<EndCriteria> endCriteria = nullptr,
97 ext::shared_ptr<AdditionalBootstrapVariables> additionalVariables = nullptr);
98 void setup(Curve *ts);
99 void calculate() const;
100
101 private:
102 void initialize() const;
103 Curve *ts_;
105 ext::shared_ptr<OptimizationMethod> optimizer_;
106 ext::shared_ptr<EndCriteria> endCriteria_;
107 mutable std::vector<ext::shared_ptr<typename Traits::helper> > additionalHelpers_;
108 std::function<std::vector<Date>()> additionalDates_;
110 ext::shared_ptr<AdditionalBootstrapVariables> additionalVariables_;
111 mutable bool initialized_ = false, validCurve_ = false;
114};
115
116// template definitions
117
118template <class Curve>
120 Real accuracy,
121 ext::shared_ptr<OptimizationMethod> optimizer,
122 ext::shared_ptr<EndCriteria> endCriteria)
123: ts_(nullptr), accuracy_(accuracy), optimizer_(std::move(optimizer)),
124 endCriteria_(std::move(endCriteria)) {}
125
126template <class Curve>
128 std::vector<ext::shared_ptr<typename Traits::helper> > additionalHelpers,
129 std::function<std::vector<Date>()> additionalDates,
130 AdditionalPenalties additionalPenalties,
131 Real accuracy,
132 ext::shared_ptr<OptimizationMethod> optimizer,
133 ext::shared_ptr<EndCriteria> endCriteria,
134 ext::shared_ptr<AdditionalBootstrapVariables> additionalVariables)
135: ts_(nullptr), accuracy_(accuracy), optimizer_(std::move(optimizer)),
136 endCriteria_(std::move(endCriteria)), additionalHelpers_(std::move(additionalHelpers)),
137 additionalDates_(std::move(additionalDates)), additionalPenalties_(std::move(additionalPenalties)),
138 additionalVariables_(std::move(additionalVariables)) {}
139
140template <class Curve>
142 std::vector<ext::shared_ptr<typename Traits::helper> > additionalHelpers,
143 std::function<std::vector<Date>()> additionalDates,
144 std::function<Array()> additionalPenalties,
145 Real accuracy,
146 ext::shared_ptr<OptimizationMethod> optimizer,
147 ext::shared_ptr<EndCriteria> endCriteria,
148 ext::shared_ptr<AdditionalBootstrapVariables> additionalVariables)
149: GlobalBootstrap(std::move(additionalHelpers), std::move(additionalDates),
150 additionalPenalties
151 ? [f=std::move(additionalPenalties)](const std::vector<Time>&, const std::vector<Real>&) {
152 return f();
153 }
154 : AdditionalPenalties(),
155 accuracy, std::move(optimizer), std::move(endCriteria),
156 std::move(additionalVariables)) {}
157
158template <class Curve> void GlobalBootstrap<Curve>::setup(Curve *ts) {
159 ts_ = ts;
160 for (Size j = 0; j < ts_->instruments_.size(); ++j)
161 ts_->registerWithObservables(ts_->instruments_[j]);
162 for (Size j = 0; j < additionalHelpers_.size(); ++j)
163 ts_->registerWithObservables(additionalHelpers_[j]);
164
165 // setup optimizer and EndCriteria
166 Real accuracy = accuracy_ != Null<Real>() ? accuracy_ : ts_->accuracy_;
167 if (!optimizer_) {
168 optimizer_ = ext::make_shared<LevenbergMarquardt>(accuracy, accuracy, accuracy);
169 }
170 if (!endCriteria_) {
171 endCriteria_ = ext::make_shared<EndCriteria>(1000, 10, accuracy, accuracy, accuracy);
172 }
173
174 // do not initialize yet: instruments could be invalid here
175 // but valid later when bootstrapping is actually required
176}
177
178template <class Curve> void GlobalBootstrap<Curve>::initialize() const {
179
180 // ensure helpers are sorted
181 std::sort(ts_->instruments_.begin(), ts_->instruments_.end(), detail::BootstrapHelperSorter());
182 std::sort(additionalHelpers_.begin(), additionalHelpers_.end(), detail::BootstrapHelperSorter());
183
184 // skip expired helpers
185 const Date firstDate = Traits::initialDate(ts_);
186
187 firstHelper_ = 0;
188 if (!ts_->instruments_.empty()) {
189 while (firstHelper_ < ts_->instruments_.size() && ts_->instruments_[firstHelper_]->pillarDate() <= firstDate)
190 ++firstHelper_;
191 }
192 numberHelpers_ = ts_->instruments_.size() - firstHelper_;
193
194 // skip expired additional helpers
195 firstAdditionalHelper_ = 0;
196 if (!additionalHelpers_.empty()) {
197 while (firstAdditionalHelper_ < additionalHelpers_.size() &&
198 additionalHelpers_[firstAdditionalHelper_]->pillarDate() <= firstDate)
199 ++firstAdditionalHelper_;
200 }
201 numberAdditionalHelpers_ = additionalHelpers_.size() - firstAdditionalHelper_;
202
203 // skip expired additional dates
204 std::vector<Date> additionalDates;
205 if (additionalDates_)
206 additionalDates = additionalDates_();
207 if (!additionalDates.empty()) {
208 additionalDates.erase(
209 std::remove_if(additionalDates.begin(), additionalDates.end(),
210 [=](const Date& date) { return date <= firstDate; }),
211 additionalDates.end()
212 );
213 }
214 const Size numberAdditionalDates = additionalDates.size();
215
216 QL_REQUIRE(numberHelpers_ + numberAdditionalDates >= Interpolator::requiredPoints - 1,
217 "not enough alive instruments (" << numberHelpers_ << ") + additional dates (" << numberAdditionalDates
218 << ") = " << numberHelpers_ + numberAdditionalDates << " provided, "
219 << Interpolator::requiredPoints - 1 << " required");
220
221 // calculate dates and times
222 std::vector<Date> &dates = ts_->dates_;
223 std::vector<Time> &times = ts_->times_;
224
225 // first populate the dates vector and make sure they are sorted and there are no duplicates
226 dates.clear();
227 dates.push_back(firstDate);
228 for (Size j = 0; j < numberHelpers_; ++j)
229 dates.push_back(ts_->instruments_[firstHelper_ + j]->pillarDate());
230 dates.insert(dates.end(), additionalDates.begin(), additionalDates.end());
231 std::sort(dates.begin(), dates.end());
232 auto it = std::unique(dates.begin(), dates.end());
233 QL_REQUIRE(it == dates.end(), "duplicate dates among alive instruments and additional dates");
234
235 // build times vector
236 times.clear();
237 for (auto& date : dates)
238 times.push_back(ts_->timeFromReference(date));
239
240 // determine maxDate
241 Date maxDate = dates.back();
242 for (Size j = 0; j < numberHelpers_; ++j) {
243 maxDate = std::max(ts_->instruments_[firstHelper_ + j]->latestRelevantDate(), maxDate);
244 }
245 ts_->maxDate_ = maxDate;
246
247 // set initial guess only if the current curve cannot be used as guess
248 if (!validCurve_ || ts_->data_.size() != dates.size()) {
249 // ts_->data_[0] is the only relevant item,
250 // but reasonable numbers might be needed for the whole data vector
251 // because, e.g., of interpolation's early checks
252 ts_->data_ = std::vector<Real>(dates.size(), Traits::initialValue(ts_));
253 validCurve_ = false;
254 }
255 initialized_ = true;
256}
257
258template <class Curve> void GlobalBootstrap<Curve>::calculate() const {
259
260 // we might have to call initialize even if the curve is initialized
261 // and not moving, just because helpers might be date relative and change
262 // with evaluation date change.
263 // anyway it makes little sense to use date relative helpers with a
264 // non-moving curve if the evaluation date changes
265 if (!initialized_ || ts_->moving_)
266 initialize();
267
268 // setup helpers
269 for (Size j = 0; j < numberHelpers_; ++j) {
270 const ext::shared_ptr<typename Traits::helper> &helper = ts_->instruments_[firstHelper_ + j];
271 // check for valid quote
272 QL_REQUIRE(helper->quote()->isValid(), io::ordinal(j + 1)
273 << " instrument (maturity: " << helper->maturityDate()
274 << ", pillar: " << helper->pillarDate() << ") has an invalid quote");
275 // don't try this at home!
276 // This call creates helpers, and removes "const".
277 // There is a significant interaction with observability.
278 helper->setTermStructure(const_cast<Curve *>(ts_));
279 }
280
281 // setup additional helpers
282 for (Size j = 0; j < numberAdditionalHelpers_; ++j) {
283 const ext::shared_ptr<typename Traits::helper> &helper = additionalHelpers_[firstAdditionalHelper_ + j];
284 QL_REQUIRE(helper->quote()->isValid(), io::ordinal(j + 1)
285 << " additional instrument (maturity: " << helper->maturityDate()
286 << ") has an invalid quote");
287 helper->setTermStructure(const_cast<Curve *>(ts_));
288 }
289
290 // setup interpolation
291 if (!validCurve_) {
292 ts_->interpolation_ =
293 ts_->interpolator_.interpolate(ts_->times_.begin(), ts_->times_.end(), ts_->data_.begin());
294 }
295
296 // Setup initial guess. We have guesses for the curve values first (numberPillars),
297 // followed by guesses for the additional variables.
298 const Size numberPillars = ts_->times_.size() - 1;
299 Array additionalGuesses;
300 if (additionalVariables_) {
301 additionalGuesses = additionalVariables_->initialize(validCurve_);
302 }
303 Array guess(numberPillars + additionalGuesses.size());
304 for (Size i = 0; i < numberPillars; ++i) {
305 // just pass zero as the first alive helper, it's not used in the standard QL traits anyway
306 // update ts_->data_ since Traits::guess() usually depends on previous values
307 Traits::updateGuess(ts_->data_, Traits::guess(i + 1, ts_, validCurve_, 0), i + 1);
308 guess[i] = Traits::transformInverse(ts_->data_[i + 1], i + 1, ts_);
309 }
310 std::copy(additionalGuesses.begin(), additionalGuesses.end(), guess.begin() + numberPillars);
311
312 // setup cost function
313 SimpleCostFunction cost([&](const Array& x) {
314 // x has the same layout as guess above: the first numberPillars values go into
315 // the curve, while the rest are new values for the additional variables.
316 for (Size i = 0; i < numberPillars; ++i) {
317 Traits::updateGuess(ts_->data_, Traits::transformDirect(x[i], i + 1, ts_), i + 1);
318 }
319 ts_->interpolation_.update();
320 if (additionalVariables_) {
321 additionalVariables_->update(Array(x.begin() + numberPillars, x.end()));
322 }
323
324 Array additionalErrors;
325 if (additionalPenalties_) {
326 additionalErrors = additionalPenalties_(ts_->times_, ts_->data_);
327 }
328 Array result(numberHelpers_ + additionalErrors.size());
329 std::transform(ts_->instruments_.begin() + firstHelper_, ts_->instruments_.end(),
330 result.begin(),
331 [](const auto& helper) { return helper->quoteError(); });
332 std::copy(additionalErrors.begin(), additionalErrors.end(),
333 result.begin() + numberHelpers_);
334 return result;
335 });
336
337 // setup problem
338 NoConstraint noConstraint;
339 Problem problem(cost, noConstraint, guess);
340
341 // run optimization
342 EndCriteria::Type endType = optimizer_->minimize(problem, *endCriteria_);
343
344 // check the end criteria
346 "global bootstrap failed to minimize to required accuracy: " << endType);
347
348 // set valid flag
349 validCurve_ = true;
350}
351
352} // namespace QuantLib
353
354#endif
boostrap error.
base helper class used for bootstrapping
virtual Array initialize(bool validData)=0
virtual ~AdditionalBootstrapVariables()=default
virtual void update(const Array &x)=0
1-D array used in linear algebra.
Definition: array.hpp:52
const_iterator end() const
Definition: array.hpp:499
Size size() const
dimension of the array
Definition: array.hpp:483
const_iterator begin() const
Definition: array.hpp:491
Concrete date class.
Definition: date.hpp:125
static bool succeeded(EndCriteria::Type ecType)
ext::shared_ptr< EndCriteria > endCriteria_
GlobalBootstrap(Real accuracy=Null< Real >(), ext::shared_ptr< OptimizationMethod > optimizer=nullptr, ext::shared_ptr< EndCriteria > endCriteria=nullptr)
std::function< std::vector< Date >()> additionalDates_
ext::shared_ptr< AdditionalBootstrapVariables > additionalVariables_
Curve::interpolator_type Interpolator
ext::shared_ptr< OptimizationMethod > optimizer_
std::function< Array(const std::vector< Time > &, const std::vector< Real > &)> AdditionalPenalties
Curve::traits_type Traits
AdditionalPenalties additionalPenalties_
std::vector< ext::shared_ptr< typename Traits::helper > > additionalHelpers_
No constraint.
Definition: constraint.hpp:79
template class providing a null value for a given type.
Definition: null.hpp:59
Constrained optimization problem.
Definition: problem.hpp:42
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...
Real Time
continuous quantity with 1-year units
Definition: types.hpp:62
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
Definition: any.hpp:37
STL namespace.