/src/quantlib/ql/pricingengines/basket/operatorsplittingspreadengine.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) 2024 Klaus Spanderen |
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 | | <http://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/math/functional.hpp> |
21 | | #include <ql/math/distributions/normaldistribution.hpp> |
22 | | #include <ql/pricingengines/basket/operatorsplittingspreadengine.hpp> |
23 | | |
24 | | #include <utility> |
25 | | |
26 | | namespace QuantLib { |
27 | | |
28 | | OperatorSplittingSpreadEngine::OperatorSplittingSpreadEngine( |
29 | | ext::shared_ptr<GeneralizedBlackScholesProcess> process1, |
30 | | ext::shared_ptr<GeneralizedBlackScholesProcess> process2, |
31 | | Real correlation, |
32 | | Order order) |
33 | 0 | : SpreadBlackScholesVanillaEngine(std::move(process1), std::move(process2), correlation), |
34 | 0 | order_(order) { |
35 | 0 | } |
36 | | |
37 | | Real OperatorSplittingSpreadEngine::calculate( |
38 | | Real f1, Real f2, Real k, Option::Type optionType, |
39 | 0 | Real variance1, Real variance2, DiscountFactor df) const { |
40 | |
|
41 | 0 | const auto callPutParityPrice = [f1, f2, df, k, optionType](Real callPrice) -> Real { |
42 | 0 | if (optionType == Option::Call) |
43 | 0 | return callPrice; |
44 | 0 | else |
45 | 0 | return callPrice - df*(f1-f2-k); |
46 | 0 | }; |
47 | |
|
48 | 0 | const Real vol1 = std::sqrt(variance1); |
49 | 0 | const Real vol2 = std::sqrt(variance2); |
50 | 0 | const Real sig2 = vol2*f2/(f2+k); |
51 | 0 | const Real sig_m = std::sqrt(variance1 +sig2*(sig2 - 2*rho_*vol1)); |
52 | |
|
53 | 0 | const Real d1 = (std::log(f1) - std::log(f2 + k))/sig_m + 0.5*sig_m; |
54 | 0 | const Real d2 = d1 - sig_m; |
55 | |
|
56 | 0 | const CumulativeNormalDistribution N; |
57 | |
|
58 | 0 | const Real kirkCallNPV = df*(f1*N(d1) - (f2 + k)*N(d2)); |
59 | |
|
60 | 0 | const Real vs = vol2/(sig_m*sig_m); |
61 | 0 | const Real rs = squared(rho_*vol1 - sig2); |
62 | |
|
63 | 0 | const Real oPlt = -sig2*sig2*k*df*NormalDistribution()(d2)*vs |
64 | 0 | *( -d2*rs/sig2 - 0.5*vs*sig_m * k / (f2+k) |
65 | 0 | *(rs*d1*d2 + (1-rho_*rho_)*variance1)); |
66 | |
|
67 | 0 | if (order_ == First) |
68 | 0 | return callPutParityPrice(kirkCallNPV + 0.5*oPlt); |
69 | | |
70 | 0 | QL_REQUIRE(order_ == Second, "unknown approximation type"); |
71 | | |
72 | | /* |
73 | | In the original paper, the second-order approximation was computed using numerical differentiation. |
74 | | The following Mathematica scripts calculates the approximation to the n'th order. |
75 | | |
76 | | vol2Hat[R2_] := vol2*(R2 - K)/R2 |
77 | | volMinusHat[R2_] := Sqrt[vol1^2 - 2*rho*vol1*vol2Hat[R2] + vol2Hat[R2]^2] |
78 | | zeta1[R1_, R2_] := 1/(volMinusHat[R2]*Sqrt[t])*(Log[R1] + volMinusHat[R2]^2*t/2) |
79 | | zeta2[R1_, R2_] := zeta1[R1, R2] - volMinusHat[R2]*Sqrt[t] |
80 | | pLT[R1_, R2_] := Exp[-r*t]*R2*(R1*CDF[NormalDistribution[0, 1], zeta1[R1, R2]] |
81 | | - CDF[NormalDistribution[0, 1], zeta2[R1, R2]]) |
82 | | opt[R1_, R2_] := (1/2*vol2Hat[R2]^2*R2^2*D[#, {R2, 2}] + (rho*vol1 - vol2Hat[R2])*vol2Hat[R2]*R1*R2* |
83 | | D[#, R1, R2] - (rho*vol1 - vol2Hat[R2])*vol2Hat[R2]*R1*D[#, R1]) & |
84 | | |
85 | | pStrange1[R1_, R2_] := pLT[R1, R2] + (t/2)^1/Factorial[1]*opt[R1, R2][pLT[R1, R2]] |
86 | | pStrange2[R1_, R2_] := pStrange1[R1, R2] + (t/2)^2/Factorial[2]*opt[R1, R2][opt[R1, R2][pLT[R1, R2]]] |
87 | | */ |
88 | | |
89 | 0 | const Real R2 = f2+k; |
90 | 0 | const Real R1 = f1/R2; |
91 | 0 | const Real vol12 = vol1*vol1; |
92 | 0 | const Real vol22 = vol2*vol2; |
93 | 0 | const Real vol23 = vol22*vol2; |
94 | |
|
95 | 0 | if (rs < std::pow(QL_EPSILON, 0.625)) { |
96 | 0 | const Real vol24 = vol22*vol22; |
97 | 0 | const Real vol26 = vol22*vol24; |
98 | 0 | const Real k2 = k*k; |
99 | 0 | const Real R22 = R2*R2; |
100 | 0 | const Real R24 = R22*R22; |
101 | 0 | const Real kmR22 = squared(k-R2); |
102 | 0 | const Real lnR1 = std::log(R1); |
103 | | |
104 | 0 | const Real ooPlt = -0.0625*(k2*kmR22*vol26*(-8*R22*R24*(7*k2 - 7*k*R2 + R22)*vol12*vol12 |
105 | 0 | + kmR22*R24*vol12*(-112*k*R2 + 16*R22 + k2*(124 + 3*vol12))*vol22 - 2*kmR22*kmR22*R22* |
106 | 0 | (-28*k*R2 + 4*R22 + k2*(34 + 3*vol12))*vol24 + 3*k2*kmR22*kmR22*kmR22*vol26 - 4*k*(k -R2)* |
107 | 0 | R24*lnR1*(-4*R22*vol12 + 4*kmR22*vol22 + 3*k*(k - R2)*vol22*lnR1))) |
108 | 0 | /(std::exp(squared(-(R22*vol12) + kmR22*vol22 + 2*R22*lnR1)/(8*R24*vol12 - 8*kmR22*R22*vol22))* |
109 | 0 | M_SQRTPI*M_SQRT2*R22*R24*R2*squared(R22*vol12 - kmR22*vol22)*std::sqrt(vol12 - (kmR22*vol22)/R22)); |
110 | |
|
111 | 0 | return callPutParityPrice(kirkCallNPV + 0.5*oPlt + 0.125*ooPlt); |
112 | 0 | } |
113 | | |
114 | 0 | const Real F2 = f2; |
115 | |
|
116 | 0 | const Real F22 = F2*F2; |
117 | 0 | const Real F23 = F22*F2; |
118 | 0 | const Real F24 = F22*F22; |
119 | |
|
120 | 0 | const Real iR2 = 1.0/R2; |
121 | 0 | const Real iR22 = iR2*iR2; |
122 | 0 | const Real iR23 = iR22*iR2; |
123 | 0 | const Real iR24 = iR22*iR22; |
124 | 0 | const Real a = vol12 - 2*F2*iR2*rho_*vol1*vol2 + F22*iR22*vol22; |
125 | 0 | const Real a2 = a*a; |
126 | 0 | const Real b = a/2+std::log(R1); |
127 | 0 | const Real b2 = b*b; |
128 | 0 | const Real c = std::sqrt(a); |
129 | 0 | const Real d = b/c; |
130 | 0 | const Real e = rho_*vol1 - F2*iR2*vol2; |
131 | 0 | const Real e2 = e*e; |
132 | 0 | const Real f = d-c; |
133 | 0 | const Real g = -2*iR2*rho_*vol1*vol2 + 2*F2*iR22*rho_*vol1*vol2 + 2*F2*iR22*vol22 - 2*F22*iR23*vol22; |
134 | 0 | const Real h = rho_*rho_; |
135 | 0 | const Real j = 1-h; |
136 | 0 | const Real iat = 1/c; |
137 | 0 | const Real l = b*iat - c; |
138 | 0 | const Real m = f*(1 - (R2*rho_*vol1)/(F2*vol2)) - (e*iR2*k*(d*l + (j*vol12)/(e*e))*vol2)/(2.*c); |
139 | 0 | const Real n = (iat*(1 - (R2*rho_*vol1)/(F2*vol2)))/R1 - (e*iR2*k*((f*iat)/R1 + b/(a*R1))*vol2)/(2.*c); |
140 | 0 | const Real o = df*std::exp(-0.5*f*f); |
141 | 0 | const Real p = d*l + (j*vol12)/(e*e); |
142 | 0 | const Real q = (-2*j*vol12*(-(iR2*vol2) + F2*iR22*vol2))/(e*e*e); |
143 | 0 | const Real s = q - (b2*g)/(2.*a2) - (b*f*g)/(2.*a*c) + (f*g)/(2.*c); |
144 | 0 | const Real u = f*(-((rho_*vol1)/(F2*vol2)) + (R2*rho_*vol1)/(F22*vol2)); |
145 | 0 | const Real v = -0.5*(b*g*(1 - (R2*rho_*vol1)/(F2*vol2)))/(a*c); |
146 | 0 | const Real w = (3*g*g)/(4.*a2*c) - (4*iR22*rho_*vol1*vol2 - 4*F2*iR23*rho_*vol1*vol2 + |
147 | 0 | 2*iR22*vol22 - 8*F2*iR23*vol22 + 6*F22*iR24*vol22)/(2.*a*c); |
148 | 0 | const Real x = u + v + (e*g*iR2*k*p*vol2)/(4.*a*c) + (e*iR22*k*p*vol2)/(2.*c) - |
149 | 0 | (e*iR2*k*s*vol2)/(2.*c) - (iR2*k*p*vol2*(-(iR2*vol2) + F2*iR22*vol2))/(2.*c); |
150 | 0 | const Real y = (4*iR22 - 4*F2*iR23)*rho_*vol1*vol2 + (2*iR22 - 8*F2*iR23 + 6*F22*iR24)*vol22; |
151 | 0 | const Real z = 4*iR22*rho_*vol1*vol2 - 4*F2*iR23*rho_*vol1*vol2 + 2*iR22*vol22 - |
152 | 0 | 8*F2*iR23*vol22 + 6*F22*iR24*vol22; |
153 | |
|
154 | 0 | const Real ooPlt = (k*o*vol23*(-2*c*b2*e2*e*(-1 + f*f)*F23*F24*g*g*iR22*m*vol23 + |
155 | 0 | 2*b2*e2*e2*F23*F24*g*g*iR2*iR22*k*vol22*vol22 + 2*a*b*e2*e*F23*F22*g*iR22*vol2*(-8* |
156 | 0 | e2*F2*iR2*k*vol22 + 7*f*F22*g*m*vol22) - a*c*e2*e*F23*F22*g*iR22*vol2*(4*e*F2* |
157 | 0 | vol2*(-2*b*(-1 + f*f)*m + e*f*iR2*k*vol2) + F22*g*(16*m + e*(2*f + 3*b*iat)*iR2*k*vol2)*vol22) - |
158 | 0 | 4*a2*a*c*e2*(e2*F22*vol2*(4*F22*iat*iR22*R2*rho_*vol1 + 8*F23*iR22*n*R1*vol2 - |
159 | 0 | 4*F24*3*iR23*n*R1*vol2 - F23*iR22*(4*iat*rho_*vol1 + F22*iR2*k*p*vol23*w)) |
160 | 0 | + 4*F23*F22*vol22*vol22*(iR22*(-2*F2*iR2 + 3*F22*iR22)*m + F22*(2*iR2 - 3*F2*iR22)*iR23*m |
161 | 0 | + F22*iR22*(-iR2 + F2*iR22)*x) + 2*e*F22*(2*F24*F2*iR24*n*R1*vol23 + |
162 | 0 | 2*f*F2*F22*iR22*rho_*vol1*vol22 - 2*f*F22*iR22*R2*rho_*vol1*vol22 - |
163 | 0 | b*F24*iR22*R2*rho_*vol1*vol22*w - 2*F24*vol2*(iR23*n*R1*vol22 + 4*iR23*m*vol22 - 2*iR22*vol22*x) + |
164 | 0 | F23*(2*iR22*m*vol23 + 6*F22*iR24*m*vol23 + b*F22*iR22*vol23*w - 4*F22*iR23*vol23*x))) + |
165 | 0 | 2*a2*c*e2*F23*F22*vol2*(8*F22*g*iR22*(-iR2 + F2*iR22)*m*vol23 + |
166 | 0 | e2*iR22*vol2*(8*F2*g*n*R1 + b*F22*iat*iR2*k*vol22*(y - z)) + |
167 | 0 | 4*e*vol22*(4*F2*g*iR22*m + F22*(-4*g*iR23*m + 2*g*iR22*x + iR22*m*z))) + |
168 | 0 | 2*a2*a*F22*(-4*e2*e2*e*f*F24*iat*iR24*k*vol23 + 8*e*F2*F24*iR23*(-iR22 + F2*iR23)*j*k*vol12*vol23*vol22 + |
169 | 0 | 12*F2*F24*iR23*squared(iR2 - F2*iR22)*j*k*vol12*vol23*vol23 + |
170 | 0 | e2*e2*F2*vol22*(2*F24*iR22*k*vol22*(2*(iR23*p - iR22*s) + b2*iat*iR2*w) + f*(4*F22*iR22*(4*m + |
171 | 0 | F22*iat*iR2*iR22*k*vol22) - 4*F23*(6*iR23*m + iat*iR24*k*vol22 - 2*iR22*x) |
172 | 0 | + F24*iR23*k*vol22*(2*b*w + iat*y))) - 2*e2*e*F22*iR22*(4*f*F22*(iR2 - F2*iR22)*m*vol23 + |
173 | 0 | F22*vol22*(F2*vol2*(2*k*(F2*iR24*p + F2*iR24*p + iR22*s - iR23*(2*p + F2*s))*vol22 + y - z) |
174 | 0 | + R2*rho_*vol1*(-y + z)))) - 2*a2*e2*F23*(2*e2*e*F23*iR22*k*(2*b*iR22 + g*(-1 + f*iat)*iR2)*vol23 + |
175 | 0 | 4*b*f*F22*F22*g*iR22*(-iR2 + F2*iR22)*m*vol22*vol22 + |
176 | 0 | 2*e2*F22*iR22*vol2*(2*b*F2*iR2*(iR2 - F2*iR22)*k*vol23 + g*(2*R2*rho_*vol1 + 2*F2*(-1 + 3*f*m + |
177 | 0 | b*f*n*R1)*vol2 + F22*k*(-(iR22*p) + iR2*s)*vol23)) + e*vol22*(F2*F22*g*iR22*(g*R2*rho_*vol1 + F2*g*(-1 + f*m)*vol2 + |
178 | 0 | 2*F2*iR2*(-iR2 + F2*iR22)*k*p*vol23) + 2*b*(2*F2*F22*g*iR22*rho_*vol1 - 2*F22*g*iR22*R2*rho_*vol1 + |
179 | 0 | 4*f*F23*g*iR22*m*vol2 + f*F24*vol2*(-4*g*iR23*m + 2*g*iR22*x + |
180 | 0 | iR22*m*z))))))/(16.*a2*a2*c*e2*F23*M_SQRT2*M_SQRTPI*vol2); |
181 | |
|
182 | 0 | return callPutParityPrice(kirkCallNPV + 0.5*oPlt + 0.125*ooPlt); |
183 | 0 | } |
184 | | } |
185 | | |
186 | | |