QuantLib: a free/open-source library for quantitative finance
fully annotated source code - version 1.34
Loading...
Searching...
No Matches
analytic_discr_geom_av_price_heston.cpp
Go to the documentation of this file.
1/* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2
3/*
4 Copyright (C) 2020, 2021 Jack Gillett
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
21#include <utility>
22
23namespace QuantLib {
24
25 // A class to perform the integrations in Eqs (23) and (24)
26 class AnalyticDiscreteGeometricAveragePriceAsianHestonEngine::Integrand {
27 private:
28 Real t_, T_, K_, logK_;
29 Size kStar_;
30 const std::vector<Time> t_n_, tauK_;
31 const AnalyticDiscreteGeometricAveragePriceAsianHestonEngine* const parent_;
32 Real xiRightLimit_;
33 std::complex<Real> i_;
34
35 public:
36 Integrand(Real t,
37 Real T,
38 Size kStar,
39 std::vector<Time> t_n,
40 std::vector<Time> tauK,
41 Real K,
42 const AnalyticDiscreteGeometricAveragePriceAsianHestonEngine* const parent,
43 Real xiRightLimit)
44 : t_(t), T_(T), K_(K), logK_(std::log(K)), kStar_(kStar), t_n_(std::move(t_n)),
45 tauK_(std::move(tauK)), parent_(parent), xiRightLimit_(xiRightLimit),
46 i_(std::complex<Real>(0.0, 1.0)) {}
47
48 Real operator()(Real xi) const {
49 Real xiDash = (0.5+1e-8+0.5*xi) * xiRightLimit_; // Map xi to full range
50
51 std::complex<Real> inner1 = parent_->Phi(1.0 + xiDash*i_, 0, t_, T_, kStar_, t_n_, tauK_);
52 std::complex<Real> inner2 = -K_*parent_->Phi(xiDash*i_, 0, t_, T_, kStar_, t_n_, tauK_);
53
54 return 0.5*xiRightLimit_*std::real((inner1 + inner2) * std::exp(-xiDash*logK_*i_) / (xiDash*i_));
55 }
56 };
57
58
61 ext::shared_ptr<HestonProcess> process, Real xiRightLimit)
62 : process_(std::move(process)), xiRightLimit_(xiRightLimit), integrator_(128) {
64
65 v0_ = process_->v0();
66 rho_ = process_->rho();
67 kappa_ = process_->kappa();
68 theta_ = process_->theta();
69 sigma_ = process_->sigma();
70 s0_ = process_->s0();
71 logS0_ = std::log(s0_->value());
72
73 riskFreeRate_ = process_->riskFreeRate();
74 dividendYield_ = process_->dividendYield();
75 }
76
78 const std::complex<Real>& z1,
79 const std::complex<Real>& z2,
80 Time tau) const {
81 std::complex<Real> temp = std::sqrt(kappa_*kappa_-2.0*z1*sigma_*sigma_);
82 if (std::abs(kappa_*kappa_-2.0*sigma_*sigma_) < 1e-8) {
83 return 1.0 + 0.5*(kappa_-z2*sigma_*sigma_);
84 } else {
85 return cosh(0.5*tau*temp) + (kappa_-z2*sigma_*sigma_)*sinh(0.5*tau*temp)/temp;
86 }
87 }
88
90 const std::complex<Real>& z1,
91 const std::complex<Real>& z2,
92 Time tau) const {
93 std::complex<Real> temp = std::sqrt(kappa_*kappa_ - 2.0*z1*sigma_*sigma_);
94 return 0.5*temp*sinh(0.5*tau*temp) + 0.5*(kappa_ - z2*sigma_*sigma_)*cosh(0.5*tau*temp);
95 }
96
98 const std::complex<Real>& s, const std::complex<Real>& w, Size k, Size n) const {
99 auto k_ = Real(k);
100 auto n_ = Real(n);
101 std::complex<Real> term1 = (2*rho_*kappa_ - sigma_)*((n_-k_+1)*s + n_*w)/(2*sigma_*n_);
102 std::complex<Real> term2 = (1-rho_*rho_)*pow(((n_-k_+1)*s + n_*w), 2)/(2*n_*n_);
103
104 return term1 + term2;
105 }
106
108 const std::complex<Real>& s, const std::complex<Real>& w, Size k, Size kStar, Size n) const {
109 if (k==kStar) {
110 return 0;
111 } else if (k==n+1) {
112 return rho_*w/sigma_;
113 } else {
114 return rho_*s/(sigma_*n);
115 }
116 }
117
119 const std::complex<Real>& s,
120 const std::complex<Real>& w,
121 Time t, Time T, Size kStar,
122 const std::vector<Time>& t_n) const {
123 auto kStar_ = Real(kStar);
124 auto n_ = Real(t_n.size());
125 Real temp = -rho_*kappa_*theta_/sigma_;
126
127 Time summation = 0.0;
128 Real summation2 = 0.0;
129 for (Size i=kStar+1; i<=t_n.size(); i++) {
130 summation += t_n[i-1];
131 summation2 += tkr_tk_[i-1];
132 }
133 // This is Eq (16) modified for non-constant rates
134 std::complex<Real> term1 = (s*(n_-kStar_)/n_ + w)*(logS0_ - rho_*v0_/sigma_ - t*temp - tr_t_);
135 std::complex<Real> term2 = temp*(s*summation/n_ + w*T) + w*Tr_T_ + summation2*s/n_;
136
137 return term1 + term2;
138 }
139
141 const std::complex<Real>& s,
142 const std::complex<Real>& w,
143 Size k, Size kStar, Size n,
144 const std::vector<Time>& tauK) const {
145 std::complex<Real> omega_k = omega(s, w, k, kStar, n);
146 if (k==n+1) {
147 return omega_k;
148 } else {
149 Time dTauk = tauK[k+1] - tauK[k];
150 std::complex<Real> z_kp1 = z(s, w, k+1, n);
151
152 // omega_tilde calls itself recursivly, use lookup map to avoid extreme slowdown when k large
153 std::complex<Real> omega_kp1 = 0.0;
154
155 std::map<Size, std::complex<Real> >::const_iterator position = omegaTildeLookupTable_.find(k+1);
156
157 if (position != omegaTildeLookupTable_.end()) {
158 std::complex<Real> value = position->second;
159 omega_kp1 = value;
160 } else {
161 omega_kp1 = omega_tilde(s, w, k+1, kStar, n, tauK);
162 }
163
164 std::complex<Real> ratio = F_tilde(z_kp1,omega_kp1,dTauk)/F(z_kp1,omega_kp1,dTauk);
165 std::complex<Real> result = omega_k + kappa_/pow(sigma_,2) - 2.0*ratio/pow(sigma_,2);
166
167 // Store this value in our mutable lookup map
168 omegaTildeLookupTable_[k] = result;
169
170 return result;
171 }
172 }
173
175 const std::complex<Real> s,
176 const std::complex<Real> w,
177 Time t, Time T, Size kStar,
178 const std::vector<Time>& t_n,
179 const std::vector<Time>& tauK) const {
180
181 // Clear the mutable lookup map before evaluating Phi
182 omegaTildeLookupTable_ = std::map<Size, std::complex<Real> >();
183
184 Size n = t_n.size();
185 std::complex<Real> aTerm = a(s, w, t, T, kStar, t_n);
186 std::complex<Real> omegaTerm = v0_*omega_tilde(s, w, kStar, kStar, n, tauK);
187 Real term3 = kappa_*kappa_*theta_*(T-t)/pow(sigma_,2);
188
189 std::complex<Real> summation = 0.0;
190 for (Size i=kStar+1; i<=n+1; i++) {
191 Real dTau = tauK[i] - tauK[i-1];
192 std::complex<Real> z_k = z(s, w, i, n);
193 std::complex<Real> omega_tilde_k = omega_tilde(s, w, i, kStar, n, tauK);
194
195 summation += std::log(F(z_k, omega_tilde_k, dTau));
196 }
197 std::complex<Real> term4 = 2*kappa_*theta_*summation/pow(sigma_,2);
198
199 return std::exp(aTerm + omegaTerm + term3 - term4);
200}
201
203 /* this engine cannot really check for the averageType==Geometric
204 since it can be used as control variate for the Arithmetic version
205 QL_REQUIRE(arguments_.averageType == Average::Geometric,
206 "not a geometric average option");
207 */
208 QL_REQUIRE(arguments_.exercise->type() == Exercise::European,
209 "not an European Option");
210
211 Real runningLog;
212 Size pastFixings;
215 "positive running product required: "
216 << arguments_.runningAccumulator << " not allowed");
217 runningLog = std::log(arguments_.runningAccumulator);
218 pastFixings = arguments_.pastFixings;
219 } else { // it is being used as control variate
220 runningLog = 0.0;
221 pastFixings = 0;
222 }
223
224 ext::shared_ptr<PlainVanillaPayoff> payoff =
225 ext::dynamic_pointer_cast<PlainVanillaPayoff>(arguments_.payoff);
226 QL_REQUIRE(payoff, "non-plain payoff given");
227
228 Real strike = payoff->strike();
229 Date exercise = arguments_.exercise->lastDate();
230
231 Time expiryTime = this->process_->time(exercise);
232 QL_REQUIRE(expiryTime >= 0.0, "Expiry Date cannot be in the past");
233
234 Real expiryDcf = riskFreeRate_->discount(expiryTime);
235
236 Time startTime = 0.0;
237 std::vector<Time> fixingTimes, tauK;
238 for (auto& fixingDate : arguments_.fixingDates) {
239 fixingTimes.push_back(this->process_->time(fixingDate));
240 }
241 std::sort(fixingTimes.begin(), fixingTimes.end());
242 tauK = fixingTimes;
243
244 // tauK is just a vector of the sorted future fixing times (ie. from the kStar element
245 // onwards), with t pushed on the front and T pushed on the back!
246 tauK.insert(tauK.begin(), startTime);
247 tauK.push_back(expiryTime);
248
249 // In the paper, seasoned asians are dealt with by letting the start time variable be greater
250 // than 0. We can achieve the same by fixing the start time to 0.0, but attaching 'dummy'
251 // fixing times at t=-1 for each past fixing, at the front of the fixing times arrays
252 for (Size i=0; i<pastFixings; i++) {
253 fixingTimes.insert(fixingTimes.begin(), -1.0);
254 tauK.insert(tauK.begin(), -1.0);
255 }
256
257 Size kStar = pastFixings;
258
259 // Need the log of some discount factors to calculate the r-adjusted a factor (Eq 16)
260 tr_t_ = 0;
261 Tr_T_ = 0;
262 tkr_tk_ = std::vector<Real>();
263 tr_t_ = -std::log(riskFreeRate_->discount(startTime) / dividendYield_->discount(startTime));
264 Tr_T_ = -std::log(riskFreeRate_->discount(expiryTime) / dividendYield_->discount(expiryTime));
265 for (Real fixingTime : fixingTimes) {
266 if (fixingTime < 0) {
267 tkr_tk_.push_back(1.0);
268 } else {
269 tkr_tk_.push_back(-std::log(riskFreeRate_->discount(fixingTime) /
270 dividendYield_->discount(fixingTime)));
271 }
272 }
273
274 // To account for seasoning, we need to calculate an 'adjusted' strike (Eq 6)
275 Real prefactor = std::exp(runningLog / fixingTimes.size());
276 Real adjustedStrike = strike / prefactor;
277
278 // Calculate the two terms in eq (23) - Phi(1,0) is real (asian forward) but need to type convert
279 Real term1 = 0.5 * (std::real(Phi(1,0, startTime, expiryTime, kStar, fixingTimes, tauK)) - adjustedStrike);
280
281 Integrand integrand(startTime, expiryTime, kStar, fixingTimes, tauK, adjustedStrike, this, xiRightLimit_);
282 Real term2 = integrator_(integrand) / M_PI;
283
284 // Apply the payoff functions
285 Real value = 0.0;
286 switch (payoff->optionType()){
287 case Option::Call:
288 value = expiryDcf * prefactor * (term1 + term2);
289 break;
290 case Option::Put:
291 value = expiryDcf * prefactor * (-term1 + term2);
292 break;
293 default:
294 QL_FAIL("unknown option type");
295 }
296
297 results_.value = value;
298
299 results_.additionalResults["dcf"] = expiryDcf;
300 results_.additionalResults["s0"] = s0_->value();
301 results_.additionalResults["strike"] = strike;
302 results_.additionalResults["expiryTime"] = expiryTime;
303 results_.additionalResults["term1"] = term1;
304 results_.additionalResults["term2"] = term2;
305 results_.additionalResults["xiRightLimit"] = xiRightLimit_;
306 results_.additionalResults["fixingTimes"] = fixingTimes;
307 results_.additionalResults["tauK"] = tauK;
308 results_.additionalResults["adjustedStrike"] = adjustedStrike;
309 results_.additionalResults["prefactor"] = prefactor;
310 results_.additionalResults["kStar"] = kStar;
311 }
312}
Analytic engine for discrete geometric average price Asian in the Heston model.
std::complex< Real > F_tilde(const std::complex< Real > &z1, const std::complex< Real > &z2, Time tau) const
std::complex< Real > omega(const std::complex< Real > &s, const std::complex< Real > &w, Size k, Size kStar, Size n) const
std::complex< Real > z(const std::complex< Real > &s, const std::complex< Real > &w, Size k, Size n) const
AnalyticDiscreteGeometricAveragePriceAsianHestonEngine(ext::shared_ptr< HestonProcess > process, Real xiRightLimit=100.0)
std::complex< Real > Phi(std::complex< Real > s, std::complex< Real > w, Time t, Time T, Size kStar, const std::vector< Time > &t_n, const std::vector< Time > &tauK) const
std::complex< Real > a(const std::complex< Real > &s, const std::complex< Real > &w, Time t, Time T, Size kStar, const std::vector< Time > &t_n) const
std::complex< Real > F(const std::complex< Real > &z1, const std::complex< Real > &z2, Time tau) const
std::complex< Real > omega_tilde(const std::complex< Real > &s, const std::complex< Real > &w, Size k, Size kStar, Size n, const std::vector< Time > &tauK) const
Concrete date class.
Definition: date.hpp:125
std::map< std::string, ext::any > additionalResults
Definition: instrument.hpp:123
std::pair< iterator, bool > registerWith(const ext::shared_ptr< Observable > &)
Definition: observable.hpp:228
Size k_
const DefaultType & t
#define QL_REQUIRE(condition, message)
throw an error if the given pre-condition is not verified
Definition: errors.hpp:117
#define QL_FAIL(message)
throw an error (possibly with file and line information)
Definition: errors.hpp:92
Real Time
continuous quantity with 1-year units
Definition: types.hpp:62
QL_REAL Real
real number
Definition: types.hpp:50
std::size_t Size
size of a container
Definition: types.hpp:58
ext::shared_ptr< QuantLib::Payoff > payoff
#define M_PI
Definition: any.hpp:35
STL namespace.
Real F
Definition: sabr.cpp:200