Hull-White 短期利率模型:用 Ornstein-Uhlenbeck 过程给整条收益率曲线定价
Vasicek 模型能漂到负利率,在真实利率贴近 0 时很尴尬。Hull-White(1990)把扩散系数保留为常数、让中枢可时变,用 Ornstein-Uhlenbeck 过程给整条收益率曲线一个解析闭式。本文用自洽合成数据从零模拟利率路径、推导债券定价闭式、画出整条曲线随利率切换上凸/倒挂,并用池化 OU 最大似然把参数还原到真值,附完整 Python 与六类真实陷阱(中阶)。
Vasicek(1977)用 dr = a(b − r)dt + σ dW 把利率写成均值回复的 OU 过程,最妙的是债券价格和整条收益率曲线都有解析闭式。但它有个致命尴尬:扩散系数是常数 σ,无论利率多低,向下冲击的概率都一样大——于是利率会漂到负值。在 2010 年代欧日央行把政策利率压到 0 附近甚至负的世界里,“利率能变负” 反而成了优点;可一旦你研究的曲线中枢在 3%~6%,“模型告诉你利率有 20% 概率跌到 −2%” 就明显失真了。
Hull-White(1990) 在 Vasicek 骨架上做了一处关键升级:把常数中枢 b 换成一个随时间变化的中枢 θ(t),即
dr = (θ(t) − a·r) dt + σ dW
当 θ(t) 被取成常数 a·b 时,它退化回 Vasicek;而一般形式里 θ(t) 是专门选来让模型精确拟合当前整条收益率曲线的。本文先用最清晰的常数-θ 形式(= Vasicek 结构)把机制讲透,再指出真实 HW 怎么用 θ(t) 校准。
一、模型性质:为什么它永不穿零、又能回复#
短期利率 r_t 是看不见的隐状态,用 OU 过程驱动:
- 均值回复:
a越大,利率被拉回中枢越快;b是长期中枢。 - 永不穿零(常数 σ 的好处):当
r → 0时,扩散项σ dW的方差仍是σ² dt,但漂移项a(b − r)dt因为b > 0而始终向上拉。所以r会在 0 附近被弹簧拽住,不会持续为负——这正是 Vasicek/HW 比 “随机游走 + 对数” 类模型更贴合正利率区间的原因。 - 平稳分布:
r_∞ ~ N(b, σ²/2a)。取a=0.15, b=6%, σ=2.5%,稳态波动σ/√(2a) ≈ 4.56%。
下图用 1500 条精确 OU 递推模拟(r_t = α·r_{t−1} + b(1−α) + σ√(1−α²)/(2a)·Z,α=e^{−a dt}),利率清清楚楚向 6% 中枢回复,没有一条路径长期躺平在 0 以下。

二、解析定价:债券价格与整条曲线都是闭式#
OU 过程的条件分布是正态的,Hull-White 由此给出债券在 t 时刻、到期 T 的零息价格闭式:
B(τ) = (1 − e^{−a·τ}) / a # τ = T − t
A(t,T) = exp( B(τ) − τ + σ²/(2a²)·(τ − 2B + aB²)
− σ²/(4a³)·(1 − e^{−2aτ})·(1 − e^{−2at}) )
P(t,T) = A(t,T) · exp( −B(τ) · r_t ) # 核心定价公式plaintext收益率 y(t,T) = −ln P(t,T) / τ。把 P 的闭式代进去,收益率曲线相对中枢的偏移有极其干净的结构:
y(t,T) − b = −(r_t − b) · B(τ) / τ
也就是说:曲线是上凸还是倒挂,只由「当前短期利率 r_t 相对中枢 b 的偏离」乘以一个只依赖期限 τ 的因子决定。
r_t < b(利率偏低)→y − b > 0→ 曲线上凸(steepener),长端高于中枢。r_t > b(利率偏高)→y − b < 0→ 曲线倒挂(inverter),短端高于中枢。
下面取同一条路径上 4 个不同时刻的 r_t,画出当时整条收益率曲线——你能直接看到曲线随 r_t 在 “上凸 → 倒挂” 之间切换,而所有曲线都收敛到同一个中枢 b=6%。

三、量化曲线形态:期限溢价机制#
把上面 y − b = −(r_t − b)·B(τ)/τ 在固定期限上画出,就是一张 “期限溢价散点图”。取 2 年期,因子 B(2y)=1.728:
r_t − b = +3pp→2y期限溢价= −5.18%(曲线倒挂)r_t − b = −3pp→2y期限溢价= +5.18%(曲线上凸)
散点精确落在理论直线 y − b = −(r_t − b)·A 上——这说明 Hull-White 的曲线形态是完全由 r_t 相对 b 的位置决定的解析函数,没有任何自由参数在作怪。

更长期限的因子更大:A(5y)=3.518、A(10y)=5.179,意味着同样 3pp 的偏离,10 年期曲线摆动幅度接近 2 年期的 3 倍——长端对利率水平更敏感,这也正是 Hull-White 能刻画 “整条曲线平行移动 + 斜率变化” 的原因。
四、校准:把参数从噪声里还原出来#
OU 过程有个极大似然闭式:把所有路径的日度增量 Δr 对 r_{t−1} 池化(pooled),做一次 OLS 即可同时解出 a 和 b:
Δr = a·b·dt − a·dt·r_{t−1} + ε
=> 斜率 = −a·dt → a = −斜率 / dt
=> 截距 = a·b·dt → b = 截距 / (a·dt)plaintext我用 200 次「抽 300 条路径」的 Bootstrap 看抽样分布,池化 MLE 漂亮地还原了真值:
â = 0.1450(真值0.15,RMSE0.0147)、b̂ = 0.0599(真值0.06,RMSE0.0038)

但这里藏着全文最该记下的陷阱:如果你偷懒,对单条路径做 OLS(而不是池化),得到的 a 的 RMSE 高达 0.8827——是池化法的 60 倍。原因是 a=0.15 对应半衰期 ln2/a ≈ 4.6 年,单条 8 年路径里利率只来得及回复一两次,噪声完全淹没了漂移。结论:OU/HW 校准必须池化所有可用路径的增量,单路径估计在慢回复设定下不可用。
五、完整 Python 实现#
下面是从零复现上述全部图表的精简代码(与配图脚本逻辑一致,可直接运行):
import numpy as np
# ===== 1. 模型参数 =====
a, b, sigma, r0, dt = 0.15, 0.06, 0.025, 0.03, 1/252
T = int(8 / dt)
# ===== 2. 精确 OU 递推模拟路径 =====
rng = np.random.default_rng(20260719)
alpha = np.exp(-a * dt)
mu_step = b * (1 - alpha)
sd_step = sigma * np.sqrt((1 - alpha**2) / (2 * a))
paths = np.empty((1500, T + 1)); paths[:, 0] = r0
Z = rng.standard_normal((1500, T))
for i in range(T):
paths[:, i + 1] = alpha * paths[:, i] + mu_step + sd_step * Z[:, i]
# ===== 3. HW 债券定价闭式 =====
def hw_B(tau, a): return (1 - np.exp(-a * tau)) / a
def hw_A(t, T, a, sigma):
tau = T - t; B = hw_B(tau, a)
val = B - tau + (sigma**2/(2*a**2))*(tau - 2*B + a*B**2) \
- (sigma**2/(4*a**3))*(1 - np.exp(-2*a*tau))*(1 - np.exp(-2*a*t))
return np.exp(-val)
def bond_price(t, T, r_t, a, sigma):
return hw_A(t, T, a, sigma) * np.exp(-hw_B(T - t, a) * r_t)
# ===== 4. 收益率曲线快照 =====
m_list = np.array([0.25, 0.5, 1, 2, 3, 5, 7, 10])
for sd in [0, 63, 126, 189]:
r_t = paths[0, sd]; t_y = sd * dt
P = np.array([bond_price(t_y, t_y + m, r_t, a, sigma) for m in m_list])
y = -np.log(P) / m_list # 整条曲线
print(f"r_t={r_t:.3f} -> 2y 期限溢价={(-(r_t-b)*hw_B(2,a)*100):.2f}%")
# ===== 5. 池化 OU 校准 (正确做法) =====
dR = (paths[:, 1:] - paths[:, :-1]).ravel()
X = paths[:, :-1].ravel()
A_mat = np.vstack([np.ones_like(X), X]).T
intercept, slope = np.linalg.lstsq(A_mat, dR, rcond=None)[0]
a_hat, b_hat = -slope / dt, intercept / (-slope) # = b
print(f"池化校准: a={a_hat:.4f} b={b_hat:.4f}")python六、六类真实陷阱#
- 常数
θ退化成 Vasicek:本文为讲清机制用了θ(t)常数形式,但真实 Hull-White 必须用θ(t)拟合当前整条曲线,否则模型定出的曲线和市面曲线对不上、定价债券全是错的。 - 单路径 OLS 校准
a极噪:如上,a=0.15(半衰期 4.6 年)时单路径 RMSE 是池化法的 60 倍。慢回复设定下务必池化。 - 常数
σ假设装不下波动率微笑:HW 的波动率是期限无关的常数,但真实市场上短端隐含波动更陡(rough/SABR 效应)——它天生给不出波动率曲面,只能定价波动率,不能解释它。 - 正态假设在极端利率下失真:
r_t全程正态,意味着深负利率和利率飙升的概率被对称高估/低估;负利率深谷或政策急转弯时尾部会系统性偏薄。 - 允许
r < 0不等于业务允许:HW 数学上允许负利率。若你的抵押品/期权合约不允许负利率,需要加 floor 或换 CIR(平方根扩散,硬性非负)。 - 校准到的是风险中性测度:用市场价格校准出来的
a, b, σ是 Q 测度下的参数,用来定价正确;但用它做”真实世界利率预测”需要额外叠加市场风险价格λ(t),直接拿 Q 参数当 P 测度预测会系统性偏误。
结语#
Hull-White 的价值不在”预测利率”,而在给整条收益率曲线一个可解析、可校准、永不穿零的定价引擎:债券价格闭式 P = A·e^{−B·r_t}、曲线形态由 r_t 相对中枢的偏离干净决定、参数能用池化 OU 似然精确还原。理解它,你就拿到了利率衍生品(利率互换、 swaption、可赎回债)定价的钥匙——下一篇我们用 Dupire 局部波动率把同样的思想从”利率”搬到”股票波动率曲面”,看一条额外的状态变量如何把微笑也装进模型。