22#include <ql/math/comparison.hpp>
23#include <ql/math/distributions/normaldistribution.hpp>
24#include <ql/math/matrixutilities/choleskydecomposition.hpp>
25#include <ql/math/matrixutilities/symmetricschurdecomposition.hpp>
26#include <ql/math/solvers1d/brent.hpp>
31void check(
const Real p) { QL_REQUIRE(p >= 0.0 && p <= 1.0,
"p (" << p <<
") must be in [0,1] in VaR calculation"); }
33void check(
const Matrix& omega,
const Array& delta) {
34 QL_REQUIRE(omega.rows() == omega.columns(),
35 "omega (" << omega.rows() <<
"x" << omega.columns() <<
") must be square in VaR calculation");
36 QL_REQUIRE(delta.size() == omega.rows(),
"delta vector size (" << delta.size() <<
") must match omega ("
37 << omega.rows() <<
"x" << omega.columns() <<
")");
40void check(
const Matrix& omega,
const Array& delta,
const Matrix& gamma) {
41 QL_REQUIRE(gamma.rows() == omega.rows() && gamma.columns() == omega.columns(),
42 "gamma (" << gamma.rows() <<
"x" << gamma.columns() <<
") must have same dimensions as omega ("
43 << omega.rows() <<
"x" << omega.columns() <<
")");
48void moments(
const Matrix& omega,
const Array& delta,
const Matrix& gamma, Real& num, Real& mu, Real&
variance) {
60 Array tmpDelta = 1.0 / num * delta;
61 Matrix tmpGamma = 1.0 / num * gamma;
63 Real dOd = DotProduct(tmpDelta, omega * tmpDelta);
64 Matrix go = tmpGamma * omega;
66 Real trGo2 =
Trace(go2);
72void moments(
const Matrix& omega,
const Array& delta,
const Matrix& gamma, Real& num, Real& mu, Real&
variance,
73 Real& tau, Real& kappa) {
85 Array tmpDelta = 1.0 / num * delta;
86 Matrix tmpGamma = 1.0 / num * gamma;
88 Real dOd = DotProduct(tmpDelta, omega * tmpDelta);
89 Matrix go = tmpGamma * omega;
91 Real trGo2 =
Trace(go2);
96 Matrix go3 = go2 * go;
97 Matrix go4 = go2 * go2;
98 Matrix ogo = omega * go;
99 Matrix o_go2 = omega * go2;
100 Real trGo3 =
Trace(go3);
101 Real trGo4 =
Trace(go4);
103 tau = (trGo3 + 3.0 * DotProduct(tmpDelta, ogo * tmpDelta)) / std::pow(
variance, 1.5);
104 kappa = (3.0 * trGo4 + 12.0 * DotProduct(tmpDelta, o_go2 * tmpDelta)) / (
variance *
variance);
107std::pair<Real, Real> bracketRoot(
const std::function<Real(Real)>& p,
const Real h,
const Real growth,
const Real tol,
108 const Size maxSteps,
const Real leftBoundary,
const Real rightBoundary) {
111 Real start = std::min(std::max(leftBoundary + QL_EPSILON, 0.0), rightBoundary - QL_EPSILON);
112 Real xl = start, xr = start;
116 if (std::abs(yl) < tol)
117 return std::make_pair(start, start);
119 bool movingright =
true, movingleft =
true;
120 while (iter++ < maxSteps && (movingleft || movingright)) {
123 Real xlh = std::max(xl - h0, leftBoundary + QL_EPSILON);
125 if (std::abs(tmpl) < tol)
126 return std::make_pair(xlh, xlh);
128 return std::make_pair(std::min(xlh, xl), xl);
129 if (xl - h0 > leftBoundary) {
138 Real xrh = std::min(xr + h0, rightBoundary - QL_EPSILON);
140 if (std::abs(tmpr) < tol)
141 return std::make_pair(xrh, xrh);
143 return std::make_pair(xr, std::max(xrh, xr));
144 if (xr + h0 < rightBoundary) {
156 return std::make_pair(Null<Real>(), Null<Real>());
159Real F(
const Array& lambda,
const Array& delta,
const Real x) {
160 auto KPrimeMinusX = [&lambda, &delta, &x](
const Real k) {
162 for (Size j = 0; j < lambda.size(); ++j) {
163 Real denom = (1.0 - 2.0 * lambda[j] * k);
164 sum += lambda[j] / denom + delta[j] * delta[j] * k * (1.0 - k * lambda[j]) / (denom * denom);
169 auto K = [&lambda, &delta](
const Real k) {
171 for (Size j = 0; j < lambda.size(); ++j) {
172 Real tmp = (1.0 - 2.0 * lambda[j] * k);
173 sum += -0.5 * std::log(tmp) + 0.5 * delta[j] * delta[j] * k * k / tmp;
178 auto KPrime2 = [&lambda, &delta](
const Real k) {
180 for (Size j = 0; j < lambda.size(); ++j) {
181 Real denom = (1.0 - 2.0 * lambda[j] * k);
182 Real denom2 = denom * denom;
183 sum += 2.0 * lambda[j] * lambda[j] / denom2 + delta[j] * delta[j] / (denom2 * denom);
188 auto KPrime3 = [&lambda, &delta](
const Real k) {
190 for (Size j = 0; j < lambda.size(); ++j) {
191 Real denom = (1.0 - 2.0 * lambda[j] * k);
192 Real denom2 = denom * denom;
193 Real l2 = lambda[j] * lambda[j];
194 sum += 8.0 * lambda[j] * l2 / (denom2 * denom) + 6.0 * delta[j] * delta[j] * lambda[j] / (denom2 * denom2);
199 auto KPrime4 = [&lambda, &delta](
const Real k) {
201 for (Size j = 0; j < lambda.size(); ++j) {
202 Real denom = (1.0 - 2.0 * lambda[j] * k);
203 Real denom2 = denom * denom;
204 Real denom4 = denom2 * denom2;
205 Real l2 = lambda[j] * lambda[j];
206 sum += 48.0 * l2 * l2 / (denom2 * denom2) + 48.0 * delta[j] * delta[j] * l2 / (denom4 * denom);
211 Real b1 = 0.0, b2 = 0.0, khat, yTol = 1E-7, xTol = 1E-10;
214 QL_REQUIRE(!lambda.empty(),
"lambda is empty");
215 Real c1 = -QL_MAX_REAL, c2 = QL_MAX_REAL;
216 Real minl = *std::min_element(lambda.begin(), lambda.end());
217 Real maxl = *std::max_element(lambda.begin(), lambda.end());
219 c1 = 1.0 / (2.0 * minl);
221 c2 = 1.0 / (2.0 * maxl);
223 std::vector<Real> singularities;
224 for (Size i = 0; i < lambda.size(); ++i) {
226 singularities.push_back(1.0 / (2.0 * lambda[i]));
231 singularities.push_back(-QL_MAX_REAL);
232 singularities.push_back(QL_MAX_REAL);
233 std::sort(singularities.begin(), singularities.end());
234 Size index = std::upper_bound(singularities.begin(), singularities.end(), 0.0) - singularities.begin();
236 Size stepsize = 1, counter = 0, indextmp = index;
237 while (counter < singularities.size() - 1) {
238 if (indextmp > 0 && indextmp < singularities.size()) {
240 Real a1 = std::max(std::min(singularities[indextmp - 1], c2), c1);
241 Real a2 = std::max(std::min(singularities[indextmp], c2), c1);
245 bracketRoot(KPrimeMinusX, std::min(1E-5, (a2 - a1) / 10.0), 1E-2, yTol, 100000, a1, a2);
246 if (b1 != Null<Real>())
251 indextmp = index + direction * stepsize;
255 QL_REQUIRE(stepsize < singularities.size(),
"deltaGammaVarSaddlepoint: unexpected stepsize");
258 QL_REQUIRE(b1 != Null<Real>(),
"deltaGammaVarSaddlepoint: could not bracket root to find K'(k) = x");
264 khat = b.solve(KPrimeMinusX, xTol, (b1 + b2) / 2.0, b1, b2);
267 Real eta = khat * std::sqrt(KPrime2(khat));
268 Real tmp = 2.0 * (khat * x - K(khat));
269 Real xi = (khat >= 0.0 ? 1.0 : -1.0) * std::sqrt(tmp);
271 QuantLib::CumulativeNormalDistribution cnd;
272 QuantLib::NormalDistribution nd;
274 if (std::abs(khat) > 1E-5) {
275 Real res = cnd(xi) - nd(xi) * (1.0 / eta - 1.0 / xi);
282 Real eta2 = eta * eta;
283 Real eta3 = eta2 * eta;
284 Real eta4 = eta2 * eta2;
285 Real eta6 = eta3 * eta3;
286 Real KP2 = KPrime2(khat);
287 Real a2 = KPrime2(khat) / KP2;
288 Real a3 = KPrime3(khat) / std::pow(KP2, 1.5);
289 Real a4 = KPrime4(khat) / (KP2 * KP2);
290 Real res = 1.0 - std::exp(K(khat) - khat * x + 0.5 * xi * xi) *
291 ((1.0 - cnd(eta)) * (1.0 - a3 * eta3 / 6.0 + a4 * eta4 / 24.0 + a2 * eta6 / 72.0) +
292 nd(eta) * (a3 * (eta2 - 1.0) / 6.0 - a4 * eta * (eta2 - 1.0) / 24.0 -
293 eta3 * (eta4 - eta2 + 3.0) / 72.0));
295 if (std::isnan(res)) {
311 Array tmpDelta = delta / num;
312 return std::sqrt(DotProduct(tmpDelta, sal.
salvage(omega).first * tmpDelta)) *
313 QuantLib::InverseCumulativeNormal()(p) * num;
319 Real s = QuantLib::InverseCumulativeNormal()(p);
320 Real num = 0.0, mu = 0.0,
variance = 0.0;
324 return (std::sqrt(
variance) * s + mu) * num;
331 Real s = QuantLib::InverseCumulativeNormal()(p);
332 Real num = 0.0, mu = 0.0,
variance = 0.0, tau = 0.0, kappa = 0.0;
333 moments(sal.
salvage(omega).first, delta, gamma, num, mu,
variance, tau, kappa);
338 s + tau / 6.0 * (s * s - 1.0) + kappa / 24.0 * s * (s * s - 3.0) - tau * tau / 36.0 * s * (2.0 * s * s - 5.0);
339 return (xTilde * std::sqrt(
variance) + mu) * num;
360 L = CholeskyDecomposition(omega,
true);
363 Matrix hLGL = 0.5 * transpose(L) * gamma * L;
364 SymmetricSchurDecomposition schur(hLGL);
365 Array lambda = schur.eigenvalues();
372 for (
const auto l : lambda) {
373 Real a = std::abs(l);
375 scaling = std::max(scaling, a);
383 Array tmpDelta = delta / scaling;
384 Matrix tmpGamma = gamma / scaling;
387 for (
auto& l : lambda) {
392 Array deltaBar = transpose(schur.eigenvectors()) * transpose(L) * tmpDelta;
399 Real normDeltaBar = 0.0, normGammaBar = 0.0;
400 for (Size i = 0; i < deltaBar.size(); ++i) {
401 normDeltaBar += deltaBar[i] * deltaBar[i];
402 normGammaBar += lambda[i] * lambda[i];
404 if (normGammaBar / normDeltaBar < 1E-10)
408 auto FMinusP = [&lambda, &deltaBar, &p](
const Real x) {
return F(lambda, deltaBar, x) - p; };
413 quantile = b.solve(FMinusP, 1E-6, 0.0, 1.0);
414 }
catch (
const std::exception& e) {
415 QL_FAIL(
"deltaGammaVarSaddlepoint: could no solve for quantile p = " << p <<
": " << e.what());
419 return quantile * scaling;
functions to compute delta or delta-gamma VaR numbers
Filter close_enough(const RandomVariable &x, const RandomVariable &y)
RandomVariable variance(const RandomVariable &r)
Real deltaGammaVarCornishFisher(const Matrix &omega, const Array &delta, const Matrix &gamma, const Real p, const CovarianceSalvage &sal)
Real deltaGammaVarNormal(const Matrix &omega, const Array &delta, const Matrix &gamma, const Real p, const CovarianceSalvage &sal)
function that computes a delta-gamma normal VaR
Real Trace(const Matrix &m)
Real deltaVar(const Matrix &omega, const Array &delta, const Real p, const CovarianceSalvage &sal)
function that computes a delta VaR
Real deltaGammaVarSaddlepoint(const Matrix &omega, const Array &delta, const Matrix &gamma, const Real p, const CovarianceSalvage &sal)
Real sum(const Cash &c, const Cash &d)
virtual std::pair< Matrix, Matrix > salvage(const Matrix &m) const =0
trace of a quadratic matrix