Heston with a switching volatility of variance

rl.SwitchingHestonVolOfVol(chain, S0, r, q, v0, kappa, theta, xi, rho): Heston with the volatility of variance xi switching. This is the one model on these pages with no reduced system.

The model

\[dX_t = (r - q - \tfrac12v_t)\,dt + \sqrt{v_t}\,dW_t, \qquad dv_t = \kappa\,(\theta - v_t)\,dt + \xi_{y_t}\sqrt{v_t}\,dZ_t, \qquad d\langle W, Z\rangle = \rho\,dt .\]

Why the reduction fails

The volatility of variance enters the Riccati equation for \(D\), so each regime would need its own \(D\) and the form \(e^{D(t)v}a_i(t)\) cannot hold. In operator terms the generator is

\[L_i = \bar L + \big(\xi_i^2 - \overline{\xi^2}\big)\,A_1 + \big(\xi_i - \bar\xi\big)\,A_2, \qquad A_1 = \tfrac12v\,\partial_{vv}, \quad A_2 = \rho\,v\,\partial_{xv},\]

and \(A_1\), \(A_2\) do not commute with the mean reversion in \(\bar L\).

The first-order rule

The expansion still applies to the operator-valued system. With \(f_1 = \xi^2\), \(f_2 = \xi\) the switched coefficients, the Green–Kubo matrix and the memory vector of the chain are

\[K_{jk} = \int_0^\infty\operatorname{Cov}\big(f_j(y_0), f_k(y_t)\big)\,dt = -\pi\cdot\big(\tilde f_j\odot Q^\#\tilde f_k\big), \qquad m_j(i) = \big(Q^\#\tilde f_j\big)_i,\]

with \(Q^\#\) the group inverse of the generator, and the price to first order is

\[u_i \approx e^{T\bar L}u_0 + \int_0^Te^{(T - s)\bar L}\Big(\sum_{jk}K_{jk}A_jA_k\Big)e^{s\bar L}u_0\,ds - \sum_jm_j(i)\,A_j\,e^{T\bar L}u_0 .\]

The three terms are the averaged model, the Green–Kubo correction applied through Duhamel’s formula, and the memory of the starting regime. For a symmetric two-state chain \(K_{jk} = \tilde f_j\tilde f_k/(2\lambda)\) and \(m_j = \mp\tilde f_j/(2\lambda)\). The library evaluates the three terms on a grid in \((\log S, v)\) and estimates the neglected second-order term as the square of the relative correction.

Python

S0, r, q, v0, kappa, theta, xi, rho, K, lam, T = 100.0, 0.03, 0.0, 0.04, 2.0, 0.04, [0.8, 0.2], -0.6, 100.0, 6.0, 1.0

model = rl.SwitchingHestonVolOfVol(rl.RegimeChain.twoState(lam, lam), S0, r, q, v0, kappa, theta, xi, rho)
option = rl.VanillaOption(("call", K), maturity=T)
engine = rl.FirstOrderFDEngine(model, regime=0, n=(151, 61))
option.setPricingEngine(engine); first = option.NPV()
option.setPricingEngine(rl.SwitchingFDReferee(model, regime=0, n=(151, 61))); referee = option.NPV()
print(f"averaged model     {engine.averaged:.5f}")
print(f"Green-Kubo term    {engine.correction:+.5f}")
print(f"memory term        {engine.memory:+.5f}")
print(f"first order        {first:.5f}")
print(f"switching price    {referee:.5f}   (coupled equations on the same grid)")
averaged model     8.80811
Green-Kubo term    +0.02320
memory term        -0.06697
first order        8.76435
switching price    8.75952   (coupled equations on the same grid)