/src/quantlib/ql/pricingengines/swaption/gaussian1dswaptionengine.cpp
Line | Count | Source |
1 | | /* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ |
2 | | |
3 | | /* |
4 | | Copyright (C) 2013 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/gaussian1dswaptionengine.hpp> |
21 | | #include <ql/math/interpolations/cubicinterpolation.hpp> |
22 | | #include <ql/payoff.hpp> |
23 | | |
24 | | namespace QuantLib { |
25 | | |
26 | 0 | void Gaussian1dSwaptionEngine::calculate() const { |
27 | |
|
28 | 0 | QL_REQUIRE(arguments_.settlementMethod != Settlement::ParYieldCurve, |
29 | 0 | "cash settled (ParYieldCurve) swaptions not priced with " |
30 | 0 | "Gaussian1dSwaptionEngine"); |
31 | | |
32 | 0 | QL_REQUIRE(arguments_.nominal != Null<Real>(), |
33 | 0 | "non-constant nominals are not supported yet"); |
34 | | |
35 | 0 | Date settlement = model_->termStructure()->referenceDate(); |
36 | |
|
37 | 0 | if (arguments_.exercise->dates().back() <= |
38 | 0 | settlement) { // swaption is expired, possibly generated swap is not |
39 | | // valued |
40 | 0 | results_.value = 0.0; |
41 | 0 | return; |
42 | 0 | } |
43 | | |
44 | 0 | int idx = static_cast<int>(arguments_.exercise->dates().size()) - 1; |
45 | 0 | int minIdxAlive = static_cast<int>( |
46 | 0 | std::upper_bound(arguments_.exercise->dates().begin(), |
47 | 0 | arguments_.exercise->dates().end(), settlement) - |
48 | 0 | arguments_.exercise->dates().begin()); |
49 | |
|
50 | 0 | auto swap = arguments_.swap; |
51 | 0 | Option::Type type = |
52 | 0 | arguments_.type == Swap::Payer ? Option::Call : Option::Put; |
53 | 0 | const Schedule& fixedSchedule = swap->fixedSchedule(); |
54 | 0 | const Schedule& floatSchedule = swap->floatingSchedule(); |
55 | |
|
56 | 0 | Array npv0(2 * integrationPoints_ + 1, 0.0), |
57 | 0 | npv1(2 * integrationPoints_ + 1, 0.0); |
58 | 0 | Array z = model_->yGrid(stddevs_, integrationPoints_); |
59 | 0 | Array p(z.size(), 0.0); |
60 | | |
61 | | // for probability computation |
62 | 0 | std::vector<Array> npvp0, npvp1; |
63 | 0 | if (probabilities_ != None) { |
64 | 0 | for (int i = 0; i < idx - minIdxAlive + 2; ++i) { |
65 | 0 | Array npvTmp0(2 * integrationPoints_ + 1, 0.0); |
66 | 0 | Array npvTmp1(2 * integrationPoints_ + 1, 0.0); |
67 | 0 | npvp0.push_back(npvTmp0); |
68 | 0 | npvp1.push_back(npvTmp1); |
69 | 0 | } |
70 | 0 | } |
71 | | // end probability computation |
72 | |
|
73 | 0 | Date expiry1 = Date(), expiry0; |
74 | 0 | Time expiry1Time = Null<Real>(), expiry0Time; |
75 | |
|
76 | 0 | do { |
77 | |
|
78 | 0 | if (idx == minIdxAlive - 1) |
79 | 0 | expiry0 = settlement; |
80 | 0 | else |
81 | 0 | expiry0 = arguments_.exercise->dates()[idx]; |
82 | |
|
83 | 0 | expiry0Time = std::max( |
84 | 0 | model_->termStructure()->timeFromReference(expiry0), 0.0); |
85 | |
|
86 | 0 | Size j1 = |
87 | 0 | std::upper_bound(fixedSchedule.dates().begin(), |
88 | 0 | fixedSchedule.dates().end(), expiry0 - 1) - |
89 | 0 | fixedSchedule.dates().begin(); |
90 | 0 | Size k1 = |
91 | 0 | std::upper_bound(floatSchedule.dates().begin(), |
92 | 0 | floatSchedule.dates().end(), expiry0 - 1) - |
93 | 0 | floatSchedule.dates().begin(); |
94 | | |
95 | | // a lazy object is not thread safe, neither is the caching |
96 | | // in gsrprocess. therefore we trigger computations here such |
97 | | // that neither lazy object recalculation nor write access |
98 | | // during caching occurs in the parallized loop below. |
99 | | // this is known to work for the gsr and markov functional |
100 | | // model implementations of Gaussian1dModel |
101 | | #ifdef _OPENMP |
102 | | if (expiry1Time != Null<Real>()) |
103 | | model_->yGrid(stddevs_, integrationPoints_, expiry1Time, |
104 | | expiry0Time, 0.0); |
105 | | if (expiry0 > settlement) { |
106 | | for (Size l = k1; l < arguments_.floatingCoupons.size(); l++) { |
107 | | model_->forwardRate(arguments_.floatingFixingDates[l], |
108 | | expiry0, 0.0, |
109 | | arguments_.swap->iborIndex()); |
110 | | model_->zerobond(arguments_.floatingPayDates[l], expiry0, |
111 | | 0.0, discountCurve_); |
112 | | } |
113 | | for (Size l = j1; l < arguments_.fixedCoupons.size(); l++) { |
114 | | model_->zerobond(arguments_.fixedPayDates[l], expiry0, 0.0, |
115 | | discountCurve_); |
116 | | } |
117 | | model_->numeraire(expiry0Time, 0.0, discountCurve_); |
118 | | } |
119 | | #endif |
120 | |
|
121 | 0 | #pragma omp parallel for default(shared) firstprivate(p) if(expiry0>settlement) |
122 | 0 | for (long k = 0; k < (expiry0 > settlement ? (long)npv0.size() : 1); |
123 | 0 | k++) { |
124 | |
|
125 | 0 | Real price = 0.0; |
126 | 0 | if (expiry1Time != Null<Real>()) { |
127 | 0 | Array yg = model_->yGrid(stddevs_, integrationPoints_, |
128 | 0 | expiry1Time, expiry0Time, |
129 | 0 | expiry0 > settlement ? z[k] : 0.0); |
130 | 0 | CubicInterpolation payoff0( |
131 | 0 | z.begin(), z.end(), npv1.begin(), |
132 | 0 | CubicInterpolation::Spline, true, |
133 | 0 | CubicInterpolation::Lagrange, 0.0, |
134 | 0 | CubicInterpolation::Lagrange, 0.0); |
135 | 0 | for (Size i = 0; i < yg.size(); i++) { |
136 | 0 | p[i] = payoff0(yg[i], true); |
137 | 0 | } |
138 | 0 | CubicInterpolation payoff1( |
139 | 0 | z.begin(), z.end(), p.begin(), |
140 | 0 | CubicInterpolation::Spline, true, |
141 | 0 | CubicInterpolation::Lagrange, 0.0, |
142 | 0 | CubicInterpolation::Lagrange, 0.0); |
143 | 0 | for (Size i = 0; i < z.size() - 1; i++) { |
144 | 0 | price += Gaussian1dModel::gaussianShiftedPolynomialIntegral( |
145 | 0 | 0.0, payoff1.cCoefficients()[i], |
146 | 0 | payoff1.bCoefficients()[i], |
147 | 0 | payoff1.aCoefficients()[i], p[i], z[i], z[i], |
148 | 0 | z[i + 1]); |
149 | 0 | } |
150 | 0 | if (extrapolatePayoff_) { |
151 | 0 | if (flatPayoffExtrapolation_) { |
152 | 0 | price += Gaussian1dModel::gaussianShiftedPolynomialIntegral( |
153 | 0 | 0.0, 0.0, 0.0, 0.0, p[z.size() - 2], |
154 | 0 | z[z.size() - 2], z[z.size() - 1], 100.0); |
155 | 0 | price += Gaussian1dModel::gaussianShiftedPolynomialIntegral( |
156 | 0 | 0.0, 0.0, 0.0, 0.0, p[0], z[0], -100.0, z[0]); |
157 | 0 | } else { |
158 | 0 | if (type == Option::Call) |
159 | 0 | price += |
160 | 0 | Gaussian1dModel::gaussianShiftedPolynomialIntegral( |
161 | 0 | 0.0, |
162 | 0 | payoff1.cCoefficients()[z.size() - 2], |
163 | 0 | payoff1.bCoefficients()[z.size() - 2], |
164 | 0 | payoff1.aCoefficients()[z.size() - 2], |
165 | 0 | p[z.size() - 2], z[z.size() - 2], |
166 | 0 | z[z.size() - 1], 100.0); |
167 | 0 | if (type == Option::Put) |
168 | 0 | price += |
169 | 0 | Gaussian1dModel::gaussianShiftedPolynomialIntegral( |
170 | 0 | 0.0, payoff1.cCoefficients()[0], |
171 | 0 | payoff1.bCoefficients()[0], |
172 | 0 | payoff1.aCoefficients()[0], p[0], z[0], |
173 | 0 | -100.0, z[0]); |
174 | 0 | } |
175 | 0 | } |
176 | 0 | } |
177 | |
|
178 | 0 | npv0[k] = price; |
179 | | |
180 | | // for probability computation |
181 | 0 | if (probabilities_ != None) { |
182 | 0 | for (Size m = 0; m < npvp0.size(); m++) { |
183 | 0 | Real price = 0.0; |
184 | 0 | if (expiry1Time != Null<Real>()) { |
185 | 0 | Array yg = model_->yGrid( |
186 | 0 | stddevs_, integrationPoints_, expiry1Time, |
187 | 0 | expiry0Time, expiry0 > settlement ? z[k] : 0.0); |
188 | 0 | CubicInterpolation payoff0( |
189 | 0 | z.begin(), z.end(), npvp1[m].begin(), |
190 | 0 | CubicInterpolation::Spline, true, |
191 | 0 | CubicInterpolation::Lagrange, 0.0, |
192 | 0 | CubicInterpolation::Lagrange, 0.0); |
193 | 0 | for (Size i = 0; i < yg.size(); i++) { |
194 | 0 | p[i] = payoff0(yg[i], true); |
195 | 0 | } |
196 | 0 | CubicInterpolation payoff1( |
197 | 0 | z.begin(), z.end(), p.begin(), |
198 | 0 | CubicInterpolation::Spline, true, |
199 | 0 | CubicInterpolation::Lagrange, 0.0, |
200 | 0 | CubicInterpolation::Lagrange, 0.0); |
201 | 0 | for (Size i = 0; i < z.size() - 1; i++) { |
202 | 0 | price += |
203 | 0 | Gaussian1dModel::gaussianShiftedPolynomialIntegral( |
204 | 0 | 0.0, payoff1.cCoefficients()[i], |
205 | 0 | payoff1.bCoefficients()[i], |
206 | 0 | payoff1.aCoefficients()[i], p[i], z[i], |
207 | 0 | z[i], z[i + 1]); |
208 | 0 | } |
209 | 0 | if (extrapolatePayoff_) { |
210 | 0 | if (flatPayoffExtrapolation_) { |
211 | 0 | price += |
212 | 0 | Gaussian1dModel::gaussianShiftedPolynomialIntegral( |
213 | 0 | 0.0, 0.0, 0.0, 0.0, |
214 | 0 | p[z.size() - 2], |
215 | 0 | z[z.size() - 2], |
216 | 0 | z[z.size() - 1], 100.0); |
217 | 0 | price += |
218 | 0 | Gaussian1dModel::gaussianShiftedPolynomialIntegral( |
219 | 0 | 0.0, 0.0, 0.0, 0.0, p[0], |
220 | 0 | z[0], -100.0, z[0]); |
221 | 0 | } else { |
222 | 0 | if (type == Option::Call) |
223 | 0 | price += |
224 | 0 | Gaussian1dModel::gaussianShiftedPolynomialIntegral( |
225 | 0 | 0.0, |
226 | 0 | payoff1.cCoefficients() |
227 | 0 | [z.size() - 2], |
228 | 0 | payoff1.bCoefficients() |
229 | 0 | [z.size() - 2], |
230 | 0 | payoff1.aCoefficients() |
231 | 0 | [z.size() - 2], |
232 | 0 | p[z.size() - 2], |
233 | 0 | z[z.size() - 2], |
234 | 0 | z[z.size() - 1], 100.0); |
235 | 0 | if (type == Option::Put) |
236 | 0 | price += |
237 | 0 | Gaussian1dModel::gaussianShiftedPolynomialIntegral( |
238 | 0 | 0.0, |
239 | 0 | payoff1 |
240 | 0 | .cCoefficients()[0], |
241 | 0 | payoff1 |
242 | 0 | .bCoefficients()[0], |
243 | 0 | payoff1 |
244 | 0 | .aCoefficients()[0], |
245 | 0 | p[0], z[0], -100.0, z[0]); |
246 | 0 | } |
247 | 0 | } |
248 | 0 | } |
249 | |
|
250 | 0 | npvp0[m][k] = price; |
251 | 0 | } |
252 | 0 | } |
253 | | // end probability computation |
254 | |
|
255 | 0 | if (expiry0 > settlement) { |
256 | 0 | Real floatingLegNpv = 0.0; |
257 | 0 | for (Size l = k1; l < arguments_.floatingCoupons.size(); |
258 | 0 | l++) { |
259 | 0 | floatingLegNpv += |
260 | 0 | arguments_.nominal * |
261 | 0 | arguments_.floatingAccrualTimes[l] * |
262 | 0 | (arguments_.floatingSpreads[l] + |
263 | 0 | model_->forwardRate( |
264 | 0 | arguments_.floatingFixingDates[l], expiry0, |
265 | 0 | z[k], arguments_.swap->iborIndex())) * |
266 | 0 | model_->zerobond(arguments_.floatingPayDates[l], |
267 | 0 | expiry0, z[k], discountCurve_); |
268 | 0 | } |
269 | 0 | Real fixedLegNpv = 0.0; |
270 | 0 | for (Size l = j1; l < arguments_.fixedCoupons.size(); l++) { |
271 | 0 | fixedLegNpv += |
272 | 0 | arguments_.fixedCoupons[l] * |
273 | 0 | model_->zerobond(arguments_.fixedPayDates[l], |
274 | 0 | expiry0, z[k], discountCurve_); |
275 | 0 | } |
276 | 0 | Real exerciseValue = |
277 | 0 | (type == Option::Call ? 1.0 : -1.0) * |
278 | 0 | (floatingLegNpv - fixedLegNpv) / |
279 | 0 | model_->numeraire(expiry0Time, z[k], discountCurve_); |
280 | | |
281 | | // for probability computation |
282 | 0 | if (probabilities_ != None) { |
283 | 0 | if (idx == static_cast<int>( |
284 | 0 | arguments_.exercise->dates().size()) - |
285 | 0 | 1) // if true we are at the latest date, |
286 | | // so we init |
287 | | // the no call probability |
288 | 0 | npvp0.back()[k] = |
289 | 0 | probabilities_ == Naive |
290 | 0 | ? Real(1.0) |
291 | 0 | : 1.0 / (model_->zerobond(expiry0Time, 0.0, |
292 | 0 | 0.0, |
293 | 0 | discountCurve_) * |
294 | 0 | model_->numeraire(expiry0, z[k], |
295 | 0 | discountCurve_)); |
296 | 0 | if (exerciseValue >= npv0[k]) { |
297 | 0 | npvp0[idx - minIdxAlive][k] = |
298 | 0 | probabilities_ == Naive |
299 | 0 | ? Real(1.0) |
300 | 0 | : 1.0 / |
301 | 0 | (model_->zerobond(expiry0Time, 0.0, |
302 | 0 | 0.0, |
303 | 0 | discountCurve_) * |
304 | 0 | model_->numeraire(expiry0Time, z[k], |
305 | 0 | discountCurve_)); |
306 | 0 | for (Size ii = idx - minIdxAlive + 1; |
307 | 0 | ii < npvp0.size(); ii++) |
308 | 0 | npvp0[ii][k] = 0.0; |
309 | 0 | } |
310 | 0 | } |
311 | | // end probability computation |
312 | |
|
313 | 0 | npv0[k] = std::max(npv0[k], exerciseValue); |
314 | 0 | } |
315 | 0 | } |
316 | |
|
317 | 0 | npv1.swap(npv0); |
318 | | |
319 | | // for probability computation |
320 | 0 | if (probabilities_ != None) { |
321 | 0 | for (Size i = 0; i < npvp0.size(); i++) |
322 | 0 | npvp1[i].swap(npvp0[i]); |
323 | 0 | } |
324 | | // end probability computation |
325 | |
|
326 | 0 | expiry1 = expiry0; |
327 | 0 | expiry1Time = expiry0Time; |
328 | |
|
329 | 0 | } while (--idx >= minIdxAlive - 1); |
330 | |
|
331 | 0 | results_.value = npv1[0] * model_->numeraire(0.0, 0.0, discountCurve_); |
332 | | |
333 | | // for probability computation |
334 | 0 | if (probabilities_ != None) { |
335 | 0 | std::vector<Real> prob(npvp0.size()); |
336 | 0 | for (Size i = 0; i < npvp0.size(); i++) { |
337 | 0 | prob[i] = npvp1[i][0] * |
338 | 0 | (probabilities_ == Naive |
339 | 0 | ? 1.0 |
340 | 0 | : model_->numeraire(0.0, 0.0, discountCurve_)); |
341 | 0 | } |
342 | 0 | results_.additionalResults["probabilities"] = prob; |
343 | 0 | } |
344 | | // end probability computation |
345 | 0 | } |
346 | | } |