Cox–Ingersoll–Ross
rl.SwitchingCoxIngersollRoss(chain, r0, theta, k, sigma) is QuantLib’s CoxIngersollRoss(r0, theta, k, sigma)
with the mean level theta allowed to switch.
The model
Reduction
With \(u_i = e^{-B(t)r}a_i(t)\) the terms proportional to \(r\) cancel when \(B\) solves the Riccati equation
The equation contains \(k\) and \(\sigma\) and not \(\theta\), which is why only the level may switch exactly. What remains is
Closed form
The forcing is a multiple of \(B\), so only \(I_1 = \int_0^TB\) and \(I_2 = \int_0^TB^2\) are needed. The first is the logarithm in the classical CIR bond, and the Riccati equation gives the second with no further integration:
With \(\bar\theta, \tilde\theta = \tfrac12(\theta_1 \pm \theta_2)\),
Python
r0, k, theta, sigma, lam, T = 0.03, 0.5, [0.06, 0.02], 0.10, 8.0, 5.0
h = np.sqrt(k * k + 2 * sigma ** 2)
eh = np.exp(h * T)
B = 2 * (eh - 1) / ((h + k) * (eh - 1) + 2 * h)
I1 = -2 / sigma ** 2 * np.log(2 * h * np.exp((h + k) * T / 2) / ((h + k) * (eh - 1) + 2 * h))
I2 = 2 / sigma ** 2 * (T - k * I1 - B)
thbar, tht = half(theta)
closed = np.exp(-B * r0) * second_order(
int_gbar=-k * thbar * I1,
int_gt2=k * k * tht * tht * I2,
gt_T=-k * tht * B,
gt_0=0.0,
dgt_T=-k * tht * (1 - k * B - sigma ** 2 * B * B / 2),
lam=lam)
model = rl.SwitchingCoxIngersollRoss(rl.RegimeChain.twoState(lam, lam), r0, theta, k, sigma)
print(f"closed form, second order {closed:.10f}")
print(f"library, numerical {library_bond(model, T):.10f}")
closed form, second order 0.8343376435
library, numerical 0.8343377124
regimelib.symbolic.CIRBondFirstOrder gives the first-order formula for any finite chain as a sympy expression
(Symbolic). A switching volatility or reversion speed enters the Riccati equation and falls
outside the exact reduction.