CEV

rl.SwitchingCEVProcess(chain, S0, r, q, sigma, beta) is QuantLib’s CEV process with the volatility scale sigma allowed to switch. The library prices it on a grid with FirstOrderFDEngine and SwitchingFDReferee.

The model

\[dS_t = (r - q)\,S_t\,dt + \sigma_{y_t}\,S_t^{\beta}\,dW_t, \qquad 0 < \beta < 1 .\]

Reduction

The model is not affine and there is no exponential-affine solution. In the price variable the switched operator \(\tfrac12S^{2\beta}\partial_{SS}\) does not commute with the drift, but the commutator is a multiple of the operator itself,

\[\big[\,S\,\partial_S,\ S^{2\beta}\partial_{SS}\,\big] = 2(\beta - 1)\,S^{2\beta}\partial_{SS},\]

and the forward removes it. With \(F_t = S_te^{(r - q)(T - t)}\),

\[dF_t = \sigma_{y_t}\,\sqrt{h(T - t)}\;F_t^{\beta}\,dW_t, \qquad h(\tau) = e^{\kappa_h\tau}, \quad \kappa_h = 2(1 - \beta)(r - q).\]

The regime now only scales one fixed operator, so given the regime path \(F_T\) is the unit-scale CEV diffusion run for the total variance \(V\), and the price is a mixture:

\[u_i = e^{-rT}\,\mathbb{E}\big[C(F_0, V) \mid y_0 = i\big], \qquad V = \int_0^T\sigma_{y_t}^2\,h(T - t)\,dt,\]
\[C(F, v) = F\,\big[1 - \chi^2(x;\ d + 2,\ y)\big] - K\,\chi^2(y;\ d,\ x), \qquad x = \frac{K^{2(1 - \beta)}}{(1 - \beta)^2v}, \quad y = \frac{F^{2(1 - \beta)}}{(1 - \beta)^2v}, \quad d = \frac{1}{1 - \beta},\]

with \(\chi^2(\cdot\,; d, \nu)\) the noncentral chi-square distribution function. This is exact for any chain. The law of \(V\) comes from a reduced system of the usual kind: in time to maturity, \(\mathbb{E}[e^{-zV}] = a_i(T)\) with \(g_i(\tau) = -z\,\sigma_i^2\,h(\tau)\).

Closed form

Expanding the transform with the common formula and replacing each power of \(z\) by a derivative of \(C\) in its variance argument gives, with \(\bar s, \tilde s = \tfrac12(\sigma_1^2 \pm \sigma_2^2)\),

\[u_{1,2} = e^{-rT}\Big[C + \frac{\varepsilon}{2}\tilde s^2H_2\,C_{vv} \pm \frac{\varepsilon}{2}\tilde s\,h_T\,C_v + \varepsilon^2\Big(\frac{\tilde s^4H_2^2}{8}C_{vvvv} \pm \frac{\tilde s^3H_2h_T}{4}C_{vvv} - \frac{\tilde s^2(h_T^2 + 1)}{8}C_{vv} \mp \frac{\tilde s\,\kappa_hh_T}{4}C_v\Big)\Big] + O(\varepsilon^3),\]

evaluated at \(F_0 = S_0e^{(r - q)T}\) and \(\bar v = \bar s\,H_1\), where

\[H_1 = \frac{e^{\kappa_hT} - 1}{\kappa_h}, \qquad H_2 = \frac{e^{2\kappa_hT} - 1}{2\kappa_h}, \qquad h_T = e^{\kappa_hT}.\]

The first-order terms read as on every page: half the variance of \(V\) times the convexity of the price in variance, and the shift of the mean of \(V\) towards the starting regime times the sensitivity to variance.

Python

from scipy.stats import ncx2
S0, r, q, beta, sigma, K, lam, T = 100.0, 0.02, 0.0, 0.6, [2.5, 1.2], 100.0, 10.0, 1.0
F = S0 * np.exp((r - q) * T)

def C(v):
    scale = (1 - beta) ** 2 * v
    x, y, d = K ** (2 * (1 - beta)) / scale, F ** (2 * (1 - beta)) / scale, 1 / (1 - beta)
    return F * (1 - ncx2.cdf(x, d + 2, y)) - K * ncx2.cdf(y, d, x)

kh = 2 * (1 - beta) * (r - q)
H1, H2, hT = (np.exp(kh * T) - 1) / kh, (np.exp(2 * kh * T) - 1) / (2 * kh), np.exp(kh * T)
sbar, st = half([s * s for s in sigma])
vbar, dv = sbar * H1, 1e-3 * sbar * H1
Cv = (C(vbar + dv) - C(vbar - dv)) / (2 * dv)
Cvv = (C(vbar + dv) - 2 * C(vbar) + C(vbar - dv)) / dv ** 2
eps, disc = 1 / lam, np.exp(-r * T)
averaged = disc * C(vbar)
green_kubo = disc * eps / 2 * st ** 2 * H2 * Cvv
memory = disc * eps / 2 * st * hT * Cv

model = rl.SwitchingCEVProcess(rl.RegimeChain.twoState(lam, lam), S0, r, q, sigma, beta)
option = rl.VanillaOption(("call", K), maturity=T)
engine = rl.FirstOrderFDEngine(model, regime=0, n=801)
option.setPricingEngine(engine); first = option.NPV()
option.setPricingEngine(rl.SwitchingFDReferee(model, regime=0, n=801)); referee = option.NPV()
print(f"                 closed form   library grid")
print(f"averaged         {averaged:11.6f}   {engine.averaged:11.6f}")
print(f"Green-Kubo term  {green_kubo:11.6f}   {engine.correction:11.6f}")
print(f"memory term      {memory:11.6f}   {engine.memory:11.6f}")
print(f"first order      {averaged + green_kubo + memory:11.6f}   {first:11.6f}")
print(f"switching price                {referee:11.6f}   (coupled equations on the grid)")
                 closed form   library grid
averaged           13.249063     13.248985
Green-Kubo term    -0.060274     -0.060275
memory term         0.191121      0.191122
first order        13.379911     13.379832
switching price                  13.383492   (coupled equations on the grid)

The grid engine applies the first-order rule in the price variable without the change of variable, and returns the same two terms.