Hull-White Model Class Guide¶
This guide describes the design and implementation of the HullWhite class, the central object for one-factor Hull-White model computations. The class encapsulates the model parameters \((\sigma, \lambda)\) and the market yield curve \(P^M(0,T)\), and provides methods for computing the named functions, bond prices, short rate simulation, and derivative pricing. The companion .py file contains the full implementation; this page explains the design choices and maps each method to its mathematical formula.
Class Overview¶
The HullWhite class is initialized with three inputs:
sigma: the short rate volatility \(\sigma > 0\)lambd: the mean-reversion speed \(\lambda > 0\)P: a callable representing the market discount curve \(P^M(0, T)\)
All other quantities (\(\theta(t)\), \(A(t,T)\), \(B(t,T)\), the short rate distribution parameters) are derived from these three inputs.
Constructor and Parameters¶
python
class HullWhite:
def __init__(self, sigma, lambd, P):
self.sigma = sigma
self.lambd = lambd
self.P = P
The market curve P is a function \(P^M: [0, \infty) \to (0, 1]\) satisfying \(P^M(0) = 1\). In practice, this is obtained by interpolating market bond prices or by fitting a parametric model (e.g., Nelson-Siegel) to swap rates.
Named Function Methods¶
compute_B(t, T)¶
Computes the duration-like function:
This function appears in the bond price formula, the bond volatility, and the measure change.
compute_A(t, T)¶
Computes the bond price intercept function by numerical integration:
The integral is evaluated using the trapezoidal rule on a grid of 250 points. The function compute_theta provides the integrand.
compute_theta(t)¶
Computes the time-dependent drift:
where \(f^M(0, t)\) is the instantaneous forward rate, obtained by numerical differentiation of \(\ln P^M(0, t)\).
compute_theta_T(t, T)¶
Computes the drift under the \(T\)-forward measure:
Bond Pricing Methods¶
compute_ZCB(t, T, r_t)¶
Computes the zero-coupon bond price:
compute_sigma_P(t, T)¶
Computes the bond price volatility:
Short Rate Distribution Methods¶
compute_mu_r_T(T)¶
Computes the conditional mean of \(r_T\) under \(\mathbb{Q}\):
compute_mu_r_T_ForwardMeasure(T)¶
Computes the conditional mean of \(r_T\) under \(\mathbb{Q}^T\):
compute_sigma_square_r_T(T)¶
Computes the conditional variance of \(r_T\):
This variance is the same under both \(\mathbb{Q}\) and \(\mathbb{Q}^T\) because the Girsanov transformation only changes the drift, not the diffusion.
Simulation Methods¶
generate_sample_paths(num_paths, num_steps, T, seed)¶
Generates short rate paths using Euler-Maruyama discretization:
where \(Z_{i+1} \sim N(0, 1)\). The method also computes the money market account \(M(t_i) = \prod_{k=0}^{i-1}e^{r_{t_k}\Delta t}\).
Returns: time grid t, short rate paths R, money market account M.
Moment Matching
When num_paths > 1, the normal draws are centered and standardized: \(Z_i \leftarrow (Z_i - \bar{Z}) / \text{std}(Z)\). This ensures that the sample mean and variance match the theoretical values exactly, reducing Monte Carlo bias for finite samples.
Derivative Pricing Methods¶
compute_ZCB_Option_Price(K, T1, T2, CP)¶
Prices a European call or put on a ZCB \(P(T_1, T_2)\) with strike \(K\):
The put price uses put-call parity: \(\text{Put} = \text{Call} - P(0, T_2) + K\,P(0, T_1)\).
compute_Caplet_Floorlet_Price(N, K, T1, T2, CP)¶
Prices a caplet or floorlet using the caplet-as-ZCB-put relationship:
where \(\delta = T_2 - T_1\).
compute_SwapPrice(t, r_t, notional, K, Ti, Tm, n, CP)¶
Computes the swap value at time \(t\) given \(r_t\):
Design Principles¶
- Single responsibility: each method computes one named function or formula
- Composability: complex computations (e.g., swaption pricing via Jamshidian) compose simpler methods
- Market curve as input: the class takes \(P^M(0,T)\) as a callable, allowing any curve representation (interpolation, parametric, bootstrap)
- Numerical differentiation: forward rates and their derivatives are computed by central differences with step size \(10^{-4}\), avoiding the need for an explicit forward curve representation
Summary¶
The HullWhite class implements the complete one-factor Hull-White model computation chain: from model parameters and market curve through named functions (\(\theta\), \(A\), \(B\)), bond pricing, short rate simulation, and derivative pricing (ZCB options, caplets, swaps). Each method maps directly to a mathematical formula from the theory sections, with numerical integration and differentiation used where closed-form expressions require the initial forward curve. The class is designed for composability, enabling the construction of higher-level pricing and calibration routines.
Exercises¶
Exercise 1. Using a flat market curve at 4\% and Hull-White parameters \(\sigma = 0.01\), \(\lambda = 0.10\), compute \(\theta(t)\) for \(t = 0.5, 1, 5, 10\) years. Verify that in the flat-curve case, \(\theta(t)\) converges to the long-run level \(r + \sigma^2/(2\lambda^2)\) as \(t \to \infty\). What is the numerical value of this limit?
Solution to Exercise 1
For a flat market curve at 4%, we have \(P^M(0, t) = e^{-0.04\,t}\), so the instantaneous forward rate is \(f^M(0, t) = 0.04\) for all \(t\), and \(\partial f^M / \partial t = 0\). The \(\theta(t)\) formula simplifies to:
Computing at the specified times:
- \(t = 0.5\): \(\theta(0.5) = 0.04 + 0.005(1 - e^{-0.1}) = 0.04 + 0.005 \times 0.09516 = 0.04048\)
- \(t = 1\): \(\theta(1) = 0.04 + 0.005(1 - e^{-0.2}) = 0.04 + 0.005 \times 0.18127 = 0.04091\)
- \(t = 5\): \(\theta(5) = 0.04 + 0.005(1 - e^{-1.0}) = 0.04 + 0.005 \times 0.63212 = 0.04316\)
- \(t = 10\): \(\theta(10) = 0.04 + 0.005(1 - e^{-2.0}) = 0.04 + 0.005 \times 0.86466 = 0.04432\)
As \(t \to \infty\), \(e^{-2\lambda t} \to 0\), so:
The long-run level is \(0.045\) or 4.5%. The convexity adjustment \(\sigma^2/(2\lambda^2)\) raises the drift target above the flat rate to compensate for the Jensen inequality effect in bond pricing.
Exercise 2. The compute_B(t, T) method returns \((e^{-\lambda(T-t)} - 1)/\lambda\). Show that \(B(t, T) \to -(T-t)\) as \(\lambda \to 0\) and \(B(t, T) \to -1/\lambda\) as \(T - t \to \infty\). Interpret each limit financially in terms of the bond price sensitivity to the short rate.
Solution to Exercise 2
Limit as \(\lambda \to 0\): Apply L'Hopital's rule or a Taylor expansion. Write \(e^{-\lambda\tau} \approx 1 - \lambda\tau + \frac{1}{2}\lambda^2\tau^2 - \cdots\), so:
As \(\lambda \to 0\), \(B(t, T) \to -(T - t)\). This is the Vasicek limit with zero mean reversion: the bond price has log-linear sensitivity to the short rate with coefficient \(-(T-t)\), identical to the duration of a zero-coupon bond under flat rates.
Limit as \(T - t \to \infty\): As \(\tau \to \infty\), \(e^{-\lambda\tau} \to 0\), so:
This means the sensitivity of the bond price to the short rate saturates at \(-1/\lambda\). Financially, mean reversion implies that the short rate will eventually revert to its long-run level regardless of its current value. Therefore, very long-dated bonds are insensitive to the current short rate beyond the mean-reversion horizon \(1/\lambda\). A rate shock today has an effect that decays exponentially, so the bond price sensitivity cannot grow without bound.
Exercise 3. The variance of the short rate is \(\sigma_r^2(T) = \frac{\sigma^2}{2\lambda}(1 - e^{-2\lambda T})\). This is the same under both \(\mathbb{Q}\) and \(\mathbb{Q}^T\). Explain why the Girsanov theorem guarantees this invariance. Compute \(\sigma_r(T)\) for \(T = 1\) and \(T = 10\) with \(\sigma = 0.01\), \(\lambda = 0.05\).
Solution to Exercise 3
The Girsanov theorem states that the change of measure from \(\mathbb{Q}\) to \(\mathbb{Q}^T\) transforms the drift of the Brownian motion but does not alter the diffusion coefficient. In the Hull-White SDE:
Under \(\mathbb{Q}^T\), the SDE becomes:
The volatility \(\sigma\) is unchanged because the Girsanov density only introduces a drift adjustment to the Brownian motion. Since the conditional variance depends only on \(\sigma\) and \(\lambda\) (not on the drift), it is invariant under the measure change.
Computing \(\sigma_r(T)\) with \(\sigma = 0.01\), \(\lambda = 0.05\):
For \(T = 1\):
For \(T = 10\):
The standard deviation grows from 0.976% at 1 year to 2.514% at 10 years, approaching the asymptotic limit \(\sigma/\sqrt{2\lambda} = 0.01/\sqrt{0.1} \approx 3.162\%\).
Exercise 4. The generate_sample_paths method uses moment matching: \(Z_i \leftarrow (Z_i - \bar{Z})/\text{std}(Z)\). Explain why this reduces Monte Carlo bias. Generate 10,000 paths for \(T = 5\) years with \(\sigma = 0.01\), \(\lambda = 0.05\), flat curve at 3\%, and compare the sample mean and variance of \(r_5\) with the analytical values from compute_mu_r_T(5) and compute_sigma_square_r_T(5).
Solution to Exercise 4
Why moment matching reduces bias: In a standard Monte Carlo simulation, finite-sample draws from \(N(0,1)\) will have sample mean \(\bar{Z} \neq 0\) and sample variance \(s^2 \neq 1\). These sampling errors propagate through the Euler-Maruyama scheme, biasing the terminal distribution of \(r_T\). By applying \(Z_i \leftarrow (Z_i - \bar{Z})/\text{std}(Z)\), we enforce \(\bar{Z} = 0\) and \(s^2 = 1\) exactly at each time step, eliminating the first two sources of finite-sample bias. This ensures the simulated paths have the correct drift and diffusion properties at every step.
For the numerical comparison with \(\sigma = 0.01\), \(\lambda = 0.05\), flat curve at 3%, \(T = 5\):
Analytical values:
With a flat curve, \(\theta(s) = r + \frac{\sigma^2}{2\lambda^2}(1 - e^{-2\lambda s})\) where \(r = 0.03\). The analytical conditional mean evaluates to approximately \(\mu_r(5) \approx 0.03 + \frac{\sigma^2}{2\lambda^2}(1 - e^{-2\lambda \cdot 5}) \times (\text{integrated contribution}) \approx 0.03079\).
With moment matching and 10,000 paths, the sample mean and variance of \(r_5\) should match these analytical values to within \(O(1/\sqrt{N})\) statistical error, which for \(N = 10{,}000\) is of order \(10^{-4}\) for the mean.
Exercise 5. The compute_theta_T method adjusts \(\theta(t)\) for the \(T\)-forward measure. Show that the adjustment \(\sigma^2 B(T-t)/\lambda\) has the correct sign to produce a lower expected short rate under the \(T\)-forward measure compared to \(\mathbb{Q}\) (when \(T > t\)). Why does a lower expected rate under the \(T\)-forward measure make economic sense for bond pricing?
Solution to Exercise 5
The adjustment to the drift under the \(T\)-forward measure is:
Since \(B(\tau) = (e^{-\lambda\tau} - 1)/\lambda < 0\) for all \(\tau > 0\) (because \(e^{-\lambda\tau} < 1\)), the adjustment \(\frac{\sigma^2}{\lambda}B(T - t)\) is negative when \(T > t\).
Therefore \(\theta^{\mathbb{T}}(t) < \theta(t)\), which means the drift of the short rate is lower under the \(T\)-forward measure than under \(\mathbb{Q}\). This produces a lower expected short rate under \(\mathbb{Q}^T\).
Economic interpretation: Under the \(T\)-forward measure \(\mathbb{Q}^T\), the numeraire is the zero-coupon bond \(P(t, T)\). When the short rate is lower, bond prices are higher. The \(T\)-forward measure "tilts" the distribution toward states where the bond (the numeraire) has high value, i.e., low-rate states. This is the Radon-Nikodym effect: the measure change reweights paths by \(P(t, T)/P(0, T)\), giving more probability weight to paths with low rates. This tilt is essential for the forward-measure pricing formula \(\mathbb{E}^{\mathbb{Q}^T}[V(T)] = V(0)/P(0, T)\) to hold.
Exercise 6. Using the compute_SwapPrice method, price a 5-year payer swap (annual payments) with fixed rate \(K = 3\%\) at \(t = 0\) with \(r_0 = 3\%\) and a flat market curve at 3\%. Verify that the swap value is approximately zero (since \(K\) equals the par rate). Then compute the swap value at \(t = 0\) for \(K = 4\%\) and interpret the sign.
Solution to Exercise 6
For a 5-year payer swap with annual payments, \(K = 3\%\), \(t = 0\), \(r_0 = 3\%\), flat curve at 3%:
With \(\delta_k = 1\) (annual), \(T_k = k\), and \(P(0, k) = e^{-0.03k}\):
The par rate for this swap satisfies \(P(0, 0) - P(0, 5) = K_{\text{par}} \sum P(0, T_k)\), giving \(K_{\text{par}} = (1 - e^{-0.15})/4.57377 = 0.13929/4.57377 \approx 0.03046\) or about 3.046% (slightly above 3% due to continuous vs. discrete compounding effects).
Since the par rate is approximately 3.046%, a swap with \(K = 3\%\) has a small positive value for the payer (receiving floating, paying fixed at a rate slightly below par): \(V \approx N \times (K_{\text{par}} - K) \times \text{annuity} \approx N \times 0.00046 \times 4.574 \approx 0.0021 \times N\).
For \(K = 4\%\), the payer swap has a negative value because the fixed rate paid (4%) exceeds the par rate (3.046%). The payer is locked into paying above-market fixed rates:
The negative sign confirms the payer is at a disadvantage.
Exercise 7. The compute_ZCB_Option_Price method prices European options on zero-coupon bonds. Price a call on \(P(2, 5)\) with strike \(K = 0.92\), using \(\sigma = 0.01\), \(\lambda = 0.05\), and a flat curve at 3\%. Then use put-call parity to obtain the put price. Verify by calling the method directly with CP=OptionType.PUT.
Solution to Exercise 7
With \(\sigma = 0.01\), \(\lambda = 0.05\), flat curve at 3%, we price a call on \(P(2, 5)\) with strike \(K = 0.92\).
First, compute the bond price volatility:
Next, compute the relevant bond prices:
Compute \(d_1\) and \(d_2\):
The call price:
Using put-call parity:
Calling the method with CP=OptionType.PUT directly should return the same value of approximately \(0.01596\).