Coverage Report

Created: 2026-10-31 06:47

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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