QuantLib: a free/open-source library for quantitative finance
fully annotated source code - version 1.38
Loading...
Searching...
No Matches
choibasketengine.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/*! \file choibasketengine.cpp
21*/
22
23#include <ql/exercise.hpp>
35
36#include <boost/math/special_functions/sign.hpp>
37
38namespace QuantLib {
39
41 std::vector<ext::shared_ptr<GeneralizedBlackScholesProcess> > processes,
42 Matrix rho, Real lambda,
43 Size maxNrIntegrationSteps,
44 bool calcFwdDelta, bool controlVariate)
45 : n_(processes.size()),
46 processes_(std::move(processes)),
47 rho_(std::move(rho)),
48 lambda_(lambda),
49 maxNrIntegrationSteps_(maxNrIntegrationSteps),
50 calcFwdDelta_(calcFwdDelta || controlVariate),
51 controlVariate_(controlVariate) {
52
53 QL_REQUIRE(n_ > 0, "No Black-Scholes process is given.");
54 QL_REQUIRE(n_ == rho_.size1() && rho_.size1() == rho_.size2(),
55 "process and correlation matrix must have the same size.");
56
57 QL_REQUIRE(lambda_ > 0.0, "lambda must be positive");
58
59 std::for_each(processes_.begin(), processes_.end(),
60 [this](const auto& p) { registerWith(p); });
61 }
62
64 const ext::shared_ptr<EuropeanExercise> exercise =
65 ext::dynamic_pointer_cast<EuropeanExercise>(arguments_.exercise);
66 QL_REQUIRE(exercise, "not an European exercise");
67
68 const Date maturityDate = exercise->lastDate();
69
71 const Array s = pExtractor.getSpot();
72 const Array dq = pExtractor.getDividendYieldDf(maturityDate);
73 const Array stdDev = Sqrt(pExtractor.getBlackVariance(maturityDate));
74 const DiscountFactor dr0 = pExtractor.getInterestRateDf(maturityDate);
75
76 const Array fwd = s * dq/dr0;
77
78 const ext::shared_ptr<AverageBasketPayoff> avgPayoff =
79 (ext::dynamic_pointer_cast<AverageBasketPayoff>(arguments_.payoff) != nullptr)
80 ? ext::dynamic_pointer_cast<AverageBasketPayoff>(arguments_.payoff)
81 : (ext::dynamic_pointer_cast<SpreadBasketPayoff>(arguments_.payoff) != nullptr)
82 ? ext::make_shared<AverageBasketPayoff>(
83 ext::dynamic_pointer_cast<SpreadBasketPayoff>(
84 arguments_.payoff)->basePayoff(),
85 Array({1.0, -1.0})
86 )
87 : ext::shared_ptr<AverageBasketPayoff>();
88
89 QL_REQUIRE(avgPayoff, "average or spread basket payoff expected");
90
91 const Array weights = avgPayoff->weights();
92 QL_REQUIRE(n_ == weights.size() && n_ > 1,
93 "wrong number of weights arguments in payoff");
94
95 const Array g = weights*fwd / Norm2(weights*fwd);
96
97 const Matrix Sigma = getCovariance(stdDev.begin(), stdDev.end(), rho_);
98 Array vStar1 = Sigma*g;
99 vStar1 /= std::sqrt(DotProduct(g, vStar1));
100
101 const Matrix C = CholeskyDecomposition(Sigma);
102
103 const Real eps = 100*std::sqrt(QL_EPSILON);
104 // publication sets tol=0, pyfeng implementation sets tol=0.01
105 const Real tol = 100*std::sqrt(QL_EPSILON);
106
107 bool flip = false;
108 for (Size i=0; i < n_; ++i)
109 if (boost::math::sign(g[i])*vStar1[i] < tol*stdDev[i]) {
110 flip = true;
111 vStar1[i] = eps * boost::math::sign(g[i]) * stdDev[i];
112 }
113
114 Array q1(n_);
115 if (flip) {
116 //q1 = inverse(C)*vStar1;
117 for (Size i=0; i < n_; ++i)
118 q1[i] = (vStar1[i] - std::inner_product(
119 C.row_begin(i), C.row_begin(i) + i, q1.begin(), Real(0.0)))/C[i][i];
120
121 vStar1 /= Norm2(q1);
122 }
123 else {
124 q1 = transpose(C)*g;
125 }
126 q1 /= Norm2(q1);
127
128 Array e1(n_, 0.0);
129 e1[0] = 1.0;
130
132 HouseholderReflection(e1).reflectionVector(q1)).getMatrix();
133 Matrix R_2_n = Matrix(n_, n_-1);
134 for (Size i=0; i < n_; ++i)
135 std::copy(R.row_begin(i)+1, R.row_end(i), R_2_n.row_begin(i));
136
137 const SVD svd(C*R_2_n);
138 const Matrix& U = svd.U();
139 const Array& sv = svd.singularValues();
140
141 Matrix v(n_, n_-1);
142 for (Size i=0; i < n_-1; ++i)
143 std::transform(
144 U.column_begin(i), U.column_end(i), v.column_begin(i),
145 [i, &sv](Real x) -> Real { return sv[i]*x; }
146 );
147
148 std::vector<Size> nIntOrder(n_-1);
149 Real lambda = lambda_;
150 const Real alpha = 1/std::abs(DotProduct(g, vStar1));
151 do {
152 const Real intScale = lambda * alpha;
153 for (Size i=0; i < n_-1; ++i)
154 nIntOrder[i] = Size(std::lround(1 + intScale*sv[i]));
155
156 lambda*=0.9;
157 QL_REQUIRE(lambda/lambda_ > 1e-10,
158 "can not rescale lambda to fit max integration order");
159 } while (std::accumulate(
160 nIntOrder.begin(), nIntOrder.end(), 1.0, std::multiplies<>())
162
163 std::vector<ext::shared_ptr<SimpleQuote> > quotes;
164 std::vector<ext::shared_ptr<GeneralizedBlackScholesProcess> > p;
165 for (Size i=0; i < n_; ++i) {
166 quotes.push_back(ext::make_shared<SimpleQuote>(fwd[i]));
167
168 const Handle<BlackVolTermStructure> bv = processes_[i]->blackVolatility();
169 const Volatility vol = vStar1[i] / std::sqrt(
170 bv->dayCounter().yearFraction(bv->referenceDate(), maturityDate)
171 );
172 p.push_back(
173 ext::make_shared<BlackProcess>(
174 Handle<Quote>(quotes[i]),
175 processes_[i]->riskFreeRate(),
177 ext::make_shared<BlackConstantVol>(
178 bv->referenceDate(), bv->calendar(),
179 Handle<Quote>(ext::make_shared<SimpleQuote>(vol)),
180 bv->dayCounter()
181 )
182 )
183 )
184 );
185 }
186
187 BasketOption option(avgPayoff, exercise);
188 option.setPricingEngine(ext::make_shared<SingleFactorBsmBasketEngine>(p));
189
190 Array vq(n_);
191 for (Size i=0; i < n_; ++i)
192 vq[i] = 0.5*std::accumulate(
193 v.row_begin(i), v.row_end(i), Real(0.0),
194 [](Real acc, Real x) -> Real { return acc + x*x; }
195 );
196
198 nIntOrder,
199 [](const Size n) { return ext::make_shared<GaussHermiteIntegration>(n); }
200 );
201 const Real normFactor = std::pow(M_PI, -0.5*nIntOrder.size());
202
203 std::vector<Real> dStore;
204 dStore.reserve(ghq.weights().size());
205 const auto bsm1dPricer = [&](const Array& z) -> Real {
206 const Array f = Exp(-M_SQRT2*(v*z) - vq) * fwd;
207
208 for (Size i=0; i < f.size(); ++i)
209 quotes[i]->setValue(f[i]);
210
211 dStore.push_back(ext::any_cast<Real>(option.additionalResults().at("d")));
212 return std::exp(-DotProduct(z, z)) * option.NPV();
213 };
214
215 results_.value = ghq(bsm1dPricer) * normFactor;
216
217 if (calcFwdDelta_) {
218 const ext::shared_ptr<PlainVanillaPayoff> payoff =
219 ext::dynamic_pointer_cast<PlainVanillaPayoff>(avgPayoff->basePayoff());
220 QL_REQUIRE(payoff, "non-plain vanilla payoff given");
221 const Real putIndicator = (payoff->optionType() == Option::Call) ? 0.0 : -1.0;
222
223 Size dStoreCounter;
225
226 Array fwdDelta(n_), fHat(n_);
227 for (Size k=0; k < n_; ++k) {
228 dStoreCounter = 0;
229
230 const auto deltaPricer = [&](const Array& z) -> Real {
231 const Real d = dStore[dStoreCounter++];
232 const Real vz = std::inner_product(
233 v.row_begin(k), v.row_end(k), z.begin(), Real(0.0));
234 const Real f = std::exp(-M_SQRT2*vz - vq[k]);
235
236 return std::exp(-DotProduct(z, z)) * f * N(d + vStar1[k]);
237 };
238
239 fwdDelta[k] = dr0*weights[k]*(ghq(deltaPricer) * normFactor + putIndicator);
240
241 const std::string deltaName = "forwardDelta " + std::to_string(k);
242 results_.additionalResults[deltaName] = fwdDelta[k];
243 }
244
245 if (controlVariate_) {
246 for (Size k=0; k < n_; ++k) {
247 const auto fHatPricer = [&](const Array& z) -> Real {
248 const Real vz = std::inner_product(
249 v.row_begin(k), v.row_end(k), z.begin(), Real(0.0));
250 const Real f = std::exp(-M_SQRT2*vz - vq[k]);
251
252 return std::exp(-DotProduct(z, z)) * f;
253 };
254 fHat[k] = ghq(fHatPricer) * normFactor;
255 }
256 const Array cv = fwdDelta*fwd*(fHat-1.0);
257 results_.value -= std::accumulate(cv.begin(), cv.end(), Real(0.0));
258 }
259 }
260 }
261}
Black constant volatility, no time dependence, no strike dependence.
Jaehyuk Choi: Sum of all Black-Scholes-Merton Models.
Cholesky decomposition.
1-D array used in linear algebra.
Definition: array.hpp:52
const_iterator end() const
Definition: array.hpp:499
Size size() const
dimension of the array
Definition: array.hpp:483
const_iterator begin() const
Definition: array.hpp:491
Basket option on a number of assets.
ChoiBasketEngine(std::vector< ext::shared_ptr< GeneralizedBlackScholesProcess > > processes, Matrix rho, Real lambda=10.0, Size maxNrIntegrationSteps=std::numeric_limits< Size >::max(), bool calcfwdDelta=false, bool controlVariate=false)
void calculate() const override
const std::vector< ext::shared_ptr< GeneralizedBlackScholesProcess > > processes_
Cumulative normal distribution function.
Concrete date class.
Definition: date.hpp:125
Shared handle to an observable.
Definition: handle.hpp:41
std::map< std::string, ext::any > additionalResults
Definition: instrument.hpp:123
const std::map< std::string, ext::any > & additionalResults() const
returns all additional result returned by the pricing engine.
Definition: instrument.hpp:198
Real NPV() const
returns the net present value of the instrument.
Definition: instrument.hpp:167
void setPricingEngine(const ext::shared_ptr< PricingEngine > &)
set the pricing engine to be used.
Definition: instrument.cpp:35
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
ext::shared_ptr< Exercise > exercise
Definition: option.hpp:65
ext::shared_ptr< Payoff > payoff
Definition: option.hpp:64
Singular value decomposition.
Definition: svd.hpp:54
const Array & singularValues() const
Definition: svd.cpp:493
const Matrix & U() const
Definition: svd.cpp:485
Array getDividendYieldDf(const Date &maturityDate) const
DiscountFactor getInterestRateDf(const Date &maturityDate) const
Array getBlackVariance(const Date &maturityDate) const
#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.
Integral of a 1-dimensional function using the Gauss quadratures.
Covariance matrix calculation.
#define QL_EPSILON
Definition: qldefines.hpp:178
QL_REAL Real
real number
Definition: types.hpp:50
Real DiscountFactor
discount factor between dates
Definition: types.hpp:66
Real Volatility
volatility
Definition: types.hpp:78
std::size_t Size
size of a container
Definition: types.hpp:58
Real rho
Householder transformation and Householder projection.
ext::shared_ptr< QuantLib::Payoff > payoff
#define M_SQRT2
#define M_PI
Definition: any.hpp:37
Matrix CholeskyDecomposition(const Matrix &S, bool flexible)
Matrix getCovariance(DataIterator stdDevBegin, DataIterator stdDevEnd, const Matrix &corr, Real tolerance=1.0e-12)
Calculation of covariance from correlation and standard deviations.
Array Exp(const Array &v)
Definition: array.hpp:863
Array Sqrt(const Array &v)
Definition: array.hpp:835
Real Norm2(const Array &v)
Definition: array.hpp:548
Matrix transpose(const Matrix &m)
Definition: matrix.hpp:700
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
Real alpha
Definition: sabr.cpp:200
simple quote class
Basket engine where all underlyings are driven by one stochastic factor.
singular value decomposition
helper class to extract underlying, volatility etc from a vector of processes