• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

lballabio / QuantLib / 20336169357

18 Dec 2025 11:58AM UTC coverage: 74.15% (-0.1%) from 74.295%
20336169357

Pull #2368

github

web-flow
Merge 57372ce41 into c15a6fecb
Pull Request #2368: Fx options utils - `BlackVolatilitySurfaceDelta` class

109 of 170 new or added lines in 5 files covered. (64.12%)

423 existing lines in 23 files now uncovered.

57618 of 77705 relevant lines covered (74.15%)

8750508.14 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

83.33
/ql/termstructures/yield/nonlinearfittingmethods.cpp
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) 2010 Alessandro Roveda
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
#include <ql/math/bernsteinpolynomial.hpp>
23
#include <ql/termstructures/yield/nonlinearfittingmethods.hpp>
24
#include <utility>
25

26
namespace QuantLib {
27

28
    ExponentialSplinesFitting::ExponentialSplinesFitting(
3✔
29
        bool constrainAtZero,
30
        const Array& weights,
31
        const ext::shared_ptr<OptimizationMethod>& optimizationMethod,
32
        const Array& l2,
33
        const Real minCutoffTime,
34
        const Real maxCutoffTime,
35
        const Size numCoeffs,
36
        const Real fixedKappa,
37
        Constraint constraint)
3✔
38
    : FittedBondDiscountCurve::FittingMethod(constrainAtZero, weights, optimizationMethod, l2,
39
                                             minCutoffTime, maxCutoffTime, std::move(constraint)),
40
      numCoeffs_(numCoeffs), fixedKappa_(fixedKappa) {
9✔
41
        QL_REQUIRE(ExponentialSplinesFitting::size() > 0, "At least 1 unconstrained coefficient required");
3✔
42
    }
3✔
43

44
    ExponentialSplinesFitting::ExponentialSplinesFitting(
×
45
        bool constrainAtZero,
46
        const Array& weights,
47
        const Array& l2, const Real minCutoffTime, const Real maxCutoffTime,
48
        const Size numCoeffs, const Real fixedKappa,
49
        Constraint constraint)
×
50
    : ExponentialSplinesFitting(constrainAtZero, weights, {}, l2,
51
                                minCutoffTime, maxCutoffTime,
52
                                numCoeffs, fixedKappa, std::move(constraint)) {}
×
53

54
    ExponentialSplinesFitting::ExponentialSplinesFitting(
1✔
55
        bool constrainAtZero,
56
        const Size numCoeffs,
57
        const Real fixedKappa,
58
        const Array& weights,
59
        Constraint constraint)
1✔
60
    : ExponentialSplinesFitting(constrainAtZero, weights, {}, Array(),
1✔
61
                                0.0, QL_MAX_REAL,
62
                                numCoeffs, fixedKappa, std::move(constraint)) {}
3✔
63

64
    std::unique_ptr<FittedBondDiscountCurve::FittingMethod>
65
    ExponentialSplinesFitting::clone() const {
6✔
66
        return std::make_unique<ExponentialSplinesFitting>(*this); 
6✔
67
    }
68

69
    Size ExponentialSplinesFitting::size() const {
6,085,015✔
70
        Size N = constrainAtZero_ ? numCoeffs_ : numCoeffs_ + 1;
6,085,015✔
71
        
72
        return (fixedKappa_ != Null<Real>()) ? N-1 : N; //One fewer optimization parameters if kappa is fixed
6,085,015✔
73
    }
74

75
    DiscountFactor ExponentialSplinesFitting::discountFunction(const Array& x,
6,084,998✔
76
                                                               Time t) const {
77
        DiscountFactor d = 0.0;
78
        Size N = size();
6,084,998✔
79
        //Use the interal fixedKappa_ member if non-zero, otherwise take kappa from the passed x[] array
80
        Real kappa = (fixedKappa_ != Null<Real>()) ? fixedKappa_: x[N-1];
6,084,998✔
81
        Real coeff = 0;
82

83
        if (!constrainAtZero_) {
6,084,998✔
84
            for (Size i = 0; i < N - 1; ++i) {
×
85
                d += x[i] * std::exp(-kappa * (i + 1) * t);
×
86
            }
87
        } else {
88
            //  notation:
89
            //  d(t) = coeff* exp(-kappa*1*t) + x[0]* exp(-kappa*2*t) +
90
            //  x[1]* exp(-kappa*3*t) + ..+ x[7]* exp(-kappa*9*t)
91
            for (Size i = 0; i < N - 1; i++) {
50,834,298✔
92
                d += x[i] * std::exp(-kappa * (i + 2) * t);
44,749,300✔
93
                coeff += x[i];
44,749,300✔
94
            }
95
            coeff = 1.0 - coeff;
6,084,998✔
96
            d += coeff * std::exp(-kappa * t);
6,084,998✔
97
        }
98

99
        return d;
6,084,998✔
100
    }
101

102

103
    NelsonSiegelFitting::NelsonSiegelFitting(
6✔
104
        const Array& weights,
105
        const ext::shared_ptr<OptimizationMethod>& optimizationMethod,
106
        const Array& l2,
107
        const Real minCutoffTime,
108
        const Real maxCutoffTime,
109
        Constraint constraint)
6✔
110
    : FittedBondDiscountCurve::FittingMethod(true, weights, optimizationMethod, l2,
111
                                             minCutoffTime, maxCutoffTime, std::move(constraint)) {}
18✔
112

113
    NelsonSiegelFitting::NelsonSiegelFitting(
×
114
        const Array& weights,
115
        const Array& l2,
116
        const Real minCutoffTime,
117
        const Real maxCutoffTime,
118
        Constraint constraint)
×
119
    : NelsonSiegelFitting(weights, {}, l2,
120
                          minCutoffTime, maxCutoffTime, std::move(constraint)) {}
×
121

122
    std::unique_ptr<FittedBondDiscountCurve::FittingMethod>
123
    NelsonSiegelFitting::clone() const {
6✔
124
        return std::make_unique<NelsonSiegelFitting>(*this);
6✔
125
    }
126

127
    Size NelsonSiegelFitting::size() const {
3,822,799✔
128
        return 4;
3,822,799✔
129
    }
130

131
    DiscountFactor NelsonSiegelFitting::discountFunction(const Array& x,
3,822,777✔
132
                                                         Time t) const {
133
        Real kappa = x[size()-1];
3,822,777✔
134
        Real zeroRate = x[0] + (x[1] + x[2])*
3,822,777✔
135
                        (1.0 - std::exp(-kappa*t))/
3,822,777✔
136
                        ((kappa+QL_EPSILON)*(t+QL_EPSILON)) -
3,822,777✔
137
                        (x[2])*std::exp(-kappa*t);
3,822,777✔
138
        DiscountFactor d = std::exp(-zeroRate * t) ;
3,822,777✔
139
        return d;
3,822,777✔
140
    }
141

142

143
    SvenssonFitting::SvenssonFitting(const Array& weights,
1✔
144
                                     const ext::shared_ptr<OptimizationMethod>& optimizationMethod,
145
                                     const Array& l2,
146
                                     const Real minCutoffTime,
147
                                     const Real maxCutoffTime,
148
                                     Constraint constraint)
1✔
149
    : FittedBondDiscountCurve::FittingMethod(true, weights, optimizationMethod, l2,
150
                                             minCutoffTime, maxCutoffTime, std::move(constraint)) {}
3✔
151

152
    SvenssonFitting::SvenssonFitting(const Array& weights,
×
153
                                     const Array& l2,
154
                                     const Real minCutoffTime,
155
                                     const Real maxCutoffTime,
156
                                     Constraint constraint)
×
157
    : SvenssonFitting(weights, {}, l2,
158
                      minCutoffTime, maxCutoffTime, std::move(constraint)) {}
×
159

160
    std::unique_ptr<FittedBondDiscountCurve::FittingMethod>
161
    SvenssonFitting::clone() const {
2✔
162
        return std::make_unique<SvenssonFitting>(*this);
2✔
163
    }
164

165
    Size SvenssonFitting::size() const {
6,372,360✔
166
        return 6;
6,372,360✔
167
    }
168

169
    DiscountFactor SvenssonFitting::discountFunction(const Array& x,
3,186,177✔
170
                                                     Time t) const {
171
        Real kappa = x[size()-2];
3,186,177✔
172
        Real kappa_1 = x[size()-1];
3,186,177✔
173

174
        Real zeroRate = x[0] + (x[1] + x[2])*
3,186,177✔
175
                        (1.0 - std::exp(-kappa*t))/
3,186,177✔
176
                        ((kappa+QL_EPSILON)*(t+QL_EPSILON)) -
3,186,177✔
177
                        (x[2])*std::exp(-kappa*t) +
3,186,177✔
178
                        x[3]* (((1.0 - std::exp(-kappa_1*t))/((kappa_1+QL_EPSILON)*(t+QL_EPSILON)))- std::exp(-kappa_1*t));
3,186,177✔
179
        DiscountFactor d = std::exp(-zeroRate * t) ;
3,186,177✔
180
        return d;
3,186,177✔
181
    }
182

183

184
    CubicBSplinesFitting::CubicBSplinesFitting(
1✔
185
        const std::vector<Time>& knots,
186
        bool constrainAtZero,
187
        const Array& weights,
188
        const ext::shared_ptr<OptimizationMethod>& optimizationMethod,
189
        const Array& l2,
190
        const Real minCutoffTime,
191
        const Real maxCutoffTime,
192
        Constraint constraint)
1✔
193
    : FittedBondDiscountCurve::FittingMethod(constrainAtZero, weights, optimizationMethod, l2,
194
                                             minCutoffTime, maxCutoffTime, std::move(constraint)),
195
      splines_(3, knots.size() - 5, knots) {
3✔
196

197
        QL_REQUIRE(knots.size() >= 8,
1✔
198
                   "At least 8 knots are required" );
199
        Size basisFunctions = knots.size() - 4;
1✔
200

201
        if (constrainAtZero) {
1✔
202
            size_ = basisFunctions-1;
1✔
203

204
            // Note: A small but nonzero N_th basis function at t=0 may
205
            // lead to an ill conditioned problem
206
            N_ = 1;
1✔
207

208
            QL_REQUIRE(std::abs(splines_(N_, 0.0)) > QL_EPSILON,
1✔
209
                       "N_th cubic B-spline must be nonzero at t=0");
210
        } else {
211
            size_ = basisFunctions;
×
212
            N_ = 0;
×
213
        }
214
    }
1✔
215

216
    CubicBSplinesFitting::CubicBSplinesFitting(
×
217
        const std::vector<Time>& knots,
218
        bool constrainAtZero,
219
        const Array& weights,
220
        const Array& l2,
221
        const Real minCutoffTime,
222
        const Real maxCutoffTime,
223
        Constraint constraint)
×
224
    : CubicBSplinesFitting(knots, constrainAtZero, weights, {}, l2,
225
                           minCutoffTime, maxCutoffTime, std::move(constraint)) {}
×
226

227
    Real CubicBSplinesFitting::basisFunction(Integer i, Time t) const {
×
228
        return splines_(i,t);
×
229
    }
230

231
    std::unique_ptr<FittedBondDiscountCurve::FittingMethod>
232
    CubicBSplinesFitting::clone() const {
2✔
233
        return std::make_unique<CubicBSplinesFitting>(*this);
2✔
234
    }
235

236
    Size CubicBSplinesFitting::size() const {
6✔
237
        return size_;
6✔
238
    }
239

240
    DiscountFactor CubicBSplinesFitting::discountFunction(const Array& x,
652,245✔
241
                                                          Time t) const {
242
        DiscountFactor d = 0.0;
243

244
        if (!constrainAtZero_) {
652,245✔
245
            for (Size i=0; i<size_; ++i) {
×
246
                d += x[i] * splines_(i,t);
×
247
            }
248
        } else {
249
            const Real T = 0.0;
250
            Real sum = 0.0;
251
            for (Size i=0; i<size_; ++i) {
4,565,715✔
252
                if (i < N_) {
3,913,470✔
253
                    d += x[i] * splines_(i,t);
652,245✔
254
                    sum += x[i] * splines_(i,T);
652,245✔
255
                } else {
256
                    d += x[i] * splines_(i+1,t);
3,261,225✔
257
                    sum += x[i] * splines_(i+1,T);
3,261,225✔
258
                }
259
            }
260
            Real coeff = 1.0 - sum;
652,245✔
261
            coeff /= splines_(N_,T);
652,245✔
262
            d += coeff * splines_(N_,t);
652,245✔
263
        }
264

265
        return d;
652,245✔
266
    }
267

268
    NaturalCubicFitting::NaturalCubicFitting(
1✔
269
        const std::vector<Time>& knotTimes,
270
        const Array& weights,
271
        const ext::shared_ptr<OptimizationMethod>& optimizationMethod,
272
        const Array& l2,
273
        Real minCutoffTime,
274
        Real maxCutoffTime,
275
        Constraint constraint)
1✔
276
    : FittedBondDiscountCurve::FittingMethod(true, weights, optimizationMethod, l2,
277
                                             minCutoffTime, maxCutoffTime, std::move(constraint)),
278
      knotTimes_(knotTimes) {
3✔
279
        knotTimes_.push_back(0.0);
1✔
280
        std::sort(knotTimes_.begin(), knotTimes_.end());
1✔
281

282
        auto last = std::unique(knotTimes_.begin(), knotTimes_.end(),
1✔
283
                                [](Time a, Time b){ return std::fabs(a - b) <= 1e-14; });
5✔
284
        knotTimes_.erase(last, knotTimes_.end());
285

286
        QL_REQUIRE(knotTimes_.size() >= 2,
1✔
287
                   "NaturalCubicFitting: at least two knot times required");
288

289
        const Size n = knotTimes_.size();
290
        size_ = n - 1;
1✔
291

292
        for (Size i = 0; i + 1 < n; ++i) {
6✔
293
            Real h = knotTimes_[i+1] - knotTimes_[i];
5✔
294
            QL_REQUIRE(h > 1e-14,
5✔
295
                       "NaturalCubicFitting: knot times must be strictly increasing (non-zero spacing)");
296
            QL_REQUIRE(std::isfinite(h),
5✔
297
                       "NaturalCubicFitting: non-finite knot spacing");
298
        }
299
    }
1✔
300

UNCOV
301
    NaturalCubicFitting::NaturalCubicFitting(
×
302
        const std::vector<Time>& knotTimes,
303
        const Array& weights,
304
        const Array& l2,
305
        Real minCutoffTime,
306
        Real maxCutoffTime,
307
        Constraint constraint)
×
308
    : NaturalCubicFitting(knotTimes, weights, {}, l2,
UNCOV
309
                          minCutoffTime, maxCutoffTime, std::move(constraint)) {}
×
310

311
    std::unique_ptr<FittedBondDiscountCurve::FittingMethod>
312
    NaturalCubicFitting::clone() const {
2✔
313
        return std::make_unique<NaturalCubicFitting>(*this);
2✔
314
    }
315

316
    Size NaturalCubicFitting::size() const {
463,566✔
317
        return size_;
463,566✔
318
    }
319

320
    DiscountFactor NaturalCubicFitting::discountFunction(const Array& x, Time t) const {
463,560✔
321
        const Size n = knotTimes_.size();
322
        const Size expected = size();
463,560✔
323
        QL_REQUIRE(x.size() == expected,
463,560✔
324
                   "NaturalCubicFitting::discountFunction(): parameter size mismatch: expected "
325
                   << expected << " got " << x.size());
326

327
        Array y(n);
463,560✔
328
        y[0] = 1.0;
463,560✔
329
        for (Size i = 1; i < n; ++i)
2,781,360✔
330
            y[i] = x[i - 1];
2,317,800✔
331

332
        for (Size i = 0; i < n; ++i)
3,244,920✔
333
            QL_REQUIRE(std::isfinite(y[i]), "NaturalCubicFitting::discountFunction(): non-finite nodal value");
2,781,360✔
334

335
        CubicNaturalSpline spline(knotTimes_.begin(), knotTimes_.end(), y.begin());
463,560✔
336
        spline.update();
463,560✔
337
        return spline(std::clamp(t, knotTimes_.front(), knotTimes_.back()));
927,120✔
338
    }
339

340

341
    SimplePolynomialFitting::SimplePolynomialFitting(
1✔
342
        Natural degree,
343
        bool constrainAtZero,
344
        const Array& weights,
345
        const ext::shared_ptr<OptimizationMethod>& optimizationMethod,
346
        const Array& l2,
347
        const Real minCutoffTime,
348
        const Real maxCutoffTime,
349
        Constraint constraint)
1✔
350
    : FittedBondDiscountCurve::FittingMethod(constrainAtZero, weights, optimizationMethod, l2,
351
                                             minCutoffTime, maxCutoffTime, std::move(constraint)),
352
      size_(constrainAtZero ? degree : degree + 1) {}
3✔
353

UNCOV
354
    SimplePolynomialFitting::SimplePolynomialFitting(
×
355
        Natural degree,
356
        bool constrainAtZero,
357
        const Array& weights,
358
        const Array& l2,
359
        const Real minCutoffTime,
360
        const Real maxCutoffTime,
UNCOV
361
        Constraint constraint)
×
362
    : SimplePolynomialFitting(degree, constrainAtZero, weights, {}, l2,
UNCOV
363
                              minCutoffTime, maxCutoffTime, std::move(constraint)) {}
×
364

365
    std::unique_ptr<FittedBondDiscountCurve::FittingMethod>
366
    SimplePolynomialFitting::clone() const {
2✔
367
        return std::make_unique<SimplePolynomialFitting>(*this);
2✔
368
    }
369

370
    Size SimplePolynomialFitting::size() const {
6✔
371
        return size_;
6✔
372
    }
373

374
    DiscountFactor SimplePolynomialFitting::discountFunction(const Array& x,
274,173✔
375
                                                             Time t) const {
376
        DiscountFactor d = 0.0;
377

378
        if (!constrainAtZero_) {
274,173✔
UNCOV
379
            for (Size i=0; i<size_; ++i)
×
UNCOV
380
                d += x[i] * BernsteinPolynomial::get(i,i,t);
×
381
        } else {
382
            d = 1.0;
383
            for (Size i=0; i<size_; ++i)
1,096,692✔
384
                d += x[i] * BernsteinPolynomial::get(i+1,i+1,t);
822,519✔
385
        }
386
        return d;
274,173✔
387
    }
388

389
    SpreadFittingMethod::SpreadFittingMethod(const ext::shared_ptr<FittingMethod>& method,
1✔
390
                                             Handle<YieldTermStructure> discountCurve,
391
                                             const Real minCutoffTime,
392
                                             const Real maxCutoffTime)
1✔
393
    : FittedBondDiscountCurve::FittingMethod(
394
          method != nullptr ? method->constrainAtZero() : true,
1✔
395
          method != nullptr ? method->weights() : Array(),
2✔
396
          method != nullptr ? method->optimizationMethod() : ext::shared_ptr<OptimizationMethod>(),
2✔
397
          method != nullptr ? method->l2() : Array(),
2✔
398
          minCutoffTime,
399
          maxCutoffTime),
400
      method_(method), discountingCurve_(std::move(discountCurve)) {
4✔
401
        QL_REQUIRE(method, "Fitting method is empty");
1✔
402
        QL_REQUIRE(!discountingCurve_.empty(), "Discounting curve cannot be empty");
1✔
403
    }
1✔
404

405
    std::unique_ptr<FittedBondDiscountCurve::FittingMethod>
406
    SpreadFittingMethod::clone() const {
2✔
407
        return std::make_unique<SpreadFittingMethod>(*this);
2✔
408
    }
409

410
    Size SpreadFittingMethod::size() const {
6✔
411
        return method_->size();
6✔
412
    }
413

414
    DiscountFactor SpreadFittingMethod::discountFunction(const Array& x, Time t) const{
1,183,622✔
415
        return method_->discount(x, t)*discountingCurve_->discount(t, true)/rebase_;
1,183,622✔
416
    }
417

418
    void SpreadFittingMethod::init(){
4✔
419
        //In case discount curve has a different reference date,
420
        //discount to this curve's reference date
421
        if (curve_->referenceDate() != discountingCurve_->referenceDate()){
4✔
UNCOV
422
            rebase_ = discountingCurve_->discount(curve_->referenceDate());
×
423
        }
424
        else{
425
            rebase_ = 1.0;
4✔
426
        }
427
        //Call regular init
428
        FittedBondDiscountCurve::FittingMethod::init();
4✔
429
    }
4✔
430
}
431

STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc