from tensorflow import Variable
import tensorflow
from tensorflow.python.framework import dtypes
from .stochasticprocess import StochasticProcess
from .ornsteinuhlenbeck import OrnsteinUhlenbeckProcess
from ..markethandles.ircurve import RateCurve
[docs]
class HullWhiteProcess(StochasticProcess):
"""
Hull-White interest rate model class, which models the evolution of short rates under a mean-reverting stochastic process.
Attributes:
_process (OrnsteinUhlenbeckProcess): Ornstein-Uhlenbeck process to simulate the short rate evolution.
_a (tf.Variable): Mean reversion speed (alpha).
_sigma (tf.Variable): Volatility of the process (sigma).
_term_structure (RateCurve): The term structure (yield curve) used for forward rate calculations.
"""
def __init__(self, term_structure: RateCurve, a: float, sigma: float):
"""
Initializes the Hull-White process.
Args:
term_structure (RateCurve): The term structure (yield curve) used for forward rate calculations.
a (float): Mean reversion speed (alpha) of the process.
sigma (float): Volatility of the process (sigma).
"""
self._process = OrnsteinUhlenbeckProcess(
mr_speed=a, volatility=sigma, x0=term_structure.inst_fwd(0)
)
self._a = Variable(a, dtype=dtypes.float64)
self._sigma = Variable(sigma, dtype=dtypes.float64)
self._term_structure = term_structure
[docs]
def size(self) -> int:
"""
Returns the dimensionality of the process.
Returns:
int: The dimensionality (size) of the underlying process.
"""
return self._process.size()
[docs]
def initial_values(self) -> float:
"""
Returns the initial value of the short rate.
Returns:
float: The initial value of the short rate (x0).
"""
return self._process.x0
@property
def a(self) -> float:
"""
Mean reversion speed (alpha) of the process.
Returns:
float: The mean reversion speed.
"""
return self._a.numpy()
@property
def sigma(self) -> float:
"""
Volatility (sigma) of the process.
Returns:
float: The volatility of the process.
"""
return self._sigma.numpy()
@property
def x0(self) -> float:
"""
Initial value of the process.
Returns:
float: The initial short rate (x0).
"""
return self._process.x0.numpy()
[docs]
def drift(self, t: float, x: float) -> float:
"""
Calculates the drift term of the process.
Args:
t (float): Current time.
x (float): Current value of the process.
Returns:
float: The drift value.
"""
alpha_drift = (
self._sigma**2 / (2 * self._a) * (1 - tensorflow.math.exp(-2 * self._a * t))
)
shift = 0.0001
f = self._term_structure.forward_rate(t, t)
fup = self._term_structure.forward_rate(t + shift, t + shift)
f_prime = (fup - f) / shift
alpha_drift += self._a * f + f_prime
return self._process.drift(t, x) + alpha_drift
[docs]
def diffusion(self, t: float, x: float) -> float:
"""
Calculates the diffusion term of the process.
Args:
t (float): Current time.
x (float): Current value of the process.
Returns:
float: The diffusion value.
"""
return self._process.diffusion(t, x)
[docs]
def expectation(self, t0: float, x0: float, dt: float) -> float:
"""
Computes the expectation of the process over time interval dt.
Args:
t0 (float): Start time of the interval.
x0 (float): Initial value of the process at time t0.
dt (float): Time increment.
Returns:
float: The expected value of the process at time t0 + dt.
"""
return (
self._process.expectation(x0=x0, dt=dt)
+ self.alpha(t0 + dt)
- self.alpha(t0) * tensorflow.math.exp(-self._a * dt)
)
[docs]
def std_deviation(self, dt: float, t0=None, x0=None) -> float:
"""
Returns the standard deviation of the process over the time interval dt.
Args:
dt (float): Time increment.
t0 (float, optional): Interface placeholder for starting time (default is None).
x0 (float, optional): Interface placeholder for starting value (default is None).
Returns:
float: The standard deviation of the process.
"""
return self._process.std_deviation(dt=dt)
[docs]
def variance(self, dt: float) -> float:
"""
Returns the variance of the process over the time interval dt.
Args:
dt (float): Time increment.
Returns:
float: The variance of the process.
"""
return self._process.variance(dt)
[docs]
def alpha(self, t: float) -> float:
"""
Computes the alpha (mean reversion level) term at time t.
Args:
t (float): The time at which alpha is calculated.
Returns:
float: The alpha value.
"""
if self.a > 1e-10:
alfa = (self._sigma / self._a) * (1 - tensorflow.math.exp(-self._a * t))
else:
alfa = self._sigma * t
alfa = 0.5 * alfa**2
alfa += self._term_structure.inst_fwd(t)
return alfa
[docs]
def A_B(self, S: float, T: float) -> tuple:
"""
Computes the time-dependent parameters A(S, T) and B(S, T) of a zero-coupon bond.
Args:
S (float): Start time in years (S <= T).
T (float): Maturity time in years.
Returns:
tuple: A(S, T) and B(S, T) parameters used in bond pricing.
"""
f0S = self._term_structure.inst_fwd(S)
P0T = self._term_structure.discount(T)
P0S = self._term_structure.discount(S)
B = 1 - tensorflow.math.exp(-self._a * (T - S))
B /= self._a
exponent = self.sigma * (
tensorflow.math.exp(-self._a * T) - tensorflow.math.exp(-self._a * S)
)
exponent *= exponent
exponent *= tensorflow.math.exp(2 * self._a * S) - 1
exponent /= -4 * (self._a**3)
exponent += B * f0S
A = tensorflow.math.exp(exponent) * P0T / P0S
return A, B
[docs]
def zero_bond(self, S: float, T: float, rs: float) -> float:
"""
Computes the price of a zero-coupon bond at future time S with maturity T.
Args:
S (float): Future reference time in years.
T (float): Maturity time in years.
rs (float): Short rate at time S.
Returns:
float: Price of the zero-coupon bond.
"""
A, B = self.A_B(S, T)
return A * tensorflow.math.exp(-B * rs)