Calibration#
Early pointers
https://github.com/rlabbe/filterpy
Calibrating ABC#
For calibration we use [Mer14]. Lets consider the Heston model as a test case
from quantflow.sp.heston import Heston
pr = Heston.create(vol=0.6, kappa=1.3, sigma=0.8, rho=-0.6)
pr.variance_process.is_positive
True
The Heston model is a classical example where the calibration of parameters requires to deal with the estimation of an unobserved random variable, the stochastic variance. The model can be discretized as follow:
noting that
which leads to
and finally
Our problem is to find the best estimate of \(\nu_t\) given by ths equation based on the observations \(s_t\).
The Heston model is a dynamic model which can be represented by a state-space form: \(X_t\) is the state while \(Z_t\) is the observable
\(f\) is the state transition equation while \(h\) is the measurement equation.
the state equation is given by
[p for p in pr.variance_process.parameters]
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
Cell In[2], line 1
----> 1 [p for p in pr.variance_process.parameters]
File ~/.cache/pypoetry/virtualenvs/quantflow-lUXkzsy2-py3.12/lib/python3.12/site-packages/pydantic/main.py:856, in BaseModel.__getattr__(self, item)
853 return super().__getattribute__(item) # Raises AttributeError if appropriate
854 else:
855 # this is the current error
--> 856 raise AttributeError(f'{type(self).__name__!r} object has no attribute {item!r}')
AttributeError: 'CIR' object has no attribute 'parameters'
Calibration against historical timeseries#
We calibrate the Heston model agais historical time series, in this case the measurement is the log change for a given frequency.
The observation vector is given by
from quantflow.data.fmp import FMP
frequency = "1min"
async with FMP() as cli:
df = await cli.prices("ETHUSD", frequency)
df = df.sort_values("date").reset_index(drop=True)
df
import plotly.express as px
fig = px.line(df, x="date", y="close", markers=True)
fig.show()
import numpy as np
from quantflow.utils.volatility import parkinson_estimator, GarchEstimator
df["returns"] = np.log(df["close"]) - np.log(df["open"])
df["pk"] = parkinson_estimator(df["high"], df["low"])
ds = df.dropna()
dt = cli.historical_frequencies_annulaized()[frequency]
fig = px.line(ds["returns"], markers=True)
fig.show()
import plotly.express as px
from quantflow.utils.bins import pdf
df = pdf(ds["returns"], num=20)
fig = px.bar(df, x="x", y="f")
fig.show()
g1 = GarchEstimator.returns(ds["returns"], dt)
g2 = GarchEstimator.pk(ds["returns"], ds["pk"], dt)
import pandas as pd
yf = pd.DataFrame(dict(returns=g2.y2, pk=g2.p))
fig = px.line(yf, markers=True)
fig.show()
r1 = g1.fit()
r1
r2 = g2.fit()
r2
sig2 = pd.DataFrame(dict(returns=np.sqrt(g2.filter(r1["params"])), pk=np.sqrt(g2.filter(r2["params"]))))
fig = px.line(sig2, markers=False, title="Stochastic volatility")
fig.show()
class HestonCalibration:
def __init__(self, dt: float, initial_std = 0.5):
self.dt = dt
self.kappa = 1
self.theta = initial_std*initial_std
self.sigma = 0.2
self.x0 = np.array((self.theta, 0))
def prediction(self, x):
return np.array((x[0] + self.kappa*(self.theta - x[0])*self.dt, -0.5*x[0]*self.dt))
def state_jacobian(self):
"""THe Jacobian of the state equation"""
return np.array(((1-self.kappa*self.dt, 0),(-0.5*self.dt, 0)))
c = HestonCalibration(dt)
c.x0
c.prediction(c.x0)
c.state_jacobian()