Basics
Regime chains
- rl.RegimeChain(generator)
The hidden regime is a continuous-time Markov chain given by its generator matrix Q (rows sum to zero, off-diagonal
entries are the switching rates). Any number of regimes is allowed.
chain = rl.RegimeChain([[-5.0, 3.0, 2.0],
[4.0, -9.0, 5.0],
[1.0, 6.0, -7.0]])
chain.numberOfRegimes() # 3
chain.stationaryDistribution() # the long-run occupation of each regime
chain.meanHoldingTime() # n / -trace Q, the expansion parameter
- rl.RegimeChain.twoState(rate12, rate21)
The two-regime chain with switching rate rate12 out of regime 0 and rate21 out of regime 1.
chain = rl.RegimeChain.twoState(3.0, 5.0)
Per-regime parameters
A model parameter that may switch is given as a list with one value per regime, or as a scalar for the same value in every regime. The frozen limit — every regime equal — is the QuantLib model with those parameters, whatever the chain does; the certificates check each model against QuantLib in that limit.
rl.SwitchingVasicek(chain, r0=0.03, a=0.5, b=[0.06, 0.02], sigma=0.012) # b switches, sigma does not
Maturities and dates
Times are years. Wherever a maturity is expected, a QuantLib Date is also accepted and is measured from
ql.Settings.instance().evaluationDate with Actual/365 unless a dayCounter is given; a VanillaOption takes
its maturity from a QuantLib exercise object.
ql.Settings.instance().evaluationDate = ql.Date(1, 1, 2020)
bond = rl.ZeroCouponBond(ql.Date(1, 1, 2025))
option = rl.VanillaOption(ql.PlainVanillaPayoff(ql.Option.Put, 100.0),
ql.EuropeanExercise(ql.Date(1, 1, 2021)))
Instruments, engines and results
- instrument.setPricingEngine(engine)
- instrument.NPV()
- instrument.delta(), instrument.gamma(), instrument.theta(), instrument.vega(), instrument.rho()
As in QuantLib, an instrument holds its terms, an engine holds the model and the method, and NPV() triggers the
calculation. The greeks are computed by differentiating the pricing formula, never by bumping; a greek the engine
does not provide raises RuntimeError("... not provided by the engine"), as QuantLib’s “not provided” does.
Options under the characteristic-function engines:
delta,gamma,theta,rhoin the spot, andvegain the initial variance for stochastic-volatility models.Bonds under Vasicek and CIR:
deltaandgammain the short rate.Options on the finite-difference engine:
delta,gamma,thetafrom the grid.
option.setPricingEngine(rl.NumericalSwitchingEngine(model, regime=1))
option.NPV()
option.delta()
option.impliedVolatility() # the Black volatility reproducing the price
Regimes and the starting regime
Every engine takes regime, the regime in force today. The price of the same instrument differs by starting
regime; the difference is the memory of the regime, which the expansion carries in its initial-layer terms.
for regime in range(chain.numberOfRegimes()):
option.setPricingEngine(rl.FastSwitchingEngine(model, order=4, regime=regime))
print(regime, option.NPV())
Warnings and diagnostics
- class rl.ExpansionWarning
The expansion is asymptotic in the mean holding time times the forcing, so it can fail to converge: for a slow chain,
a large difference between regimes, or at high Fourier frequencies. FastSwitchingEngine raises an
ExpansionWarning when the last term kept is not smaller than the one before it, when it is more than a
thousandth of the value, or when nodes of the Fourier integral had to be solved numerically because the series
diverged there. instrument._result("diagnostics") returns the holding time, the order used, the relative size
of the last term and the count of numerical nodes.
import warnings
with warnings.catch_warnings(record=True) as caught:
warnings.simplefilter("always")
price = option.NPV()
for w in caught:
print(w.message)
option._result("diagnostics")