Heston Model and Option Pricing#
A very important example of time-changed Lévy process useful for option pricing is the Heston model. In this model, the Lévy process is a standard Brownian motion, while the activity rate follows a CIR process. The leverage effect can be accommodated by correlating the two Brownian motions as the following equations illustrate:
This means that the characteristic function of \(y_t=x_{\tau_t}\) can be represented as
from quantflow.sp.heston import Heston
pr = Heston.create(vol=0.6, kappa=2, sigma=1.5, rho=-0.1)
pr
Heston(variance_process=CIR(rate=0.36, kappa=2.0, sigma=1.5, theta=0.36, sample_algo=<SamplingAlgorithm.implicit: 'implicit'>), rho=-0.1)
# check that the variance CIR process is positive
pr.variance_process.is_positive, pr.variance_process.marginal(1).std()
(False, np.float64(0.44585993666737017))
Characteristic Function#
from quantflow.utils import plot
m = pr.marginal(0.1)
plot.plot_characteristic(m)
The immaginary part of the characteristic function is given by the correlation coefficient.
Marginal Distribution#
Here we compare the marginal distribution at a time in the future \(t=1\) with a normal distribution with the same standard deviation.
plot.plot_marginal_pdf(m, 128, normal=True, analytical=False)
Using log scale on the y axis highlighs the probability on the tails much better
plot.plot_marginal_pdf(m, 128, normal=True, analytical=False, log_y=True)
Option pricing#
from quantflow.options.pricer import OptionPricer
from quantflow.sp.heston import Heston
pricer = OptionPricer(Heston.create(vol=0.6, kappa=2, sigma=0.8, rho=-0.2))
pricer
OptionPricer(model=Heston(variance_process=CIR(rate=0.36, kappa=2.0, sigma=0.8, theta=0.36, sample_algo=<SamplingAlgorithm.implicit: 'implicit'>), rho=-0.2), n=128, max_moneyness_ttm=1.5)
import plotly.express as px
import plotly.graph_objects as go
from quantflow.options.bs import black_call
r = pricer.maturity(0.1)
b = r.black()
fig = px.line(x=r.moneyness_ttm, y=r.time_value, markers=True, title=r.name)
fig.add_trace(go.Scatter(x=r.moneyness_ttm, y=b.time_value, name=b.name, line=dict()))
fig.show()
fig = None
for ttm in (0.05, 0.1, 0.2, 0.4, 0.6, 1):
fig = pricer.maturity(ttm).plot(fig=fig, name=f"t={ttm}")
fig.update_layout(title="Implied black vols", height=500)
Simulation#
The simulation of the Heston model is heavily dependent on the simulation of the activity rate, mainly how the behavior near zero is handled.
The code implements algorithms from {cite:p}heston-simulation
from quantflow.sp.heston import Heston
pr = Heston.create(vol=0.6, kappa=2, sigma=0.8, rho=-0.4)
pr
Heston(variance_process=CIR(rate=0.36, kappa=2.0, sigma=0.8, theta=0.36, sample_algo=<SamplingAlgorithm.implicit: 'implicit'>), rho=-0.4)
pr.sample(20, time_horizon=1, time_steps=1000).plot().update_traces(line_width=0.5)
import pandas as pd
from quantflow.utils import plot
paths = pr.sample(1000, time_horizon=1, time_steps=1000)
mean = dict(mean=pr.marginal(paths.time).mean(), simulated=paths.mean())
df = pd.DataFrame(mean, index=paths.time)
plot.plot_lines(df)
std = dict(std=pr.marginal(paths.time).std(), simulated=paths.std())
df = pd.DataFrame(std, index=paths.time)
plot.plot_lines(df)