Logo
Fully annotated reference manual - version 1.8.12
Loading...
Searching...
No Matches
kienitzlawsonswaynesabrpdedensity.cpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2024 Quaternion Risk Management Ltd
3 All rights reserved.
4
5 This file is part of ORE, a free-software/open-source library
6 for transparent pricing and risk analysis - http://opensourcerisk.org
7
8 ORE is free software: you can redistribute it and/or modify it
9 under the terms of the Modified BSD License. You should have received a
10 copy of the license along with this program.
11 The license is also available online at <http://opensourcerisk.org>
12
13 This program is distributed on the basis that it will form a useful
14 contribution to risk analytics and model standardisation, but WITHOUT
15 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
16 FITNESS FOR A PARTICULAR PURPOSE. See the license for more details.
17*/
18
20
21#include <ql/math/comparison.hpp>
22
23namespace QuantExt {
24
25KienitzLawsonSwayneSabrPdeDensity::KienitzLawsonSwayneSabrPdeDensity(const Real alpha, const Real beta, const Real nu,
26 const Real rho, const Real forward,
27 const Real expiryTime, const Real displacement,
28 const Size zSteps, const Size tSteps,
29 const Real nStdDev)
30 : alpha_(alpha), beta_(beta), nu_(nu), rho_(rho), forward_(forward), expiryTime_(expiryTime),
31 displacement_(displacement), zSteps_(zSteps), tSteps_(tSteps), nStdDev_(nStdDev) {
32
33 QL_REQUIRE(alpha > 0.0, "KienitzLawsonSwayneSabrPdeDensity: alpha (" << alpha << ") must be positive");
34 QL_REQUIRE(beta >= 0.0 && beta < 1.0, "KienitzLawsonSwayneSabrPdeDensity: beta (" << beta << ") must be in [0,1)");
35 QL_REQUIRE(nu > 0.0, "KienitzLawsonSwayneSabrPdeDensity: nu (" << nu << ") must be positive");
36 QL_REQUIRE(rho > -1.0 && rho < 1.0, "KienitzLawsonSwayneSabrPdeDensity: rho (" << rho << ") must be in (-1,1)");
37 QL_REQUIRE(expiryTime > 0.0,
38 "KienitzLawsonSwayneSabrPdeDensity: expiryTime (" << expiryTime << ") must be positve");
39 QL_REQUIRE(zSteps > 1, "KienitzLawsonSwayneSabrPdeDensity: zSteps (" << zSteps << ") must be >1");
40 QL_REQUIRE(tSteps > 0, "KienitzLawsonSwayneSabrPdeDensity: tSteps (" << tSteps << ") must be positive");
41 QL_REQUIRE(nStdDev > 0.0, "KienitzLawsonSwayneSabrPdeDensity: nStdDev (" << nStdDev << ") must be positive");
42
43 calculate();
44}
45
46Real KienitzLawsonSwayneSabrPdeDensity::yf(const Real f) const {
47 Real betam = 1.0 - beta_;
48 return (std::pow(f + displacement_, betam) - std::pow(forward_ + displacement_, betam)) / betam;
49}
50
51Real KienitzLawsonSwayneSabrPdeDensity::fy(const Real y) const {
52 Real betam = 1.0 - beta_;
53 Real par = std::pow(forward_ + displacement_, betam) + betam * y;
54 if (beta_ > 0) {
55 par = std::max(par, 0.0);
56 return std::pow(par, 1.0 / betam) - displacement_;
57 } else {
58 return par - displacement_;
59 }
60}
61
62Real KienitzLawsonSwayneSabrPdeDensity::yz(const Real z) const {
63 return alpha_ / nu_ * (std::sinh(nu_ * z) + rho_ * (std::cosh(nu_ * z) - 1.0));
64}
65
66Real KienitzLawsonSwayneSabrPdeDensity::zy(const Real y) const {
67 Real tmp = rho_ + nu_ * y / alpha_;
68 return -1.0 / nu_ *
69 std::log((std::sqrt(1.0 - rho_ * rho_ + tmp * tmp) - rho_ - nu_ * y / alpha_) / (1.0 - rho_)); // ???
70}
71
72void KienitzLawsonSwayneSabrPdeDensity::tridag(const Array& a, const Array& b, const Array& c, const Array& R, Array& u,
73 const Size n, bool firstLastRZero) {
74 Array gam(n);
75 if (QuantLib::close_enough(b[0], 0.0)) {
76 std::fill(u.begin(), u.end(), 0.0); // ???
77 return;
78 }
79 Real bet = b[0];
80 u[0] = firstLastRZero ? 0.0 : (R[0] / bet);
81 for (Size j = 1; j < n; ++j) {
82 gam[j] = c[j - 1] / bet;
83 bet = b[j] - a[j] * gam[j];
84 if (QuantLib::close_enough(bet, 0.0)) {
85 QL_FAIL("KienitzLawsonSwayneSabrPdeDensity: tridag failed");
86 }
87 if (j < n - 1 || !firstLastRZero)
88 u[j] = (R[j] - a[j] * u[j - 1]) / bet;
89 else
90 u[j] = (-a[j] * u[j - 1]) / bet;
91 }
92 for (Size j = n - 1; j >= 1; --j) {
93 u[j - 1] -= gam[j] * u[j];
94 }
95} // tridag
96
97void KienitzLawsonSwayneSabrPdeDensity::solveTimeStep_LS(const Array& fm, const Array& cm, const Array& Em,
98 const Real dt, const Array& PP_in, const Real PL_in,
99 const Real PR_in, const Size n, Array& PP_out, Real& PL_out,
100 Real& PR_out) {
101
102 Array bb(n), aa(n), cc(n);
103
104 Real frac = dt / (2.0 * hh_);
105
106 for (Size i = 1; i < n - 1; ++i) {
107 aa[i] = -frac * cm[i - 1] * Em[i - 1] / (fm[i] - fm[i - 1]); // lower diagonal
108 bb[i] = 1.0 + frac * (cm[i] * Em[i] * (1.0 / (fm[i + 1] - fm[i]) + 1.0 / (fm[i] - fm[i - 1]))); // diagonal
109 cc[i] = -frac * cm[i + 1] * Em[i + 1] / (fm[i + 1] - fm[i]); // upper diagonal
110 }
111
112 bb[0] = cm[0] / (fm[1] - fm[0]) * Em[0];
113 bb[n - 1] = cm[n - 1] / (fm[n - 1] - fm[n - 2]) * Em[n - 1];
114 aa[0] = 0.0; // need to init this ?
115 aa[n - 1] = cm[n - 2] / (fm[n - 1] - fm[n - 2]) * Em[n - 2];
116 cc[0] = cm[1] / (fm[1] - fm[0]) * Em[1];
117 cc[n - 1] = 0.0; // need to init this ?
118
119 tridag(aa, bb, cc, PP_in, PP_out, n, true);
120
121 PL_out = PL_in + dt * cm[1] / (fm[1] - fm[0]) * Em[1] * PP_out[1];
122 PR_out = PR_in + dt * cm[n - 2] / (fm[n - 1] - fm[n - 2]) * Em[n - 2] * PP_out[n - 2];
123} // solveTimeStep_LS
124
125void KienitzLawsonSwayneSabrPdeDensity::PDE_method(const Array& fm, const Array& Ccm, const Array& Gamma_Vec,
126 const Real dt, const Size Nj) {
127
128 Array Emdt1_vec(Nj), Emdt2_vec(Nj), PP1(Nj), PP2(Nj), Em(Nj, 1.0), EmO(Nj);
129 constexpr Real b = 1.0 - M_SQRT_2;
130 Real dt1 = dt * b;
131 Real dt2 = dt * (1.0 - 2.0 * b);
132 for (Size i = 1; i < Nj - 1; ++i) {
133 Emdt1_vec[i] = std::exp(rho_ * nu_ * alpha_ * Gamma_Vec[i] * dt1);
134 Emdt2_vec[i] = std::exp(rho_ * nu_ * alpha_ * Gamma_Vec[i] * dt2);
135 }
136 Emdt1_vec[0] = Emdt1_vec[1];
137 Emdt1_vec[Nj - 1] = Emdt1_vec[Nj - 2];
138 Emdt2_vec[0] = Emdt2_vec[1];
139 Emdt2_vec[Nj - 1] = Emdt2_vec[Nj - 2];
140 pL_ = pR_ = 0.0;
141
142 // we need two time steps
143
144 Real PL1, PR1, PL2, PR2;
145 for (Size it = 0; it < tSteps_; ++it) {
146 for (Size j = 1; j < Nj - 1; ++j) {
147 Em[j] *= Emdt1_vec[j];
148 }
149 solveTimeStep_LS(fm, Ccm, Em, dt1, SABR_Prob_Vec_, pL_, pR_, Nj, PP1, PL1, PR1);
150 for (Size j = 1; j < Nj - 1; ++j) {
151 Em[j] *= Emdt1_vec[j];
152 }
153 solveTimeStep_LS(fm, Ccm, Em, dt1, PP1, PL1, PR1, Nj, PP2, PL2, PR2);
154 for (Size j = 1; j < Nj - 1; ++j) {
155 SABR_Prob_Vec_[j] = (M_SQRT2 + 1.0) * PP2[j] - M_SQRT2 * PP1[j];
156 Em[j] *= Emdt2_vec[j];
157 }
159 SABR_Prob_Vec_[Nj - 1] = -SABR_Prob_Vec_[Nj - 2];
160 pL_ = (M_SQRT2 + 1.0) * PL2 - M_SQRT2 * PL1;
161 pR_ = (M_SQRT2 + 1.0) * PR2 - M_SQRT2 * PR1;
162 }
163
164} // PDE_method
165
167
168 Real betam = 1.0 - beta_;
169 zMin_ = -nStdDev_ * std::sqrt(expiryTime_);
170 zMax_ = -zMin_;
171
172 if (beta_ > 0.0) {
173 zMin_ = std::max(zMin_, zy(yf(-displacement_)));
174 }
175
176 Size j = zSteps_ - 2;
177 Real h0 = (zMax_ - zMin_) / static_cast<Real>(j);
178 Size j0 = static_cast<int>(-zMin_ / h0 + 0.5);
179 hh_ = -zMin_ / (static_cast<Real>(j0) - 0.5);
180
181 Array zm(zSteps_), ym(zSteps_), fm(zSteps_), CCm(zSteps_), Gamma_vec(zSteps_);
182
183 for (Size i = 0; i < zSteps_; ++i) {
184 zm[i] = static_cast<Real>(i) * hh_ + zMin_ - 0.5 * hh_;
185 ym[i] = yz(zm[i]);
186 fm[i] = fy(ym[i]);
187 }
188
189 zMax_ = static_cast<Real>(zSteps_ - 1) * hh_ + zMin_;
190 Real ymax = yz(zMax_);
191 Real ymin = yz(zMin_);
192 Real fmax = fy(ymax);
193 Real fmin = fy(ymin);
194
195 fm[0] = 2 * fmin - fm[1];
196 fm[zSteps_ - 1] = 2 * fmax - fm[zSteps_ - 2];
197
198 for (Size i = 1; i < zSteps_ - 1; ++i) {
199 CCm[i] = std::sqrt(alpha_ * alpha_ + 2.0 * rho_ * alpha_ * nu_ * ym[i] + nu_ * nu_ * ym[i] * ym[i]) *
200 std::pow(fm[i] + displacement_, beta_);
201 if (i != j0)
202 Gamma_vec[i] = (std::pow(fm[i] + displacement_, beta_) - std::pow(forward_ + displacement_, beta_)) /
203 (fm[i] - forward_);
204 }
205 CCm[0] = CCm[1];
206 CCm[zSteps_ - 1] = CCm[zSteps_ - 2];
207 Gamma_vec[0] = 0.0; // need to init this ?
208 Gamma_vec[zSteps_ - 1] = 0.0; // need to init this ?
209
210 SABR_Prob_Vec_ = Array(zSteps_, 0.0);
211
212 Gamma_vec[j0] = beta_ / std::pow(forward_ + displacement_, betam);
213 SABR_Prob_Vec_[j0] = 1.0 / hh_;
214
215 PDE_method(fm, CCm, Gamma_vec, expiryTime_ / static_cast<Real>(tSteps_), zSteps_);
217
218 // cumulative probability
219 SABR_Cum_Prob_Vec_ = Array(SABR_Prob_Vec_.size());
220 SABR_Cum_Prob_Vec_[0] = pL();
221 for (Size k = 1; k < SABR_Cum_Prob_Vec_.size() - 1; ++k)
223 SABR_Cum_Prob_Vec_.back() = 1.0;
224
225} // calculate
226
228 int idx = std::upper_bound(SABR_Cum_Prob_Vec_.begin(), SABR_Cum_Prob_Vec_.end(), std::max(std::min(p, 1.0), 0.0)) -
229 SABR_Cum_Prob_Vec_.begin();
230 if (idx == 0)
231 return fy(yz(zMin()));
232 else if (idx == SABR_Cum_Prob_Vec_.size())
233 return fy(yz(zMax()));
234 Real zl = zMin() + static_cast<Real>(idx - 1) * hh();
235 Real zr = zMin() + static_cast<Real>(idx) * hh();
236 Real cl = SABR_Cum_Prob_Vec_[idx - 1];
237 Real cr = SABR_Cum_Prob_Vec_[idx];
238 Real alpha = (cr - p) / (cr - cl);
239 return fy(yz(alpha * zl + (1.0 - alpha) * zr));
240}
241
242std::vector<Real> KienitzLawsonSwayneSabrPdeDensity::callPrices(const std::vector<Real>& strikes) const {
243 std::vector<Real> result;
244 for (auto const& strike : strikes) {
245 Real zstrike = zy(yf(strike));
246 if (zstrike <= zMin())
247 result.push_back(forward() - strike);
248 else if (zstrike >= zMax())
249 result.push_back(0.0);
250 else {
251 Real fmax = fy(yz(zMax()));
252 Real p = (fmax - strike) * pR(); // rightmost value
253 // unused ???
254 // Real k0 = std::ceil((zstrike - zMin()) / hh());
255 // Real ftilde = fy(yz(zMin() + k0 * hh));
256 // Real term1 = ftilde - strike;
257 // in between points
258 Real ft1;
259 Size k = zSteps() - 2;
260 for (; k >= 1; --k) {
261 Real zm1 = zMin() + (static_cast<Real>(k) - 0.5) * hh();
262 ft1 = fy(yz(zm1));
263 if (ft1 > strike)
264 p += (ft1 - strike) * sabrProb()[k] * hh();
265 else
266 break;
267 }
268 // k=k+1 ???
269 // now k is the value where the payoff is zero and at k+1 the payoff is positive
270 // calculating the value for subgridscale
271 Real zmK = zMin() + static_cast<Real>(k) * hh(); // last admissable value ft > strike
272 Real zmKm1 = zMin() + static_cast<Real>(k - 1) * hh(); // first value with ft < strike
273 Real ftK = fy(yz(zmK));
274 Real ftKm1 = fy(yz(zmKm1));
275 Real diff = ftK - ftKm1;
276 Real b = (2.0 * ft1 - ftKm1 - ftK) / diff;
277 Real subgridadjust = 0.5 * hh() * sabrProb()[k] * (ftK - strike) * (ftK - strike) / diff *
278 (1.0 + b * (ftK + 2.0 * strike - 3.0 * ftKm1) / diff);
279 p += subgridadjust;
280 result.push_back(p);
281 }
282 }
283 return result;
284} // callPrices
285
286std::vector<Real> KienitzLawsonSwayneSabrPdeDensity::putPrices(const std::vector<Real>& strikes) const {
287 std::vector<Real> result;
288 for (auto const& strike : strikes) {
289 Real zstrike = zy(yf(strike));
290 if (zstrike <= zMin())
291 result.push_back(0.0);
292 else if (zstrike >= zMax())
293 result.push_back(strike - forward()); // wrong in VBA ???
294 else {
295 Real fmin = fy(yz(zMin()));
296 Real p = (strike - fmin) * pL(); // leftmost value
297 // in between points
298 Real ft1;
299 Size k = 1;
300 for (; k <= zSteps() - 2; ++k) {
301 Real zm1 = zMin() + (static_cast<Real>(k) - 0.5) * hh();
302 ft1 = fy(yz(zm1));
303 if (strike >= ft1)
304 p += (strike - ft1) * sabrProb()[k] * hh();
305 else
306 break;
307 }
308 // ???
309 if (k > 1)
310 --k;
311 Real zmK = zMin() + static_cast<Real>(k) * hh(); // last admissable value ft > strike
312 Real zmKm1 = zMin() + static_cast<Real>(k - 1) * hh(); // first value with ft < strike
313 Real ftK = fy(yz(zmK));
314 Real ftKm1 = fy(yz(zmKm1));
315 Real diff = ftK - ftKm1;
316 Real b = (2.0 * ft1 - ftKm1 - ftK) / diff;
317 Real subgridadjust = 0.5 * hh() * sabrProb()[k] * (strike - ftKm1) * (strike - ftKm1) / diff *
318 (1.0 - b * (3.0 * ftK - 2.0 * strike - ftKm1) / diff);
319 p += subgridadjust;
320 result.push_back(p);
321 }
322 }
323 return result;
324} // putPrices
325
326} // namespace QuantExt
std::vector< Real > callPrices(const std::vector< Real > &strikes) const
void solveTimeStep_LS(const Array &fm, const Array &cm, const Array &Em, const Real dt, const Array &PP_in, const Real PL_in, const Real PR_in, const Size n, Array &PP_out, Real &PL_out, Real &PR_out)
void PDE_method(const Array &fm, const Array &Ccm, const Array &Gamma_Vec, const Real dt, const Size Nj)
std::vector< Real > putPrices(const std::vector< Real > &strikes) const
KienitzLawsonSwayneSabrPdeDensity(const Real alpha, const Real beta, const Real nu, const Real rho, const Real forward, const Real expiryTime, const Real displacement, const Size zSteps, const Size tSteps, const Real nStdDev)
void tridag(const Array &a, const Array &b, const Array &c, const Array &R, Array &u, const Size n, const bool firstLastRZero)
Adaption of VBA code by Jörg Kienitz, 2017, to create a SABR density with PDE methods.
vector< Real > strikes