Source code for economic.BasicHullWhite.HullWhite

"""The main Space in the :mod:`~economic.BasicHullWhite` model.

Mathematical notations are defined consistent with those in Brigo and Mercurio (2001, 2nd Ed. 2006)

.. seealso::

   * Damiano Brigo, Fabio Mercurio (2001, 2nd Ed. 2006). Interest Rate Models — Theory and Practice with Smile, Inflation and Credit


Attributes:

    scen_size: Number of scenarios. 1000 by default.

    np: numpy module.

    step_size: Number of time steps. 360 by default.

    time_len: Simulation length in years. 30 by default.

    a: Parameter :math:`a` in the Hull-White stochastic differential equation. 0.1 by default.

    sigma: Parameter :math:`\sigma` in the Hull-White stochastic differential equation. 0.1 by default.

    seed1: Seed number to generate random numbers. 1234 by default. See :meth:`std_norm_rand`.

    seed2: Seed for the second random numbers used for :meth:`accum_short_rate2`.
"""

from modelx.serialize.jsonvalues import *

_formula = None

_bases = []

_allow_none = None

_spaces = []

# ---------------------------------------------------------------------------
# Cells

[docs] def A_t_T(i, j): r""":math:`A(t_i, t_j)` in :math:`P(t_i, t_j)` See :meth:`P_t_T`. """ t = t_(i) P_t = mkt_zcb(i) P_T = mkt_zcb(j) f_t = mkt_fwd(i) B = B_t_T(i, j) return P_T / P_t * np.exp( B * f_t - sigma**2 / (4*a) * (1 - np.exp(-2*a*t)) * B**2 )
[docs] def B_t_T(i, j): r""":math:`B(t_i, t_j)` in :math:`P(t_i, t_j)` See :meth:`P_t_T`. """ t, T = t_(i), t_(j) return (1 / a) * (1 - np.exp(-a * (T-t)))
[docs] def E_rt(): """The expected values of :math:`r(t_i)` at time 0 for all :math:`t_i`. Returns, in a numpy array, the expected values of :math:`r(t_i)` for all :math:`t_i`. Calculated as :math:`E\{r(t_i) | \mathcal{F}_{0}\}`. .. seealso:: * :meth:`E_rt_s` """ return np.array([E_rt_s(0, i)[0] for i in range(step_size + 1)])
[docs] def E_rt_s(i, j): r"""Conditional expected values of :math:`r(t_j)` Returns, in a numpy array, :math:`E\{r(t_j) | \mathcal{F}_{i}\}`, the expected values of :math:`r(t_j)` conditional on :math:`\mathcal{F}_{i}` for all scenarios. :math:`E\{r(t) | \mathcal{F}_{s}\}` is calculated as: .. math:: r(s)e^{-a(t-s)} + \alpha(t) - \alpha(s)e^{-a(t-s)} where :math:`\alpha(t)` is calculated by :meth:`alpha`. .. seealso: * :meth:`short_rate` * :meth:`alpha` """ s, t = t_(i), t_(j) r_s = short_rate(i) return r_s * np.exp(-a * (t-s)) + alpha(j) - alpha(i) * np.exp(-a * (t-s))
[docs] def P_t_T(i, j): r"""The price at :math:`t_i` of a zero-coupon bond paying off 1 at :math:`t_j` This formula corresponds to :math:`P(t, T)` in Brigo and Mercurio, which is defined as: .. math:: P(t, T)=A(t, T)e^{-B(t, T)r(t)} where :math:`t_i` and :math:`t_j` substitute for :math:`t` and :math:`T`. .. seealso:: * :meth:`A_t_T` for :math:`A(t, T)` * :meth:`B_t_T` for :math:`B(t, T)` * :meth:`short_rate` for :math:`r(t)` * Brigo and Mercurio (2001, 2nd Ed. 2006). Interest Rate Models — Theory and Practice with Smile, Inflation and Credit """ return A_t_T(i, j) * np.exp(-B_t_T(i, j) * short_rate(i))
[docs] def V_t_T(i, j): r"""The variance of :math:`\int_{t_j}^{t_i} r(u)du|\mathcal{F}_{t_i}` This formula corresponds to :math:`V(t, T)` in Brigo and Mercurio, which is defined as: .. math:: V(t, T)=\frac{\sigma^2}{a^2}\left[T-t+\frac{2}{a}e^{-a(T-t)}-\frac{1}{2a}e^{-2a(T-t)}-\frac{3}{2a}\right] where :math:`t_i` and :math:`t_j` substitute for :math:`t` and :math:`T`. .. seealso:: * :attr:`sigma` for :math:`\sigma` * :attr:`a` for :math:`a` * Brigo and Mercurio (2001, 2nd Ed. 2006). Interest Rate Models — Theory and Practice with Smile, Inflation and Credit """ dt = t_(j) - t_(i) return sigma**2 / a**2 * (dt + (2/a)*np.exp(-a*dt) - (1/(2*a))*np.exp(-2*a*dt) - (3/(2*a)))
[docs] def Var_rt(): r"""The variance of :math:`r(t_i)` at time 0 for all :math:`t_i`. Returns, in a numpy array, the variance of :math:`r(t_i)` for all :math:`t_i`. Calculated as :math:`Var\{r(t_i) | \mathcal{F}_{0}\}`. .. seealso:: * :meth:`Var_rt_s` """ return np.array([Var_rt_s(0, i) for i in range(step_size + 1)])
[docs] def Var_rt_s(i, j): r"""The variance of :math:`r(t_j)` conditional on :math:`\mathcal{F}_{t_i}` :math:`Var\{r(t_{j}) | \mathcal{F}_{t_i}\}`, the variance of :math:`r(t_j)` conditional on :math:`\mathcal{F}_{t_i}`, calculated as: .. math:: Var\{ r(t) | \mathcal{F}_s \} = \frac{\sigma^2}{2a} (1 - e^{-2a(t-s)}) .. seealso:: * :attr:`a` * :attr:`sigma` """ s, t = t_(i), t_(j) return sigma**2 / (2*a) * (1 - np.exp(-2 * a * (t-s)))
[docs] def accum_short_rate(i): r"""Accumulated short rates. a descrete approximation to the integral :math:`\int_0^{t_i}r(t)dt`, calculated as :math:`\sum_{j=1}^{i}r(t_{j-1})(t_j-t_{j-1})` .. seealso:: * :meth:`disc_factor` """ if i == 0: return np.full(scen_size, 0.0) else: dt = t_(i) - t_(i-1) return accum_short_rate(i-1) + short_rate(i-1) * dt
[docs] def accum_short_rate2(i): r"""Alternative implementation of accumulated short rates. An alternative approach to simulate :math:`Y(t_i)=\int_0^{t_i}r(t)dt` by using the fact that :math:`Y(t_i)` follows a normal distribution, and by simulating the joint distribution of :math:`(r(t_i), Y(t_i))`, as suggested in Glasserman (2003). .. seealso:: * :meth:`accum_short_rate` * :attr:`seed2` * Paul Glasserman (2003). Monte Carlo Methods in Financial Engineering """ if i == 0: return np.full(scen_size, 0.0) else: t, T = t_(i-1), t_(i) dt = T - t cov = sigma**2/(2*a**2)*(1 + np.exp(-2*a*dt) -2 * np.exp(-a*dt)) z1 = std_norm_rand(seed1)[:, i-1] z2 = std_norm_rand(seed2)[:, i-1] rho = cov / (Var_rt_s(i-1, i)**0.5 * V_t_T(i-1, i)**0.5) mean = B_t_T(i-1, i) * (short_rate(i-1) - alpha(i-1)) + np.log(mkt_zcb(i-1)/mkt_zcb(i)) + 0.5*(V_t_T(0, i)-V_t_T(0, i-1)) return accum_short_rate2(i-1) + mean + V_t_T(i-1, i)**0.5 * (rho*z1 + (1-rho**2)**0.5*z2)
[docs] def alpha(i): r""":math:`\alpha(t_i)` Returns, in a numpy array, :math:`\alpha(t_i)` for all scenarios. :math:`\alpha` appears in the expression of :math:`E\{r(t) | \mathcal{F}_{s}\}` and is defined as: .. math:: \alpha(t) = f^M(0, t) + \frac{\sigma^2} {2a^2}(1-e^{-at})^2 .. seealso:: * :meth:`E_rt_s` """ t = t_(i) return mkt_fwd(i) + 0.5 * sigma**2 / a**2 * (1 - np.exp(-a*t))**2
[docs] def disc_factor(i): """Discount factors Returns, in a numpy array, the discount factors for cashflows at :math:`t_i` for all scenarios. Defined as:: np.exp(-accum_short_rate(i)) .. seealso:: * accum_short_rate """ return np.exp(-accum_short_rate(i))
[docs] def disc_factor_paths(): """Discount factor scenarios. Returns, as a 2D numpy array, the simulated discount factors for all scenarios. .. seealso:: * :meth:`disc_factor` """ return np.array([disc_factor(i) for i in range(step_size + 1)]).transpose()
[docs] def mean_disc_factor(): """Discount factor means Returns, as a numpy array, the mean of discount factors of all scenarios for each :math:`t_i`. .. seealso:: * :meth:`disc_factor` """ return np.array([np.mean(disc_factor(i)) for i in range(step_size + 1)])
[docs] def mean_short_rate(): """The means of generated short rates Returns, as a numpy array, the means of short rates of all scenarios for all :math:`t_i`. This should converge to the theoretical variances calculated by :meth:`E_rt`. .. seealso:: * :meth:`short_rate` * :meth:`E_rt` """ return np.array([np.mean(short_rate(i)) for i in range(step_size + 1)])
[docs] def mkt_fwd(i): """The initial instantaneous forward rate for :attr:`t_(i)<t_>`. By default, returns 0.05 for all ``i``. """ return 0.05
[docs] def mkt_zcb(i): """The initial price of zero coupon bond The initial price of the unit zero coupon bond maturing at :attr:`t_(i)<t_>`. If ``i=0`` returns 1. Otherwise, defined as:: mkt_zcb(i-1) * np.exp(-mkt_fwd(i-1)*dt) where ``dt = t_(i) - t_(i-1)``. .. seealso:: * :attr:`t_` * :attr:`mkt_fwd` """ if i == 0: return 1.0 else: dt = t_(i) - t_(i-1) return mkt_zcb(i-1) * np.exp(-mkt_fwd(i-1)*dt)
[docs] def short_rate(i): r"""Stochastic short rates at :attr:`t_(i)<t_>` Returns, in a numpy array, simulated stochastic short rates at :attr:`t_(i)<t_>` for all scenarios. For ``i=0``, defined as :meth:`mkt_fwd(0)<mkt_fwd>`. For ``i>0``, defined as :math:`r(t_i) = E\{r(t_i) | \mathcal{F}_{i-1}\} + \sqrt{Var\{ r(t_i) | \mathcal{F}_{i-1} \}} * Z`, where :math:`E\{r(t_i) | \mathcal{F}_{i-1}\}`, the expected value of :math:`r(t_i)` conditional on :math:`\mathcal{F}_{i-1}` is calculated by :meth:`E_rt_s(i-1, i)<E_rt_s>`, :math:`Var\{ r(t_i) | \mathcal{F}_{i-1} \}` the variance of :math:`r(t_i)` conditional on :math:`\mathcal{F}_{i-1}` is calculated by :meth:`Var_rt_s(i-1, i)<Var_rt_s>`, and :math:`Z`, a random number drawn from :math:`\mathcal{N}(0, 1)` a standard normal distribution calculated by :meth:`std_norm_rand`. .. seealso:: * :attr:`scen_size` * :meth:`mkt_fwd` * :meth:`E_rt_s` * :meth:`Var_rt_s` * :meth:`std_norm_rand` * :attr:`seed1` """ if i == 0: return np.full(scen_size, mkt_fwd(0)) else: return E_rt_s(i-1, i) + Var_rt_s(i-1, i)**0.5 * std_norm_rand(seed1)[:, i-1]
[docs] def short_rate_paths(): """Short rate paths. Returns, as a 2D numpy array, the simulated short rate paths for all scenarios. .. seealso:: * :meth:`short_rate` """ return np.array([short_rate(i) for i in range(step_size + 1)]).transpose()
[docs] def std_norm_rand(seed=1234): """Random numbers from the standard normal distribution. Returns a numpy array shaped :attr:`scen_size` x :attr:`step_size`. The elements are random numbers drawn from the standard normal distribution. """ size = (scen_size, step_size) if hasattr(np.random, 'default_rng'): gen = np.random.default_rng(seed) return gen.standard_normal(size) else: np.random.seed(seed) return np.random.standard_normal(size)
t_ = lambda i: i * time_len / step_size """time index :math:`t_i`"""
[docs] def var_short_rate(): """Variance of generated short rates Returns, as a vector in a numpy array, the variances of the generated short rates for all :math:`t_i`. This should converge to the theoretical variances calculated by :meth:`Var_rt`. .. seealso:: * :meth:`short_rate` * :meth:`Var_rt` """ return np.array([np.var(short_rate(i)) for i in range(step_size + 1)])
# --------------------------------------------------------------------------- # References np = ("Module", "numpy") step_size = 360 time_len = 30 a = 0.1 sigma = 0.1 seed1 = 1234 seed2 = 5678 scen_size = 1000