The Heston / Hull-White model is a Heston model, where the dynamic of the risk-free rate is governed by a Hull-White one-factor model:
with:
\begin{equation} <Z_t, W^1_t> = \rho_1 \ \ \ \ <Z_t, W^2_t> = \rho_2 \end{equation}The QuantLib open source library implements a finite-difference option pricer for the Heston / Hull-White model.
import numpy as np
from quantlib.settings import Settings
from quantlib.instruments.option import (
EuropeanExercise, VanillaOption)
from quantlib.instruments.payoffs import (
PlainVanillaPayoff, Put, Call, PAYOFF_TO_STR)
from quantlib.models.shortrate.onefactormodels.hullwhite import HullWhite
from quantlib.time.api import (today, Years, Actual365Fixed,
Period, May, Date,
NullCalendar)
from quantlib.processes.api import (BlackScholesMertonProcess,
HestonProcess,
HullWhiteProcess)
from quantlib.models.equity.heston_model import (
HestonModel)
from quantlib.termstructures.yields.api import ZeroCurve, FlatForward
from quantlib.termstructures.volatility.api import BlackConstantVol
from quantlib.pricingengines.api import (
AnalyticEuropeanEngine,
AnalyticBSMHullWhiteEngine,
AnalyticHestonEngine,
AnalyticHestonHullWhiteEngine,
FdHestonHullWhiteVanillaEngine)
from quantlib.quotes import SimpleQuote
from quantlib.methods.finitedifferences.solvers.fdmbackwardsolver import FdmSchemeDesc
We reproduce here the numerical experiment presented by Briani, Caramellino and Zanette #cite-Briani2015. The calculations are found in Table 1, p. 14.
The zero-coupon yield curve is flat ($4\%$), and so is the dividend yield curve ($3\%$).
dc = Actual365Fixed()
todays_date = today()
settings = Settings()
settings.evaluation_date = todays_date
# constant yield and div curves
dates = [todays_date + Period(i, Years) for i in range(3)]
rates = [0.04 for i in range(3)]
divRates = [0.03 for i in range(3)]
r_ts = ZeroCurve(dates, rates, dc)
q_ts = ZeroCurve(dates, divRates, dc)
s0 = SimpleQuote(100)
# Heston model
v0 = .1
kappa_v = 2
theta_v = 0.1
sigma_v = 0.3
rho_sv = -0.5
hestonProcess = HestonProcess(
risk_free_rate_ts=r_ts,
dividend_ts=q_ts,
s0=s0,
v0=v0,
kappa=kappa_v,
theta=theta_v,
sigma=sigma_v,
rho=rho_sv)
hestonModel = HestonModel(hestonProcess)
# Hull-White model
kappa_r = 1
sigma_r = .2
hullWhiteProcess = HullWhiteProcess(r_ts, a=kappa_r, sigma=sigma_r)
We define a European option, maturity 1 year and strike $K=100$.
strike = 100
maturity = 1
type = Call
maturity_date = todays_date + Period(maturity, Years)
exercise = EuropeanExercise(maturity_date)
payoff = PlainVanillaPayoff(type, strike)
option = VanillaOption(payoff, exercise)
The finite difference scheme involves a four-dimensional grid. The discretization is specified by the following parameters that define the mesh along each dimension:
def price_cal(rho, tGrid, xGrid=100, vGrid=40, rGrid=20):
fd_hestonHwEngine = FdHestonHullWhiteVanillaEngine(
hestonModel,
hullWhiteProcess,
rho,
tGrid, xGrid, vGrid, rGrid, 0, True, FdmSchemeDesc.Hundsdorfer())
option.set_pricing_engine(fd_hestonHwEngine)
return option.npv
To reproduce Table 1 of #cite-Briani2015, we vary the stock-rate correlation from $-0.5$ to $0.5$. The resulting prices, for various time discretization levels, are displayed below. They are in very good agreement with the published results.
calc_price = []
rho = [-0.5]*4 + [0]*4 + [0.5]*4
tGrid = [50, 100, 150, 200] * 3
calc_price = [price_cal(r, t) for (r, t) in zip(rho, tGrid)]
expected_price = [11.38, ] * 4 + [12.81, ] * 4 + [14.08, ] * 4
print(expected_price)
[11.38, 11.38, 11.38, 11.38, 12.81, 12.81, 12.81, 12.81, 14.08, 14.08, 14.08, 14.08]
from tabulate import tabulate
table = {"Rho":rho, "Time Grid":tGrid, "Computed Price":calc_price,
"Published (AMC2)": expected_price}
print tabulate(table, headers='keys', floatfmt='.3f')
Computed Price Published (AMC2) Time Grid Rho ---------------- ------------------ ----------- ------ 11.390 11.380 50 -0.500 11.390 11.380 100 -0.500 11.390 11.380 150 -0.500 11.390 11.380 200 -0.500 12.810 12.810 50 0.000 12.810 12.810 100 0.000 12.810 12.810 150 0.000 12.810 12.810 200 0.000 14.081 14.080 50 0.500 14.082 14.080 100 0.500 14.082 14.080 150 0.500 14.082 14.080 200 0.500