File: analytichestonforwardeuropeanengine.cpp

package info (click to toggle)
quantlib 1.41-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 41,480 kB
  • sloc: cpp: 400,885; makefile: 6,547; python: 214; sh: 150; lisp: 86
file content (274 lines) | stat: -rw-r--r-- 12,640 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
/* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */

/*
 Copyright (C) 2020 Jack Gillett
 
 This file is part of QuantLib, a free-software/open-source library
 for financial quantitative analysts and developers - http://quantlib.org/

 QuantLib is free software: you can redistribute it and/or modify it
 under the terms of the QuantLib license.  You should have received a
 copy of the license along with this program; if not, please email
 <quantlib-dev@lists.sf.net>. The license is also available online at
 <https://www.quantlib.org/license.shtml>.

 This program is distributed in the hope that it will be useful, but WITHOUT
 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 FOR A PARTICULAR PURPOSE.  See the license for more details.
*/

#include <ql/experimental/forward/analytichestonforwardeuropeanengine.hpp>
#include <complex>
#include <utility>

namespace QuantLib {


    class P12Integrand {
      private:
        ext::shared_ptr<AnalyticHestonEngine>& engine_;
        Real logK_, phiRightLimit_;
        Time tenor_;
        std::complex<Real> i_, adj_;
      public:
        P12Integrand(ext::shared_ptr<AnalyticHestonEngine>& engine,
                     Real logK,
                     Time tenor,
                     bool P1, // true for P1, false for P2
                     Real phiRightLimit = 100) : engine_(engine), logK_(logK),
            phiRightLimit_(phiRightLimit), tenor_(tenor), i_(std::complex<Real>(0.0, 1.0)) {

            // Only difference between P1 and P2 integral is the additional term in the chF evaluation
            if (P1) {
                adj_ = std::complex<Real>(0.0, -1.0);
            } else {
                adj_ = std::complex<Real>(0.0, 0.0);
            }
        }

        // QL Gaussian Quadrature - map phi from [-1 to 1] to {0, phiRightLimit] 
        Real operator()(Real phi) const {
            Real phiDash = (0.5+1e-8+0.5*phi) * phiRightLimit_; // Map phi to full range
            return 0.5*phiRightLimit_*std::real((std::exp(-phiDash*logK_*i_) / (phiDash*i_)) * engine_->chF(phiDash+adj_, tenor_));
        }
    };


    class P12HatIntegrand {
      private:
        Time tenor_, resetTime_;
        Handle<Quote>& s0_;
        bool P1_;
        Real logK_, phiRightLimit_, nuRightLimit_;
        const AnalyticHestonForwardEuropeanEngine* const parent_;
        GaussLegendreIntegration innerIntegrator_;
      public:
        P12HatIntegrand(Time tenor,
                        Time resetTime,
                        Handle<Quote>& s0,
                        Real logK,
                        bool P1, // true for P1, false for P2
                        const AnalyticHestonForwardEuropeanEngine* const parent,
                        Real phiRightLimit,
                        Real nuRightLimit) : tenor_(tenor), resetTime_(resetTime),
            s0_(s0), P1_(P1), logK_(logK), phiRightLimit_(phiRightLimit),
            nuRightLimit_(nuRightLimit), parent_(parent), innerIntegrator_(128) {}
        Real operator()(Real nu) const {

            // Rescale nu to [-1, 1]
            Real nuDash = nuRightLimit_ * (0.5 * nu + 0.5 + 1e-8);

            // Calculate the chF from var(t) to expiry
            ext::shared_ptr<AnalyticHestonEngine> engine = parent_->forwardChF(s0_, nuDash);
            P12Integrand pIntegrand(engine, logK_, tenor_, P1_, phiRightLimit_);
            Real p1Integral = innerIntegrator_(pIntegrand);

            // Calculate the value of the propagator to nu
            Real propagator = parent_->propagator(resetTime_, nuDash);

            // Take the product, and scale integral's value back up to [0, right_lim]
            return propagator * (0.5 + p1Integral/M_PI);
        }
    };


    AnalyticHestonForwardEuropeanEngine::AnalyticHestonForwardEuropeanEngine(
        ext::shared_ptr<HestonProcess> process, Size integrationOrder)
    : process_(std::move(process)), integrationOrder_(integrationOrder), outerIntegrator_(128) {

        v0_ = process_->v0();
        rho_ = process_->rho();
        kappa_ = process_->kappa();
        theta_ = process_->theta();
        sigma_ = process_->sigma();
        s0_ = process_->s0();

        QL_REQUIRE(sigma_ > 0.1,
                   "Very low values (<~10%) for Heston Vol-of-Vol cause numerical issues" \
                   "in this implementation of the propagator function, try using" \
                   "MCForwardEuropeanHestonEngine Monte-Carlo engine instead");

        riskFreeRate_ = process_->riskFreeRate();
        dividendYield_ = process_->dividendYield();

        // Some of the required constant intermediate variables can be calculated now
        kappaHat_ = kappa_ - rho_ * sigma_;
        thetaHat_ = kappa_ * theta_ / kappaHat_;
        R_ = 4 * kappaHat_ * thetaHat_ / (sigma_ * sigma_);
    }


    void AnalyticHestonForwardEuropeanEngine::calculate() const {
        // This is a european option pricer
        QL_REQUIRE(this->arguments_.exercise->type() == Exercise::European,
                   "not an European option");

        // We only price plain vanillas
        ext::shared_ptr<PlainVanillaPayoff> payoff =
            ext::dynamic_pointer_cast<PlainVanillaPayoff>(this->arguments_.payoff);
        QL_REQUIRE(payoff, "non plain vanilla payoff given");

        Time resetTime = this->process_->time(this->arguments_.resetDate);
        Time expiryTime = this->process_->time(this->arguments_.exercise->lastDate());
        Time tenor = expiryTime - resetTime;
        Real moneyness = this->arguments_.moneyness;

        // K needs to be scaled to forward AT RESET TIME, not spot...
        Real expiryDcf = riskFreeRate_->discount(expiryTime);
        Real resetDcf = riskFreeRate_->discount(resetTime);
        Real expiryDividendDiscount = dividendYield_->discount(expiryTime);
        Real resetDividendDiscount = dividendYield_->discount(resetTime);
        Real expiryRatio = expiryDcf / expiryDividendDiscount;
        Real resetRatio = resetDcf / resetDividendDiscount;

        QL_REQUIRE(resetTime >= 0.0, "Reset Date cannot be in the past");
        QL_REQUIRE(expiryTime >= 0.0, "Expiry Date cannot be in the past");

        // Use some heuristics to decide upon phiRightLimit and nuRightLimit
        Real phiRightLimit = 100.0;
        Real nuRightLimit = std::max(2.0, 10.0 * (1+std::max(0.0, rho_)) * sigma_ * std::sqrt(resetTime * std::max(v0_, theta_)));

        // do the 2D integral calculation. For very short times, we just fall back on the standard
        // calculation, both for accuracy and because tStar==0 causes some numerical issues...
        std::pair<Real, Real> P1HatP2Hat;
        if (resetTime <= 1e-3) {
            Handle<Quote> tempQuote(ext::shared_ptr<Quote>(new SimpleQuote(s0_->value())));
            P1HatP2Hat = calculateP1P2(tenor, tempQuote, moneyness * s0_->value(), expiryRatio, phiRightLimit);
        } else {
            P1HatP2Hat = calculateP1P2Hat(tenor, resetTime, moneyness, expiryRatio/resetRatio, phiRightLimit, nuRightLimit);
        }

        // Apply the payoff functions
        Real value = 0.0;
        Real F = s0_->value() / expiryRatio;
        switch (payoff->optionType()){
            case Option::Call:
                value = expiryDcf * (F*P1HatP2Hat.first - moneyness*s0_->value()*P1HatP2Hat.second/resetRatio);
                break;
            case Option::Put:
                value = expiryDcf * (moneyness*s0_->value()*(1-P1HatP2Hat.second)/resetRatio - F*(1-P1HatP2Hat.first));
                break;
            default:
                QL_FAIL("unknown option type");
            }

        results_.value = value;

        results_.additionalResults["dcf"] = expiryDcf;
        results_.additionalResults["qf"] = expiryDividendDiscount;
        results_.additionalResults["expiryRatio"] = expiryRatio;
        results_.additionalResults["resetRatio"] = resetRatio;
        results_.additionalResults["moneyness"] = moneyness;
        results_.additionalResults["s0"] = s0_->value();
        results_.additionalResults["fwd"] = F;
        results_.additionalResults["resetTime"] = resetTime;
        results_.additionalResults["expiryTime"] = expiryTime;
        results_.additionalResults["P1Hat"] = P1HatP2Hat.first;
        results_.additionalResults["P2Hat"] = P1HatP2Hat.second;
        results_.additionalResults["phiRightLimit"] = phiRightLimit;
        results_.additionalResults["nuRightLimit"] = nuRightLimit;
    }


    std::pair<Real, Real> AnalyticHestonForwardEuropeanEngine::calculateP1P2Hat(Time tenor,
                                                                                Time resetTime,
                                                                                Real moneyness,
                                                                                Real ratio,
                                                                                Real phiRightLimit,
                                                                                Real nuRightLimit) const {

        Handle<Quote> unitQuote(ext::shared_ptr<Quote>(new SimpleQuote(1.0)));

        // Re-expressing moneyness in terms of the forward here (strike fixes to spot, but in
        // our pricing calculation we need to compare it to the future at expiry)
        Real logMoneyness = std::log(moneyness*ratio);

        P12HatIntegrand p1HatIntegrand(tenor, resetTime, unitQuote, logMoneyness, true, this, phiRightLimit, nuRightLimit);
        P12HatIntegrand p2HatIntegrand(tenor, resetTime, unitQuote, logMoneyness, false, this, phiRightLimit, nuRightLimit);

        Real p1HatIntegral = 0.5 * nuRightLimit * outerIntegrator_(p1HatIntegrand);
        Real p2HatIntegral = 0.5 * nuRightLimit * outerIntegrator_(p2HatIntegrand);

        std::pair<Real, Real> P1HatP2Hat(p1HatIntegral, p2HatIntegral);

        return P1HatP2Hat;
    }


    Real AnalyticHestonForwardEuropeanEngine::propagator(Time resetTime,
                                                         Real varReset) const {
        Real B, Lambda, term1, term2, term3;

        B = 4 * kappaHat_ / (sigma_ * sigma_ * (1 - std::exp(-kappaHat_ * resetTime)));
        Lambda = B * std::exp(-kappaHat_ * resetTime) * v0_;

        // Now construct equation (18) from the paper term-by-term
        term1 = std::exp(-0.5*(B * varReset + Lambda)) * B / 2;
        term2 = std::pow(B * varReset / Lambda, 0.5*(R_/2 - 1));
        term3 = modifiedBesselFunction_i(Real(R_/2 - 1),Real(std::sqrt(Lambda * B * varReset)));

        return term1 * term2 * term3;
    }

    ext::shared_ptr<AnalyticHestonEngine> AnalyticHestonForwardEuropeanEngine::forwardChF(
                                      Handle<Quote>& spotReset,
                                      Real varReset) const {

        // Probably a wasteful implementation here, could be improved by importing
        // only the CF-generating parts of the AnalyticHestonEngine (currently private)
        ext::shared_ptr<HestonProcess> hestonProcess(new
            HestonProcess(riskFreeRate_, dividendYield_, spotReset,
                varReset, kappa_, theta_, sigma_, rho_));

        ext::shared_ptr<HestonModel> hestonModel(new HestonModel(hestonProcess));

        ext::shared_ptr<AnalyticHestonEngine> analyticHestonEngine(
            new AnalyticHestonEngine(hestonModel, integrationOrder_));

        // Not sure how to pass only the chF, so just pass the whole thing for now!
        return analyticHestonEngine;
    }


    std::pair<Real, Real> AnalyticHestonForwardEuropeanEngine::calculateP1P2(Time tenor,
                                                                             Handle<Quote>& St,
                                                                             Real K,
                                                                             Real ratio,
                                                                             Real phiRightLimit) const {

        ext::shared_ptr<AnalyticHestonEngine> engine = forwardChF(St, v0_);
        Real logK = std::log(K*ratio/St->value());

        // Integrate the CF and the complex integrand over positive phi
        GaussLegendreIntegration integrator = GaussLegendreIntegration(128);
        P12Integrand p1Integrand(engine, logK, tenor, true, phiRightLimit);
        P12Integrand p2Integrand(engine, logK, tenor, false, phiRightLimit);

        Real p1Integral = integrator(p1Integrand);
        Real p2Integral = integrator(p2Integrand);

        std::pair<Real, Real> P1P2(0.5 + p1Integral/M_PI, 0.5 + p2Integral/M_PI);

        return P1P2;
    }
}