/src/quantlib/ql/pricingengines/swaption/basketgeneratingengine.cpp
Line | Count | Source |
1 | | /* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ |
2 | | |
3 | | /* |
4 | | Copyright (C) 2013, 2015 Peter Caspers |
5 | | |
6 | | This file is part of QuantLib, a free-software/open-source library |
7 | | for financial quantitative analysts and developers - http://quantlib.org/ |
8 | | |
9 | | QuantLib is free software: you can redistribute it and/or modify it |
10 | | under the terms of the QuantLib license. You should have received a |
11 | | copy of the license along with this program; if not, please email |
12 | | <quantlib-dev@lists.sf.net>. The license is also available online at |
13 | | <https://www.quantlib.org/license.shtml>. |
14 | | |
15 | | This program is distributed in the hope that it will be useful, but WITHOUT |
16 | | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
17 | | FOR A PARTICULAR PURPOSE. See the license for more details. |
18 | | */ |
19 | | |
20 | | #include <ql/pricingengines/swaption/basketgeneratingengine.hpp> |
21 | | #include <ql/rebatedexercise.hpp> |
22 | | #include <ql/math/optimization/levenbergmarquardt.hpp> |
23 | | #include <ql/math/optimization/simplex.hpp> |
24 | | #include <ql/models/shortrate/calibrationhelpers/swaptionhelper.hpp> |
25 | | #include <ql/termstructures/volatility/swaption/swaptionvolcube.hpp> |
26 | | #include <ql/quotes/simplequote.hpp> |
27 | | #include <cmath> |
28 | | |
29 | | using std::exp; |
30 | | using std::fabs; |
31 | | |
32 | | namespace QuantLib { |
33 | | |
34 | | std::vector<ext::shared_ptr<BlackCalibrationHelper>> |
35 | | BasketGeneratingEngine::calibrationBasket( |
36 | | const ext::shared_ptr<Exercise>& exercise, |
37 | | const ext::shared_ptr<SwapIndex>& standardSwapBase, |
38 | | const ext::shared_ptr<SwaptionVolatilityStructure>& swaptionVolatility, |
39 | 0 | const CalibrationBasketType basketType) const { |
40 | |
|
41 | 0 | QL_REQUIRE( |
42 | 0 | !standardSwapBase->forwardingTermStructure().empty(), |
43 | 0 | "standard swap base forwarding term structure must not be empty."); |
44 | 0 | QL_REQUIRE( |
45 | 0 | !standardSwapBase->exogenousDiscount() || |
46 | 0 | !standardSwapBase->discountingTermStructure().empty(), |
47 | 0 | "standard swap base discounting term structure must not be empty."); |
48 | | |
49 | 0 | std::vector<ext::shared_ptr<BlackCalibrationHelper> > result; |
50 | |
|
51 | 0 | Date today = Settings::instance().evaluationDate(); |
52 | 0 | Size minIdxAlive = static_cast<Size>( |
53 | 0 | std::upper_bound(exercise->dates().begin(), exercise->dates().end(), |
54 | 0 | today) - |
55 | 0 | exercise->dates().begin()); |
56 | |
|
57 | 0 | ext::shared_ptr<RebatedExercise> rebEx = |
58 | 0 | ext::dynamic_pointer_cast<RebatedExercise>(exercise); |
59 | |
|
60 | 0 | for (Size i = minIdxAlive; i < exercise->dates().size(); i++) { |
61 | |
|
62 | 0 | Date expiry = exercise->date(i); |
63 | 0 | Real rebate = 0.0; |
64 | 0 | Date rebateDate = expiry; |
65 | 0 | if (rebEx != nullptr) { |
66 | 0 | rebate = rebEx->rebate(i); |
67 | 0 | rebateDate = rebEx->rebatePaymentDate(i); |
68 | 0 | } |
69 | |
|
70 | 0 | ext::shared_ptr<SwaptionHelper> helper; |
71 | |
|
72 | 0 | switch (basketType) { |
73 | | |
74 | 0 | case Naive: { |
75 | 0 | Real swapLength = swaptionVolatility->dayCounter().yearFraction( |
76 | 0 | standardSwapBase->valueDate(expiry), underlyingLastDate()); |
77 | 0 | ext::shared_ptr<SmileSection> sec = |
78 | 0 | swaptionVolatility->smileSection( |
79 | 0 | expiry, |
80 | 0 | static_cast<Size>(std::lround(swapLength * 12.0)) * Months, |
81 | 0 | true); |
82 | 0 | Real atmStrike = sec->atmLevel(); |
83 | 0 | Real atmVol; |
84 | 0 | if (atmStrike == Null<Real>()) |
85 | 0 | atmVol = sec->volatility(0.03); |
86 | 0 | else |
87 | 0 | atmVol = sec->volatility(atmStrike); |
88 | 0 | Real shift = sec->shift(); |
89 | |
|
90 | 0 | helper = ext::make_shared<SwaptionHelper>( |
91 | 0 | expiry, underlyingLastDate(), |
92 | 0 | Handle<Quote>(ext::make_shared<SimpleQuote>(atmVol)), |
93 | 0 | standardSwapBase->iborIndex(), |
94 | 0 | standardSwapBase->fixedLegTenor(), |
95 | 0 | standardSwapBase->dayCounter(), |
96 | 0 | standardSwapBase->iborIndex()->dayCounter(), |
97 | 0 | standardSwapBase->exogenousDiscount() |
98 | 0 | ? standardSwapBase->discountingTermStructure() |
99 | 0 | : standardSwapBase->forwardingTermStructure(), |
100 | 0 | BlackCalibrationHelper::RelativePriceError, Null<Real>(), 1.0, |
101 | 0 | swaptionVolatility->volatilityType() ,shift); |
102 | |
|
103 | 0 | break; |
104 | 0 | } |
105 | | |
106 | 0 | case MaturityStrikeByDeltaGamma: { |
107 | | |
108 | | // determine the npv, first and second order derivatives at |
109 | | // $y=0$ of the underlying swap |
110 | |
|
111 | 0 | const Real h = 0.0001; // finite difference step in $y$, make |
112 | | // this a parameter of the engine ? |
113 | 0 | Real zSpreadDsc = |
114 | 0 | oas_.empty() ? Real(1.0) |
115 | 0 | : exp(-oas_->value() * |
116 | 0 | onefactormodel_->termStructure() |
117 | 0 | ->dayCounter() |
118 | 0 | .yearFraction(expiry, rebateDate)); |
119 | |
|
120 | 0 | Real npvm = underlyingNpv(expiry, -h) + |
121 | 0 | rebate * |
122 | 0 | onefactormodel_->zerobond(rebateDate, expiry, |
123 | 0 | -h, discountCurve_) * |
124 | 0 | zSpreadDsc; |
125 | 0 | Real npv = underlyingNpv(expiry, 0.0) + |
126 | 0 | rebate * onefactormodel_->zerobond( |
127 | 0 | rebateDate, expiry, 0, discountCurve_) * |
128 | 0 | zSpreadDsc; |
129 | 0 | Real npvp = underlyingNpv(expiry, h) + |
130 | 0 | rebate * |
131 | 0 | onefactormodel_->zerobond(rebateDate, expiry, h, |
132 | 0 | discountCurve_) * |
133 | 0 | zSpreadDsc; |
134 | |
|
135 | 0 | Real delta = (npvp - npvm) / (2.0 * h); |
136 | 0 | Real gamma = (npvp - 2.0 * npv + npvm) / (h * h); |
137 | |
|
138 | 0 | QL_REQUIRE(npv * npv + delta * delta + gamma * gamma > 0.0, |
139 | 0 | "(npv,delta,gamma) must have a positive norm"); |
140 | | |
141 | | // debug output |
142 | | // std::cout << "EXOTIC npv " << npv << " delta " << delta |
143 | | // << " gamma " << gamma << std::endl; |
144 | | // Real xtmp = -5.0; |
145 | | // std::cout |
146 | | // << "********************************************EXERCISE " |
147 | | // << expiry << " ******************" << std::endl; |
148 | | // std::cout << "globalExoticNpv;"; |
149 | | // while (xtmp <= 5.0 + QL_EPSILON) { |
150 | | // std::cout << underlyingNpv(expiry, xtmp) << ";"; |
151 | | // xtmp += 0.1; |
152 | | // } |
153 | | // std::cout << std::endl; |
154 | | // end debug output |
155 | | |
156 | | // play safe, we restrict the maximum maturity so to easily fit |
157 | | // in the date class restriction |
158 | 0 | Real maxMaturity = |
159 | 0 | swaptionVolatility->dayCounter().yearFraction( |
160 | 0 | expiry, Date::maxDate() - 365); |
161 | |
|
162 | 0 | ext::shared_ptr<MatchHelper> matchHelper_; |
163 | 0 | matchHelper_ = ext::make_shared<MatchHelper>( |
164 | 0 | underlyingType(), npv, delta, gamma, *onefactormodel_, |
165 | 0 | standardSwapBase, expiry, maxMaturity, h); |
166 | | |
167 | | // Optimize |
168 | 0 | Array initial = initialGuess(expiry); |
169 | 0 | QL_REQUIRE(initial.size() == 3, |
170 | 0 | "initial guess must have size 3 (but is " |
171 | 0 | << initial.size() << ")"); |
172 | | |
173 | 0 | EndCriteria ec(1000, 200, 1E-8, 1E-8, 1E-8); // make these |
174 | | // criteria and the |
175 | | // optimizer itself |
176 | | // parameters of |
177 | | // the method ? |
178 | 0 | Constraint constraint = NoConstraint(); |
179 | 0 | Problem p(*matchHelper_, constraint, initial); |
180 | 0 | LevenbergMarquardt lm; |
181 | |
|
182 | 0 | EndCriteria::Type ret = lm.minimize(p, ec); |
183 | 0 | QL_REQUIRE(ret != EndCriteria::None && |
184 | 0 | ret != EndCriteria::Unknown && |
185 | 0 | ret != EndCriteria::MaxIterations, |
186 | 0 | "optimizer returns error (" << ret << ")"); |
187 | 0 | Array solution = p.currentValue(); |
188 | |
|
189 | 0 | Real maturity = fabs(solution[1]); |
190 | |
|
191 | 0 | Size years = (Size)std::floor(maturity); |
192 | 0 | maturity -= (Real)years; |
193 | 0 | maturity *= 12.0; |
194 | 0 | Size months = (Size)std::floor(maturity + 0.5); |
195 | 0 | if (years == 0 && months == 0) |
196 | 0 | months = 1; // ensure a maturity of at least one months |
197 | | // maturity -= (Real)months; maturity *= 365.25; |
198 | | // Size days = (Size)std::floor(maturity); |
199 | |
|
200 | 0 | Period matPeriod = |
201 | 0 | years * Years + months * Months; //+days*Days; |
202 | |
|
203 | 0 | ext::shared_ptr<SmileSection> sec = |
204 | 0 | swaptionVolatility->smileSection(expiry, matPeriod, true); |
205 | 0 | Real shift = sec->shift(); |
206 | | |
207 | | // we have to floor the strike of the calibration instrument, |
208 | | // see warning in the header |
209 | 0 | solution[2] = std::max( |
210 | 0 | solution[2], 0.00001 - shift); // floor at 0.1bp - shift |
211 | | |
212 | | // also the calibrated nominal may be zero, so we floor it, too |
213 | 0 | solution[0] = |
214 | 0 | std::max(solution[0], 0.000001); // float at 0.01bp |
215 | |
|
216 | 0 | Real vol = sec->volatility(solution[2]); |
217 | |
|
218 | 0 | helper = ext::make_shared<SwaptionHelper>( |
219 | 0 | expiry, matPeriod, |
220 | 0 | Handle<Quote>(ext::make_shared<SimpleQuote>( |
221 | 0 | vol)), |
222 | 0 | standardSwapBase->iborIndex(), |
223 | 0 | standardSwapBase->fixedLegTenor(), |
224 | 0 | standardSwapBase->dayCounter(), |
225 | 0 | standardSwapBase->iborIndex()->dayCounter(), |
226 | 0 | standardSwapBase->exogenousDiscount() |
227 | 0 | ? standardSwapBase->discountingTermStructure() |
228 | 0 | : standardSwapBase->forwardingTermStructure(), |
229 | 0 | BlackCalibrationHelper::RelativePriceError, solution[2], |
230 | 0 | fabs(solution[0]), swaptionVolatility->volatilityType(), shift); |
231 | 0 | break; |
232 | 0 | } |
233 | | |
234 | 0 | default: |
235 | 0 | QL_FAIL("Calibration basket type not known (" << basketType |
236 | 0 | << ")"); |
237 | 0 | } |
238 | | |
239 | 0 | result.push_back(helper); |
240 | 0 | } |
241 | | |
242 | 0 | return result; |
243 | 0 | } |
244 | | } |