Coverage Report

Created: 2025-08-11 06:28

/src/quantlib/ql/experimental/varianceoption/integralhestonvarianceoptionengine.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) 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
 <http://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 <ql/functional.hpp>
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
        std::unique_ptr<double[]> xiv(new double[2048*2048+1]);
62
0
        double nris=0.0;
63
0
        int j=0,mm=0;
64
0
        double pi=0,pi2=0;
65
0
        double dstep=0;
66
0
        Real option=0, impart=0;
67
68
0
        std::unique_ptr<Complex[]> ff(new Complex[2048*2048]);
69
0
        Complex xi;
70
0
        Complex ui,beta,zita,gamma,csum,vero;
71
0
        Complex contrib, caux, caux1,caux2,caux3;
72
73
0
        ui=Complex(0.0,1.0);
74
75
        /*
76
         **********************************************************
77
         **   i0: initial integrated variance i0=0
78
         **********************************************************
79
         */
80
0
        Real i0=0.0;
81
        //s=2.0*chi*theta/(eps*eps)-1.0;
82
83
        //s=s+1;
84
85
        /*
86
         *************************************************
87
         ** Start integration procedure
88
         *************************************************
89
         */
90
91
0
        pi= 3.14159265358979324;
92
0
        pi2=2.0*pi;
93
0
        Real s=2.0*chi*theta/(eps*eps)-1.0;
94
        /*
95
         ****************************************
96
         ** Note that s must be greater than zero
97
         ****************************************
98
         */
99
100
0
        if(s<=0)
101
0
        {
102
0
            QL_FAIL("this parameter must be greater than zero-> " << s);
103
0
        }
104
105
0
        ss=s+1;
106
107
        /*
108
         *************************************************
109
         ** Start integration procedure
110
         *************************************************
111
112
         **************************************************************
113
         ** The oscillatory integral that approximates the price of
114
         ** the realized variance option is computed using the method
115
         ** proposed by Bailey, Swarztrauber in the paper published in
116
         ** Siam Journal on Scientific Computing Vol 15(5) 1994
117
         ** p. 1105-1110
118
         **************************************************************
119
120
         **************************************************************
121
         ** dstep: real number, generally a power of two, that must be
122
         **        assigned to determine the grid of
123
         **        integration. Hint: dstep=256 or 512 (dstep<=2048)
124
         **************************************************************
125
         */
126
0
        dstep=256.0;
127
0
        nris=std::sqrt(pi2)/dstep;
128
0
        mm=(int)(pi2/(nris*nris));
129
130
        /*
131
         ******************************************
132
         **  Definition of the integration grid  **
133
         ******************************************
134
         */
135
0
        for (j=0;j<=mm-1;j++)
136
0
        {
137
0
            xiv[j+1]=(j-mm/2.0)*nris;
138
0
        }
139
140
0
        for (j=0;j<=mm-1;j++)
141
0
        {
142
0
            xi=xiv[j+1];
143
0
            caux=chi*chi;
144
0
            caux1=2.0*eps*eps;
145
0
            caux1=caux1*xi;
146
0
            caux1=caux1*ui;
147
0
            caux2=caux1+caux;
148
149
0
            zita=0.5*std::sqrt(caux2);
150
151
0
            caux1=std::exp(-2.0*tau*zita);
152
153
0
            beta=0.5*chi+zita;
154
0
            beta=beta+caux1*(zita-0.5*chi);
155
0
            gamma=1.0-caux1;
156
157
0
            caux=-ss*tau;
158
0
            caux2=caux*(zita-0.5*chi);
159
0
            caux=ss*std::log(2.0*(zita/beta));
160
0
            caux3=-v0*ui*xi*(gamma/beta);
161
0
            caux=caux+caux3;
162
0
            caux=caux+caux2;
163
164
0
            ff[j+1]=std::exp(caux);
165
0
            if(std::sqrt(std::imag(xi)*std::imag(xi)+std::real(xi)*std::real(xi))>1.e-06)
166
0
            {
167
0
                contrib=-eprice/(ui*xi);
168
0
                caux=ui*xi;
169
0
                caux=caux*eprice;
170
0
                caux=std::exp(caux);
171
0
                caux=caux-1.0;
172
0
                caux2=ui*xi*ui*xi;
173
0
                contrib=contrib+caux/caux2;
174
0
            }
175
0
            else
176
0
            {
177
0
                contrib=eprice*eprice*0.5;
178
0
            }
179
0
            ff[j+1]=ff[j+1]*contrib;
180
0
        }
181
0
        csum=0.0;
182
0
        for (j=0;j<=mm-1;j++)
183
0
        {
184
0
            caux=std::pow(-1.0,j);
185
0
            caux2=-2.0*pi*(double)mm*(double)j*0.5/(double)mm;
186
0
            caux3=ui*caux2;
187
0
            csum=csum+ff[j+1]*caux*std::exp(caux3);
188
0
        }
189
0
        csum=csum*std::sqrt(std::pow(-1.0,mm))*nris/pi2;
190
0
        vero=i0-eprice+theta*tau+(1.0-std::exp(-chi*tau))*(v0-theta)/chi;
191
0
        csum=csum+vero;
192
0
        option=std::exp(-rtax*tau)*std::real(csum);
193
0
        impart=std::imag(csum);
194
0
        QL_ENSURE(impart <= 1e-12,
195
0
                  "imaginary part option (must be zero) = " << impart);
196
0
        return option;
197
0
    }
198
199
200
201
    Real IvopTwoDim(Real eps, Real chi, Real theta, Real /*rho*/,
202
                    Real v0, Time tau, Real rtax,
203
0
                    const std::function<Real(Real)>& payoff) {
204
205
0
        Real ss=0.0;
206
0
        std::unique_ptr<double[]> xiv(new double[2048*2048+1]);
207
0
        std::unique_ptr<double[]> ivet(new double[2048 * 2048 + 1]);
208
0
        double nris=0.0;
209
0
        int j=0,mm=0,k=0;
210
0
        double pi=0,pi2=0;
211
212
0
        double dstep=0;
213
0
        Real ip=0;
214
0
        Real payoffval=0;
215
0
        Real option=0/*, impart=0*/;
216
217
0
        Real sumr=0;//,sumi=0;
218
0
        Complex dxi,z;
219
220
0
        std::unique_ptr<Complex[]> ff(new Complex[2048*2048]);
221
0
        Complex xi;
222
0
        Complex ui,beta,zita,gamma,csum;
223
0
        Complex caux,caux1,caux2,caux3;
224
225
0
        ui=Complex(0.0,1.0);
226
227
        /*
228
         **********************************************************
229
         **   i0: initial integrated variance i0=0
230
         **********************************************************
231
         */
232
0
        Real i0=0.0;
233
234
        /*
235
         *************************************************
236
         ** Start integration procedure
237
         *************************************************
238
         */
239
240
0
        pi= 3.14159265358979324;
241
0
        pi2=2.0*pi;
242
243
0
        Real s=2.0*chi*theta/(eps*eps)-1.0;
244
        /*
245
         ****************************************
246
         ** Note that s must be greater than zero
247
         ****************************************
248
         */
249
250
0
        if(s<=0)
251
0
        {
252
0
            QL_FAIL("this parameter must be greater than zero-> " << s);
253
0
        }
254
255
0
        ss=s+1;
256
257
        /*
258
         *************************************************
259
         ** Start integration procedure
260
         *************************************************
261
262
         **************************************************************
263
         ** The oscillatory integral that approximates the price of
264
         ** the realized variance option is computed using the method
265
         ** proposed by Bailey, Swarztrauber in the paper published in
266
         ** Siam Journal on Scientific Computing Vol 15(5) 1994
267
         ** p. 1105-1110
268
         **************************************************************
269
270
         **************************************************************
271
         ** dstep: real number, generally a power of two that must be
272
         **        assigned to determine the grid of
273
         **        integration. Hint: dstep=256 or 512 (dstep<=2048)
274
         **************************************************************
275
         */
276
0
        dstep=64.0;
277
0
        nris=std::sqrt(pi2)/dstep;
278
0
        mm=(int)(pi2/(nris*nris));
279
280
        /*
281
         ******************************************
282
         **  Definition of the integration grid  **
283
         ******************************************
284
         */
285
286
0
        for (j=0;j<=mm-1;j++)
287
0
        {
288
0
            xiv[j+1]=(j-mm/2.0)*nris;
289
0
            ivet[j+1]=(j-mm/2.0)*pi2/((double)mm*nris);
290
0
        }
291
292
0
        for (j=0;j<=mm-1;j++)
293
0
        {
294
0
            xi=xiv[j+1];
295
296
0
            caux=chi*chi;
297
0
            caux1=2.0*eps*eps;
298
0
            caux1=caux1*xi;
299
0
            caux1=caux1*ui;
300
0
            caux2=caux1+caux;
301
302
0
            zita=0.5*std::sqrt(caux2);
303
0
            caux1=std::exp(-2.0*tau*zita);
304
305
0
            beta=0.5*chi+zita;
306
0
            beta=beta+caux1*(zita-0.5*chi);
307
308
0
            gamma=1.0-caux1;
309
310
0
            caux=-ss*tau;
311
0
            caux2=caux*(zita-0.5*chi);
312
0
            caux=ss*std::log(2.0*(zita/beta));
313
0
            caux3=-v0*ui*xi*(gamma/beta);
314
0
            caux=caux+caux3;
315
0
            caux=caux+caux2;
316
0
            ff[j+1]=std::exp(caux);
317
0
        }
318
319
0
        sumr=0.0;
320
        //sumi=0.0;
321
0
        for (k=0;k<=mm-1;k++)
322
0
        {
323
0
            ip=i0-ivet[k+1];
324
0
            payoffval=payoff(ip);
325
326
0
            dxi=2.0*pi*(double)k/(double)mm*ui;
327
0
            csum=0.0;
328
0
            for (j=0;j<=mm-1;j++)
329
0
            {
330
0
                z=-(double)j*dxi;
331
0
                caux=std::pow(-1.0,j);
332
0
                csum=csum+ff[j+1]*caux*std::exp(z);
333
0
            }
334
0
            csum=csum*std::pow(-1.0,k)*nris/pi2;
335
336
0
            sumr=sumr+payoffval*std::real(csum);
337
            //sumi=sumi+payoffval*std::imag(csum);
338
0
        }
339
0
        sumr=sumr*nris;
340
        //sumi=sumi*nris;
341
342
0
        option=std::exp(-rtax*tau)*sumr;
343
        //impart=sumi;
344
        //QL_ENSURE(impart <= 1e-3,
345
        //          "imaginary part option (must be close to zero) = " << impart);
346
0
        return option;
347
0
    }
348
349
    struct payoff_adapter {
350
        ext::shared_ptr<QuantLib::Payoff> payoff;
351
        explicit payoff_adapter(ext::shared_ptr<QuantLib::Payoff> payoff)
352
0
        : payoff(std::move(payoff)) {}
353
0
        Real operator()(Real S) const {
354
0
            return (*payoff)(S);
355
0
        }
356
    };
357
358
    }
359
360
    IntegralHestonVarianceOptionEngine::IntegralHestonVarianceOptionEngine(
361
        ext::shared_ptr<HestonProcess> process)
362
0
    : process_(std::move(process)) {
363
0
        registerWith(process_);
364
0
    }
365
366
0
    void IntegralHestonVarianceOptionEngine::calculate() const {
367
368
0
        QL_REQUIRE(process_->dividendYield().empty(),
369
0
                   "this engine does not manage dividend yields");
370
371
0
        Handle<YieldTermStructure> riskFreeRate = process_->riskFreeRate();
372
373
0
        Real epsilon = process_->sigma();
374
0
        Real chi = process_->kappa();
375
0
        Real theta = process_->theta();
376
0
        Real rho = process_->rho();
377
0
        Real v0 = process_->v0();
378
379
0
        Time tau = riskFreeRate->dayCounter().yearFraction(
380
0
                                        Settings::instance().evaluationDate(),
381
0
                                        arguments_.maturityDate);
382
0
        Rate r = riskFreeRate->zeroRate(arguments_.maturityDate,
383
0
                                        riskFreeRate->dayCounter(),
384
0
                                        Continuous);
385
386
0
        ext::shared_ptr<PlainVanillaPayoff> plainPayoff =
387
0
            ext::dynamic_pointer_cast<PlainVanillaPayoff>(arguments_.payoff);
388
0
        if ((plainPayoff != nullptr) && plainPayoff->optionType() == Option::Call) {
389
            // a specialization for Call options is available
390
0
            Real strike = plainPayoff->strike();
391
0
            results_.value = IvopOneDim(epsilon, chi, theta, rho,
392
0
                                        v0, strike, tau, r)
393
0
                * arguments_.notional;
394
0
        } else {
395
0
            results_.value = IvopTwoDim(epsilon, chi, theta, rho, v0, tau, r,
396
0
                                        payoff_adapter(arguments_.payoff))
397
0
                * arguments_.notional;
398
0
        }
399
0
    }
400
401
}
402