/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 | | |