Coverage Report

Created: 2025-12-08 06:13

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/quantlib/ql/experimental/variancegamma/analyticvariancegammaengine.cpp
Line
Count
Source
1
/* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2
3
/*
4
Copyright (C) 2010 Adrian O' Neill
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
<https://www.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/exercise.hpp>
21
#include <ql/experimental/variancegamma/analyticvariancegammaengine.hpp>
22
#include <ql/math/distributions/gammadistribution.hpp>
23
#include <ql/math/integrals/gausslobattointegral.hpp>
24
#include <ql/math/integrals/kronrodintegral.hpp>
25
#include <ql/math/integrals/segmentintegral.hpp>
26
#include <ql/pricingengines/blackscholescalculator.hpp>
27
#include <utility>
28
29
namespace QuantLib {
30
31
    namespace {
32
33
        class Integrand {
34
        public:
35
          Integrand(ext::shared_ptr<StrikedTypePayoff> payoff,
36
                    Real s0,
37
                    Real t,
38
                    Real riskFreeDiscount,
39
                    Real dividendDiscount,
40
                    Real sigma,
41
                    Real nu,
42
                    Real theta)
43
0
          : payoff_(std::move(payoff)), s0_(s0), t_(t), riskFreeDiscount_(riskFreeDiscount),
44
0
            dividendDiscount_(dividendDiscount), sigma_(sigma), nu_(nu), theta_(theta) {
45
0
              omega_ = std::log(1.0 - theta_ * nu_ - (sigma_ * sigma_ * nu_) / 2.0) / nu_;
46
              // We can precompute the denominator of the gamma pdf (does not depend on x)
47
              // shape = t_/nu_, scale = nu_
48
0
              GammaFunction gf;
49
0
              gammaDenom_ = std::exp(gf.logValue(t_ / nu_)) * std::pow(nu_, t_ / nu_);
50
0
          }
51
52
0
            Real operator()(Real x) const {
53
                // Compute adjusted black scholes price
54
0
                Real s0_adj = s0_ * std::exp(theta_ * x + omega_ * t_ + (sigma_ * sigma_ * x) / 2.0);
55
0
                Real vol_adj = sigma_ * std::sqrt(x / t_);
56
0
                vol_adj *= std::sqrt(t_);
57
58
0
                BlackScholesCalculator bs(payoff_, s0_adj, dividendDiscount_, vol_adj, riskFreeDiscount_);
59
0
                Real bsprice = bs.value();
60
61
                // Multiply by gamma distribution
62
0
                Real gamp = (std::pow(x, t_ / nu_ - 1.0) * std::exp(-x / nu_)) / gammaDenom_;
63
0
                Real result = bsprice * gamp;
64
0
                return result;
65
0
            }
66
67
        private:
68
            ext::shared_ptr<StrikedTypePayoff> payoff_;
69
            Real s0_;
70
            Real t_;
71
            Real riskFreeDiscount_;
72
            Real dividendDiscount_;
73
            Rate sigma_;
74
            Real nu_;
75
            Real theta_;
76
            Real omega_;
77
            Real gammaDenom_;
78
        };
79
    }
80
81
82
    VarianceGammaEngine::VarianceGammaEngine(ext::shared_ptr<VarianceGammaProcess> process,
83
                                             Real absoluteError)
84
0
    : process_(std::move(process)), absErr_(absoluteError) {
85
0
        QL_REQUIRE(absErr_ > 0, "absolute error must be positive");
86
0
        registerWith(process_);
87
0
    }
88
89
0
    void VarianceGammaEngine::calculate() const {
90
91
0
        QL_REQUIRE(arguments_.exercise->type() == Exercise::European,
92
0
            "not an European Option");
93
94
0
        ext::shared_ptr<StrikedTypePayoff> payoff =
95
0
            ext::dynamic_pointer_cast<StrikedTypePayoff>(arguments_.payoff);
96
0
        QL_REQUIRE(payoff, "non-striked payoff given");
97
98
0
        DiscountFactor dividendDiscount =
99
0
            process_->dividendYield()->discount(
100
0
            arguments_.exercise->lastDate());
101
0
        DiscountFactor riskFreeDiscount =
102
0
            process_->riskFreeRate()->discount(arguments_.exercise->lastDate());
103
104
0
        DayCounter rfdc  = process_->riskFreeRate()->dayCounter();
105
0
        Time t = rfdc.yearFraction(process_->riskFreeRate()->referenceDate(),
106
0
            arguments_.exercise->lastDate());
107
108
0
        Integrand f(payoff,
109
0
            process_->x0(),
110
0
            t, riskFreeDiscount, dividendDiscount,
111
0
            process_->sigma(), process_->nu(), process_->theta());
112
113
0
        Real infinity = 15.0 * std::sqrt(process_->nu() * t);
114
0
        Real target = absErr_*1e-4;
115
0
        Real val = f(infinity);
116
0
        while (std::abs(val)>target){
117
0
          infinity*=1.5;
118
0
          val = f(infinity);
119
0
        }
120
        // the integration is split due to occasional singularities at 0
121
0
        Real split = 0.1;
122
0
        GaussKronrodNonAdaptive integrator1(absErr_, 1000, 0);
123
0
        Real pvA = integrator1(f, 0, split);
124
0
        GaussLobattoIntegral integrator2(2000, absErr_);
125
0
        Real pvB = integrator2(f, split, infinity);
126
0
        results_.value = pvA + pvB;
127
0
    }
128
129
}