Logo
Fully annotated reference manual - version 1.8.12
Loading...
Searching...
No Matches
iterativebootstrap.hpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2019 Quaternion Risk Management Ltd
3 All rights reserved.
4
5 This file is part of ORE, a free-software/open-source library
6 for transparent pricing and risk analysis - http://opensourcerisk.org
7
8 ORE is free software: you can redistribute it and/or modify it
9 under the terms of the Modified BSD License. You should have received a
10 copy of the license along with this program.
11 The license is also available online at <http://opensourcerisk.org>
12
13 This program is distributed on the basis that it will form a useful
14 contribution to risk analytics and model standardisation, but WITHOUT
15 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
16 FITNESS FOR A PARTICULAR PURPOSE. See the license for more details.
17*/
18
19/*! \file qle/termstructures/iterativebootstrap.hpp
20 \brief Straight copy of ql/termstructures/iterativebootstrap.hpp with minor changes
21*/
22
23#ifndef quantext_iterative_bootstrap_hpp
24#define quantext_iterative_bootstrap_hpp
25
26#include <ql/math/interpolations/linearinterpolation.hpp>
27#include <ql/math/solvers1d/brent.hpp>
28#include <ql/math/solvers1d/finitedifferencenewtonsafe.hpp>
29#include <ql/termstructures/bootstraperror.hpp>
30#include <ql/termstructures/bootstraphelper.hpp>
31#include <ql/utilities/dataformatters.hpp>
32
33namespace QuantExt {
34
35namespace detail {
36
37/*! If \c dontThrow is \c true in QuantExt::IterativeBootstrap and on a given pillar the bootstrap fails when
38 searching for a helper root between \c xMin and \c xMax, we use this function to return the value that gives
39 the minimum absolute helper error in the interval between \c xMin and \c xMax inclusive.
40*/
41template <class Curve>
42QuantLib::Real dontThrowFallback(const QuantLib::BootstrapError<Curve>& error, QuantLib::Real xMin, QuantLib::Real xMax,
43 QuantLib::Size steps) {
44
45 QL_REQUIRE(xMin < xMax, "Expected xMin to be less than xMax");
46
47 QuantLib::Real result = xMin;
48 QuantLib::Real minError = QL_MAX_REAL;
49 QuantLib::Real stepSize = (xMax - xMin) / steps;
50
51 for (QuantLib::Size i = 0; i <= steps; ++i) {
52 QuantLib::Real x = xMin + stepSize * static_cast<double>(i);
53 QuantLib::Real absError = QL_MAX_REAL;
54 try {
55 absError = std::abs(error(x));
56 } catch (...) {
57 }
58
59 if (absError < minError) {
60 result = x;
61 minError = absError;
62 }
63 }
64
65 return result;
66}
67
68} // namespace detail
69
70/*! Straight copy of QuantLib::IterativeBootstrap with the following modifications
71 - addition of a \c globalAccuracy parameter to allow the global bootstrap accuracy to be different than the
72 \c accuracy specified in the \c Curve. In particular, allows for the \c globalAccuracy to be greater than the
73 \c accuracy specified in the \c Curve which is useful in some situations e.g. cubic spline and optionlet
74 stripping. If the \c globalAccuracy is set less than the \c accuracy in the \c Curve, the \c accuracy in the
75 \c Curve is used instead.
76*/
77template <class Curve> class IterativeBootstrap {
78 typedef typename Curve::traits_type Traits;
79 typedef typename Curve::interpolator_type Interpolator;
80
81public:
82 /*! Constructor
83 \param accuracy Accuracy for the bootstrap. If \c Null<Real>(), its value is taken from the
84 termstructure's accuracy.
85 \param globalAccuracy Accuracy for the global bootstrap stopping criterion. If it is set to
86 \c Null<Real>(), its value is taken from the termstructure's accuracy.
87 \param dontThrow If set to \c true, the bootstrap doesn't throw and returns a <em>fall back</em>
88 result
89 \param maxAttempts Number of attempts on each iteration. A number greater than implies retries.
90 \param maxFactor Factor for max value retry on each iteration if there is a failure.
91 \param minFactor Factor for min value retry on each iteration if there is a failure.
92 \param dontThrowSteps If \p dontThrow is \c true, this gives the number of steps to use when searching
93 for a fallback curve pillar value that gives the minimum bootstrap helper error.
94 */
95 IterativeBootstrap(QuantLib::Real accuracy = QuantLib::Null<QuantLib::Real>(),
96 QuantLib::Real globalAccuracy = QuantLib::Null<QuantLib::Real>(), bool dontThrow = false,
97 QuantLib::Size maxAttempts = 1, QuantLib::Real maxFactor = 2.0, QuantLib::Real minFactor = 2.0,
98 QuantLib::Size dontThrowSteps = 10);
99
100 void setup(Curve* ts);
101 void calculate() const;
102
103private:
104 void initialize() const;
105 Curve* ts_;
106 QuantLib::Size n_;
107 QuantLib::Brent firstSolver_;
108 QuantLib::FiniteDifferenceNewtonSafe solver_;
110 mutable QuantLib::Size firstAliveHelper_, alive_;
111 mutable std::vector<QuantLib::Real> previousData_;
112 mutable std::vector<QuantLib::ext::shared_ptr<QuantLib::BootstrapError<Curve> > > errors_;
113 QuantLib::Real accuracy_;
114 QuantLib::Real globalAccuracy_;
116 QuantLib::Size maxAttempts_;
117 QuantLib::Real maxFactor_;
118 QuantLib::Real minFactor_;
119 QuantLib::Size dontThrowSteps_;
120};
121
122template <class Curve>
123IterativeBootstrap<Curve>::IterativeBootstrap(QuantLib::Real accuracy, QuantLib::Real globalAccuracy, bool dontThrow,
124 QuantLib::Size maxAttempts, QuantLib::Real maxFactor,
125 QuantLib::Real minFactor, QuantLib::Size dontThrowSteps)
126 : ts_(0), n_(0), initialized_(false), validCurve_(false), loopRequired_(Interpolator::global),
127 firstAliveHelper_(0), alive_(0), accuracy_(accuracy), globalAccuracy_(globalAccuracy), dontThrow_(dontThrow),
128 maxAttempts_(maxAttempts), maxFactor_(maxFactor), minFactor_(minFactor), dontThrowSteps_(dontThrowSteps) {}
129
130template <class Curve> void IterativeBootstrap<Curve>::setup(Curve* ts) {
131 ts_ = ts;
132 n_ = ts_->instruments_.size();
133 QL_REQUIRE(n_ > 0, "no bootstrap helpers given");
134 for (QuantLib::Size j = 0; j < n_; ++j)
135 ts_->registerWith(ts_->instruments_[j]);
136}
137
138template <class Curve> void IterativeBootstrap<Curve>::initialize() const {
139
140 // ensure helpers are sorted
141 std::sort(ts_->instruments_.begin(), ts_->instruments_.end(), QuantLib::detail::BootstrapHelperSorter());
142
143 // skip expired helpers
144 QuantLib::Date firstDate = Traits::initialDate(ts_);
145 QL_REQUIRE(ts_->instruments_[n_ - 1]->pillarDate() > firstDate, "all instruments expired");
146 firstAliveHelper_ = 0;
147 while (ts_->instruments_[firstAliveHelper_]->pillarDate() <= firstDate)
148 ++firstAliveHelper_;
149 alive_ = n_ - firstAliveHelper_;
150 QL_REQUIRE(alive_ >= Interpolator::requiredPoints - 1,
151 "not enough alive instruments: " << alive_ << " provided, " << Interpolator::requiredPoints - 1
152 << " required");
153
154 // calculate dates and times, create errors_
155 std::vector<QuantLib::Date>& dates = ts_->dates_;
156 std::vector<QuantLib::Time>& times = ts_->times_;
157 dates.resize(alive_ + 1);
158 times.resize(alive_ + 1);
159 errors_.resize(alive_ + 1);
160 dates[0] = firstDate;
161 times[0] = ts_->timeFromReference(dates[0]);
162
163 QuantLib::Date latestRelevantDate, maxDate = firstDate;
164
165 // pillar counter: i
166 // helper counter: j
167 for (QuantLib::Size i = 1, j = firstAliveHelper_; j < n_; ++i, ++j) {
168
169 const QuantLib::ext::shared_ptr<typename Traits::helper>& helper = ts_->instruments_[j];
170 dates[i] = helper->pillarDate();
171 times[i] = ts_->timeFromReference(dates[i]);
172
173 // check for duplicated pillars
174 QL_REQUIRE(dates[i - 1] != dates[i], "more than one instrument with pillar " << dates[i]);
175
176 latestRelevantDate = helper->latestRelevantDate();
177 // check that the helper is really extending the curve, i.e. that
178 // pillar-sorted helpers are also sorted by latestRelevantDate
179 QL_REQUIRE(latestRelevantDate > maxDate, QuantLib::io::ordinal(j + 1)
180 << " instrument (pillar: " << dates[i]
181 << ") has latestRelevantDate (" << latestRelevantDate
182 << ") before or equal to "
183 "previous instrument's latestRelevantDate ("
184 << maxDate << ")");
185 maxDate = latestRelevantDate;
186
187 // when a pillar date is different from the last relevant date the
188 // convergence loop is required even if the Interpolator is local
189 if (dates[i] != latestRelevantDate)
190 loopRequired_ = true;
191
192 errors_[i] = QuantLib::ext::make_shared<QuantLib::BootstrapError<Curve> >(ts_, helper, i);
193 }
194 ts_->maxDate_ = maxDate;
195
196 // set initial guess only if the current curve cannot be used as guess
197 if (!validCurve_ || ts_->data_.size() != alive_ + 1) {
198 // ts_->data_[0] is the only relevant item,
199 // but reasonable numbers might be needed for the whole data vector
200 // because, e.g., of interpolation's early checks
201 ts_->data_ = std::vector<QuantLib::Real>(alive_ + 1, Traits::initialValue(ts_));
202 previousData_.resize(alive_ + 1);
203 }
204 initialized_ = true;
205}
206
207template <class Curve> void IterativeBootstrap<Curve>::calculate() const {
208
209 // we might have to call initialize even if the curve is initialized
210 // and not moving, just because helpers might be date relative and change
211 // with evaluation date change.
212 // anyway it makes little sense to use date relative helpers with a
213 // non-moving curve if the evaluation date changes
214 if (!initialized_ || ts_->moving_)
215 initialize();
216
217 // setup helpers
218 for (QuantLib::Size j = firstAliveHelper_; j < n_; ++j) {
219 const QuantLib::ext::shared_ptr<typename Traits::helper>& helper = ts_->instruments_[j];
220
221 // check for valid quote
222 QL_REQUIRE(helper->quote()->isValid(), QuantLib::io::ordinal(j + 1)
223 << " instrument (maturity: " << helper->maturityDate()
224 << ", pillar: " << helper->pillarDate() << ") has an invalid quote");
225
226 // don't try this at home!
227 // This call creates helpers, and removes "const".
228 // There is a significant interaction with observability.
229 helper->setTermStructure(const_cast<Curve*>(ts_));
230 }
231
232 const std::vector<QuantLib::Time>& times = ts_->times_;
233 const std::vector<QuantLib::Real>& data = ts_->data_;
234 QuantLib::Real accuracy = accuracy_ != QuantLib::Null<QuantLib::Real>() ? accuracy_ : ts_->accuracy_;
235 QuantLib::Real globalAccuracy = globalAccuracy_ == QuantLib::Null<QuantLib::Real>() ? accuracy : globalAccuracy_;
236
237 QuantLib::Size maxIterations = Traits::maxIterations() - 1;
238
239 // there might be a valid curve state to use as guess
240 bool validData = validCurve_;
241
242 for (QuantLib::Size iteration = 0;; ++iteration) {
243 previousData_ = ts_->data_;
244
245 std::vector<QuantLib::Real> minValues(alive_, QuantLib::Null<QuantLib::Real>());
246 std::vector<QuantLib::Real> maxValues(alive_, QuantLib::Null<QuantLib::Real>());
247 std::vector<QuantLib::Size> attempts(alive_, 1);
248
249 for (QuantLib::Size i = 1; i <= alive_; ++i) {
250
251 // bracket root and calculate guess
252 if (minValues[i - 1] == QuantLib::Null<QuantLib::Real>()) {
253 minValues[i - 1] = Traits::minValueAfter(i, ts_, validData, firstAliveHelper_);
254 } else {
255 minValues[i - 1] =
256 minValues[i - 1] < 0.0 ? minFactor_ * minValues[i - 1] : minValues[i - 1] / minFactor_;
257 }
258 if (maxValues[i - 1] == QuantLib::Null<QuantLib::Real>()) {
259 maxValues[i - 1] = Traits::maxValueAfter(i, ts_, validData, firstAliveHelper_);
260 } else {
261 maxValues[i - 1] =
262 maxValues[i - 1] > 0.0 ? maxFactor_ * maxValues[i - 1] : maxValues[i - 1] / maxFactor_;
263 }
264 QuantLib::Real guess = Traits::guess(i, ts_, validData, firstAliveHelper_);
265
266 // adjust guess if needed
267 if (guess >= maxValues[i - 1])
268 guess = maxValues[i - 1] - (maxValues[i - 1] - minValues[i - 1]) / 5.0;
269 else if (guess <= minValues[i - 1])
270 guess = minValues[i - 1] + (maxValues[i - 1] - minValues[i - 1]) / 5.0;
271
272 // extend interpolation if needed
273 if (!validData) {
274 try { // extend interpolation a point at a time
275 // including the pillar to be bootstrapped
276 ts_->interpolation_ =
277 ts_->interpolator_.interpolate(times.begin(), times.begin() + i + 1, data.begin());
278 } catch (...) {
279 if (!Interpolator::global)
280 throw; // no chance to fix it in a later iteration
281
282 // otherwise use Linear while the target
283 // interpolation is not usable yet
284 ts_->interpolation_ =
285 QuantLib::Linear().interpolate(times.begin(), times.begin() + i + 1, data.begin());
286 }
287 ts_->interpolation_.update();
288 }
289
290 try {
291 if (validData)
292 solver_.solve(*errors_[i], accuracy, guess, minValues[i - 1], maxValues[i - 1]);
293 else
294 firstSolver_.solve(*errors_[i], accuracy, guess, minValues[i - 1], maxValues[i - 1]);
295 } catch (std::exception& e) {
296 if (validCurve_) {
297 // the previous curve state might have been a
298 // bad guess, so we retry without using it.
299 // This would be tricky to do here (we're
300 // inside multiple nested for loops, we need
301 // to re-initialize...), so we invalidate the
302 // curve, make a recursive call and then exit.
303 validCurve_ = initialized_ = false;
304 calculate();
305 return;
306 }
307
308 // If we have more attempts left on this iteration, try again. Note that the max and min
309 // bounds will be widened on the retry.
310 if (attempts[i - 1] < maxAttempts_) {
311 attempts[i - 1]++;
312 i--;
313 continue;
314 }
315
316 if (dontThrow_) {
317 // Use the fallback value
318 ts_->data_[i] =
319 detail::dontThrowFallback(*errors_[i], minValues[i - 1], maxValues[i - 1], dontThrowSteps_);
320
321 // Remember to update the interpolation. If we don't and we are on the last "i", we will still
322 // have the last attempted value in the solver being used in ts_->interpolation_.
323 ts_->interpolation_.update();
324 } else {
325 QL_FAIL(QuantLib::io::ordinal(iteration + 1)
326 << " iteration: failed "
327 "at "
328 << QuantLib::io::ordinal(i)
329 << " alive instrument, "
330 "pillar "
331 << errors_[i]->helper()->pillarDate() << ", maturity "
332 << errors_[i]->helper()->maturityDate() << ", reference date " << ts_->dates_[0] << ": "
333 << e.what());
334 }
335 }
336 }
337
338 if (!loopRequired_)
339 break;
340
341 // exit condition
342 QuantLib::Real change = std::fabs(data[1] - previousData_[1]);
343 for (QuantLib::Size i = 2; i <= alive_; ++i)
344 change = std::max(change, std::fabs(data[i] - previousData_[i]));
345
346 if (change <= globalAccuracy || change <= accuracy)
347 break;
348
349 // If we hit the max number of iterations and dontThrow is true, just use what we have
350 if (iteration == maxIterations) {
351 if (dontThrow_) {
352 break;
353 } else {
354 QL_FAIL("convergence not reached after " << iteration << " iterations; last improvement " << change
355 << ", required accuracy "
356 << std::max(globalAccuracy, accuracy));
357 }
358 }
359
360 validData = true;
361 }
362
363 validCurve_ = true;
364}
365
366} // namespace QuantExt
367
368#endif
IterativeBootstrap(QuantLib::Real accuracy=QuantLib::Null< QuantLib::Real >(), QuantLib::Real globalAccuracy=QuantLib::Null< QuantLib::Real >(), bool dontThrow=false, QuantLib::Size maxAttempts=1, QuantLib::Real maxFactor=2.0, QuantLib::Real minFactor=2.0, QuantLib::Size dontThrowSteps=10)
QuantLib::FiniteDifferenceNewtonSafe solver_
Curve::interpolator_type Interpolator
std::vector< QuantLib::Real > previousData_
std::vector< QuantLib::ext::shared_ptr< QuantLib::BootstrapError< Curve > > > errors_
QuantLib::Real dontThrowFallback(const QuantLib::BootstrapError< Curve > &error, QuantLib::Real xMin, QuantLib::Real xMax, QuantLib::Size steps)
QuantLib::BootstrapHelper< QuantLib::OptionletVolatilityStructure > helper
std::vector< Size > steps