QuantLib: a free/open-source library for quantitative finance
fully annotated source code - version 1.38
Loading...
Searching...
No Matches
denglizhoubasketengine.cpp
Go to the documentation of this file.
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
21#include <ql/exercise.hpp>
28
29namespace QuantLib {
30
32 std::vector<ext::shared_ptr<GeneralizedBlackScholesProcess> > processes,
33 Matrix rho)
34 : n_(processes.size()),
35 processes_(std::move(processes)),
36 rho_(std::move(rho)) {
37
38 QL_REQUIRE(n_ > 0, "No Black-Scholes process is given.");
39 QL_REQUIRE(n_ == rho_.size1() && rho_.size1() == rho_.size2(),
40 "process and correlation matrix must have the same size.");
41
42 std::for_each(processes_.begin(), processes_.end(),
43 [this](const auto& p) { registerWith(p); });
44 }
45
47 const ext::shared_ptr<EuropeanExercise> exercise =
48 ext::dynamic_pointer_cast<EuropeanExercise>(arguments_.exercise);
49 QL_REQUIRE(exercise, "not an European exercise");
50 const Date maturityDate = exercise->lastDate();
51
52 const ext::shared_ptr<AverageBasketPayoff> avgPayoff =
53 (ext::dynamic_pointer_cast<AverageBasketPayoff>(arguments_.payoff) != nullptr)
54 ? ext::dynamic_pointer_cast<AverageBasketPayoff>(arguments_.payoff)
55 : (ext::dynamic_pointer_cast<SpreadBasketPayoff>(arguments_.payoff) != nullptr)
56 ? ext::make_shared<AverageBasketPayoff>(
57 ext::dynamic_pointer_cast<SpreadBasketPayoff>(
58 arguments_.payoff)->basePayoff(),
59 Array({1.0, -1.0})
60 )
61 : ext::shared_ptr<AverageBasketPayoff>();
62
63 QL_REQUIRE(avgPayoff, "average or spread basket payoff expected");
64
65 const Array weights = avgPayoff->weights();
66 QL_REQUIRE(n_ == weights.size() && n_ > 1,
67 "wrong number of weights arguments in payoff");
68
70 const Array s = pExtractor.getSpot();
71 const Array dq = pExtractor.getDividendYieldDf(maturityDate);
72 const Array v = pExtractor.getBlackVariance(maturityDate);
73 const DiscountFactor dr0 = pExtractor.getInterestRateDf(maturityDate);
74
75 std::vector< std::tuple<Real, Size, Real, Real, Real> > p;
76 p.reserve(n_);
77
78 for (Size i=0; i < n_; ++i)
79 p.emplace_back(weights[i], i, s[i], dq[i], v[i]);
80
81 const ext::shared_ptr<PlainVanillaPayoff> payoff =
82 ext::dynamic_pointer_cast<PlainVanillaPayoff>(avgPayoff->basePayoff());
83 QL_REQUIRE(payoff, "non-plain vanilla payoff given");
84
85 Matrix rho;
86 if (payoff->strike() < 0.0) {
87 p.emplace_back(1.0, n_, -payoff->strike(), dr0, 0.0);
88 rho = Matrix(n_+1, n_+1);
89 for (Size i=0; i < n_; ++i) {
90 std::copy(rho_.row_begin(i), rho_.row_end(i), rho.row_begin(i));
91 rho[n_][i] = rho[i][n_] = 0.0;
92 }
93 rho[n_][n_] = 1.0;
94 }
95 else
96 rho = rho_;
97
98 const Real strike = std::max(0.0, payoff->strike());
99
100 // positive weights first
101 std::sort(p.begin(), p.end(), std::greater<>());
102
103 const Size M = std::distance(
104 p.begin(),
105 std::lower_bound(p.begin(), p.end(), Real(0),
106 [](const auto& p, const Real& value) -> bool { return std::get<0>(p) > value;} )
107 );
108
109 QL_REQUIRE(M > 0, "at least one positive asset weight must be given");
110 QL_REQUIRE(M < p.size(), "at least one negative asset weight must be given");
111
112 const Size N = p.size() - M;
113
114 Matrix nRho(N+1, N+1);
115 Array _s(N+1), _dq(N+1), _v(N+1);
116
117 if (M > 1) {
118 Array F(M), vol(M);
119 for (Size i=0; i < M; ++i) {
120 vol[i] = std::sqrt(std::get<4>(p[i]));
121 F[i] = std::get<0>(p[i])*std::get<2>(p[i])*std::get<3>(p[i])/dr0;
122 }
123
124 const Real S0 = std::accumulate(
125 p.begin(), p.begin()+M, Real(0.0),
126 [](const Real& value, const auto& p) -> Real {
127 return value + std::get<0>(p)*std::get<2>(p);
128 });
129 const Real F0 = std::accumulate(F.begin(), F.end(), Real(0.0));
130 const DiscountFactor dq_S0 = F0/S0*dr0;
131
132 Real v_s = 0.0;
133 for (Size i=0; i < M; ++i)
134 for (Size j=0; j < M; ++j)
135 v_s += vol[i]*vol[j]*F[i]*F[j]
136 *rho[std::get<1>(p[i])][std::get<1>(p[j])];
137
138 v_s /= F0*F0;
139 _s[0] = S0; _dq[0] = dq_S0; _v[0] = v_s;
140
141 nRho[0][0] = 1.0;
142
143 for (Size i=0; i < N; ++i) {
144 Real rhoHat = 0.0;
145 for (Size j=0; j < M; ++j)
146 rhoHat += rho[std::get<1>(p[M+i])][std::get<1>(p[j])]*vol[j]*F[j];
147
148 nRho[i+1][0] = nRho[0][i+1]
149 = std::min(1.0, std::max(-1.0, rhoHat/(std::sqrt(v_s)*F0)));
150 }
151 }
152 else {
153 _s[0] = std::abs(std::get<0>(p[0])*std::get<2>(p[0]));
154 _dq[0] = std::get<3>(p[0]);
155 _v[0] = std::get<4>(p[0]);
156 for (Size i=0; i < N+1; ++i)
157 nRho[0][i] = nRho[i][0] = rho[std::get<1>(p[i])][std::get<1>(p[0])];
158 }
159
160 for (Size i=0; i < N; ++i) {
161 _s[i+1] = std::abs(std::get<0>(p[M+i])*std::get<2>(p[M+i]));
162 _dq[i+1] = std::get<3>(p[M+i]);
163 _v[i+1] = std::get<4>(p[M+i]);
164
165 const Size idx = std::get<1>(p[M+i]);
166 for (Size j=0; j < N; ++j)
167 nRho[i+1][j+1] = rho[idx][std::get<1>(p[M+j])];
168 }
169
170 const Real callValue
171 = DengLiZhouBasketEngine::calculate_vanilla_call(Log(_s), dr0, _dq, _v, nRho, strike);
172
173 if (payoff->optionType() == Option::Call)
174 results_.value = std::max(0.0, callValue);
175 else {
176 const Real fwd = _s[0]*_dq[0] - dr0*strike
177 - std::inner_product(_s.begin()+1, _s.end(), _dq.begin()+1, Real(0.0));
178 results_.value = std::max(0.0, callValue - fwd);
179 }
180 }
181
182 Real DengLiZhouBasketEngine::I(Real u, Real tF2, const Matrix& D, const Matrix& DF, Size i) {
183 const Real psi = 1.0/
184 (1.0 + std::inner_product(
185 D.row_begin(i), D.row_end(i), D.row_begin(i), Real(0.0)));
186 const Real sqrtPsi = std::sqrt(psi);
187
188 const Real n_uSqrtPsi = NormalDistribution()(u*sqrtPsi);
189 const Real J_0 = CumulativeNormalDistribution()(u*sqrtPsi);
190
191 const Real vFv = std::inner_product(
192 DF.row_begin(i), DF.row_end(i), D.row_begin(i), Real(0.0));
193 const Real J_1 = psi*sqrtPsi*(psi*u*u - 1.0) * vFv * n_uSqrtPsi;
194
195 const Real vFFv = std::inner_product(
196 DF.row_begin(i), DF.row_end(i), DF.row_begin(i), Real(0.0));
197 const Real J_2 = u*psi*sqrtPsi*n_uSqrtPsi*(
198 2 * tF2
199 + vFv*vFv*(squared(squared(psi*u))
200 - 10.0*psi*psi*psi*u*u + 15*psi*psi)
201 + vFFv * (4*psi*psi*u*u - 12*psi)
202 );
203
204 return J_0 + J_1 - 0.5*J_2;
205 }
206
208 const Array& x, DiscountFactor dr, const Array& dq,
209 const Array& v, const Matrix& rho, Real K) {
210
211 const Array mu = x + Log(dq/dr) - 0.5 * v;
212 const Array nu = Sqrt(v);
213
214 const Real R = std::accumulate(
215 mu.begin()+1, mu.end(), Real(0),
216 [](Real a, Real b) -> Real { return a + std::exp(b); }
217 );
218
219 const Size N = x.size()-1;
220
221 Matrix sig11(N, N);
222 for (Size i=0; i < N; ++i)
223 std::copy(rho.row_begin(i+1)+1, rho.row_end(i+1), sig11.row_begin(i));
224 const Array sig10(rho.row_begin(0)+1, rho.row_end(0));
225
226 const Matrix sqSig11 = pseudoSqrt(sig11, SalvagingAlgorithm::Principal);
227 const Array sig11Inv10 = CholeskySolveFor(CholeskyDecomposition(sig11), sig10);
228
229 const Real sig_xy = 1.0 - DotProduct(sig10, sig11Inv10);
230 QL_REQUIRE(sig_xy > 0.0, "approximation loses validity");
231 const Real sqSig_xy = std::sqrt(sig_xy);
232
233 const Real a = -0.5/sqSig_xy;
234 Matrix E(N, N);
235 for (Size i=1; i <= N; ++i)
236 for (Size j=i; j <= N; ++j)
237 E(i-1, j-1) = E(j-1, i-1) =
238 a*(((i==j)? squared(nu[j])*std::exp(mu[j])/(nu[0]*(R+K)) : Real(0.0))
239 -nu[i]*nu[j]*std::exp(mu[i]+mu[j])/(nu[0]*squared(R + K)) );
240
241 const Matrix F = sqSig11*E*sqSig11;
242
243 Real trF(0), trF2(0);
244 for (Size i=0; i < N; ++i) {
245 trF += F[i][i];
246 trF2 += squared(F[i][i]) +
247 2.0*std::accumulate(
248 F.row_begin(i)+i+1, F.row_end(i), Real(0),
249 [](Real a, Real b) -> Real {return a+b*b;}
250 );
251 }
252
253 const Real c = -(std::log(R + K) - mu[0])/(nu[0]*sqSig_xy);
254
255 const Array d = (sig11Inv10
256 - Exp(Array(mu.begin()+1, mu.end()))*Array(nu.begin()+1,nu.end())/(nu[0]*(R+K)))/sqSig_xy;
257
258 const Array Esig10 = E*sig10;
259 const Matrix Esig11 = E*sig11;
260 const Array sig11d = sig11*d;
261
262 Array C(N+2);
263 C[0] = c + trF + nu[0]*sqSig_xy + nu[0]*DotProduct(sig10, d)
264 + squared(nu[0])*DotProduct(sig10, Esig10);
265 C[N+1] = c + trF;
266
267 for (Size k=1; k < N+1; ++k)
268 C[k] = c + trF + nu[k]*sig11d[k-1] + squared(nu[k])
269 * std::inner_product(sig11.row_begin(k-1), sig11.row_end(k-1),
270 Esig11.column_begin(k-1), Real(0.0));
271
272 std::vector<Array> D(N+2);
273 D[0] = sqSig11*(d + 2*nu[0]*Esig10);
274 D[N+1] = sqSig11*d;
275 for (Size k=1; k < N+1; ++k)
276 D[k] = sqSig11*(d + 2*nu[k]*Array(Esig11.column_begin(k-1), Esig11.column_end(k-1)));
277
278 Matrix DM(N+2, N);
279 for (Size k=0; k < N+2; ++k)
280 std::copy(D[k].begin(), D[k].end(), DM.row_begin(k));
281
282 const Matrix DF = DM*F;
283
284 Real npv = dr*std::exp(mu[0] + 0.5*squared(nu[0])) * I(C[0], trF2, DM, DF, 0)
285 - K*dr*I(C.back(), trF2, DM, DF, N+1);
286
287 for (Size k=1; k <= N; ++k)
288 npv -= dr*std::exp(mu[k] + 0.5*squared(nu[k])) * I(C[k], trF2, DM, DF, k);
289
290 return npv;
291 }
292}
Cholesky decomposition.
1-D array used in linear algebra.
Definition: array.hpp:52
const_iterator end() const
Definition: array.hpp:499
Real back() const
Definition: array.hpp:446
Size size() const
dimension of the array
Definition: array.hpp:483
const_iterator begin() const
Definition: array.hpp:491
Cumulative normal distribution function.
Concrete date class.
Definition: date.hpp:125
DengLiZhouBasketEngine(std::vector< ext::shared_ptr< GeneralizedBlackScholesProcess > > processes, Matrix rho)
const std::vector< ext::shared_ptr< GeneralizedBlackScholesProcess > > processes_
static Real I(Real u, Real tF2, const Matrix &D, const Matrix &DF, Size i)
static Real calculate_vanilla_call(const Array &s, DiscountFactor dr, const Array &dq, const Array &v, const Matrix &rho, Time T)
Matrix used in linear algebra.
Definition: matrix.hpp:41
const_row_iterator row_begin(Size i) const
Definition: matrix.hpp:360
Size size2() const
Definition: matrix.hpp:516
Size size1() const
Definition: matrix.hpp:512
const_column_iterator column_begin(Size i) const
Definition: matrix.hpp:415
const_row_iterator row_end(Size i) const
Definition: matrix.hpp:378
const_column_iterator column_end(Size i) const
Definition: matrix.hpp:434
Normal distribution function.
ext::shared_ptr< Exercise > exercise
Definition: option.hpp:65
ext::shared_ptr< Payoff > payoff
Definition: option.hpp:64
Array getDividendYieldDf(const Date &maturityDate) const
DiscountFactor getInterestRateDf(const Date &maturityDate) const
Array getBlackVariance(const Date &maturityDate) const
Deng, Li and Zhou: Closed-Form Approximation for Spread option pricing.
#define QL_REQUIRE(condition, message)
throw an error if the given pre-condition is not verified
Definition: errors.hpp:117
Date d
Option exercise classes and payoff function.
std::function< Real(Real)> b
QL_REAL Real
real number
Definition: types.hpp:50
Real DiscountFactor
discount factor between dates
Definition: types.hpp:66
std::size_t Size
size of a container
Definition: types.hpp:58
Real rho
ext::shared_ptr< QuantLib::Payoff > payoff
functionals and combinators not included in the STL
Definition: any.hpp:37
Matrix pseudoSqrt(const Matrix &matrix, SalvagingAlgorithm::Type sa)
Definition: pseudosqrt.cpp:350
Array CholeskySolveFor(const Matrix &L, const Array &b)
Matrix CholeskyDecomposition(const Matrix &S, bool flexible)
Array Log(const Array &v)
Definition: array.hpp:849
T squared(T x)
Definition: functional.hpp:37
Array Exp(const Array &v)
Definition: array.hpp:863
Array Sqrt(const Array &v)
Definition: array.hpp:835
Real DotProduct(const Array &v1, const Array &v2)
Definition: array.hpp:541
STL namespace.
normal, cumulative and inverse cumulative distributions
ext::shared_ptr< BlackVolTermStructure > v
pseudo square root of a real symmetric matrix
Real F
Definition: sabr.cpp:200
Real nu
Definition: sabr.cpp:200
helper class to extract underlying, volatility etc from a vector of processes