Coverage Report

Created: 2026-08-14 07:10

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/quantlib/ql/experimental/varianceoption/integralhestonvarianceoptionengine.cpp
Line
Count
Source
1
/* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2
3
/*
4
 Copyright (C) 2008 Lorella Fatone
5
 Copyright (C) 2008 Francesca Mariani
6
 Copyright (C) 2008 Maria Cristina Recchioni
7
 Copyright (C) 2008 Francesco Zirilli
8
 Copyright (C) 2008 StatPro Italia srl
9
10
 This file is part of QuantLib, a free-software/open-source library
11
 for financial quantitative analysts and developers - http://quantlib.org/
12
13
 QuantLib is free software: you can redistribute it and/or modify it
14
 under the terms of the QuantLib license.  You should have received a
15
 copy of the license along with this program; if not, please email
16
 <quantlib-dev@lists.sf.net>. The license is also available online at
17
 <https://www.quantlib.org/license.shtml>.
18
19
 This program is distributed in the hope that it will be useful, but WITHOUT
20
 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
21
 FOR A PARTICULAR PURPOSE.  See the license for more details.
22
*/
23
24
#include <ql/errors.hpp>
25
#include <ql/experimental/varianceoption/integralhestonvarianceoptionengine.hpp>
26
#include <functional>
27
#include <complex>
28
#include <utility>
29
#include <memory>
30
31
namespace QuantLib {
32
33
    namespace {
34
35
    /*
36
     *****************************************************************
37
     **
38
     ** Parameters defining the initial condition of the Heston model
39
     ** and the European call option
40
     **
41
     *****************************************************************
42
     */
43
    /*
44
     *****************************************************************
45
     ** Assign: v0, eprice, tau, rtax
46
     ******************************************************************
47
     ******************************************************************
48
     **     v0: initial variance
49
     ** eprice: realized variance strike price
50
     **    tau: time to maturity
51
     *    rtax: risk free interest rate
52
     ****************************************************************
53
     */
54
55
    typedef std::complex<Real> Complex;
56
57
    Real IvopOneDim(Real eps, Real chi, Real theta, Real /*rho*/,
58
                      Real v0, Real eprice, Time tau, Real rtax)
59
0
    {
60
0
        Real ss = 0.0;
61
0
        auto xiv = std::make_unique<Real[]>(2048*2048+1);
62
0
        Real nris = 0.0;
63
0
        int j = 0, mm = 0;
64
0
        Real pi = 0.0, pi2 = 0.0;
65
0
        Real dstep = 0.0;
66
0
        Real option = 0.0, impart = 0.0;
67
68
0
        auto ff = std::make_unique<Complex[]>(2048*2048);
69
0
        Complex xi;
70
0
        const auto ui = Complex(0.0,1.0);
71
0
        Complex beta,zita,gamma,csum,vero;
72
0
        Complex contrib, caux, caux1,caux2,caux3;
73
74
        /*
75
         **********************************************************
76
         **   i0: initial integrated variance i0=0
77
         **********************************************************
78
         */
79
0
        const auto i0 = 0.0;
80
        //s=2.0*chi*theta/(eps*eps)-1.0;
81
82
        //s=s+1;
83
84
        /*
85
         *************************************************
86
         ** Start integration procedure
87
         *************************************************
88
         */
89
90
0
        pi= 3.14159265358979324;
91
0
        pi2=2.0*pi;
92
0
        const Real s = 2.0*chi*theta/(eps*eps)-1.0;
93
        /*
94
         ****************************************
95
         ** Note that s must be greater than zero
96
         ****************************************
97
         */
98
99
0
        if(s<=0)
100
0
        {
101
0
            QL_FAIL("this parameter must be greater than zero-> " << s);
102
0
        }
103
104
0
        ss=s+1;
105
106
        /*
107
         *************************************************
108
         ** Start integration procedure
109
         *************************************************
110
111
         **************************************************************
112
         ** The oscillatory integral that approximates the price of
113
         ** the realized variance option is computed using the method
114
         ** proposed by Bailey, Swarztrauber in the paper published in
115
         ** Siam Journal on Scientific Computing Vol 15(5) 1994
116
         ** p. 1105-1110
117
         **************************************************************
118
119
         **************************************************************
120
         ** dstep: real number, generally a power of two, that must be
121
         **        assigned to determine the grid of
122
         **        integration. Hint: dstep=256 or 512 (dstep<=2048)
123
         **************************************************************
124
         */
125
0
        dstep=256.0;
126
0
        nris=std::sqrt(pi2)/dstep;
127
0
        mm=(int)(pi2/(nris*nris));
128
129
        /*
130
         ******************************************
131
         **  Definition of the integration grid  **
132
         ******************************************
133
         */
134
0
        for (j=0;j<=mm-1;j++)
135
0
        {
136
0
            xiv[j+1]=(j-mm/2.0)*nris;
137
0
        }
138
139
0
        for (j=0;j<=mm-1;j++)
140
0
        {
141
0
            xi=xiv[j+1];
142
0
            caux=chi*chi;
143
0
            caux1=2.0*eps*eps;
144
0
            caux1=caux1*xi;
145
0
            caux1=caux1*ui;
146
0
            caux2=caux1+caux;
147
148
0
            zita=0.5*std::sqrt(caux2);
149
150
0
            caux1=std::exp(-2.0*tau*zita);
151
152
0
            beta=0.5*chi+zita;
153
0
            beta=beta+caux1*(zita-0.5*chi);
154
0
            gamma=1.0-caux1;
155
156
0
            caux=-ss*tau;
157
0
            caux2=caux*(zita-0.5*chi);
158
0
            caux=ss*std::log(2.0*(zita/beta));
159
0
            caux3=-v0*ui*xi*(gamma/beta);
160
0
            caux=caux+caux3;
161
0
            caux=caux+caux2;
162
163
0
            ff[j+1]=std::exp(caux);
164
0
            if(std::sqrt(std::imag(xi)*std::imag(xi)+std::real(xi)*std::real(xi))>1.e-06)
165
0
            {
166
0
                contrib=-eprice/(ui*xi);
167
0
                caux=ui*xi;
168
0
                caux=caux*eprice;
169
0
                caux=std::exp(caux);
170
0
                caux=caux-1.0;
171
0
                caux2=ui*xi*ui*xi;
172
0
                contrib=contrib+caux/caux2;
173
0
            }
174
0
            else
175
0
            {
176
0
                contrib=eprice*eprice*0.5;
177
0
            }
178
0
            ff[j+1]=ff[j+1]*contrib;
179
0
        }
180
0
        csum=0.0;
181
0
        for (j=0;j<=mm-1;j++)
182
0
        {
183
0
            caux=std::pow(-1.0,j);
184
0
            caux2=-2.0*pi*(double)mm*(double)j*0.5/(double)mm;
185
0
            caux3=ui*caux2;
186
0
            csum=csum+ff[j+1]*caux*std::exp(caux3);
187
0
        }
188
0
        csum=csum*std::sqrt(std::pow(-1.0,mm))*nris/pi2;
189
0
        vero=i0-eprice+theta*tau+(1.0-std::exp(-chi*tau))*(v0-theta)/chi;
190
0
        csum=csum+vero;
191
0
        option=std::exp(-rtax*tau)*std::real(csum);
192
0
        impart=std::imag(csum);
193
0
        QL_ENSURE(impart <= 1e-12,
194
0
                  "imaginary part option (must be zero) = " << impart);
195
0
        return option;
196
0
    }
197
198
199
200
    Real IvopTwoDim(Real eps, Real chi, Real theta, Real /*rho*/,
201
                    Real v0, Time tau, Real rtax,
202
0
                    const std::function<Real(Real)>& payoff) {
203
204
0
        Real ss = 0.0;
205
0
        auto xiv = std::make_unique<Real[]>(2048*2048+1);
206
0
        auto ivet = std::make_unique<Real[]>(2048 * 2048 + 1);
207
0
        Real nris = 0.0;
208
0
        int j = 0, mm = 0, k = 0;
209
0
        Real pi = 0.0, pi2 = 0.0;
210
211
0
        Real dstep = 0.0;
212
0
        Real ip = 0.0;
213
0
        Real payoffval = 0.0;
214
0
        Real option = 0.0/*, impart=0*/;
215
216
0
        Real sumr = 0.0;//,sumi=0;
217
0
        Complex dxi,z;
218
219
0
        auto ff = std::make_unique<Complex[]>(2048*2048);
220
0
        Complex xi;
221
0
        const auto ui = Complex(0.0,1.0);
222
0
        Complex beta,zita,gamma,csum;
223
0
        Complex caux,caux1,caux2,caux3;
224
225
        /*
226
         **********************************************************
227
         **   i0: initial integrated variance i0=0
228
         **********************************************************
229
         */
230
0
        const auto i0 = 0.0;
231
232
        /*
233
         *************************************************
234
         ** Start integration procedure
235
         *************************************************
236
         */
237
238
0
        pi= 3.14159265358979324;
239
0
        pi2=2.0*pi;
240
241
0
        const Real s = 2.0*chi*theta/(eps*eps)-1.0;
242
        /*
243
         ****************************************
244
         ** Note that s must be greater than zero
245
         ****************************************
246
         */
247
248
0
        if(s<=0)
249
0
        {
250
0
            QL_FAIL("this parameter must be greater than zero-> " << s);
251
0
        }
252
253
0
        ss=s+1;
254
255
        /*
256
         *************************************************
257
         ** Start integration procedure
258
         *************************************************
259
260
         **************************************************************
261
         ** The oscillatory integral that approximates the price of
262
         ** the realized variance option is computed using the method
263
         ** proposed by Bailey, Swarztrauber in the paper published in
264
         ** Siam Journal on Scientific Computing Vol 15(5) 1994
265
         ** p. 1105-1110
266
         **************************************************************
267
268
         **************************************************************
269
         ** dstep: real number, generally a power of two that must be
270
         **        assigned to determine the grid of
271
         **        integration. Hint: dstep=256 or 512 (dstep<=2048)
272
         **************************************************************
273
         */
274
0
        dstep=64.0;
275
0
        nris=std::sqrt(pi2)/dstep;
276
0
        mm=(int)(pi2/(nris*nris));
277
278
        /*
279
         ******************************************
280
         **  Definition of the integration grid  **
281
         ******************************************
282
         */
283
284
0
        for (j=0;j<=mm-1;j++)
285
0
        {
286
0
            xiv[j+1]=(j-mm/2.0)*nris;
287
0
            ivet[j+1]=(j-mm/2.0)*pi2/((double)mm*nris);
288
0
        }
289
290
0
        for (j=0;j<=mm-1;j++)
291
0
        {
292
0
            xi=xiv[j+1];
293
294
0
            caux=chi*chi;
295
0
            caux1=2.0*eps*eps;
296
0
            caux1=caux1*xi;
297
0
            caux1=caux1*ui;
298
0
            caux2=caux1+caux;
299
300
0
            zita=0.5*std::sqrt(caux2);
301
0
            caux1=std::exp(-2.0*tau*zita);
302
303
0
            beta=0.5*chi+zita;
304
0
            beta=beta+caux1*(zita-0.5*chi);
305
306
0
            gamma=1.0-caux1;
307
308
0
            caux=-ss*tau;
309
0
            caux2=caux*(zita-0.5*chi);
310
0
            caux=ss*std::log(2.0*(zita/beta));
311
0
            caux3=-v0*ui*xi*(gamma/beta);
312
0
            caux=caux+caux3;
313
0
            caux=caux+caux2;
314
0
            ff[j+1]=std::exp(caux);
315
0
        }
316
317
0
        sumr=0.0;
318
        //sumi=0.0;
319
0
        for (k=0;k<=mm-1;k++)
320
0
        {
321
0
            ip=i0-ivet[k+1];
322
0
            payoffval=payoff(ip);
323
324
0
            dxi=2.0*pi*(double)k/(double)mm*ui;
325
0
            csum=0.0;
326
0
            for (j=0;j<=mm-1;j++)
327
0
            {
328
0
                z=-(double)j*dxi;
329
0
                caux=std::pow(-1.0,j);
330
0
                csum=csum+ff[j+1]*caux*std::exp(z);
331
0
            }
332
0
            csum=csum*std::pow(-1.0,k)*nris/pi2;
333
334
0
            sumr=sumr+payoffval*std::real(csum);
335
            //sumi=sumi+payoffval*std::imag(csum);
336
0
        }
337
0
        sumr=sumr*nris;
338
        //sumi=sumi*nris;
339
340
0
        option=std::exp(-rtax*tau)*sumr;
341
        //impart=sumi;
342
        //QL_ENSURE(impart <= 1e-3,
343
        //          "imaginary part option (must be close to zero) = " << impart);
344
0
        return option;
345
0
    }
346
347
    struct payoff_adapter {
348
        ext::shared_ptr<QuantLib::Payoff> payoff;
349
        explicit payoff_adapter(ext::shared_ptr<QuantLib::Payoff> payoff)
350
0
        : payoff(std::move(payoff)) {}
351
0
        Real operator()(Real S) const {
352
0
            return (*payoff)(S);
353
0
        }
354
    };
355
356
    }
357
358
    IntegralHestonVarianceOptionEngine::IntegralHestonVarianceOptionEngine(
359
        ext::shared_ptr<HestonProcess> process)
360
0
    : process_(std::move(process)) {
361
0
        registerWith(process_);
362
0
    }
363
364
0
    void IntegralHestonVarianceOptionEngine::calculate() const {
365
366
0
        QL_REQUIRE(process_->dividendYield().empty(),
367
0
                   "this engine does not manage dividend yields");
368
369
0
        const auto riskFreeRate = process_->riskFreeRate();
370
371
0
        const auto epsilon = process_->sigma();
372
0
        const auto chi = process_->kappa();
373
0
        const auto theta = process_->theta();
374
0
        const auto rho = process_->rho();
375
0
        const auto v0 = process_->v0();
376
377
0
        const auto tau = riskFreeRate->dayCounter().yearFraction(
378
0
                                        Settings::instance().evaluationDate(),
379
0
                                        arguments_.maturityDate);
380
0
        const auto r = riskFreeRate->zeroRate(arguments_.maturityDate,
381
0
                                        riskFreeRate->dayCounter(),
382
0
                                        Continuous);
383
384
0
        const auto plainPayoff =
385
0
            ext::dynamic_pointer_cast<PlainVanillaPayoff>(arguments_.payoff);
386
0
        if ((plainPayoff != nullptr) && plainPayoff->optionType() == Option::Call) {
387
            // a specialization for Call options is available
388
0
            const auto strike = plainPayoff->strike();
389
0
            results_.value = IvopOneDim(epsilon, chi, theta, rho,
390
0
                                        v0, strike, tau, r)
391
0
                * arguments_.notional;
392
0
        } else {
393
0
            results_.value = IvopTwoDim(epsilon, chi, theta, rho, v0, tau, r,
394
0
                                        payoff_adapter(arguments_.payoff))
395
0
                * arguments_.notional;
396
0
        }
397
0
    }
398
399
}