/src/quantlib/ql/termstructures/yield/fittedbonddiscountcurve.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ |
2 | | |
3 | | /* |
4 | | Copyright (C) 2009 Ferdinando Ametrano |
5 | | Copyright (C) 2007 Allen Kuo |
6 | | |
7 | | This file is part of QuantLib, a free-software/open-source library |
8 | | for financial quantitative analysts and developers - http://quantlib.org/ |
9 | | |
10 | | QuantLib is free software: you can redistribute it and/or modify it |
11 | | under the terms of the QuantLib license. You should have received a |
12 | | copy of the license along with this program; if not, please email |
13 | | <quantlib-dev@lists.sf.net>. The license is also available online at |
14 | | <http://quantlib.org/license.shtml>. |
15 | | |
16 | | This program is distributed in the hope that it will be useful, but WITHOUT |
17 | | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
18 | | FOR A PARTICULAR PURPOSE. See the license for more details. |
19 | | */ |
20 | | |
21 | | #include <ql/cashflows/cashflows.hpp> |
22 | | #include <ql/math/optimization/constraint.hpp> |
23 | | #include <ql/math/optimization/costfunction.hpp> |
24 | | #include <ql/math/optimization/simplex.hpp> |
25 | | #include <ql/pricingengines/bond/bondfunctions.hpp> |
26 | | #include <ql/termstructures/yield/fittedbonddiscountcurve.hpp> |
27 | | #include <ql/time/daycounters/simpledaycounter.hpp> |
28 | | #include <ql/utilities/dataformatters.hpp> |
29 | | #include <utility> |
30 | | |
31 | | using std::vector; |
32 | | |
33 | | namespace QuantLib { |
34 | | |
35 | | class FittedBondDiscountCurve::FittingMethod::FittingCost |
36 | | : public CostFunction { |
37 | | friend class FittedBondDiscountCurve::FittingMethod; |
38 | | public: |
39 | | explicit FittingCost( |
40 | | FittedBondDiscountCurve::FittingMethod* fittingMethod); |
41 | | Real value(const Array& x) const override; |
42 | | Array values(const Array& x) const override; |
43 | | |
44 | | private: |
45 | | FittedBondDiscountCurve::FittingMethod* fittingMethod_; |
46 | | }; |
47 | | |
48 | | |
49 | | FittedBondDiscountCurve::FittedBondDiscountCurve( |
50 | | Natural settlementDays, |
51 | | const Calendar& calendar, |
52 | | vector<ext::shared_ptr<BondHelper> > bondHelpers, |
53 | | const DayCounter& dayCounter, |
54 | | const FittingMethod& fittingMethod, |
55 | | Real accuracy, |
56 | | Size maxEvaluations, |
57 | | Array guess, |
58 | | Real simplexLambda, |
59 | | Size maxStationaryStateIterations) |
60 | 0 | : YieldTermStructure(settlementDays, calendar, dayCounter), accuracy_(accuracy), |
61 | 0 | maxEvaluations_(maxEvaluations), simplexLambda_(simplexLambda), |
62 | 0 | maxStationaryStateIterations_(maxStationaryStateIterations), guessSolution_(std::move(guess)), |
63 | 0 | bondHelpers_(std::move(bondHelpers)), fittingMethod_(fittingMethod) { |
64 | 0 | fittingMethod_->curve_ = this; |
65 | 0 | setup(); |
66 | 0 | } Unexecuted instantiation: QuantLib::FittedBondDiscountCurve::FittedBondDiscountCurve(unsigned int, QuantLib::Calendar const&, std::__1::vector<boost::shared_ptr<QuantLib::BondHelper>, std::__1::allocator<boost::shared_ptr<QuantLib::BondHelper> > >, QuantLib::DayCounter const&, QuantLib::FittedBondDiscountCurve::FittingMethod const&, double, unsigned long, QuantLib::Array, double, unsigned long) Unexecuted instantiation: QuantLib::FittedBondDiscountCurve::FittedBondDiscountCurve(unsigned int, QuantLib::Calendar const&, std::__1::vector<boost::shared_ptr<QuantLib::BondHelper>, std::__1::allocator<boost::shared_ptr<QuantLib::BondHelper> > >, QuantLib::DayCounter const&, QuantLib::FittedBondDiscountCurve::FittingMethod const&, double, unsigned long, QuantLib::Array, double, unsigned long) |
67 | | |
68 | | |
69 | | FittedBondDiscountCurve::FittedBondDiscountCurve( |
70 | | const Date& referenceDate, |
71 | | vector<ext::shared_ptr<BondHelper> > bondHelpers, |
72 | | const DayCounter& dayCounter, |
73 | | const FittingMethod& fittingMethod, |
74 | | Real accuracy, |
75 | | Size maxEvaluations, |
76 | | Array guess, |
77 | | Real simplexLambda, |
78 | | Size maxStationaryStateIterations) |
79 | 0 | : YieldTermStructure(referenceDate, Calendar(), dayCounter), accuracy_(accuracy), |
80 | 0 | maxEvaluations_(maxEvaluations), simplexLambda_(simplexLambda), |
81 | 0 | maxStationaryStateIterations_(maxStationaryStateIterations), guessSolution_(std::move(guess)), |
82 | 0 | bondHelpers_(std::move(bondHelpers)), fittingMethod_(fittingMethod) { |
83 | |
|
84 | 0 | fittingMethod_->curve_ = this; |
85 | 0 | setup(); |
86 | 0 | } Unexecuted instantiation: QuantLib::FittedBondDiscountCurve::FittedBondDiscountCurve(QuantLib::Date const&, std::__1::vector<boost::shared_ptr<QuantLib::BondHelper>, std::__1::allocator<boost::shared_ptr<QuantLib::BondHelper> > >, QuantLib::DayCounter const&, QuantLib::FittedBondDiscountCurve::FittingMethod const&, double, unsigned long, QuantLib::Array, double, unsigned long) Unexecuted instantiation: QuantLib::FittedBondDiscountCurve::FittedBondDiscountCurve(QuantLib::Date const&, std::__1::vector<boost::shared_ptr<QuantLib::BondHelper>, std::__1::allocator<boost::shared_ptr<QuantLib::BondHelper> > >, QuantLib::DayCounter const&, QuantLib::FittedBondDiscountCurve::FittingMethod const&, double, unsigned long, QuantLib::Array, double, unsigned long) |
87 | | |
88 | | FittedBondDiscountCurve::FittedBondDiscountCurve( |
89 | | Natural settlementDays, |
90 | | const Calendar& calendar, |
91 | | const FittingMethod& fittingMethod, |
92 | | Array parameters, |
93 | | Date maxDate, |
94 | | const DayCounter& dayCounter) |
95 | 0 | : YieldTermStructure(settlementDays, calendar, dayCounter), accuracy_(1e-10), |
96 | 0 | maxEvaluations_(0), guessSolution_(std::move(parameters)), |
97 | 0 | maxDate_(maxDate), fittingMethod_(fittingMethod) { |
98 | |
|
99 | 0 | fittingMethod_->curve_ = this; |
100 | 0 | setup(); |
101 | 0 | } Unexecuted instantiation: QuantLib::FittedBondDiscountCurve::FittedBondDiscountCurve(unsigned int, QuantLib::Calendar const&, QuantLib::FittedBondDiscountCurve::FittingMethod const&, QuantLib::Array, QuantLib::Date, QuantLib::DayCounter const&) Unexecuted instantiation: QuantLib::FittedBondDiscountCurve::FittedBondDiscountCurve(unsigned int, QuantLib::Calendar const&, QuantLib::FittedBondDiscountCurve::FittingMethod const&, QuantLib::Array, QuantLib::Date, QuantLib::DayCounter const&) |
102 | | |
103 | | FittedBondDiscountCurve::FittedBondDiscountCurve( |
104 | | const Date& referenceDate, |
105 | | const FittingMethod& fittingMethod, |
106 | | Array parameters, |
107 | | Date maxDate, |
108 | | const DayCounter& dayCounter) |
109 | 0 | : YieldTermStructure(referenceDate, Calendar(), dayCounter), accuracy_(1e-10), |
110 | 0 | maxEvaluations_(0), guessSolution_(std::move(parameters)), |
111 | 0 | maxDate_(maxDate), fittingMethod_(fittingMethod) { |
112 | |
|
113 | 0 | fittingMethod_->curve_ = this; |
114 | 0 | setup(); |
115 | 0 | } Unexecuted instantiation: QuantLib::FittedBondDiscountCurve::FittedBondDiscountCurve(QuantLib::Date const&, QuantLib::FittedBondDiscountCurve::FittingMethod const&, QuantLib::Array, QuantLib::Date, QuantLib::DayCounter const&) Unexecuted instantiation: QuantLib::FittedBondDiscountCurve::FittedBondDiscountCurve(QuantLib::Date const&, QuantLib::FittedBondDiscountCurve::FittingMethod const&, QuantLib::Array, QuantLib::Date, QuantLib::DayCounter const&) |
116 | | |
117 | | |
118 | 0 | void FittedBondDiscountCurve::resetGuess(const Array& guess) { |
119 | 0 | QL_REQUIRE(guess.empty() || guess.size() == fittingMethod_->size(), "guess is of wrong size"); |
120 | 0 | guessSolution_ = guess; |
121 | 0 | update(); |
122 | 0 | } |
123 | | |
124 | | |
125 | 0 | void FittedBondDiscountCurve::performCalculations() const { |
126 | |
|
127 | 0 | if (maxEvaluations_!= 0) { |
128 | | // we need to fit, so we require helpers |
129 | 0 | QL_REQUIRE(!bondHelpers_.empty(), "no bond helpers given"); |
130 | 0 | } |
131 | | |
132 | 0 | if (maxEvaluations_ == 0) { |
133 | | // no fit, but we need either an explicit max date or |
134 | | // helpers from which to deduce it |
135 | 0 | QL_REQUIRE(maxDate_ != Date() || !bondHelpers_.empty(), |
136 | 0 | "no bond helpers or max date given"); |
137 | 0 | } |
138 | | |
139 | 0 | if (!bondHelpers_.empty()) { |
140 | 0 | maxDate_ = Date::minDate(); |
141 | 0 | Date refDate = referenceDate(); |
142 | | |
143 | | // double check bond quotes still valid and/or instruments not expired |
144 | 0 | for (Size i=0; i<bondHelpers_.size(); ++i) { |
145 | 0 | ext::shared_ptr<Bond> bond = bondHelpers_[i]->bond(); |
146 | 0 | QL_REQUIRE(bondHelpers_[i]->quote()->isValid(), |
147 | 0 | io::ordinal(i+1) << " bond (maturity: " << |
148 | 0 | bond->maturityDate() << ") has an invalid price quote"); |
149 | 0 | Date bondSettlement = bond->settlementDate(); |
150 | 0 | QL_REQUIRE(bondSettlement>=refDate, |
151 | 0 | io::ordinal(i+1) << " bond settlemente date (" << |
152 | 0 | bondSettlement << ") before curve reference date (" << |
153 | 0 | refDate << ")"); |
154 | 0 | QL_REQUIRE(BondFunctions::isTradable(*bond, bondSettlement), |
155 | 0 | io::ordinal(i+1) << " bond non tradable at " << |
156 | 0 | bondSettlement << " settlement date (maturity" |
157 | 0 | " being " << bond->maturityDate() << ")"); |
158 | 0 | maxDate_ = std::max(maxDate_, bondHelpers_[i]->pillarDate()); |
159 | 0 | bondHelpers_[i]->setTermStructure( |
160 | 0 | const_cast<FittedBondDiscountCurve*>(this)); |
161 | 0 | } |
162 | 0 | } |
163 | | |
164 | 0 | fittingMethod_->init(); |
165 | 0 | fittingMethod_->calculate(); |
166 | 0 | } |
167 | | |
168 | | |
169 | | FittedBondDiscountCurve::FittingMethod::FittingMethod( |
170 | | bool constrainAtZero, |
171 | | const Array& weights, |
172 | | ext::shared_ptr<OptimizationMethod> optimizationMethod, |
173 | | Array l2, |
174 | | const Real minCutoffTime, |
175 | | const Real maxCutoffTime, |
176 | | Constraint constraint) |
177 | 0 | : constrainAtZero_(constrainAtZero), weights_(weights), l2_(std::move(l2)), |
178 | 0 | calculateWeights_(weights.empty()), optimizationMethod_(std::move(optimizationMethod)), |
179 | 0 | constraint_(std::move(constraint)), |
180 | 0 | minCutoffTime_(minCutoffTime), maxCutoffTime_(maxCutoffTime) { |
181 | 0 | if (constraint_.empty()) |
182 | 0 | constraint_ = NoConstraint(); |
183 | 0 | } |
184 | | |
185 | 0 | void FittedBondDiscountCurve::FittingMethod::init() { |
186 | |
|
187 | 0 | if (curve_->maxEvaluations_ == 0) |
188 | 0 | return; // we can skip the rest |
189 | | |
190 | | // yield conventions |
191 | 0 | DayCounter yieldDC = curve_->dayCounter(); |
192 | 0 | Compounding yieldComp = Compounded; |
193 | 0 | Frequency yieldFreq = Annual; |
194 | |
|
195 | 0 | Size n = curve_->bondHelpers_.size(); |
196 | 0 | costFunction_ = ext::make_shared<FittingCost>(this); |
197 | |
|
198 | 0 | for (auto& bondHelper : curve_->bondHelpers_) { |
199 | 0 | bondHelper->setTermStructure(curve_); |
200 | 0 | } |
201 | |
|
202 | 0 | if (calculateWeights_) { |
203 | 0 | if (weights_.empty()) |
204 | 0 | weights_ = Array(n); |
205 | |
|
206 | 0 | Real squaredSum = 0.0; |
207 | 0 | for (Size i=0; i<curve_->bondHelpers_.size(); ++i) { |
208 | 0 | ext::shared_ptr<Bond> bond = curve_->bondHelpers_[i]->bond(); |
209 | |
|
210 | 0 | Real amount = curve_->bondHelpers_[i]->quote()->value(); |
211 | 0 | Bond::Price price(amount, curve_->bondHelpers_[i]->priceType()); |
212 | |
|
213 | 0 | Date bondSettlement = bond->settlementDate(); |
214 | 0 | Rate ytm = BondFunctions::yield(*bond, price, |
215 | 0 | yieldDC, yieldComp, yieldFreq, |
216 | 0 | bondSettlement); |
217 | |
|
218 | 0 | Time dur = BondFunctions::duration(*bond, ytm, |
219 | 0 | yieldDC, yieldComp, yieldFreq, |
220 | 0 | Duration::Modified, |
221 | 0 | bondSettlement); |
222 | 0 | weights_[i] = 1.0/dur; |
223 | 0 | squaredSum += weights_[i]*weights_[i]; |
224 | 0 | } |
225 | 0 | weights_ /= std::sqrt(squaredSum); |
226 | 0 | } |
227 | |
|
228 | 0 | QL_REQUIRE(weights_.size() == n, |
229 | 0 | "Given weights do not cover all boostrapping helpers"); |
230 | | |
231 | 0 | if (!l2_.empty()) { |
232 | 0 | QL_REQUIRE(l2_.size() == size(), |
233 | 0 | "Given penalty factors do not cover all parameters"); |
234 | | |
235 | 0 | QL_REQUIRE(!curve_->guessSolution_.empty(), "L2 penalty requires a guess"); |
236 | 0 | } |
237 | 0 | } |
238 | | |
239 | 0 | void FittedBondDiscountCurve::FittingMethod::calculate() { |
240 | |
|
241 | 0 | if (curve_->maxEvaluations_ == 0) |
242 | 0 | { |
243 | | // Don't calculate, simply use the given parameters to |
244 | | // provide a fitted curve. This turns the instance into |
245 | | // an evaluator of the parametric curve, for example |
246 | | // allowing to use the parameters for a credit spread |
247 | | // curve calculated with bonds in one currency to be |
248 | | // coupled to a discount curve in another currency. |
249 | |
|
250 | 0 | QL_REQUIRE(curve_->guessSolution_.size() == size(), |
251 | 0 | "wrong number of parameters"); |
252 | | |
253 | 0 | solution_ = curve_->guessSolution_; |
254 | |
|
255 | 0 | numberOfIterations_ = 0; |
256 | 0 | costValue_ = Null<Real>(); |
257 | 0 | errorCode_ = EndCriteria::None; |
258 | |
|
259 | 0 | return; |
260 | 0 | } |
261 | | |
262 | 0 | FittingCost& costFunction = *costFunction_; |
263 | | |
264 | | // start with the guess solution, if it exists |
265 | 0 | Array x(size(), 0.0); |
266 | 0 | if (!curve_->guessSolution_.empty()) { |
267 | 0 | QL_REQUIRE(curve_->guessSolution_.size() == size(), "wrong size for guess"); |
268 | 0 | x = curve_->guessSolution_; |
269 | 0 | } |
270 | | |
271 | | // workaround for backwards compatibility |
272 | 0 | ext::shared_ptr<OptimizationMethod> optimization = optimizationMethod_; |
273 | 0 | if (!optimization) { |
274 | 0 | optimization = ext::make_shared<Simplex>(curve_->simplexLambda_); |
275 | 0 | } |
276 | 0 | Problem problem(costFunction, constraint_, x); |
277 | |
|
278 | 0 | Real rootEpsilon = curve_->accuracy_; |
279 | 0 | Real functionEpsilon = curve_->accuracy_; |
280 | 0 | Real gradientNormEpsilon = curve_->accuracy_; |
281 | |
|
282 | 0 | EndCriteria endCriteria(curve_->maxEvaluations_, |
283 | 0 | curve_->maxStationaryStateIterations_, |
284 | 0 | rootEpsilon, |
285 | 0 | functionEpsilon, |
286 | 0 | gradientNormEpsilon); |
287 | |
|
288 | 0 | errorCode_ = optimization->minimize(problem,endCriteria); |
289 | 0 | solution_ = problem.currentValue(); |
290 | |
|
291 | 0 | numberOfIterations_ = problem.functionEvaluation(); |
292 | 0 | costValue_ = problem.functionValue(); |
293 | | |
294 | | // save the results as the guess solution, in case of recalculation |
295 | 0 | curve_->guessSolution_ = solution_; |
296 | 0 | } |
297 | | |
298 | | |
299 | | FittedBondDiscountCurve::FittingMethod::FittingCost::FittingCost( |
300 | | FittedBondDiscountCurve::FittingMethod* fittingMethod) |
301 | 0 | : fittingMethod_(fittingMethod) {} |
302 | | |
303 | | |
304 | | Real FittedBondDiscountCurve::FittingMethod::FittingCost::value( |
305 | 0 | const Array& x) const { |
306 | 0 | Real squaredError = 0.0; |
307 | 0 | Array vals = values(x); |
308 | 0 | for (Real val : vals) { |
309 | 0 | squaredError += val; |
310 | 0 | } |
311 | 0 | return squaredError; |
312 | 0 | } |
313 | | |
314 | 0 | Array FittedBondDiscountCurve::FittingMethod::FittingCost::values(const Array &x) const { |
315 | 0 | Size n = fittingMethod_->curve_->bondHelpers_.size(); |
316 | 0 | Size N = fittingMethod_->l2_.size(); |
317 | | |
318 | | // set solution so that fittingMethod_->curve_ represents the current trial |
319 | | // the final solution will be set in FittingMethod::calculate() later on |
320 | 0 | fittingMethod_->solution_ = x; |
321 | |
|
322 | 0 | Array values(n + N); |
323 | 0 | for (Size i=0; i<n; ++i) { |
324 | 0 | ext::shared_ptr<BondHelper> helper = fittingMethod_->curve_->bondHelpers_[i]; |
325 | 0 | Real error = helper->impliedQuote() - helper->quote()->value(); |
326 | 0 | Real weightedError = fittingMethod_->weights_[i] * error; |
327 | 0 | values[i] = weightedError * weightedError; |
328 | 0 | } |
329 | |
|
330 | 0 | if (N != 0) { |
331 | 0 | for (Size i = 0; i < N; ++i) { |
332 | 0 | Real error = x[i] - fittingMethod_->curve_->guessSolution_[i]; |
333 | 0 | values[i + n] = fittingMethod_->l2_[i] * error * error; |
334 | 0 | } |
335 | 0 | } |
336 | 0 | return values; |
337 | 0 | } |
338 | | |
339 | | } |