Coverage Report

Created: 2025-08-28 06:30

/src/quantlib/ql/experimental/variancegamma/fftengine.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) 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
<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/exercise.hpp>
21
#include <ql/experimental/variancegamma/fftengine.hpp>
22
#include <ql/math/fastfouriertransform.hpp>
23
#include <ql/math/interpolations/linearinterpolation.hpp>
24
#include <complex>
25
#include <utility>
26
27
namespace QuantLib {
28
29
    FFTEngine::FFTEngine(ext::shared_ptr<StochasticProcess1D> process, Real logStrikeSpacing)
30
0
    : process_(std::move(process)), lambda_(logStrikeSpacing) {
31
0
        registerWith(process_);
32
0
    }
33
34
    void FFTEngine::calculate() const
35
0
    {
36
0
        QL_REQUIRE(arguments_.exercise->type() == Exercise::European,
37
0
            "not an European Option");
38
39
0
        ext::shared_ptr<StrikedTypePayoff> payoff =
40
0
            ext::dynamic_pointer_cast<StrikedTypePayoff>(arguments_.payoff);
41
0
        QL_REQUIRE(payoff, "non-striked payoff given");
42
43
0
        auto r1 = resultMap_.find(arguments_.exercise->lastDate());
44
0
        if (r1 != resultMap_.end())
45
0
        {
46
0
            auto r2 = r1->second.find(payoff);
47
0
            if (r2 != r1->second.end())
48
0
            {
49
0
                results_.value = r2->second;
50
0
                return;
51
0
            }
52
0
        }
53
        
54
        // Option not precalculated - do entire FFT for one option.  Not very efficient - call precalculate!
55
0
        calculateUncached(payoff, arguments_.exercise);
56
0
    }
57
58
    void FFTEngine::update()
59
0
    {
60
        // Process has changed so cached values may no longer be correct
61
0
        resultMap_.clear();
62
63
        // Call base class implementation
64
0
        VanillaOption::engine::update();
65
0
    }
66
67
    void FFTEngine::calculateUncached(const ext::shared_ptr<StrikedTypePayoff>& payoff,
68
0
                                      const ext::shared_ptr<Exercise>& exercise) const {
69
0
        ext::shared_ptr<VanillaOption> option(new VanillaOption(payoff, exercise));
70
0
        std::vector<ext::shared_ptr<Instrument> > optionList;
71
0
        optionList.push_back(option);
72
73
0
        ext::shared_ptr<FFTEngine> tempEngine(clone().release());
74
0
        tempEngine->precalculate(optionList);
75
0
        option->setPricingEngine(tempEngine);
76
0
        results_.value = option->NPV();
77
0
    }
78
79
0
    void FFTEngine::precalculate(const std::vector<ext::shared_ptr<Instrument> >& optionList) {
80
        // Group payoffs by expiry date
81
        // as with FFT we can compute a bunch of these at once
82
0
        resultMap_.clear();
83
84
0
        typedef std::vector<ext::shared_ptr<StrikedTypePayoff> > PayoffList;
85
0
        typedef std::map<Date, PayoffList> PayoffMap;
86
0
        PayoffMap payoffMap;
87
88
0
        for (const auto& optIt : optionList) {
89
0
            ext::shared_ptr<VanillaOption> option = ext::dynamic_pointer_cast<VanillaOption>(optIt);
90
0
            QL_REQUIRE(option, "instrument must be option");
91
0
            QL_REQUIRE(option->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>(option->payoff());
96
0
            QL_REQUIRE(payoff, "non-striked payoff given");
97
98
0
            payoffMap[option->exercise()->lastDate()].push_back(payoff);
99
0
        }
100
101
0
        std::complex<Real> i1(0, 1);
102
0
        Real alpha = 1.25;
103
104
0
        for (auto & payIt : payoffMap)
105
0
        {
106
0
            Date expiryDate = payIt.first;
107
108
            // Calculate n large enough for maximum strike, and round up to a power of 2
109
0
            Real maxStrike = 0.0;
110
0
            for (const auto& payoff : payIt.second) {
111
0
                if (payoff->strike() > maxStrike)
112
0
                    maxStrike = payoff->strike();
113
0
            }
114
0
            Real nR = 2.0 * (std::log(maxStrike) + lambda_) / lambda_;
115
0
      Size log2_n = (static_cast<Size>((std::log(nR) / std::log(2.0))) + 1);
116
0
            Size n = static_cast<std::size_t>(1) << log2_n;
117
118
            // Strike range (equation 19,20)
119
0
            Real b = n * lambda_ / 2.0;
120
121
            // Grid spacing (equation 23)
122
0
            Real eta = 2.0 * M_PI / (lambda_ * n);
123
124
            // Discount factor
125
0
            Real df = discountFactor(expiryDate);
126
0
            Real div = dividendYield(expiryDate);
127
128
            // Input to fourier transform
129
0
            std::vector<std::complex<Real> > fti;
130
0
            fti.resize(n);
131
132
            // Precalculate any discount factors etc.
133
0
            precalculateExpiry(expiryDate);
134
135
0
            for (Size i=0; i<n; i++)
136
0
            {
137
0
                Real v_j = eta * i;
138
0
                Real sw = eta * (3.0 + ((i % 2) == 0 ? -1.0 : 1.0) - ((i == 0) ? 1.0 : 0.0)) / 3.0; 
139
140
0
                std::complex<Real> psi = df * complexFourierTransform(v_j - (alpha + 1)* i1);
141
0
                psi = psi / (alpha*alpha + alpha - v_j*v_j + i1 * (2 * alpha + 1.0) * v_j);
142
143
0
                fti[i] = std::exp(i1 * b * v_j)  * sw * psi;
144
0
            }
145
146
            // Perform fft
147
0
            std::vector<std::complex<Real> > results(n);
148
0
            FastFourierTransform fft(log2_n);
149
0
            fft.transform(fti.begin(), fti.end(), results.begin());
150
151
            // Call prices
152
0
            std::vector<Real> prices, strikes;
153
0
            prices.resize(n);
154
0
            strikes.resize(n);
155
0
            for (Size i=0; i<n; i++)
156
0
            {
157
0
                Real k_u = -b + lambda_ * i;
158
0
                prices[i] = (std::exp(-alpha * k_u) / M_PI) * results[i].real();
159
0
                strikes[i] = std::exp(k_u);
160
0
            }
161
162
0
            for (const auto& payoff : payIt.second) {
163
0
                Real callPrice = LinearInterpolation(strikes.begin(), strikes.end(),
164
0
                                                     prices.begin())(payoff->strike());
165
0
                switch (payoff->optionType())
166
0
                {
167
0
                case Option::Call:
168
0
                    resultMap_[expiryDate][payoff] = callPrice;
169
0
                    break;
170
0
                case Option::Put:
171
0
                    resultMap_[expiryDate][payoff] = callPrice - process_->x0() * div + payoff->strike() * df;
172
0
                    break;
173
0
                default:
174
0
                    QL_FAIL("Invalid option type");
175
0
                }
176
0
            }
177
0
        }
178
0
    }
179
180
}
181