QuantLib: a free/open-source library for quantitative finance
fully annotated source code - version 1.34
Loading...
Searching...
No Matches
iterativebootstrap.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) 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
23/*! \file iterativebootstrap.hpp
24 \brief universal piecewise-term-structure boostrapper.
25*/
26
27#ifndef quantlib_iterative_bootstrap_hpp
28#define quantlib_iterative_bootstrap_hpp
29
36
37namespace QuantLib {
38
39namespace detail {
40
41 /*! If \c dontThrow is \c true in IterativeBootstrap and on a given pillar the bootstrap fails when
42 searching for a helper root between \c xMin and \c xMax, we use this function to return the value that
43 gives the minimum absolute helper error in the interval between \c xMin and \c xMax inclusive.
44 */
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
76 //! Universal piecewise-term-structure boostrapper.
77 template <class Curve>
79 typedef typename Curve::traits_type Traits;
80 typedef typename Curve::interpolator_type Interpolator;
81 public:
82 /*! Constructor
83 \param accuracy Accuracy for the bootstrap stopping criterion. If it is set to
84 \c Null<Real>(), its value is taken from the termstructure's accuracy.
85 \param minValue Allow to override the initial minimum value coming from traits.
86 \param maxValue Allow to override the initial maximum value coming from traits.
87 \param maxAttempts Number of attempts on each iteration. A number greater than 1 implies retries.
88 \param maxFactor Factor for max value retry on each iteration if there is a failure.
89 \param minFactor Factor for min value retry on each iteration if there is a failure.
90 \param dontThrow If set to \c true, the bootstrap doesn't throw and returns a <em>fall back</em>
91 result.
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 */
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
boostrap error.
base helper class used for bootstrapping
Brent 1-D solver.
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
output manipulators
#define QL_REQUIRE(condition, message)
throw an error if the given pre-condition is not verified
Definition: errors.hpp:117
#define QL_FAIL(message)
throw an error (possibly with file and line information)
Definition: errors.hpp:92
Safe (bracketed) Newton 1-D solver with finite difference derivatives.
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
linear interpolation between discrete points
Real dontThrowFallback(const BootstrapError< Curve > &error, Real xMin, Real xMax, Size steps)
Definition: any.hpp:35
#define MAX_FUNCTION_EVALUATIONS
Definition: solver1d.hpp:36