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;
}
}
|