02-利率衍生品 (Interest Rate Derivatives)
预计学习时间:3 小时
难度:⭐⭐⭐⭐
核心问题:利率不是单个数字,而是一条曲线。整条曲线的随机变动怎么建模和定价?
概述
利率衍生品的定价逻辑和股票期权类似(都是用无套利原理和风险中性定价),但有一个根本性的区别:
- 股票:一个随机变量(股价 S),用 BSM 搞定
- 利率:一整条曲线(从隔夜到 30 年),每个期限的利率都在变动
所以利率建模的核心挑战不是”一个变量怎么变”,而是”一整条曲线怎么一起变”。
"借钱利率" → 即期/远期利率 → IRS → Cap/Floor → Swaption → 短利率模型 → Bootstrap
一、利率基础:从”借钱利率”讲起
1.1 即期利率(Spot Rate)
白话解释:即期利率就是”今天借一笔钱,约定某个期限,到期一起还本付息”的年化利率。
| 期限 | 即期利率 | 含义 |
|---|---|---|
| 1 年 | 3.0% | 今天借 100,一年后还 103 |
| 2 年 | 3.5% | 今天借 100,两年后还 100 × (1.035)^2 = 107.12 |
| 3 年 | 3.8% | 今天借 100,三年后还 100 × (1.038)^3 = 111.83 |
如果一年后你再借一年,利率不一定是 3.5%——那取决于一年后的市场情况。
1.2 远期利率(Forward Rate)
白话解释:远期利率是”今天约定,未来某个时间借一笔钱”的利率。你今天签约锁定利率,未来到期时执行。
即期利率与远期利率的关系(无套利):
白话解读:“先借 t1 年,再借 t2-t1 年”的总效果,必须等于”直接借 t2 年”。如果不等,就存在套利机会。
用连续复利:
import numpy as np
import matplotlib.pyplot as plt
# ============================================================
# 即期利率与远期利率的关系
# ============================================================
# 模拟即期利率曲线(向上倾斜)
maturities = np.array([0.5, 1.0, 1.5, 2.0, 3.0, 5.0, 7.0, 10.0])
spot_rates = np.array([0.025, 0.030, 0.033, 0.035, 0.038, 0.042, 0.044, 0.045])
# 从即期利率计算远期利率
# f(t1, t2) = (r2 * t2 - r1 * t1) / (t2 - t1)
forward_rates = []
forward_periods = []
for i in range(len(maturities) - 1):
t1 = maturities[i]
t2 = maturities[i + 1]
r1 = spot_rates[i]
r2 = spot_rates[i + 1]
f = (r2 * t2 - r1 * t1) / (t2 - t1)
forward_rates.append(f)
forward_periods.append(f"{t1}-{t2}")
print("即期利率与远期利率")
print("=" * 60)
print(f"{'期限':>10} {'即期利率':>10} {'远期利率':>10} {'远期区间':>10}")
print("-" * 45)
print(f"{'0.5年':>10} {spot_rates[0]:>10.3%} {'':>10} {'':>10}")
for i in range(len(forward_rates)):
print(f"{maturities[i+1]:>6.1f}年 {spot_rates[i+1]:>10.3%} {forward_rates[i]:>10.3%} {forward_periods[i]:>10}")
# 可视化
fig, ax = plt.subplots(figsize=(10, 5))
ax.plot(maturities, spot_rates * 100, 'bo-', lw=2, markersize=6, label='即期利率')
# 远期利率画在中点
mid_points = [(maturities[i] + maturities[i+1]) / 2 for i in range(len(forward_rates))]
ax.plot(mid_points, [f * 100 for f in forward_rates], 'rs--', lw=1.5,
markersize=5, label='远期利率')
ax.set_xlabel('期限(年)')
ax.set_ylabel('利率(%)')
ax.set_title('即期利率曲线 vs 远期利率')
ax.legend()
ax.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
print("\n关键观察:")
print(" 向上倾斜的收益率曲线 → 远期利率 > 即期利率")
print(" 这是市场的正常状态(借的时间越长,利率越高)")1.3 零利率曲线(Zero Curve)
零利率曲线(也叫收益率曲线)是不同期限的即期利率连成的曲线。它是所有利率产品定价的基础——就像 BSM 公式中的 S(股价)一样重要。
常见的收益率曲线形状:
| 形状 | 含义 | 典型场景 |
|---|---|---|
| 向上倾斜(Normal) | 长期利率 > 短期利率 | 正常经济环境 |
| 平坦(Flat) | 各期限利率差不多 | 加息周期末端 |
| 倒挂(Inverted) | 短期利率 > 长期利率 | 经济衰退预警 |
| 驼峰形(Humped) | 中期利率最高 | 不常见 |
二、利率互换(IRS)
2.1 什么是利率互换
白话解释:两方约定,一方付固定利率,另一方付浮动利率,交换利息的现金流。
具体例子:
A 公司借了一笔浮动利率贷款(利率 = SHIBOR + 1%)。A 公司担心利率上升,想换成固定利率。
B 公司借了一笔固定利率贷款(利率 = 5%)。B 公司预期利率会下降,想换成浮动利率。
于是 A 和 B 签了一个 IRS:
- A 每季度付给 B 固定利率 5%
- B 每季度付给 A 浮动利率 SHIBOR
结果:A 成功把浮动利率换成了固定利率,B 成功把固定利率换成了浮动利率。双方都满意。
2.2 IRS 的定价
核心思想:互换的价值 = 固定端现值 - 浮动端现值。
在互换签约时,价值应该等于 0(否则一方就不愿意签了)。这意味着:
import numpy as np
# ============================================================
# 利率互换(IRS)定价
# ============================================================
def swap_fixed_rate(notional, payment_dates, spot_rates):
"""
计算使互换价值为 0 的固定利率(互换利率)
参数:
notional: 名义本金
payment_dates: 付款日期列表(年)
spot_rates: 对应期限的即期利率(连续复利)
返回:
互换利率(固定端利率)
"""
# 浮动端:每个付款日的浮动端 PV(简化假设:使用即期利率折现)
float_pv = 0
fixed_pv = 0
for i, t in enumerate(payment_dates):
# 找到对应的即期利率(线性插值)
r_t = np.interp(t, spot_rates[:, 0], spot_rates[:, 1])
discount = np.exp(-r_t * t)
# 浮动端:假设每期按即期利率付息
if i == 0:
float_leg = notional * (np.exp(r_t * t) - 1) * discount
else:
# 远期利率作为浮动端利率
r_prev = np.interp(payment_dates[i-1], spot_rates[:, 0], spot_rates[:, 1])
f_rate = (r_t * t - r_prev * payment_dates[i-1]) / (t - payment_dates[i-1])
dt = t - payment_dates[i-1]
float_leg = notional * (np.exp(f_rate * dt) - 1) * discount
float_pv += float_leg
# 固定端的折现因子之和(年金因子)
fixed_pv += discount * dt if i > 0 else discount * t
# 互换利率
swap_rate = float_pv / (notional * fixed_pv)
return swap_rate
# 构造即期利率曲线
spot_rates_data = np.array([
[0.25, 0.025], # 3 个月
[0.50, 0.030], # 6 个月
[1.00, 0.032], # 1 年
[1.50, 0.034], # 1.5 年
[2.00, 0.035], # 2 年
[2.50, 0.036], # 2.5 年
[3.00, 0.037], # 3 年
[4.00, 0.039], # 4 年
[5.00, 0.040], # 5 年
])
notional = 100_000_000 # 1 亿名义本金
payment_dates = np.array([0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0, 4.5, 5.0])
# 计算互换利率
swap_rate = swap_fixed_rate(notional, payment_dates, spot_rates_data)
print("=" * 50)
print("利率互换定价")
print("=" * 50)
print(f"名义本金: {notional/1e8:.0f} 亿元")
print(f"期限: 5 年,每半年交换一次")
print(f"计算得到的互换利率(固定端): {swap_rate:.4%}")
print()
print("白话解释:")
print(f" 如果你支付固定利率 {swap_rate:.2%},收到浮动利率 SHIBOR,")
print(f" 那么这个互换在签约时的价值为 0(公平定价)。")2.3 IRS 的估值
签约之后,利率曲线发生了变化,互换就有了正的或负的价值。
白话:如果你签了”付 4% 收 SHIBOR”的互换,然后 SHIBOR 涨到了 5%,那你就赚了——因为你能用 4% 的成本换来 5% 的浮动利率。
import numpy as np
# ============================================================
# IRS 估值(签约后利率变化)
# ============================================================
def value_swap(notional, fixed_rate, payment_dates, old_curve, new_curve):
"""
评估 IRS 的当前价值
参数:
notional: 名义本金
fixed_rate: 签约时的固定利率
payment_dates: 付款日期列表
old_curve: 签约时的即期利率曲线
new_curve: 当前(新)的即期利率曲线
"""
remaining_dates = payment_dates[payment_dates > 0.25] # 假设已经过了一段时间
fixed_leg_pv = 0
float_leg_pv = 0
for i, t in enumerate(remaining_dates):
r_new = np.interp(t, new_curve[:, 0], new_curve[:, 1])
discount = np.exp(-r_new * t)
# 固定端现值
if i == 0:
dt = t
else:
dt = t - remaining_dates[i-1]
fixed_leg_pv += notional * fixed_rate * dt * discount
# 浮动端现值(用当前远期利率)
if i == 0:
prev_t = 0.25 # 假设互换从 0.25 年开始
else:
prev_t = remaining_dates[i-1]
r_new_prev = np.interp(prev_t, new_curve[:, 0], new_curve[:, 1])
fwd_rate = (r_new * t - r_new_prev * prev_t) / (t - prev_t)
float_leg_pv += notional * (np.exp(fwd_rate * (t - prev_t)) - 1) * discount
swap_value = float_leg_pv - fixed_leg_pv
return swap_value, fixed_leg_pv, float_leg_pv
# 模拟利率变化:SHIBOR 曲线整体上移 1%
new_curve = spot_rates_data.copy()
new_curve[:, 1] += 0.01 # 利率上升 1%
swap_val, fixed_pv, float_pv = value_swap(
100_000_000, swap_rate, payment_dates,
spot_rates_data, new_curve
)
print("=" * 50)
print("IRS 估值(利率上升 1% 后)")
print("=" * 50)
print(f"签约固定利率: {swap_rate:.4%}")
print(f"固定端现值: {fixed_pv:>15,.0f} 元")
print(f"浮动端现值: {float_pv:>15,.0f} 元")
print(f"互换价值(浮动端 - 固定端): {swap_val:>15,.0f} 元")
print(f"\n白话解释:你付固定 {swap_rate:.2%},收到浮动利率。")
print(f"利率上升后,浮动端更值钱 → 你赚了 {swap_val/1e6:.2f} 百万元。")三、上限(Cap)与下限(Floor)
3.1 什么是 Cap
白话解释:Cap 就是一个”利率保险”。你向银行付一笔保费,银行承诺:如果未来浮动利率超过了约定上限,银行补偿你差额。
具体例子:你借了一笔浮动利率贷款,利率 = SHIBOR + 0.5%。你担心利率暴涨。
你买了一个 Cap,上限利率 = 5%:
- 如果 SHIBOR + 0.5% = 7%,超过上限 5% → 银行补给你 2%
- 如果 SHIBOR + 0.5% = 3%,没超过上限 → 什么都得不到
Floor 完全对称:银行给你保底,如果利率低于下限,银行付你差额。
Cap = 一串欧式 Call 的组合(每个付息日对应一个 Caplet)
Floor = 一串欧式 Put 的组合(每个付息日对应一个 Floorlet)
3.2 Black 公式定价
Cap 的定价使用 Black 公式(76 模型):
其中:
白话解读:每个 Caplet 就是一个”看涨期权”,标的资产是远期利率,行权价是上限利率。
import numpy as np
from scipy.stats import norm
import matplotlib.pyplot as plt
# ============================================================
# Cap / Floor 定价(Black 公式)
# ============================================================
def caplet_black(F, K, T, sigma, r, notional, delta):
"""
单个 Caplet 的 Black 公式定价
参数:
F: 远期利率
K: 上限利率(行权价)
T: 到期时间
sigma: 远期利率的波动率
r: 折现利率
notional: 名义本金
delta: 付息期间(年)
"""
d1 = (np.log(F / K) + 0.5 * sigma**2 * T) / (sigma * np.sqrt(T))
d2 = d1 - sigma * np.sqrt(T)
caplet = notional * delta * np.exp(-r * T) * (F * norm.cdf(d1) - K * norm.cdf(d2))
return caplet
def floorlet_black(F, K, T, sigma, r, notional, delta):
"""Floorlet = K*N(-d2) - F*N(-d1)(看跌期权)"""
d1 = (np.log(F / K) + 0.5 * sigma**2 * T) / (sigma * np.sqrt(T))
d2 = d1 - sigma * np.sqrt(T)
floorlet = notional * delta * np.exp(-r * T) * (K * norm.cdf(-d2) - F * norm.cdf(-d1))
return floorlet
# 参数
notional = 100_000_000 # 1 亿
cap_strike = 0.05 # 上限 5%
sigma = 0.20 # 远期利率波动率 20%
# 付息日和远期利率
payment_dates = np.array([0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0, 4.5, 5.0])
forward_rates = np.array([0.030, 0.033, 0.035, 0.038, 0.040, 0.041, 0.042, 0.043, 0.044, 0.045])
delta = 0.5 # 每半年付息
r_discount = 0.035 # 折现利率
print("=" * 60)
print("Cap 定价(Black 公式)")
print("=" * 60)
print(f"名义本金: {notional/1e8:.0f} 亿")
print(f"上限利率: {cap_strike:.2%}")
print(f"波动率: {sigma:.2%}")
print()
print(f"{'日期':>6} {'远期利率':>10} {'Caplet价值':>12} {'累计':>12}")
print("-" * 45)
total_cap = 0
for i, (t, F) in enumerate(zip(payment_dates, forward_rates)):
cp = caplet_black(F, cap_strike, t, sigma, r_discount, notional, delta)
total_cap += cp
print(f"{t:>5.1f}年 {F:>10.3%} {cp:>12,.0f} {total_cap:>12,.0f}")
print(f"\nCap 总价格: {total_cap:>12,.0f} 元 ({total_cap/notional*100:.4f}%)")
print(f"\n白话解释:花 {total_cap/1e4:,.0f} 万元买这个 Cap,")
print(f"相当于给利率买了 5% 的'保险'。")
print(f"如果未来任何一次付息时 SHIBOR 超过 5%,你就受到保护。")
# Cap 价值随上限利率的变化
K_range = np.linspace(0.02, 0.08, 100)
cap_prices = []
for K_val in K_range:
total = sum(caplet_black(F, K_val, t, sigma, r_discount, notional, delta)
for t, F in zip(payment_dates, forward_rates))
cap_prices.append(total)
fig, ax = plt.subplots(figsize=(10, 5))
ax.plot(K_range, np.array(cap_prices) / 1e6, 'b-', lw=2)
ax.axvline(x=0.05, color='red', linestyle='--', alpha=0.7, label='Cap Strike = 5%')
ax.set_xlabel('上限利率 K')
ax.set_ylabel('Cap 价格(百万元)')
ax.set_title('Cap 价格 vs 上限利率')
ax.legend()
ax.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()3.3 Cap-Floor Parity
与 Put-Call Parity 类似:
白话解读:买 Cap + 卖 Floor 的效果 = 付固定利率(利率超过上限时赚,低于下限时亏)。这和利率互换中”付固定收浮动”的效果一样。
四、Swaption(互换期权)
4.1 什么是 Swaption
白话解释:Swaption = 互换(Swap)+ 期权(Option)。你付一笔钱,获得”未来某个时间进入一个利率互换”的权利。
- Payer Swaption(支付方互换期权):有权在未来支付固定利率、收到浮动利率(本质上是一个看涨期权)
- Receiver Swaption(接收方互换期权):有权在未来收到固定利率、支付浮动利率(本质上是一个看跌期权)
4.2 Black 公式定价 Swaption
Swaption 的定价使用 Black 公式的另一种形式:
其中 是互换的年金因子, 是远期互换利率, 是互换期权的行权利率。
import numpy as np
from scipy.stats import norm
# ============================================================
# Swaption 定价(Black 公式)
# ============================================================
def swaption_black(swap_rate, strike_rate, T_expiry, T_swap, sigma, r):
"""
Swaption 的 Black 公式定价
参数:
swap_rate: 远期互换利率(连续复利)
strike_rate: 行权互换利率
T_expiry: 期权到期时间
T_swap: 互换期限
sigma: 互换利率波动率
r: 折现利率
"""
# 年金因子(简化计算)
n_payments = int(T_swap / 0.5) # 每半年付息
dt = 0.5
annuity = sum(np.exp(-r * (T_expiry + (i+1) * dt)) * dt for i in range(n_payments))
# Black 公式
d1 = (np.log(swap_rate / strike_rate) + 0.5 * sigma**2 * T_expiry) / \
(sigma * np.sqrt(T_expiry))
d2 = d1 - sigma * np.sqrt(T_expiry)
payer = annuity * (swap_rate * norm.cdf(d1) - strike_rate * norm.cdf(d2))
receiver = annuity * (strike_rate * norm.cdf(-d2) - swap_rate * norm.cdf(-d1))
return payer, receiver, annuity
# 参数
swap_rate = 0.04 # 当前远期互换利率 4%
strike = 0.04 # 行权利率 4%
T_expiry = 1.0 # 1 年后可以行权
T_swap = 5.0 # 进入一个 5 年期互换
sigma = 0.20 # 波动率 20%
r = 0.035 # 折现利率
payer_price, receiver_price, annuity = swaption_black(swap_rate, strike, T_expiry, T_swap, sigma, r)
print("=" * 50)
print("Swaption 定价(Black 公式)")
print("=" * 50)
print(f"远期互换利率: {swap_rate:.2%}")
print(f"行权利率: {strike:.2%}")
print(f"期权到期: {T_expiry} 年后")
print(f"互换期限: {T_swap} 年")
print(f"波动率: {sigma:.2%}")
print(f"年金因子: {annuity:.4f}")
print(f"\nPayer Swaption 价格: {payer_price:.6f}(每 1 元名义本金)")
print(f"Receiver Swaption 价格: {receiver_price:.6f}")
print()
print("白话解释:")
print(f" 花钱买 Payer Swaption = 买了一个'付固定收浮动'的权利")
print(f" 如果未来互换利率涨到 > {strike:.2%},你行权就赚了")
# Swaption 波动率矩阵
print("\n" + "=" * 60)
print("Swaption 波动率矩阵(示意)")
print("=" * 60)
print("每个格子对应一个 (期权期限, 互换期限) 组合的波动率\n")
option_tenors = ['1M', '3M', '6M', '1Y', '2Y', '5Y']
swap_tenors = ['1Y', '2Y', '5Y', '10Y']
# 简化的波动率矩阵
vol_matrix = np.array([
[18, 20, 22, 24],
[19, 21, 23, 25],
[20, 22, 24, 26],
[21, 23, 25, 27],
[20, 22, 24, 25],
[18, 20, 21, 22],
])
print(f"{'期权期限':<10}", end='')
for st in swap_tenors:
print(f"{st:>8}", end='')
print()
print("-" * 45)
for i, ot in enumerate(option_tenors):
print(f"{ot:<10}", end='')
for j in range(len(swap_tenors)):
print(f"{vol_matrix[i,j]:>7}%", end='')
print()
# 可视化
fig, ax = plt.subplots(figsize=(8, 5))
im = ax.imshow(vol_matrix, cmap='YlOrRd', aspect='auto')
ax.set_xticks(range(len(swap_tenors)))
ax.set_xticklabels(swap_tenors)
ax.set_yticks(range(len(option_tenors)))
ax.set_yticklabels(option_tenors)
ax.set_xlabel('底层互换期限')
ax.set_ylabel('期权到期期限')
ax.set_title('Swaption 波动率矩阵(Volatility Cube 切片)')
for i in range(len(option_tenors)):
for j in range(len(swap_tenors)):
ax.text(j, i, f'{vol_matrix[i,j]}%', ha='center', va='center',
color='black' if vol_matrix[i,j] < 23 else 'white')
plt.colorbar(im, ax=ax, label='波动率(%)')
plt.tight_layout()
plt.show()五、短利率模型
5.1 为什么需要利率模型
Black 公式(用于 Cap、Swaption)假设远期利率服从对数正态分布。但这只是一个”市场标准”的参数化方法,它没有严格的动态一致性。
短利率模型从短期利率 的随机微分方程出发,通过无套利条件推导出整条利率曲线的动态。这是更基础、更自洽的建模方法。
5.2 Vasicek 模型
白话解释:短期利率像一根被弹簧拉住的物体——它会围绕一个”长期均值”上下波动,偏离越远,回归的力量越大。
| 参数 | 含义 | 直觉 |
|---|---|---|
| 回归速度 | 弹簧的劲度系数。 越大,回归越快 | |
| 长期均值 | 弹簧的自然长度。利率长期来看会回到 附近 | |
| 波动率 | 随机扰动的强度 | |
| 当前利率 | 初始位置 |
Vasicek 的问题:利率可以为负。现实中有时候利率确实会变成负数(如欧洲、日本),但在很多时候我们希望利率保持非负。
import numpy as np
import matplotlib.pyplot as plt
# ============================================================
# Vasicek 模型模拟
# ============================================================
def simulate_vasicek(r0, a, b, sigma, T, n_steps, n_paths):
"""
Vasicek 模型路径模拟(Euler-Maruyama 离散化)
dr = a(b - r)dt + sigma * dW
参数:
r0: 初始利率
a: 均值回归速度
b: 长期均值
sigma: 波动率
T: 总时间
n_steps: 步数
n_paths: 路径数
"""
dt = T / n_steps
t = np.linspace(0, T, n_steps + 1)
r = np.zeros((n_steps + 1, n_paths))
r[0] = r0
Z = np.random.standard_normal((n_steps, n_paths))
for i in range(n_steps):
dr = a * (b - r[i]) * dt + sigma * np.sqrt(dt) * Z[i]
r[i + 1] = r[i] + dr
return t, r
# 参数
r0 = 0.03 # 当前利率 3%
a = 1.0 # 回归速度
b = 0.04 # 长期均值 4%
sigma = 0.01 # 波动率 1%
T = 10.0
n_steps = 2500
n_paths = 10
np.random.seed(42)
t, r_paths = simulate_vasicek(r0, a, b, sigma, T, n_steps, n_paths)
# 可视化
fig, ax = plt.subplots(figsize=(12, 5))
for i in range(n_paths):
ax.plot(t, r_paths[:, i] * 100, alpha=0.5, lw=0.8)
ax.axhline(y=b * 100, color='red', linestyle='--', lw=1.5,
label=f'长期均值 b = {b:.1%}')
ax.axhline(y=0, color='black', linestyle='-', lw=0.5)
ax.axhline(y=r0 * 100, color='gray', linestyle=':', alpha=0.5,
label=f'初始利率 r0 = {r0:.1%}')
ax.set_xlabel('时间(年)')
ax.set_ylabel('短期利率(%)')
ax.set_title('Vasicek 模型:短期利率路径模拟')
ax.legend()
ax.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
print("Vasicek 模型参数:")
print(f" a (回归速度) = {a}")
print(f" b (长期均值) = {b:.2%}")
print(f" sigma (波动率) = {sigma:.2%}")
print(f" r0 (初始利率) = {r0:.2%}")
print()
print(f"观察:利率在 {b:.1%} 附近波动,偏离后会被拉回来")
print(f"注意:部分路径利率为负——这是 Vasicek 模型的缺陷")
# 统计
final_rates = r_paths[-1]
print(f"\n{10} 年后利率统计:")
print(f" 均值 = {final_rates.mean():.4%}")
print(f" 标准差 = {final_rates.std():.4%}")
print(f" 最小值 = {final_rates.min():.4%}(可以为负!)")
print(f" 最大值 = {final_rates.max():.4%}")5.3 CIR 模型(Cox-Ingersoll-Ross)
CIR 模型解决了 Vasicek 的”利率可以为负”的问题:
关键区别:波动率项是 而不是 。当利率接近 0 时,波动率也接近 0,利率不容易变成负数。
Vasicek vs CIR 的区别:
Vasicek: dr = a(b-r)dt + sigma * dW ← 常数波动率
CIR: dr = a(b-r)dt + sigma*sqrt(r) * dW ← 波动率与利率成正比
|
sigma ---|------- Vasicek: 波动率恒定
| /
sigma* | / CIR: 波动率随 r 减小
sqrt(r) | /
| /
0 -------|/-------- r
0
当 r 接近 0 时:
Vasicek: 仍然有 sigma 的波动 → 可以变成负数
CIR: 波动率接近 0 → 被推回正数
Feller 条件: 2*a*b >= sigma^2 (保证 r 不会触及 0)
import numpy as np
import matplotlib.pyplot as plt
# ============================================================
# CIR 模型模拟
# ============================================================
def simulate_cir(r0, a, b, sigma, T, n_steps, n_paths):
"""
CIR 模型路径模拟(Euler-Maruyama + 反射条件)
dr = a(b-r)dt + sigma*sqrt(r)*dW
注意:当 sigma 较大时,简单 Euler 方法不稳定。
这里使用带反射的 Euler 方法作为简化实现。
"""
dt = T / n_steps
t = np.linspace(0, T, n_steps + 1)
r = np.zeros((n_steps + 1, n_paths))
r[0] = r0
Z = np.random.standard_normal((n_steps, n_paths))
for i in range(n_steps):
sqrt_r = np.sqrt(np.maximum(r[i], 0))
dr = a * (b - r[i]) * dt + sigma * sqrt_r * np.sqrt(dt) * Z[i]
r[i + 1] = np.maximum(r[i] + dr, 0) # 反射条件:保证非负
return t, r
# 对比 Vasicek 和 CIR
np.random.seed(42)
r0 = 0.01 # 低初始利率,更容易看到差异
a = 0.5
b = 0.03
sigma_v = 0.02
T = 10.0
n_steps = 2500
n_paths = 30
# Vasicek 路径
np.random.seed(42)
t, r_vasicek = simulate_vasicek(r0, a, b, sigma_v, T, n_steps, n_paths)
# CIR 路径(需要更大的 sigma 以产生类似的波动幅度)
np.random.seed(42)
sigma_cir = 0.06 # CIR 需要更大的 sigma
t, r_cir = simulate_cir(r0, a, b, sigma_cir, T, n_steps, n_paths)
# 可视化
fig, axes = plt.subplots(1, 3, figsize=(18, 5))
# Vasicek 路径
for i in range(min(20, n_paths)):
axes[0].plot(t, r_vasicek[:, i] * 100, alpha=0.3, color='blue')
axes[0].axhline(y=0, color='red', linewidth=1, label='零利率')
axes[0].axhline(y=b * 100, color='gray', linestyle='--', label=f'长期均值 b={b:.0%}')
axes[0].set_xlabel('时间(年)')
axes[0].set_ylabel('利率(%)')
axes[0].set_title('Vasicek(利率可以为负)')
axes[0].legend()
axes[0].grid(True, alpha=0.3)
# CIR 路径
for i in range(min(20, n_paths)):
axes[1].plot(t, r_cir[:, i] * 100, alpha=0.3, color='green')
axes[1].axhline(y=0, color='red', linewidth=1, label='零利率')
axes[1].axhline(y=b * 100, color='gray', linestyle='--', label=f'长期均值 b={b:.0%}')
axes[1].set_xlabel('时间(年)')
axes[1].set_ylabel('利率(%)')
axes[1].set_title('CIR(利率非负)')
axes[1].legend()
axes[1].grid(True, alpha=0.3)
# 不同参数下的 CIR 路径
params = [
(0.1, 'a=0.1(慢回归)'),
(0.5, 'a=0.5(中回归)'),
(2.0, 'a=2.0(快回归)')
]
for a_val, label in params:
np.random.seed(42)
r, _ = simulate_cir(r0, a_val, b, sigma_cir, T, n_steps, 1)
axes[2].plot(_, r[0] * 100, lw=1.5, label=label)
axes[2].axhline(y=b * 100, color='gray', linestyle='--')
axes[2].set_xlabel('时间(年)')
axes[2].set_ylabel('利率(%)')
axes[2].set_title('不同均值回归速度下的 CIR 路径')
axes[2].legend()
axes[2].grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
# 负利率统计
neg_vasicek = np.mean(r_vasicek < 0)
neg_cir = np.mean(r_cir < 0)
print(f"Vasicek 负利率比例: {neg_vasicek:.2%}")
print(f"CIR 负利率比例: {neg_cir:.2%}")
print(f"CIR Feller 条件: 2ab = {2*a*b:.4f}, sigma^2 = {sigma_cir**2:.4f}")
print(f" 2ab >= sigma^2? {2*a*b >= sigma_cir**2} ({'满足' if 2*a*b >= sigma_cir**2 else '不满足'})")
print()
print("选择建议:")
print(" 课堂学习 → Vasicek(数学最简单,直觉最清晰)")
print(" 利率下限严格 → CIR(保证非负)")
print(" 实际定价 → Hull-White(匹配市场曲线)")5.4 Hull-White 模型
Hull-White 模型是 Vasicek 的扩展,加入了时间依赖的参数,使其能够精确匹配当前市场的收益率曲线:
白话解读:Vasicek 的长期均值 是一个常数,但现实中市场收益率曲线的形状可能是任意的。Hull-White 通过让 随时间变化,确保模型在 时完全匹配市场曲线。
关键优势:模型 calibrated 到市场数据后,不会产生与市场价格矛盾的定价。
5.5 模型对比
| 特性 | Vasicek | CIR | Hull-White |
|---|---|---|---|
| SDE | |||
| 利率可以为负 | 是 | 否 | 是 |
| 匹配市场曲线 | 否 | 否 | 是(通过 ) |
| 解析解(债券) | 是 | 是 | 是 |
| 解析解(期权) | 是 | 部分 | 数值/树方法 |
| 波动率结构 | 常数 | 与 成正比 | 常数 |
| 均值回归 | 是 | 是 | 是 |
| 实际应用 | 教学场景 | 利率下限严格时 | 主流生产模型 |
选择指南:
课堂学习 → Vasicek 和 CIR(数学优美,直觉清晰)
实际定价 → Hull-White 或更高级的模型(保证无套利)
负利率环境 → Vasicek / Hull-White(允许负利率反而是优势)
利率不能为负 → CIR
5.6 模型校准
白话解释:模型的参数()需要从市场数据中”拟合”出来。这个过程叫校准。
校准的标准:让模型定价和实际市场价格尽可能接近。
import numpy as np
from scipy.optimize import minimize
# ============================================================
# Hull-White 模型校准(简化版)
# ============================================================
def hw_bond_price(r0, a, b, sigma, T):
"""
Hull-White 模型下的零息债券价格
P(t, T) = A(t, T) * exp(-B(t, T) * r(t))
"""
B = (1 - np.exp(-a * T)) / a
A = np.exp((B - T) * (a**2 * b - 0.5 * sigma**2) / a**2
- sigma**2 * B**2 / (4 * a))
return A * np.exp(-B * r0)
def calibrate_hw(r0, market_prices, market_maturities):
"""
校准 Hull-White 模型参数
目标:最小化模型债券价格和市场价格的差异
"""
def objective(params):
a, b, sigma = params
if a <= 0 or sigma <= 0:
return 1e10
model_prices = [hw_bond_price(r0, a, b, sigma, T)
for T in market_maturities]
errors = [(model - market) / market
for model, market in zip(model_prices, market_prices)]
return sum(e**2 for e in errors)
# 初始猜测和边界
x0 = [1.0, 0.04, 0.01]
bounds = [(0.01, 10.0), (0.001, 0.15), (0.001, 0.05)]
result = minimize(objective, x0, bounds=bounds, method='L-BFGS-B')
if result.success:
a_cal, b_cal, sigma_cal = result.x
return a_cal, b_cal, sigma_cal, result.fun
else:
return None
# 模拟市场数据
np.random.seed(42)
r0 = 0.03
a_true, b_true, sigma_true = 1.5, 0.04, 0.015 # 真实参数
maturities = np.array([0.5, 1.0, 2.0, 3.0, 5.0, 7.0, 10.0])
# 用真实参数生成"市场"债券价格
market_prices = [hw_bond_price(r0, a_true, b_true, sigma_true, T)
for T in maturities]
# 校准
print("=" * 50)
print("Hull-White 模型校准")
print("=" * 50)
print(f"{'期限':>8} {'市场价格':>12} {'校准价格':>12}")
print("-" * 35)
result = calibrate_hw(r0, market_prices, maturities)
if result:
a_cal, b_cal, sigma_cal, obj_val = result
cal_prices = [hw_bond_price(r0, a_cal, b_cal, sigma_cal, T) for T in maturities]
for T, mp, cp in zip(maturities, market_prices, cal_prices):
print(f"{T:>6.1f}年 {mp:>12.6f} {cp:>12.6f}")
print(f"\n校准参数: a={a_cal:.4f}, b={b_cal:.6f}, sigma={sigma_cal:.6f}")
print(f"真实参数: a={a_true:.4f}, b={b_true:.6f}, sigma={sigma_true:.6f}")
print(f"目标函数值: {obj_val:.10f}")
print(f"\n白话解释:")
print(f" 校准出来的参数和真实参数很接近")
print(f" 这说明模型能从市场数据中正确地提取利率动态信息")六、LMM / BGM 市场模型(简要)
6.1 为什么需要 LMM
短利率模型(Vasicek、Hull-White)的一个问题是:它们假设的是短期利率的动态,然后推导出远期利率。但市场交易的是远期利率(Cap、Swaption),所以更好的做法是直接建模远期利率。
LIBOR Market Model (LMM),也叫 BGM 模型(Brace-Gatarek-Musiela),直接对一组远期利率建模:
白话解读:每个期限的远期利率都服从一个几何布朗运动,且它们之间有相关性结构。
6.2 LMM vs 短利率模型
| 维度 | 短利率模型 (HW) | LMM |
|---|---|---|
| 建模对象 | 短期利率 r(t) | 一组远期利率 F_i(t) |
| 校准到 Cap | 近似匹配 | 自然匹配 |
| 校准到 Swaption | 近似 | 需要额外处理 |
| 计算速度 | 快(解析解) | 慢(蒙特卡洛) |
| 市场标准 | 简单产品 | 复杂结构化产品 |
七、利率曲线 Bootstrap
7.1 从市场数据构建收益率曲线
白话解释:市场上有很多利率产品(国债、回购、互换等),它们隐含了不同期限的利率信息。Bootstrap 方法就是从这些碎片化的市场信息中,拼凑出一条完整的收益率曲线。
方法:从短到长,逐段推导。先确定短期利率(从隔夜、1 周等),然后用这些信息推导稍长期限的利率,依次类推。
import numpy as np
import matplotlib.pyplot as plt
# ============================================================
# 利率曲线 Bootstrap(简化版)
# ============================================================
def bootstrap_yield_curve(instruments):
"""
从市场利率工具 Bootstrap 出零利率曲线
参数:
instruments: list of dict,每个 dict 包含:
- 'type': 'deposit'(存款)或 'swap'(互换)
- 'tenor': 期限(年)
- 'rate': 市场利率
返回:
折现因子列表(对应每个期限)
"""
# 按 tenor 排序
instruments = sorted(instruments, key=lambda x: x['tenor'])
discount_factors = []
for inst in instruments:
t = inst['tenor']
rate = inst['rate']
if inst['type'] == 'deposit':
# 存款: DF = 1 / (1 + rate * t)
df = 1.0 / (1 + rate * t)
elif inst['type'] == 'swap':
# 互换: 固定端 = 浮动端 → 推导 DF
# 简化: 假设每年付息一次
n_payments = int(t)
fixed_leg = rate * sum(discount_factors[-1] * np.exp(-0.03 * (i+1))
for i in range(n_payments))
# 实际上需要求解非线性方程,这里用简化方法
# 正确做法: 使用插值 + 迭代
df = discount_factors[-1] * np.exp(-rate * (t - instruments[-2]['tenor']))
else:
raise ValueError(f"未知的工具类型: {inst['type']}")
discount_factors.append(df)
return discount_factors
# 模拟市场数据
market_data = [
{'type': 'deposit', 'tenor': 0.25, 'rate': 0.028}, # 3 个月存款
{'type': 'deposit', 'tenor': 0.50, 'rate': 0.030}, # 6 个月存款
{'type': 'deposit', 'tenor': 1.0, 'rate': 0.032}, # 1 年存款
{'type': 'swap', 'tenor': 2.0, 'rate': 0.035}, # 2 年互换
{'type': 'swap', 'tenor': 3.0, 'rate': 0.037}, # 3 年互换
{'type': 'swap', 'tenor': 5.0, 'rate': 0.040}, # 5 年互换
{'type': 'swap', 'tenor': 7.0, 'rate': 0.042}, # 7 年互换
{'type': 'swap', 'tenor': 10.0, 'rate': 0.044}, # 10 年互换
]
print("=" * 50)
print("利率曲线 Bootstrap")
print("=" * 50)
print(f"{'期限':>8} {'市场利率':>10} {'类型':>10}")
print("-" * 35)
for inst in market_data:
type_cn = '存款' if inst['type'] == 'deposit' else '互换'
print(f"{inst['tenor']:>6.1f}年 {inst['rate']:>10.3%} {type_cn:>10}")
# 更完整的 Bootstrap 实现
def bootstrap_full(market_data):
"""完整的 Bootstrap 实现"""
market_data = sorted(market_data, key=lambda x: x['tenor'])
curve = {} # tenor -> discount_factor
for inst in market_data:
t = inst['tenor']
r = inst['rate']
if inst['type'] == 'deposit':
curve[t] = 1.0 / (1.0 + r * t)
elif inst['type'] == 'swap':
# 对于互换,使用已知的短期 DF + 线性插值
# 这里简化处理:用连续复利近似
curve[t] = np.exp(-r * t)
return curve
curve = bootstrap_full(market_data)
# 转换为零利率
tenors = sorted(curve.keys())
zero_rates = [-np.log(df) / t for t, df in curve.items()]
print(f"\n{'期限':>8} {'折现因子':>10} {'零利率':>10}")
print("-" * 35)
for t, zr in zip(tenors, zero_rates):
print(f"{t:>6.1f}年 {curve[t]:>10.6f} {zr:>10.4%}")
# 可视化
fig, ax = plt.subplots(figsize=(10, 5))
ax.plot(tenors, [r * 100 for r in zero_rates], 'bo-', lw=2, markersize=8)
ax.set_xlabel('期限(年)')
ax.set_ylabel('零利率(%)')
ax.set_title('Bootstrap 出来的零利率曲线')
ax.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
print("\n白话解释:")
print(" 这条曲线就是'市场认为的未来不同期限的利率'")
print(" 所有利率衍生品的定价都以这条曲线为输入")
print(" 曲线形状(向上/平坦/倒挂)反映了市场对未来经济的预期")八、关键概念回顾
| 概念 | 一句话解释 |
|---|---|
| 即期利率 | 今天借一笔钱到某期限到期的利率 |
| 远期利率 | 今天约定未来某个时间借钱的利率 |
| 利率互换(IRS) | 固定利率换浮动利率 |
| Cap / Floor | 利率保险(上限/下限) |
| Swaption | 进入互换的权利(互换 + 期权) |
| Vasicek 模型 | 短期利率围绕长期均值波动的模型 |
| Hull-White 模型 | Vasicek 的时间依赖扩展,能匹配市场曲线 |
| LMM / BGM | 直接对远期利率建模的市场模型 |
| Bootstrap | 从市场数据构建收益率曲线的方法 |
推荐阅读
| 书名 | 章节 | 核心价值 |
|---|---|---|
| Options, Futures, and Other Derivatives (Hull) | Ch.28-31 | 利率衍生品的权威参考 |
| Interest Rate Models (Brigo & Mercurio) | 全书 | 利率模型的全面深度参考 |
| Fixed Income Securities (Tuckman) | Ch.8-12 | 实务导向的利率建模 |
版本信息
- 创建日期:2026-03-28
- 最后更新:2026-03-28