financialkit.interest_rate_models#

Equilibrium short rates: Merton’s Gaussian rate, Vasicek and Cox-Ingersoll-Ross with Feller’s condition, and Duffie-Kan affine models priced through their Riccati ODEs; curve-fitted models: Ho-Lee, Hull-White, the two-factor G2++ and the Black-Derman-Toy tree, all taking a financialkit.fixed_income yield curve; Feynman-Kac bond prices by Monte Carlo through financialkit.integrators; Jamshidian’s bond options, coupon bond options and swaptions; caps, floors and swaptions by Black’s formula (financialkit.options.black76_price()); the futures convexity adjustment; and the Heath-Jarrow-Morton drift condition with forward-curve simulation.

financialkit.interest_rate_models: short-rate models, curve-fitted models, HJM and rate options.

Equilibrium short rates: Merton’s arithmetic Brownian rate, Vasicek and Cox-Ingersoll-Ross (with Feller’s condition and exact noncentral chi-square sampling), and Duffie-Kan affine models of any dimension priced through their Riccati ODEs (scipy.integrate.solve_ivp()). Curve-fitted models take an initial YieldCurve: Ho-Lee, Hull-White, the two-factor G2++ and the Black-Derman-Toy lognormal tree (calibrated by forward induction). Every closed-form bond price is checked against Feynman-Kac Monte Carlo through financialkit.integrators.

Options: zero-coupon bond options in any Gaussian model (Jamshidian 1989, via the financialkit.options.black76_price() implementation), coupon bond options and swaptions by Jamshidian’s decomposition, caps and floors by Black’s formula on forward rates (the Brace-Gatarek-Musiela market model) or as bond options, Black swaptions under the swap market model, and the futures convexity adjustment. The Heath-Jarrow-Morton framework: the no-arbitrage drift and one-factor forward-curve simulation, with and without it.

class financialkit.interest_rate_models.AffineModel(x0, K0, K1, sigma, alpha, beta, delta0, delta1)[source]#

Bases: ShortRateModel

A \(d\)-factor affine short-rate model, priced through its Riccati ODEs.

Parameters:
  • x0 (array_like, shape (d,)) – Initial state.

  • K0 (array_like, shape (d,)) – Drift intercept.

  • K1 (array_like, shape (d, d)) – Drift matrix.

  • sigma (array_like, shape (d,)) – Volatility scale of each factor.

  • alpha (array_like, shape (d,)) – Constant part of each factor’s variance.

  • beta (array_like, shape (d, d)) – beta[k] is the loading of factor k’s variance on the state.

  • delta0 (float) – Constant part of the short rate.

  • delta1 (array_like, shape (d,)) – Loadings of the short rate on the state.

Examples

Vasicek written as an affine model gives Vasicek’s bond price:

>>> from financialkit.interest_rate_models.systems.short_rate import VasicekModel
>>> m = AffineModel([0.03], [0.5 * 0.05], [[-0.5]], [0.01], [1.0], [[0.0]], 0.0, [1.0])
>>> bool(np.isclose(m.zero_coupon_bond(5.0), VasicekModel(0.03, 0.5, 0.05, 0.01).zero_coupon_bond(5.0)))
True
classmethod cir(r0, kappa, theta, sigma)[source]#

The CIR model in affine form (state = short rate, alpha = 0).

Return type:

AffineModel

Parameters:
riccati(tau)[source]#

Solve the Riccati ODEs for \(A(\tau)\) and \(B(\tau)\).

Parameters:

tau (array_like, shape (m,)) – Non-negative times to maturity.

Return type:

tuple[ndarray, ndarray]

Returns:

  • A (ndarray, shape (m,))

  • B (ndarray, shape (m, d))

simulate(T, n_steps, n_paths, rng=None)[source]#

Risk-neutral short-rate paths on a uniform grid over [0, T].

Returns:

  • times (ndarray, shape (n_steps + 1,))

  • rates (ndarray, shape (n_paths, n_steps + 1))

Parameters:
simulate_factors(T, n_steps, n_paths, rng=None)[source]#

State paths by Euler-Maruyama, with the variances truncated at zero.

Return type:

tuple[ndarray, ndarray]

Returns:

  • times (ndarray, shape (n_steps + 1,))

  • x (ndarray, shape (n_paths, n_steps + 1, d))

Parameters:
classmethod vasicek(r0, kappa, theta, sigma)[source]#

Vasicek’s model in affine form (state = short rate, beta = 0).

Return type:

AffineModel

Parameters:
zero_coupon_bond(T, t=0.0, state=None)[source]#

Price \(P(t, T)\) of a unit zero-coupon bond.

Parameters:
  • T (array_like) – Bond maturities, T >= t.

  • t (float) – Valuation date.

  • state (float or array_like, optional) – Model state at t: the short rate for a one-factor model (default r0, only meaningful at t = 0), the factor vector otherwise. Broadcasts against T along leading axes.

Returns:

float or ndarray

class financialkit.interest_rate_models.BondMonteCarloResult(maturities, prices, std_errors, exact, n_paths=0)[source]#

Bases: object

Zero-coupon bond prices \(E[e^{-\int_0^T r\,dt}]\) estimated by simulation (Feynman-Kac).

Parameters:
property errors: ndarray#

Monte Carlo minus closed-form prices.

Type:

ndarray

exact: ndarray#

The model’s closed-form prices, for comparison.

Type:

ndarray, shape (m,)

maturities: ndarray#

Bond maturities.

Type:

ndarray, shape (m,)

n_paths: int = 0#

Number of simulated paths.

Type:

int

prices: ndarray#

Monte Carlo prices.

Type:

ndarray, shape (m,)

std_errors: ndarray#

Standard errors of the prices.

Type:

ndarray, shape (m,)

property z_scores: ndarray#

Errors in units of standard error.

Type:

ndarray

class financialkit.interest_rate_models.CIRModel(r0, kappa, theta, sigma)[source]#

Bases: ShortRateModel

Cox, Ingersoll and Ross’s (1985) square-root short rate.

\[dr = \kappa(\theta - r)\,dt + \sigma\sqrt{r}\,dW\]

Volatility falls with the rate, so (under the Feller condition) rates stay positive. Bond prices are exponential-affine,

\[B = \frac{2(e^{\gamma\tau} - 1)}{(\gamma + \kappa)(e^{\gamma\tau} - 1) + 2\gamma}, \quad A = \left[\frac{2\gamma e^{(\kappa + \gamma)\tau/2}}{(\gamma + \kappa)(e^{\gamma\tau} - 1) + 2\gamma}\right]^{2\kappa\theta/\sigma^2},\]

with \(\gamma = \sqrt{\kappa^2 + 2\sigma^2}\) and \(P = A e^{-Br}\). The transition is a scaled noncentral chi-square, and the stationary law a gamma distribution with shape \(2\kappa\theta/\sigma^2\) and scale \(\sigma^2/(2\kappa)\).

Parameters:
  • r0 (float) – Initial rate (non-negative), mean-reversion speed, long-run mean and volatility.

  • kappa (float) – Initial rate (non-negative), mean-reversion speed, long-run mean and volatility.

  • theta (float) – Initial rate (non-negative), mean-reversion speed, long-run mean and volatility.

  • sigma (float) – Initial rate (non-negative), mean-reversion speed, long-run mean and volatility.

Examples

>>> m = CIRModel(0.06, 0.1, 0.1, 0.11)
>>> round(float(m.zero_coupon_bond(10.0)), 4)
0.5039
>>> round(m.feller_ratio, 3)
1.653
property feller_ratio: float#

\(2\kappa\theta/\sigma^2\) (see feller_condition()).

Type:

float

property gamma: float#

\(\sqrt{\kappa^2 + 2\sigma^2}\).

Type:

float

simulate(T, n_steps, n_paths, rng=None, method='exact')[source]#

Short-rate paths; method="exact" samples the noncentral chi-square transition, "euler" runs full-truncation Euler-Maruyama.

Parameters:
Returns:

  • times (ndarray, shape (n_steps + 1,))

  • rates (ndarray, shape (n_paths, n_steps + 1))

stationary_shape_scale()[source]#

Shape and scale of the stationary gamma distribution of the rate.

Return type:

tuple[float, float]

zero_coupon_bond(T, t=0.0, state=None)[source]#

Price \(P(t, T)\) of a unit zero-coupon bond.

Parameters:
  • T (array_like) – Bond maturities, T >= t.

  • t (float) – Valuation date.

  • state (float or array_like, optional) – Model state at t: the short rate for a one-factor model (default r0, only meaningful at t = 0), the factor vector otherwise. Broadcasts against T along leading axes.

Returns:

float or ndarray

class financialkit.interest_rate_models.CapFloorResult(price, caplets, forwards, reset_times, payment_times, strike, kind='cap')[source]#

Bases: object

A cap or floor as a strip of caplets (floorlets), each priced by Black-76.

Parameters:
caplets: ndarray#

Price of each caplet.

Type:

ndarray, shape (n,)

forwards: ndarray#

Simple forward rate of each accrual period.

Type:

ndarray, shape (n,)

kind: str = 'cap'#

"cap" or "floor".

Type:

str

payment_times: ndarray#

Payment dates (period ends).

Type:

ndarray, shape (n,)

price: float#

Sum of the caplet prices.

Type:

float

reset_times: ndarray#

Fixing dates (option expiries).

Type:

ndarray, shape (n,)

strike: float#

Cap or floor rate.

Type:

float

class financialkit.interest_rate_models.G2PlusPlusModel(curve, a, sigma, b, eta, rho)[source]#

Bases: GaussianShortRateModel

The G2++ model, fitted to an initial curve.

Bond prices given the factors \((x, y)\) at time \(t\) are

\[P(t, T) = \frac{P(0, T)}{P(0, t)} \exp\left\{\tfrac12[V(t, T) - V(0, T) + V(0, t)] - B_a(\tau) x - B_b(\tau) y\right\},\]

with \(B_k(\tau) = (1 - e^{-k\tau})/k\) and \(V(t, T)\) the variance of \(\int_t^T (x + y)\,du\) (Brigo-Mercurio eq. 4.10).

Parameters:
  • curve (YieldCurve | float) – Initial term structure.

  • a (float) – Mean reversion and volatility of the first factor.

  • sigma (float) – Mean reversion and volatility of the first factor.

  • b (float) – Mean reversion and volatility of the second factor.

  • eta (float) – Mean reversion and volatility of the second factor.

  • rho (float) – Correlation of the two Brownian drivers, in (-1, 1).

Examples

>>> m = G2PlusPlusModel(0.03, 0.5, 0.01, 0.05, 0.008, -0.7)
>>> round(float(m.zero_coupon_bond(10.0)), 6) == round(float(np.exp(-0.3)), 6)
True
V(t, T)[source]#

Variance of \(\int_t^T [x(u) + y(u)]\,du\) given the factors at t.

bond_option_volatility(T, S)[source]#

Total volatility \(\sigma_P\) of \(\ln P(T, S)\) seen from time 0.

Parameters:
  • T (array_like) – Option expiry.

  • S (array_like) – Maturity of the underlying bond, S > T.

Returns:

float or ndarray – Standard deviation of \(\ln P(T, S)\) (not annualised).

curve()[source]#

The initial curve the model was fitted to.

Return type:

YieldCurve

n_factors: int = 2#

Number of Brownian drivers; one-factor models have the short rate as their state.

shift(t)[source]#

\(\varphi(t) = f(0, t) + \tfrac{\sigma^2}{2}B_a(t)^2 + \tfrac{\eta^2}{2}B_b(t)^2 + \rho\sigma\eta B_a(t) B_b(t)\).

simulate(T, n_steps, n_paths, rng=None)[source]#

Risk-neutral short-rate paths on a uniform grid over [0, T].

Returns:

  • times (ndarray, shape (n_steps + 1,))

  • rates (ndarray, shape (n_paths, n_steps + 1))

Parameters:
simulate_factors(T, n_steps, n_paths, rng=None)[source]#

Factor paths by Euler-Maruyama on correlated increments.

Return type:

tuple[ndarray, ndarray]

Returns:

  • times (ndarray, shape (n_steps + 1,))

  • xy (ndarray, shape (n_paths, n_steps + 1, 2))

Parameters:
yield_correlation(T1, T2)[source]#

Instantaneous correlation of the zero yields of maturities T1 and T2.

A yield \(R(t, t + \tau)\) moves by \([B_a(\tau)\sigma\,dW_1 + B_b(\tau)\eta\,dW_2]/\tau\), so two yields are perfectly correlated in any one-factor model but not here.

Parameters:
  • T1 (array_like) – Times to maturity.

  • T2 (array_like) – Times to maturity.

Returns:

float or ndarray

zero_coupon_bond(T, t=0.0, state=None)[source]#

Price \(P(t, T)\) of a unit zero-coupon bond.

Parameters:
  • T (array_like) – Bond maturities, T >= t.

  • t (float) – Valuation date.

  • state (float or array_like, optional) – Model state at t: the short rate for a one-factor model (default r0, only meaningful at t = 0), the factor vector otherwise. Broadcasts against T along leading axes.

Returns:

float or ndarray

class financialkit.interest_rate_models.GaussianShortRateModel[source]#

Bases: ShortRateModel

A short-rate model in which bond log-prices are Gaussian.

Then \(P(T, S) / P(0, T, S)\) is lognormal under the \(T\)-forward measure, and a European option on a zero-coupon bond is priced by Black-76 with total volatility \(\sigma_P\) given by bond_option_volatility() (Jamshidian 1989).

abstractmethod bond_option_volatility(T, S)[source]#

Total volatility \(\sigma_P\) of \(\ln P(T, S)\) seen from time 0.

Parameters:
  • T (array_like) – Option expiry.

  • S (array_like) – Maturity of the underlying bond, S > T.

Returns:

float or ndarray – Standard deviation of \(\ln P(T, S)\) (not annualised).

class financialkit.interest_rate_models.HJMResult(times, forwards, initial_curve, drift_condition=True)[source]#

Bases: object

Simulated forward curves \(f(t, T_j)\) under a one-factor HJM model.

Parameters:
bond_prices()[source]#

Monte Carlo \(P(0, T_j) = E[e^{-\int_0^{T_j} r\,dt}]\) (trapezoidal rule) and its standard errors.

Return type:

tuple[ndarray, ndarray]

Returns:

prices, std_errors (ndarray, shape (n + 1,))

drift_condition: bool = True#

Whether the HJM no-arbitrage drift was applied.

Type:

bool

forwards: ndarray#

forwards[p, i, j] = f(t_i, T_j); meaningful for j >= i.

Type:

ndarray, shape (n_paths, n + 1, n + 1)

initial_curve: ndarray#

\(f(0, T_j)\).

Type:

ndarray, shape (n + 1,)

property short_rates: ndarray#

\(r(t_i) = f(t_i, t_i)\).

Type:

ndarray, shape (n_paths, n + 1)

times: ndarray#

Simulation dates, which are also the maturity grid \(T_j\).

Type:

ndarray, shape (n + 1,)

class financialkit.interest_rate_models.HoLeeModel(curve, sigma)[source]#

Bases: _CurveFittedGaussian

Ho and Lee’s (1986) arbitrage-free model, in its continuous-time limit.

\[dr = \theta(t)\,dt + \sigma\,dW, \qquad \theta(t) = f_T(0, t) + \sigma^2 t,\]

where \(f(0, t)\) is today’s instantaneous forward curve. The drift is whatever it takes to reprice every zero-coupon bond, so the model fits any initial curve exactly. Bonds at later dates are

\[P(t, T) = \frac{P(0, T)}{P(0, t)} \exp\left(\tau f(0, t) - \tfrac12\sigma^2 t \tau^2 - \tau r(t)\right).\]

Ho and Lee built it as a binomial tree of whole discount curves; it is Merton’s Gaussian rate with a curve-fitting drift.

Parameters:
  • curve (YieldCurve | float) – Initial term structure (a float is a flat rate).

  • sigma (float) – Absolute volatility of the short rate.

Examples

>>> from financialkit.fixed_income import NelsonSiegelCurve
>>> curve = NelsonSiegelCurve(0.05, -0.02, 0.01, 2.0)
>>> bool(np.isclose(HoLeeModel(curve, 0.01).zero_coupon_bond(7.0), curve.discount(7.0)))
True
curve()[source]#

The initial curve the model was fitted to.

Return type:

YieldCurve

class financialkit.interest_rate_models.HullWhiteModel(curve, a, sigma)[source]#

Bases: _CurveFittedGaussian

Hull and White’s (1990) extended Vasicek model.

\[dr = (\theta(t) - a r)\,dt + \sigma\,dW, \qquad \theta(t) = f_T(0, t) + a f(0, t) + \frac{\sigma^2}{2a}(1 - e^{-2at}).\]

Vasicek’s mean reversion with Ho-Lee’s curve-fitting drift: the initial curve is matched exactly and volatility of long rates decays like \(B(\tau)/\tau\). The short rate is \(r(t) = x(t) + \varphi(t)\) with \(x\) an Ornstein-Uhlenbeck process started at 0 and \(\varphi(t) = f(0, t) + \tfrac{\sigma^2}{2a^2}(1 - e^{-at})^2\).

Parameters:
  • curve (YieldCurve | float) – Initial term structure (a float is a flat rate).

  • a (float) – Mean-reversion speed; a = 0 is Ho-Lee.

  • sigma (float) – Absolute volatility of the short rate.

Examples

>>> hw = HullWhiteModel(0.04, 0.1, 0.01)
>>> round(float(hw.zero_coupon_bond(5.0)), 6)
0.818731
curve()[source]#

The initial curve the model was fitted to.

Return type:

YieldCurve

class financialkit.interest_rate_models.JamshidianResult(price, critical_rate, payment_times, cash_flows, strikes, components)[source]#

Bases: object

An option on a coupon bond split into zero-coupon bond options (Jamshidian 1989).

Parameters:
cash_flows: ndarray#

Amounts paid at payment_times.

Type:

ndarray, shape (n,)

components: ndarray#

cash_flows[i] times the zero-bond option struck at strikes[i].

Type:

ndarray, shape (n,)

critical_rate: float#

Short rate \(r^*\) at expiry at which the bond is worth the strike.

Type:

float

payment_times: ndarray#

Payment dates of the bond.

Type:

ndarray, shape (n,)

price: float#

Option value, the sum of components.

Type:

float

strikes: ndarray#

Zero-bond strikes \(K_i = P(T, t_i; r^*)\).

Type:

ndarray, shape (n,)

class financialkit.interest_rate_models.MertonShortRateModel(r0, mu, sigma)[source]#

Bases: GaussianShortRateModel

Merton’s (1973) Gaussian short rate, \(dr = \mu\,dt + \sigma\,dW\).

The first stochastic interest rate in continuous time, from Section 6 of Merton’s “Theory of Rational Option Pricing”. Bond prices are

\[P(t, T) = \exp\left(-r\tau - \tfrac12 \mu\tau^2 + \tfrac16 \sigma^2\tau^3\right), \quad \tau = T - t,\]

so the cubic convexity term eventually makes long yields fall without bound: rates without mean reversion cannot carry a long curve.

Parameters:
  • r0 (float) – Initial short rate.

  • mu (float) – Constant drift of the short rate, per year.

  • sigma (float) – Absolute volatility of the short rate.

Examples

>>> m = MertonShortRateModel(0.05, 0.0, 0.0)
>>> round(float(m.zero_coupon_bond(2.0)), 6)
0.904837
bond_option_volatility(T, S)[source]#

Total volatility \(\sigma_P\) of \(\ln P(T, S)\) seen from time 0.

Parameters:
  • T (array_like) – Option expiry.

  • S (array_like) – Maturity of the underlying bond, S > T.

Returns:

float or ndarray – Standard deviation of \(\ln P(T, S)\) (not annualised).

simulate(T, n_steps, n_paths, rng=None)[source]#

Risk-neutral short-rate paths on a uniform grid over [0, T].

Returns:

  • times (ndarray, shape (n_steps + 1,))

  • rates (ndarray, shape (n_paths, n_steps + 1))

Parameters:
zero_coupon_bond(T, t=0.0, state=None)[source]#

Price \(P(t, T)\) of a unit zero-coupon bond.

Parameters:
  • T (array_like) – Bond maturities, T >= t.

  • t (float) – Valuation date.

  • state (float or array_like, optional) – Model state at t: the short rate for a one-factor model (default r0, only meaningful at t = 0), the factor vector otherwise. Broadcasts against T along leading axes.

Returns:

float or ndarray

class financialkit.interest_rate_models.ModelCurve(model)[source]#

Bases: YieldCurve

A model’s time-0 zero curve, \(r(t) = -\ln P(0, t) / t\), with \(r(0) = r_0\).

Parameters:

model (ShortRateModel)

discount(t)[source]#

Discount factor \(P(0, t) = e^{-r(t) t}\); broadcasts.

zero_rate(t)[source]#

Continuously compounded zero rate for maturity t (years); broadcasts.

class financialkit.interest_rate_models.RateTree(times, rates, arrow_debreu, dt, sigma=<factory>)[source]#

Bases: object

A recombining binomial short-rate lattice with probability 1/2 on each branch.

rates[i, j] is the rate over [t_i, t_{i+1}] at node j (j = 0 lowest, j <= i); entries with j > i are nan. Rates are continuously compounded over one step of length dt.

Parameters:
arrow_debreu: ndarray#

Price at time 0 of 1 paid at node (i, j).

Type:

ndarray, shape (n + 1, n + 1)

bond_option(expiry, maturity, K, option_type='call', american=False)[source]#

European or American option on a zero-coupon bond, by backward induction.

Parameters:
  • expiry (float) – Option expiry and bond maturity; node dates with expiry < maturity.

  • maturity (float) – Option expiry and bond maturity; node dates with expiry < maturity.

  • K (float) – Strike, per unit face.

  • option_type (str)

  • american (bool) – Allow exercise at every node up to expiry.

Return type:

float

Returns:

float

bond_values(T, until=0)[source]#

Value at each node of step until of a unit bond maturing at T.

Return type:

ndarray

Parameters:
dt: float#

Step length in years.

Type:

float

property n_steps: int#

Number of steps.

Type:

int

rates: ndarray#

Short rate at each node of steps 0 .. n-1.

Type:

ndarray, shape (n, n)

sigma: ndarray#

Short-rate (log) volatility used at each step.

Type:

ndarray, shape (n,)

times: ndarray#

Node dates 0, dt, ..., n dt.

Type:

ndarray, shape (n + 1,)

zero_coupon_bond(T)[source]#

Time-0 price of a unit zero-coupon bond maturing at node date T.

Return type:

float

Parameters:

T (float)

class financialkit.interest_rate_models.ShortRateModel[source]#

Bases: ABC

A risk-neutral model of the instantaneous short rate \(r(t)\).

Subclasses set r0 and n_factors and implement zero_coupon_bond() and simulate().

curve()[source]#

The model’s initial term structure as a YieldCurve.

Return type:

YieldCurve

n_factors: int = 1#

Number of Brownian drivers; one-factor models have the short rate as their state.

r0: float = 0.0#

Short rate at time 0.

abstractmethod simulate(T, n_steps, n_paths, rng=None)[source]#

Risk-neutral short-rate paths on a uniform grid over [0, T].

Return type:

tuple[ndarray, ndarray]

Returns:

  • times (ndarray, shape (n_steps + 1,))

  • rates (ndarray, shape (n_paths, n_steps + 1))

Parameters:
yields(T, t=0.0, state=None)[source]#

Continuously compounded zero yields \(-\ln P(t, T) / (T - t)\).

Parameters:

t (float)

abstractmethod zero_coupon_bond(T, t=0.0, state=None)[source]#

Price \(P(t, T)\) of a unit zero-coupon bond.

Parameters:
  • T (array_like) – Bond maturities, T >= t.

  • t (float) – Valuation date.

  • state (float or array_like, optional) – Model state at t: the short rate for a one-factor model (default r0, only meaningful at t = 0), the factor vector otherwise. Broadcasts against T along leading axes.

Returns:

float or ndarray

class financialkit.interest_rate_models.VasicekModel(r0, kappa, theta, sigma)[source]#

Bases: GaussianShortRateModel

Vasicek’s (1977) mean-reverting short rate, \(dr = \kappa(\theta - r)\,dt + \sigma\,dW\).

The rate is an Ornstein-Uhlenbeck process, so bond prices are exponential-affine,

\[P(t, T) = A(\tau) e^{-B(\tau) r}, \quad B = \frac{1 - e^{-\kappa\tau}}{\kappa}, \quad \ln A = \left(\theta - \frac{\sigma^2}{2\kappa^2}\right)(B - \tau) - \frac{\sigma^2 B^2}{4\kappa},\]

and long yields tend to \(\theta - \sigma^2/(2\kappa^2)\). Depending on \(r_0\) against that level, the curve is upward sloping, inverted or humped.

Parameters:
  • r0 (float) – Initial rate, mean-reversion speed, long-run mean and volatility.

  • kappa (float) – Initial rate, mean-reversion speed, long-run mean and volatility.

  • theta (float) – Initial rate, mean-reversion speed, long-run mean and volatility.

  • sigma (float) – Initial rate, mean-reversion speed, long-run mean and volatility.

Examples

>>> m = VasicekModel(0.06, 0.1, 0.1, 0.02)
>>> round(float(m.zero_coupon_bond(10.0)), 4)
0.4899
>>> round(m.long_yield, 4)
0.08
bond_option_volatility(T, S)[source]#

Total volatility \(\sigma_P\) of \(\ln P(T, S)\) seen from time 0.

Parameters:
  • T (array_like) – Option expiry.

  • S (array_like) – Maturity of the underlying bond, S > T.

Returns:

float or ndarray – Standard deviation of \(\ln P(T, S)\) (not annualised).

property long_yield: float#

\(\lim_{T\to\infty} R(0, T) = \theta - \sigma^2/(2\kappa^2)\).

Type:

float

simulate(T, n_steps, n_paths, rng=None)[source]#

Risk-neutral short-rate paths on a uniform grid over [0, T].

Returns:

  • times (ndarray, shape (n_steps + 1,))

  • rates (ndarray, shape (n_paths, n_steps + 1))

Parameters:
zero_coupon_bond(T, t=0.0, state=None)[source]#

Price \(P(t, T)\) of a unit zero-coupon bond.

Parameters:
  • T (array_like) – Bond maturities, T >= t.

  • t (float) – Valuation date.

  • state (float or array_like, optional) – Model state at t: the short rate for a one-factor model (default r0, only meaningful at t = 0), the factor vector otherwise. Broadcasts against T along leading axes.

Returns:

float or ndarray

financialkit.interest_rate_models.black_derman_toy_tree(curve, sigma, maturity, n_steps)[source]#

Calibrate a Black-Derman-Toy lognormal binomial short-rate tree to a zero curve.

At step \(i\) the rates are \(r_{i,j} = U_i e^{\sigma_i\sqrt{\Delta t}\,(2j - i)}\), \(j = 0, \dots, i\), with probability 1/2 on each branch, so the log short rate has volatility \(\sigma_i\) and rates stay positive. Each median level \(U_i\) is solved by scipy.optimize.brentq() so that the tree reprices the bond maturing at \(t_{i+1}\),

\[\sum_j Q_{i,j} e^{-r_{i,j}\Delta t} = P(0, t_{i+1}),\]

where the Arrow-Debreu prices \(Q\) are carried forward one level at a time (Jamshidian’s 1991 forward induction). This is the short-rate-volatility form of the model; Black, Derman and Toy’s original also matched a term structure of yield volatilities.

Parameters:
  • curve (YieldCurve | float) – Initial term structure.

  • sigma (float or array_like, shape (n_steps,)) – Volatility of the log short rate at each step.

  • maturity (float) – Tree horizon in years.

  • n_steps (int) – Number of steps.

Return type:

RateTree

Returns:

RateTree

Examples

>>> from financialkit.fixed_income import FlatCurve
>>> tree = black_derman_toy_tree(FlatCurve(0.05), 0.2, 5.0, 50)
>>> round(tree.zero_coupon_bond(5.0), 8) == round(float(np.exp(-0.25)), 8)
True
financialkit.interest_rate_models.black_swaption_price(curve, expiry, tenor, strike, vol, freq=2, payer=True, notional=1.0)[source]#

Black’s formula for a European swaption (Jamshidian’s 1997 swap market model).

Under the annuity measure the forward swap rate \(S\) is a martingale; taking it lognormal gives

\[V_{\text{payer}} = A\,[S N(d_1) - K N(d_2)], \quad V_{\text{receiver}} = A\,[K N(-d_2) - S N(-d_1)],\]

Black-76 with zero rate times the annuity \(A\).

Parameters:
  • curve (YieldCurve | float)

  • expiry (float) – Option expiry and swap length.

  • tenor (float) – Option expiry and swap length.

  • strike (array_like) – Fixed rate and Black volatility of the swap rate.

  • vol (array_like) – Fixed rate and Black volatility of the swap rate.

  • freq (int)

  • payer (bool)

  • notional (float)

Returns:

float or ndarray

Examples

Hull, 10th ed., Example 29.5: a 5 x 3 payer swaption struck at 6.2%, semiannual, flat 6% curve with continuous compounding, 20% volatility, principal 100.

>>> round(black_swaption_price(0.06, 5.0, 3.0, 0.062, 0.2, 2, notional=100.0), 2)
2.07
financialkit.interest_rate_models.cap_floor_price(curve, strike, maturity, vol, freq=4, kind='cap', notional=1.0)[source]#

A cap or floor priced caplet by caplet with Black’s formula.

Caplet \(k\) pays \(\tau_k \max(L_k - K, 0)\) at \(t_{k+1}\) on the rate \(L_k\) fixed at \(t_k\). Under the \(t_{k+1}\)-forward measure the simple forward rate \(F_k = [P(0, t_k)/P(0, t_{k+1}) - 1]/\tau_k\) is a martingale, and taking it lognormal (Brace-Gatarek-Musiela 1997) gives

\[\text{caplet}_k = \tau_k P(0, t_{k+1})\,[F_k N(d_1) - K N(d_2)].\]

The first period, whose rate is already fixed, is excluded.

Parameters:
  • curve (YieldCurve | float)

  • strike (float) – Cap (floor) rate.

  • maturity (float) – End of the last period.

  • vol (float or array_like) – Black volatility: one flat volatility, or one per caplet.

  • freq (int) – Resets per year.

  • kind (str)

  • notional (float)

Return type:

CapFloorResult

Returns:

CapFloorResult

Examples

On a curve flat at 7% with quarterly compounding every forward is 7%; cap minus floor at the same strike is the forward-starting swap:

>>> curve = 4 * np.log(1.0175)
>>> cap = cap_floor_price(curve, 0.07, 2.0, 0.20, freq=4)
>>> floor = cap_floor_price(curve, 0.07, 2.0, 0.20, freq=4, kind="floor")
>>> cap.caplets.size, round(float(cap.forwards[0]), 4), bool(np.isclose(cap.price, floor.price))
(7, 0.07, True)
financialkit.interest_rate_models.coupon_bond_option(model, T, payment_times, cash_flows, K, option_type='call')[source]#

European option on a coupon bond by Jamshidian’s (1989) decomposition.

In a one-factor model the bond’s value at expiry, \(\sum_i c_i P(T, t_i; r)\), falls with \(r\). Let \(r^*\) solve \(\sum_i c_i P(T, t_i; r^*) = K\) and \(K_i = P(T, t_i; r^*)\). The bond is above the strike exactly when every zero is above its \(K_i\), so

\[\max\left(\sum_i c_i P(T, t_i) - K, 0\right) = \sum_i c_i \max(P(T, t_i) - K_i, 0),\]

and the option is a portfolio of zero-coupon bond options.

Parameters:
  • model (GaussianShortRateModel) – A one-factor model (Merton, Vasicek, Ho-Lee or Hull-White).

  • T (float) – Option expiry.

  • payment_times (array_like, shape (n,)) – Bond payments after T.

  • cash_flows (array_like, shape (n,)) – Bond payments after T.

  • K (float) – Strike (on the bond’s dirty price).

  • option_type (str)

Return type:

JamshidianResult

Returns:

JamshidianResult

financialkit.interest_rate_models.feller_condition(kappa, theta, sigma)[source]#

Feller’s ratio \(2\kappa\theta / \sigma^2\) for the square-root process.

Feller (1951) classified the boundary at zero of \(dr = \kappa(\theta - r)\,dt + \sigma\sqrt{r}\,dW\): the process never reaches zero when the ratio is at least 1, and touches it (and is reflected) when it is below 1. The ratio is also the shape parameter of the stationary gamma distribution, whose density diverges at zero when it is below 1.

Parameters:
  • kappa (array_like) – Mean-reversion speed, long-run level and volatility.

  • theta (array_like) – Mean-reversion speed, long-run level and volatility.

  • sigma (array_like) – Mean-reversion speed, long-run level and volatility.

Returns:

float or ndarray – \(2\kappa\theta/\sigma^2\); the condition holds where it is >= 1.

Examples

>>> round(feller_condition(0.5, 0.05, 0.2), 10)
1.25
financialkit.interest_rate_models.forward_swap_annuity(curve, expiry, tenor, freq=2)[source]#

Annuity \(A = \sum_i \tau_i P(0, t_i)\) of a forward-starting swap’s fixed leg.

Return type:

float

Parameters:
financialkit.interest_rate_models.forward_swap_rate(curve, expiry, tenor, freq=2)[source]#

Forward par swap rate \(S = [P(0, T_0) - P(0, T_n)] / A\).

Return type:

float

Parameters:

Examples

>>> from financialkit.fixed_income import FlatCurve
>>> round(forward_swap_rate(FlatCurve(0.04), 1.0, 5.0, 1), 6) == round(float(np.expm1(0.04)), 6)
True
financialkit.interest_rate_models.futures_convexity_adjustment(t1, t2, sigma, a=0.0)[source]#

Futures rate minus forward rate for a futures contract on the rate over [t1, t2].

Futures are settled daily, so their rate is the risk-neutral expectation of the future rate rather than the forward rate, and is higher by a convexity adjustment. Under Ho-Lee (a = 0),

\[\text{futures} - \text{forward} = \tfrac12 \sigma^2 t_1 t_2,\]

and under Hull-White

\[\frac{B(t_1, t_2)}{t_2 - t_1}\left[B(t_1, t_2)(1 - e^{-2at_1}) + 2aB(0, t_1)^2\right]\frac{\sigma^2}{4a},\]

with \(B(s, t) = (1 - e^{-a(t-s)})/a\). Both rates are continuously compounded.

Parameters:
  • t1 (array_like) – Start and end of the underlying rate’s period, t2 > t1 >= 0.

  • t2 (array_like) – Start and end of the underlying rate’s period, t2 > t1 >= 0.

  • sigma (array_like) – Short-rate volatility.

  • a (float) – Hull-White mean reversion; 0 gives the Ho-Lee formula.

Returns:

float or ndarray

Examples

Hull, 10th ed., Example 6.4: sigma = 1.2%, an 8-year futures contract on 3-month rates.

>>> round(100 * futures_convexity_adjustment(8.0, 8.25, 0.012), 3)
0.475
financialkit.interest_rate_models.hjm_drift(sigma, t, T)[source]#

HJM no-arbitrage drift \(\alpha(t, T) = \sigma(t, T)\int_t^T \sigma(t, u)\,du\).

Parameters:
  • sigma (Callable[[float, float], float]) – sigma(t, T) -> float, the forward-rate volatility.

  • t (float) – Current time.

  • T (array_like) – Maturities, T >= t.

Return type:

ndarray | float

Returns:

float or ndarray

Examples

Constant volatility (Ho-Lee) gives \(\sigma^2 (T - t)\):

>>> round(hjm_drift(lambda t, T: 0.01, 0.0, 5.0), 10)
0.0005
financialkit.interest_rate_models.jamshidian_swaption(model, expiry, tenor, strike, freq=2, payer=True, notional=1.0)[source]#

European swaption in a one-factor model, by Jamshidian’s decomposition.

At expiry, receiving fixed at \(K\) on a swap is worth \(\sum_i c_i P(T_0, t_i) - 1\) with \(c_i = K\tau_i\) (plus 1 at the end), so a receiver swaption is a call, and a payer swaption a put, on that coupon bond struck at par.

Parameters:
  • model (GaussianShortRateModel) – One-factor model.

  • expiry (float) – Option expiry and length of the underlying swap.

  • tenor (float) – Option expiry and length of the underlying swap.

  • strike (float) – Fixed rate.

  • freq (int) – Fixed-leg payments per year.

  • payer (bool) – Payer (right to pay fixed) or receiver swaption.

  • notional (float)

Return type:

JamshidianResult

Returns:

JamshidianResult – Scaled by notional.

financialkit.interest_rate_models.model_cap_floor_price(model, strike, maturity, freq=4, kind='cap', notional=1.0)[source]#

A cap or floor in a Gaussian short-rate model, as options on zero-coupon bonds.

A caplet paying at \(t_{k+1}\) is worth, at its fixing \(t_k\), \((1 + K\tau)\max(1/(1 + K\tau) - P(t_k, t_{k+1}), 0)\): a put on a zero-coupon bond (Hull, 10th ed., Sec. 32.4). A floorlet is the call.

Parameters:
Return type:

CapFloorResult

Returns:

CapFloorResult

financialkit.interest_rate_models.monte_carlo_bond_prices(model, maturities, n_steps=200, n_paths=10000, rng=None)[source]#

Zero-coupon bond prices by simulating the short rate (Feynman-Kac).

Kac (1949) showed that the solution of the bond-pricing PDE \(\partial_t P + \mu \partial_r P + \tfrac12 \sigma^2 \partial_{rr} P - rP = 0\) is the expectation

\[P(0, T) = E^Q\left[\exp\left(-\int_0^T r(s)\,ds\right)\right],\]

so a bond is priced by averaging discount factors over simulated paths. The integral is the trapezoidal rule on the simulation grid, an \(O(\Delta t^2)\) bias well below the sampling error.

Parameters:
  • model (ShortRateModel)

  • maturities (array_like, shape (m,)) – Positive bond maturities; the paths run to the longest.

  • n_steps (int) – Time steps to the longest maturity.

  • n_paths (int)

  • rng (Generator | int | None)

Return type:

BondMonteCarloResult

Returns:

BondMonteCarloResult – With the model’s closed-form prices in exact.

Examples

>>> res = monte_carlo_bond_prices(VasicekModel(0.03, 0.5, 0.05, 0.01), [1.0, 5.0], n_paths=4000, rng=0)
>>> bool(abs(res.z_scores).max() < 4)
True
financialkit.interest_rate_models.path_discount_factors(times, rates)[source]#

Pathwise discount factors \(e^{-\int_0^{t_i} r(s)\,ds}\) by the trapezoidal rule.

Parameters:
  • times (array_like, shape (n + 1,)) – Simulation grid starting at 0.

  • rates (array_like, shape (n_paths, n + 1)) – Short-rate paths on that grid.

Return type:

ndarray

Returns:

ndarray, shape (n_paths, n + 1) – Equal to 1 in the first column.

Examples

>>> import numpy as np
>>> t = np.linspace(0.0, 2.0, 5)
>>> float(path_discount_factors(t, np.full((1, 5), 0.05))[0, -1].round(6))
0.904837
financialkit.interest_rate_models.plot_bond_monte_carlo(result, ax=None, label='Monte Carlo')[source]#

Monte Carlo bond prices with 95% error bars against the closed form.

Parameters:
Returns:

matplotlib.axes.Axes

financialkit.interest_rate_models.plot_caplets(result, ax=None, label='')[source]#

Caplet (floorlet) prices by fixing date, with the forward rates on a twin axis.

Parameters:
Returns:

matplotlib.axes.Axes

financialkit.interest_rate_models.plot_forward_curves(result, path=0, every=5, ax=None)[source]#

Snapshots of one simulated forward curve \(T \mapsto f(t, T)\) as time passes.

Parameters:
  • result (HJMResult)

  • path (int) – Which simulated path to draw.

  • every (int) – Draw every every-th simulation date.

  • ax (matplotlib.axes.Axes, optional)

Returns:

matplotlib.axes.Axes

financialkit.interest_rate_models.plot_jamshidian(result, ax=None)[source]#

Jamshidian’s decomposition: the zero-coupon option behind each payment.

Parameters:
Returns:

matplotlib.axes.Axes

financialkit.interest_rate_models.plot_model_yields(model, t_max=30.0, ax=None, label='')[source]#

A model’s time-0 zero curve, in percent.

Parameters:
Returns:

matplotlib.axes.Axes

financialkit.interest_rate_models.plot_rate_tree(tree, ax=None, max_steps=12)[source]#

The nodes of a binomial short-rate tree, in percent, joined by their branches.

Parameters:
  • tree (RateTree)

  • ax (matplotlib.axes.Axes, optional)

  • max_steps (int) – Draw only the first max_steps steps of a large tree.

Returns:

matplotlib.axes.Axes

financialkit.interest_rate_models.plot_short_rate_paths(times, rates, n_show=20, ax=None, label='', quantiles=True)[source]#

A sample of short-rate paths, in percent, with the 5-95% band and the mean.

Parameters:
  • times (array_like, shape (n + 1,))

  • rates (array_like, shape (n_paths, n + 1))

  • n_show (int) – Number of individual paths drawn.

  • ax (matplotlib.axes.Axes, optional) – Axes to draw on; a new figure is created if omitted.

  • label (str) – Legend label for the mean.

  • quantiles (bool) – Shade the 5-95% band across paths.

Returns:

matplotlib.axes.Axes

financialkit.interest_rate_models.simulate_hjm(curve, sigma, a=0.0, horizon=5.0, n_steps=50, n_paths=2000, drift_condition=True, rng=None)[source]#

Simulate one-factor HJM forward curves with \(\sigma(t, T) = \sigma e^{-a(T - t)}\).

The maturity grid is the simulation grid, so the short rate is \(r(t_i) = f(t_i, t_i)\). Every maturity shares one Brownian driver, and the drift is the HJM drift (or zero when drift_condition=False).

Parameters:
  • curve (YieldCurve | float) – Initial term structure; its instantaneous forwards start the simulation.

  • sigma (float) – Forward-rate volatility at zero time to maturity.

  • a (float) – Exponential decay of volatility with maturity; 0 is Ho-Lee.

  • horizon (float) – Last date and maturity.

  • n_steps (int)

  • n_paths (int)

  • drift_condition (bool) – Apply the no-arbitrage drift.

  • rng (Generator | int | None)

Return type:

HJMResult

Returns:

HJMResult

Examples

>>> res = simulate_hjm(0.03, 0.01, horizon=2.0, n_steps=20, n_paths=500, rng=0)
>>> res.forwards.shape
(500, 21, 21)
financialkit.interest_rate_models.swaption_schedule(expiry, tenor, freq=2)[source]#

Fixed-leg payment dates of a swap starting at expiry and running for tenor years.

Return type:

ndarray

Parameters:

Examples

>>> swaption_schedule(1.0, 2.0, 2).tolist()
[1.5, 2.0, 2.5, 3.0]
financialkit.interest_rate_models.zero_coupon_bond_option(model, T, S, K, option_type='call')[source]#

European option, expiring at T, on a unit zero-coupon bond maturing at S.

\[c = P(0, S) N(h) - K P(0, T) N(h - \sigma_P), \quad h = \frac{1}{\sigma_P}\ln\frac{P(0, S)}{K P(0, T)} + \frac{\sigma_P}{2},\]

which is Black-76 on the forward bond price \(P(0, S)/P(0, T)\) with volatility \(\sigma_P/\sqrt{T}\), discounted by \(P(0, T)\). \(\sigma_P\) is the model’s bond_option_volatility() (Jamshidian 1989 for Vasicek; the same formula holds for Ho-Lee, Hull-White and G2++).

Parameters:
  • model (GaussianShortRateModel)

  • T (array_like) – Option expiry and bond maturity, 0 < T < S.

  • S (array_like) – Option expiry and bond maturity, 0 < T < S.

  • K (array_like) – Strike per unit face.

  • option_type (str)

Returns:

float or ndarray

Examples

A 1-year option on a 5-year bond under Vasicek; call minus put is the forward value \(P(0, 5) - K P(0, 1)\):

>>> from financialkit.interest_rate_models.systems.short_rate import VasicekModel
>>> m = VasicekModel(0.1, 0.1, 0.1, 0.02)
>>> c = zero_coupon_bond_option(m, 1.0, 5.0, 0.67, "call")
>>> p = zero_coupon_bond_option(m, 1.0, 5.0, 0.67, "put")
>>> bool(np.isclose(c - p, m.zero_coupon_bond(5.0) - 0.67 * m.zero_coupon_bond(1.0)))
True