/src/quantlib/ql/termstructures/yield/fittedbonddiscountcurve.hpp
Line | Count | Source |
1 | | /* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ |
2 | | |
3 | | /* |
4 | | Copyright (C) 2007 Allen Kuo |
5 | | Copyright (C) 2009 Ferdinando Ametrano |
6 | | Copyright (C) 2015 Andres Hernandez |
7 | | |
8 | | This file is part of QuantLib, a free-software/open-source library |
9 | | for financial quantitative analysts and developers - http://quantlib.org/ |
10 | | |
11 | | QuantLib is free software: you can redistribute it and/or modify it |
12 | | under the terms of the QuantLib license. You should have received a |
13 | | copy of the license along with this program; if not, please email |
14 | | <quantlib-dev@lists.sf.net>. The license is also available online at |
15 | | <https://www.quantlib.org/license.shtml>. |
16 | | |
17 | | This program is distributed in the hope that it will be useful, but WITHOUT |
18 | | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
19 | | FOR A PARTICULAR PURPOSE. See the license for more details. |
20 | | */ |
21 | | |
22 | | /*! \file fittedbonddiscountcurve.hpp |
23 | | \brief discount curve fitted to a set of bonds |
24 | | */ |
25 | | |
26 | | #ifndef quantlib_fitted_bond_discount_curve_hpp |
27 | | #define quantlib_fitted_bond_discount_curve_hpp |
28 | | |
29 | | #include <ql/termstructures/yield/bondhelpers.hpp> |
30 | | #include <ql/math/optimization/method.hpp> |
31 | | #include <ql/patterns/lazyobject.hpp> |
32 | | #include <ql/math/array.hpp> |
33 | | #include <ql/utilities/clone.hpp> |
34 | | #include <ql/math/optimization/constraint.hpp> |
35 | | |
36 | | namespace QuantLib { |
37 | | |
38 | | //! Discount curve fitted to a set of fixed-coupon bonds |
39 | | /*! This class fits a discount function \f$ d(t) \f$ over a set of |
40 | | bonds, using a user defined fitting method. The discount |
41 | | function is fit in such a way so that all cashflows of all |
42 | | input bonds, when discounted using \f$ d(t) \f$, will |
43 | | reproduce the set of input bond prices in an optimized |
44 | | sense. Minimized price errors are weighted by the inverse of |
45 | | their respective bond duration. |
46 | | |
47 | | The FittedBondDiscountCurve class acts as a generic wrapper, |
48 | | while its inner class FittingMethod provides the |
49 | | implementation details. Developers thus need only derive new |
50 | | fitting methods from the latter. |
51 | | |
52 | | \warning The method can be slow if there are many bonds to |
53 | | fit. Speed also depends on the particular choice of |
54 | | fitting method chosen and its convergence properties |
55 | | under optimization. See also todo list for |
56 | | BondDiscountCurveFittingMethod. |
57 | | |
58 | | \todo refactor the bond helper class so that it is pure |
59 | | virtual and returns a generic bond or its cash |
60 | | flows. Derived classes would include helpers for |
61 | | fixed-rate and zero-coupon bonds. In this way, both |
62 | | bonds and bills can be used to fit a discount curve |
63 | | using the exact same machinery. At present, only |
64 | | fixed-coupon bonds are supported. An even better way to |
65 | | move forward might be to get rate helpers to return |
66 | | cashflows, in which case this class could be used to fit |
67 | | any set of cash flows, not just bonds. |
68 | | |
69 | | \todo add more fitting diagnostics: smoothness, standard |
70 | | deviation, student-t test, etc. Generic smoothness |
71 | | method may be useful for smoothing splines fitting. See |
72 | | Fisher, M., D. Nychka and D. Zervos: "Fitting the term |
73 | | structure of interest rates with smoothing splines." |
74 | | Board of Governors of the Federal Reserve System, |
75 | | Federal Resere Board Working Paper, 95-1. |
76 | | |
77 | | \todo add extrapolation routines |
78 | | |
79 | | \ingroup yieldtermstructures |
80 | | */ |
81 | | class FittedBondDiscountCurve : public YieldTermStructure, |
82 | | public LazyObject { |
83 | | public: |
84 | | class FittingMethod; |
85 | | |
86 | | //! \name Constructors |
87 | | //@{ |
88 | | //! reference date based on current evaluation date |
89 | | FittedBondDiscountCurve(Natural settlementDays, |
90 | | const Calendar& calendar, |
91 | | std::vector<ext::shared_ptr<BondHelper> > bonds, |
92 | | const DayCounter& dayCounter, |
93 | | const FittingMethod& fittingMethod, |
94 | | Real accuracy = 1.0e-10, |
95 | | Size maxEvaluations = 10000, |
96 | | Array guess = Array(), |
97 | | Real simplexLambda = 1.0, |
98 | | Size maxStationaryStateIterations = 100); |
99 | | |
100 | | //! curve reference date fixed for life of curve |
101 | | FittedBondDiscountCurve(const Date& referenceDate, |
102 | | std::vector<ext::shared_ptr<BondHelper> > bonds, |
103 | | const DayCounter& dayCounter, |
104 | | const FittingMethod& fittingMethod, |
105 | | Real accuracy = 1.0e-10, |
106 | | Size maxEvaluations = 10000, |
107 | | Array guess = Array(), |
108 | | Real simplexLambda = 1.0, |
109 | | Size maxStationaryStateIterations = 100); |
110 | | |
111 | | //! don't fit, use precalculated parameters |
112 | | FittedBondDiscountCurve(Natural settlementDays, |
113 | | const Calendar& calendar, |
114 | | const FittingMethod& fittingMethod, |
115 | | Array parameters, |
116 | | Date maxDate, |
117 | | const DayCounter& dayCounter); |
118 | | |
119 | | //! don't fit, use precalculated parameters |
120 | | FittedBondDiscountCurve(const Date& referenceDate, |
121 | | const FittingMethod& fittingMethod, |
122 | | Array parameters, |
123 | | Date maxDate, |
124 | | const DayCounter& dayCounter); |
125 | | //@} |
126 | | |
127 | | //! \name Inspectors |
128 | | //@{ |
129 | | //! total number of bonds used to fit the yield curve |
130 | | Size numberOfBonds() const; |
131 | | //! the latest date for which the curve can return values |
132 | | Date maxDate() const override; |
133 | | //! class holding the results of the fit |
134 | | const FittingMethod& fitResults() const; |
135 | | //@} |
136 | | |
137 | | //! \name Other utilities |
138 | | //@{ |
139 | | /*! This allows to try out multiple guesses and avoid local minima */ |
140 | | void resetGuess(const Array& guess); |
141 | | //@} |
142 | | |
143 | | //! \name Observer interface |
144 | | //@{ |
145 | | void update() override; |
146 | | //@} |
147 | | |
148 | | private: |
149 | | void setup(); |
150 | | void performCalculations() const override; |
151 | | DiscountFactor discountImpl(Time) const override; |
152 | | // target accuracy level to be used in the optimization routine |
153 | | Real accuracy_; |
154 | | // max number of evaluations to be used in the optimization routine |
155 | | Size maxEvaluations_; |
156 | | // sets the scale in the (Simplex) optimization routine |
157 | | Real simplexLambda_; |
158 | | // max number of evaluations where no improvement to solution is made |
159 | | Size maxStationaryStateIterations_; |
160 | | // a guess solution may be passed into the constructor to speed calcs |
161 | | Array guessSolution_; |
162 | | mutable Date maxDate_; |
163 | | std::vector<ext::shared_ptr<BondHelper> > bondHelpers_; |
164 | | Clone<FittingMethod> fittingMethod_; |
165 | | }; |
166 | | |
167 | | |
168 | | //! Base fitting method used to construct a fitted bond discount curve |
169 | | /*! This base class provides the specific methodology/strategy |
170 | | used to construct a FittedBondDiscountCurve. Derived classes |
171 | | need only define the virtual function discountFunction() based |
172 | | on the particular fitting method to be implemented, as well as |
173 | | size(), the number of variables to be solved for/optimized. The |
174 | | generic fitting methodology implemented here can be termed |
175 | | nonlinear, in contrast to (typically faster, computationally) |
176 | | linear fitting method. |
177 | | |
178 | | Optional parameters for FittingMethod include an Array of |
179 | | weights, which will be used as weights to each bond. If not given |
180 | | or empty, then the bonds will be weighted by inverse duration |
181 | | |
182 | | An optional Array may be provided as an L2 regularizor in this case |
183 | | a L2 (gaussian) penalty is applied to each parameter starting from the |
184 | | initial guess. This is the same as giving a Gaussian prior on the parameters |
185 | | |
186 | | \todo derive the special-case class LinearFittingMethods from |
187 | | FittingMethod. A linear fitting to a set of basis |
188 | | functions \f$ b_i(t) \f$ is any fitting of the form |
189 | | \f[ |
190 | | d(t) = \sum_{i=0} c_i b_i(t) |
191 | | \f] |
192 | | i.e., linear in the unknown coefficients \f$ c_i |
193 | | \f$. Such a fitting can be reduced to a linear algebra |
194 | | problem \f$ Ax = b \f$, and for large numbers of bonds, |
195 | | would typically be much faster computationally than the |
196 | | generic non-linear fitting method. |
197 | | |
198 | | \warning some parameters to the Simplex optimization method |
199 | | may need to be tweaked internally to the class, |
200 | | depending on the fitting method used, in order to get |
201 | | proper/reasonable/faster convergence. |
202 | | */ |
203 | | class FittedBondDiscountCurve::FittingMethod { |
204 | | friend class FittedBondDiscountCurve; |
205 | | // internal class |
206 | | class FittingCost; |
207 | | public: |
208 | 0 | virtual ~FittingMethod() = default; |
209 | | //! total number of coefficients to fit/solve for |
210 | | virtual Size size() const = 0; |
211 | | //! output array of results of optimization problem |
212 | | Array solution() const; |
213 | | //! final number of iterations used in the optimization problem |
214 | | Integer numberOfIterations() const; |
215 | | //! final value of cost function after optimization |
216 | | Real minimumCostValue() const; |
217 | | //! error code of the optimization |
218 | | EndCriteria::Type errorCode() const; |
219 | | //! clone of the current object |
220 | | virtual std::unique_ptr<FittingMethod> clone() const = 0; |
221 | | //! return whether there is a constraint at zero |
222 | | bool constrainAtZero() const; |
223 | | //! return weights being used |
224 | | Array weights() const; |
225 | | //! return l2 penalties being used |
226 | | Array l2() const; |
227 | | //! return optimization method being used |
228 | | ext::shared_ptr<OptimizationMethod> optimizationMethod() const; |
229 | | //! return optimization contraint |
230 | | const Constraint& constraint() const; |
231 | | //! open discountFunction to public |
232 | | DiscountFactor discount(const Array& x, Time t) const; |
233 | | protected: |
234 | | //! constructors |
235 | | FittingMethod(bool constrainAtZero = true, |
236 | | const Array& weights = Array(), |
237 | | ext::shared_ptr<OptimizationMethod> optimizationMethod = |
238 | | ext::shared_ptr<OptimizationMethod>(), |
239 | | Array l2 = Array(), |
240 | | Real minCutoffTime = 0.0, |
241 | | Real maxCutoffTime = QL_MAX_REAL, |
242 | | Constraint constraint = NoConstraint()); |
243 | | //! rerun every time instruments/referenceDate changes |
244 | | virtual void init(); |
245 | | //! discount function called by FittedBondDiscountCurve |
246 | | virtual DiscountFactor discountFunction(const Array& x, |
247 | | Time t) const = 0; |
248 | | |
249 | | //! constrains discount function to unity at \f$ T=0 \f$, if true |
250 | | bool constrainAtZero_; |
251 | | //! internal reference to the FittedBondDiscountCurve instance |
252 | | FittedBondDiscountCurve* curve_; |
253 | | //! solution array found from optimization, set in calculate() |
254 | | Array solution_; |
255 | | //! optional guess solution to be passed into constructor. |
256 | | /*! The idea is to use a previous solution as a guess solution to |
257 | | the discount curve, in an attempt to speed up calculations. |
258 | | */ |
259 | | Array guessSolution_; |
260 | | //! base class sets this cost function used in the optimization routine |
261 | | ext::shared_ptr<FittingCost> costFunction_; |
262 | | private: |
263 | | // curve optimization called here- adjust optimization parameters here |
264 | | void calculate(); |
265 | | // array of normalized (duration) weights, one for each bond helper |
266 | | Array weights_; |
267 | | // array of l2 penalties one for each parameter |
268 | | Array l2_; |
269 | | // whether or not the weights should be calculated internally |
270 | | bool calculateWeights_; |
271 | | // total number of iterations used in the optimization routine |
272 | | // (possibly including gradient evaluations) |
273 | | Integer numberOfIterations_; |
274 | | // final value for the minimized cost function |
275 | | Real costValue_; |
276 | | // error code returned by OptimizationMethod::minimize() |
277 | | EndCriteria::Type errorCode_ = EndCriteria::None; |
278 | | // optimization method to be used, if none provided use Simplex |
279 | | ext::shared_ptr<OptimizationMethod> optimizationMethod_; |
280 | | // optimization constraint, if none provided use NoConstraint |
281 | | Constraint constraint_; |
282 | | // flat extrapolation of instantaneous forward before / after cutoff |
283 | | Real minCutoffTime_, maxCutoffTime_; |
284 | | }; |
285 | | |
286 | | // inline |
287 | | |
288 | 0 | inline Size FittedBondDiscountCurve::numberOfBonds() const { |
289 | 0 | return bondHelpers_.size(); |
290 | 0 | } |
291 | | |
292 | 0 | inline Date FittedBondDiscountCurve::maxDate() const { |
293 | 0 | calculate(); |
294 | 0 | return maxDate_; |
295 | 0 | } |
296 | | |
297 | | inline const FittedBondDiscountCurve::FittingMethod& |
298 | 0 | FittedBondDiscountCurve::fitResults() const { |
299 | 0 | calculate(); |
300 | 0 | return *fittingMethod_; |
301 | 0 | } |
302 | | |
303 | 0 | inline void FittedBondDiscountCurve::update() { |
304 | 0 | YieldTermStructure::update(); |
305 | 0 | LazyObject::update(); |
306 | 0 | } |
307 | | |
308 | 0 | inline void FittedBondDiscountCurve::setup() { |
309 | 0 | for (auto& bondHelper : bondHelpers_) |
310 | 0 | registerWith(bondHelper); |
311 | 0 | } |
312 | | |
313 | 0 | inline DiscountFactor FittedBondDiscountCurve::discountImpl(Time t) const { |
314 | 0 | calculate(); |
315 | 0 | return fittingMethod_->discount(fittingMethod_->solution_, t); |
316 | 0 | } |
317 | | |
318 | | inline Integer |
319 | 0 | FittedBondDiscountCurve::FittingMethod::numberOfIterations() const { |
320 | 0 | return numberOfIterations_; |
321 | 0 | } |
322 | | |
323 | | inline |
324 | 0 | Real FittedBondDiscountCurve::FittingMethod::minimumCostValue() const { |
325 | 0 | return costValue_; |
326 | 0 | } |
327 | | |
328 | | inline |
329 | 0 | EndCriteria::Type FittedBondDiscountCurve::FittingMethod::errorCode() const { |
330 | 0 | return errorCode_; |
331 | 0 | } |
332 | | |
333 | 0 | inline Array FittedBondDiscountCurve::FittingMethod::solution() const { |
334 | 0 | return solution_; |
335 | 0 | } |
336 | | |
337 | 0 | inline bool FittedBondDiscountCurve::FittingMethod::constrainAtZero() const { |
338 | 0 | return constrainAtZero_; |
339 | 0 | } |
340 | | |
341 | 0 | inline Array FittedBondDiscountCurve::FittingMethod::weights() const { |
342 | 0 | return weights_; |
343 | 0 | } |
344 | | |
345 | 0 | inline Array FittedBondDiscountCurve::FittingMethod::l2() const { |
346 | 0 | return l2_; |
347 | 0 | } |
348 | | |
349 | | inline ext::shared_ptr<OptimizationMethod> |
350 | 0 | FittedBondDiscountCurve::FittingMethod::optimizationMethod() const { |
351 | 0 | return optimizationMethod_; |
352 | 0 | } |
353 | | |
354 | 0 | inline const Constraint& FittedBondDiscountCurve::FittingMethod::constraint() const { |
355 | 0 | return constraint_; |
356 | 0 | } |
357 | | |
358 | 0 | inline DiscountFactor FittedBondDiscountCurve::FittingMethod::discount(const Array& x, Time t) const { |
359 | 0 | if (t < minCutoffTime_) { |
360 | | // flat fwd extrapolation before min cutoff time |
361 | 0 | return std::exp(std::log(discountFunction(x, minCutoffTime_)) / minCutoffTime_ * t); |
362 | 0 | } else if (t > maxCutoffTime_) { |
363 | | // flat fwd extrapolation after max cutoff time |
364 | 0 | return discountFunction(x, maxCutoffTime_) * |
365 | 0 | std::exp((std::log(discountFunction(x, maxCutoffTime_ + 1E-4)) - |
366 | 0 | std::log(discountFunction(x, maxCutoffTime_))) * |
367 | 0 | 1E4 * (t - maxCutoffTime_)); |
368 | 0 | } else { |
369 | 0 | return discountFunction(x, t); |
370 | 0 | } |
371 | 0 | } |
372 | | } |
373 | | |
374 | | #endif |