QuantLib: a free/open-source library for quantitative finance
Fully annotated sources - version 1.32
Loading...
Searching...
No Matches
iterativebootstrap.hpp
1/* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2
3/*
4 Copyright (C) 2008, 2011, 2015 Ferdinando Ametrano
5 Copyright (C) 2007 Chris Kenyon
6 Copyright (C) 2007 StatPro Italia srl
7 Copyright (C) 2015 Paolo Mazzocchi
8
9 This file is part of QuantLib, a free-software/open-source library
10 for financial quantitative analysts and developers - http://quantlib.org/
11
12 QuantLib is free software: you can redistribute it and/or modify it
13 under the terms of the QuantLib license. You should have received a
14 copy of the license along with this program; if not, please email
15 <quantlib-dev@lists.sf.net>. The license is also available online at
16 <http://quantlib.org/license.shtml>.
17
18 This program is distributed in the hope that it will be useful, but WITHOUT
19 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
20 FOR A PARTICULAR PURPOSE. See the license for more details.
21*/
22
27#ifndef quantlib_iterative_bootstrap_hpp
28#define quantlib_iterative_bootstrap_hpp
29
30#include <ql/termstructures/bootstraphelper.hpp>
31#include <ql/termstructures/bootstraperror.hpp>
32#include <ql/math/interpolations/linearinterpolation.hpp>
33#include <ql/math/solvers1d/finitedifferencenewtonsafe.hpp>
34#include <ql/math/solvers1d/brent.hpp>
35#include <ql/utilities/dataformatters.hpp>
36
37namespace QuantLib {
38
39namespace detail {
40
45 template <class Curve>
47 Real xMin, Real xMax, Size steps) {
48
49 QL_REQUIRE(xMin < xMax, "Expected xMin to be less than xMax");
50
51 // Set the initial value of the result to xMin and store the absolute bootstrap error at xMin
52 Real result = xMin;
53 Real absError = std::abs(error(xMin));
54 Real minError = absError;
55
56 // Step out to xMax
57 Real stepSize = (xMax - xMin) / steps;
58 for (Size i = 0; i < steps; i++) {
59
60 // Get absolute bootstrap error at updated x value
61 xMin += stepSize;
62 absError = std::abs(error(xMin));
63
64 // If this absolute bootstrap error is less than the minimum, update result and minError
65 if (absError < minError) {
66 result = xMin;
67 minError = absError;
68 }
69 }
70
71 return result;
72 }
73
74}
75
77 template <class Curve>
79 typedef typename Curve::traits_type Traits;
80 typedef typename Curve::interpolator_type Interpolator;
81 public:
96 Real minValue = Null<Real>(),
97 Real maxValue = Null<Real>(),
98 Size maxAttempts = 1,
99 Real maxFactor = 2.0,
100 Real minFactor = 2.0,
101 bool dontThrow = false,
102 Size dontThrowSteps = 10,
103 Size maxEvaluations = MAX_FUNCTION_EVALUATIONS);
104 void setup(Curve* ts);
105 void calculate() const;
106 private:
107 void initialize() const;
115 Curve* ts_;
116 Size n_ = 0;
119 mutable bool initialized_ = false, validCurve_ = false, loopRequired_;
120 mutable Size firstAliveHelper_ = 0, alive_ = 0;
121 mutable std::vector<Real> previousData_;
122 mutable std::vector<ext::shared_ptr<BootstrapError<Curve> > > errors_;
123 };
124
125
126 // template definitions
127
128 template <class Curve>
130 Real minValue,
131 Real maxValue,
132 Size maxAttempts,
133 Real maxFactor,
134 Real minFactor,
135 bool dontThrow,
136 Size dontThrowSteps,
137 Size maxEvaluations)
138 : accuracy_(accuracy), minValue_(minValue), maxValue_(maxValue), maxAttempts_(maxAttempts),
139 maxFactor_(maxFactor), minFactor_(minFactor), dontThrow_(dontThrow),
140 dontThrowSteps_(dontThrowSteps), ts_(nullptr), loopRequired_(Interpolator::global) {
141 QL_REQUIRE(maxFactor_ >= 1.0, "Expected that maxFactor would be at least 1.0 but got " << maxFactor_);
142 QL_REQUIRE(minFactor_ >= 1.0, "Expected that minFactor would be at least 1.0 but got " << minFactor_);
143 firstSolver_.setMaxEvaluations(maxEvaluations);
144 solver_.setMaxEvaluations(maxEvaluations);
145 }
146
147 template <class Curve>
149 ts_ = ts;
150 n_ = ts_->instruments_.size();
151 QL_REQUIRE(n_ > 0, "no bootstrap helpers given");
152 for (Size j=0; j<n_; ++j)
153 ts_->registerWithObservables(ts_->instruments_[j]);
154
155 // do not initialize yet: instruments could be invalid here
156 // but valid later when bootstrapping is actually required
157 }
158
159 template <class Curve>
161 // ensure helpers are sorted
162 std::sort(ts_->instruments_.begin(), ts_->instruments_.end(),
164 // skip expired helpers
165 Date firstDate = Traits::initialDate(ts_);
166 QL_REQUIRE(ts_->instruments_[n_-1]->pillarDate()>firstDate,
167 "all instruments expired");
168 firstAliveHelper_ = 0;
169 while (ts_->instruments_[firstAliveHelper_]->pillarDate() <= firstDate)
170 ++firstAliveHelper_;
171 alive_ = n_-firstAliveHelper_;
172 Size nodes = alive_+1;
173 QL_REQUIRE(nodes >= Interpolator::requiredPoints,
174 "not enough alive instruments: " << alive_ <<
175 " provided, " << Interpolator::requiredPoints-1 <<
176 " required");
177
178 // calculate dates and times, create errors_
179 std::vector<Date>& dates = ts_->dates_;
180 std::vector<Time>& times = ts_->times_;
181 dates.resize(alive_+1);
182 times.resize(alive_+1);
183 errors_.resize(alive_+1);
184 dates[0] = firstDate;
185 times[0] = ts_->timeFromReference(dates[0]);
186
187 Date latestRelevantDate, maxDate = firstDate;
188 // pillar counter: i
189 // helper counter: j
190 for (Size i=1, j=firstAliveHelper_; j<n_; ++i, ++j) {
191 const ext::shared_ptr<typename Traits::helper>& helper =
192 ts_->instruments_[j];
193 dates[i] = helper->pillarDate();
194 times[i] = ts_->timeFromReference(dates[i]);
195 // check for duplicated pillars
196 QL_REQUIRE(dates[i-1]!=dates[i],
197 "more than one instrument with pillar " << dates[i]);
198
199 latestRelevantDate = helper->latestRelevantDate();
200 // check that the helper is really extending the curve, i.e. that
201 // pillar-sorted helpers are also sorted by latestRelevantDate
202 QL_REQUIRE(latestRelevantDate > maxDate,
203 io::ordinal(j+1) << " instrument (pillar: " <<
204 dates[i] << ") has latestRelevantDate (" <<
205 latestRelevantDate << ") before or equal to "
206 "previous instrument's latestRelevantDate (" <<
207 maxDate << ")");
208 maxDate = latestRelevantDate;
209
210 // when a pillar date is different from the last relevant date the
211 // convergence loop is required even if the Interpolator is local
212 if (dates[i] != latestRelevantDate)
213 loopRequired_ = true;
214
215 errors_[i] = ext::shared_ptr<BootstrapError<Curve> >(new
216 BootstrapError<Curve>(ts_, helper, i));
217 }
218 ts_->maxDate_ = maxDate;
219
220 // set initial guess only if the current curve cannot be used as guess
221 if (!validCurve_ || ts_->data_.size()!=alive_+1) {
222 // ts_->data_[0] is the only relevant item,
223 // but reasonable numbers might be needed for the whole data vector
224 // because, e.g., of interpolation's early checks
225 ts_->data_ = std::vector<Real>(alive_+1, Traits::initialValue(ts_));
226 previousData_.resize(alive_+1);
227 validCurve_ = false;
228 }
229 initialized_ = true;
230 }
231
232 template <class Curve>
234
235 // we might have to call initialize even if the curve is initialized
236 // and not moving, just because helpers might be date relative and change
237 // with evaluation date change.
238 // anyway it makes little sense to use date relative helpers with a
239 // non-moving curve if the evaluation date changes
240 if (!initialized_ || ts_->moving_)
241 initialize();
242
243 // setup helpers
244 for (Size j=firstAliveHelper_; j<n_; ++j) {
245 const ext::shared_ptr<typename Traits::helper>& helper =
246 ts_->instruments_[j];
247 // check for valid quote
248 QL_REQUIRE(helper->quote()->isValid(),
249 io::ordinal(j + 1) << " instrument (maturity: " <<
250 helper->maturityDate() << ", pillar: " <<
251 helper->pillarDate() << ") has an invalid quote");
252 // don't try this at home!
253 // This call creates helpers, and removes "const".
254 // There is a significant interaction with observability.
255 helper->setTermStructure(const_cast<Curve*>(ts_));
256 }
257
258 const std::vector<Time>& times = ts_->times_;
259 const std::vector<Real>& data = ts_->data_;
260 Real accuracy = accuracy_ != Null<Real>() ? accuracy_ : ts_->accuracy_;
261
262 Size maxIterations = Traits::maxIterations()-1;
263
264 // there might be a valid curve state to use as guess
265 bool validData = validCurve_;
266
267 for (Size iteration=0; ; ++iteration) {
268 previousData_ = ts_->data_;
269
270 // Store min value and max value at each pillar so that we can expand search if necessary.
271 std::vector<Real> minValues(alive_+1, Null<Real>());
272 std::vector<Real> maxValues(alive_+1, Null<Real>());
273 std::vector<Size> attempts(alive_+1, 1);
274
275 for (Size i=1; i<=alive_; ++i) { // pillar loop
276
277 // shorter aliases for readability and to avoid duplication
278 Real& min = minValues[i];
279 Real& max = maxValues[i];
280
281 // bracket root and calculate guess
282 if (min == Null<Real>()) {
283 // First attempt; we take min and max either from
284 // explicit constructor parameter or from traits
285 min = (minValue_ != Null<Real>() ? minValue_ :
286 Traits::minValueAfter(i, ts_, validData, firstAliveHelper_));
287 max = (maxValue_ != Null<Real>() ? maxValue_ :
288 Traits::maxValueAfter(i, ts_, validData, firstAliveHelper_));
289 } else {
290 // Extending a previous attempt. A negative min
291 // is enlarged; a positive one is shrunk towards 0.
292 min = (min < 0.0 ? Real(min * minFactor_) : Real(min / minFactor_));
293 // The opposite holds for the max.
294 max = (max > 0.0 ? Real(max * maxFactor_) : Real(max / maxFactor_));
295 }
296 Real guess = Traits::guess(i, ts_, validData, firstAliveHelper_);
297
298 // adjust guess if needed
299 if (guess >= max)
300 guess = max - (max - min) / 5.0;
301 else if (guess <= min)
302 guess = min + (max - min) / 5.0;
303
304 // extend interpolation if needed
305 if (!validData) {
306 try { // extend interpolation a point at a time
307 // including the pillar to be boostrapped
308 ts_->interpolation_ = ts_->interpolator_.interpolate(
309 times.begin(), times.begin()+i+1, data.begin());
310 } catch (...) {
311 if (!Interpolator::global)
312 throw; // no chance to fix it in a later iteration
313
314 // otherwise use Linear while the target
315 // interpolation is not usable yet
316 ts_->interpolation_ = Linear().interpolate(
317 times.begin(), times.begin()+i+1, data.begin());
318 }
319 ts_->interpolation_.update();
320 }
321
322 try {
323 if (validData)
324 solver_.solve(*errors_[i], accuracy, guess, min, max);
325 else
326 firstSolver_.solve(*errors_[i], accuracy, guess, min, max);
327 } catch (std::exception &e) {
328 if (validCurve_) {
329 // the previous curve state might have been a
330 // bad guess, so we retry without using it.
331 // This would be tricky to do here (we're
332 // inside multiple nested for loops, we need
333 // to re-initialize...), so we invalidate the
334 // curve, make a recursive call and then exit.
335 validCurve_ = initialized_ = false;
336 calculate();
337 return;
338 }
339
340 // If we have more attempts left on this iteration, try again. Note that the max and min
341 // bounds will be widened on the retry.
342 if (attempts[i] < maxAttempts_) {
343 attempts[i]++;
344 i--;
345 continue;
346 }
347
348 if (dontThrow_) {
349 // Use the fallback value
350 ts_->data_[i] = detail::dontThrowFallback(*errors_[i], min, max, dontThrowSteps_);
351
352 // Remember to update the interpolation. If we don't and we are on the last "i", we will still
353 // have the last attempted value in the solver being used in ts_->interpolation_.
354 ts_->interpolation_.update();
355 } else {
356 QL_FAIL(io::ordinal(iteration + 1) << " iteration: failed "
357 "at " << io::ordinal(i) << " alive instrument, "
358 "pillar " << errors_[i]->helper()->pillarDate() <<
359 ", maturity " << errors_[i]->helper()->maturityDate() <<
360 ", reference date " << ts_->dates_[0] <<
361 ": " << e.what());
362 }
363 }
364 }
365
366 if (!loopRequired_)
367 break;
368
369 // exit condition
370 Real change = std::fabs(data[1]-previousData_[1]);
371 for (Size i=2; i<=alive_; ++i)
372 change = std::max(change, std::fabs(data[i]-previousData_[i]));
373 if (change<=accuracy) // convergence reached
374 break;
375
376 // If we hit the max number of iterations and dontThrow is true, just use what we have
377 if (iteration == maxIterations) {
378 if (dontThrow_) {
379 break;
380 } else {
381 QL_FAIL("convergence not reached after " << iteration <<
382 " iterations; last improvement " << change <<
383 ", required accuracy " << accuracy);
384 }
385 }
386
387 validData = true;
388 }
389 validCurve_ = true;
390 }
391
392}
393
394#endif
Brent 1-D solver
Definition: brent.hpp:37
Concrete date class.
Definition: date.hpp:125
safe Newton 1-D solver with finite difference derivatives
Universal piecewise-term-structure boostrapper.
std::vector< ext::shared_ptr< BootstrapError< Curve > > > errors_
Curve::interpolator_type Interpolator
IterativeBootstrap(Real accuracy=Null< Real >(), Real minValue=Null< Real >(), Real maxValue=Null< Real >(), Size maxAttempts=1, Real maxFactor=2.0, Real minFactor=2.0, bool dontThrow=false, Size dontThrowSteps=10, Size maxEvaluations=MAX_FUNCTION_EVALUATIONS)
FiniteDifferenceNewtonSafe solver_
Linear-interpolation factory and traits
Interpolation interpolate(const I1 &xBegin, const I1 &xEnd, const I2 &yBegin) const
template class providing a null value for a given type.
Definition: null.hpp:76
void setMaxEvaluations(Size evaluations)
Definition: solver1d.hpp:238
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
Real dontThrowFallback(const BootstrapError< Curve > &error, Real xMin, Real xMax, Size steps)
Definition: any.hpp:35