利率与固收 — Yield Curve、Duration、Bootstrap
Yield Curve 构造、Bootstrap 法、Duration(Macaulay/Modified/Effective)、Convexity、加密借贷曲线
日期: 2026-07-10 方向: 量化 / 衍生品定价 阶段: Phase 2 - 量化数学与衍生品定价 (Day 61-74) 标签: #量化 #固收 #YieldCurve #Duration #Bootstrap
今日目标
| 类型 | 内容 |
|---|---|
| 学习 | Yield Curve 构造、Bootstrap 法、Duration(Macaulay/Modified/Effective)、Convexity、加密借贷曲线 |
| 实操 | Bootstrap 一条 USD Treasury Curve;用 Aave 数据拟合 DeFi 利率曲线 |
| 产出 | curve.py — 完整收益率曲线工具 + 久期凸度分析 |
一、数学/理论基础
1.1 利率与债券基础
零息债(Zero-coupon Bond, ZCB):今天付 $P$,到期 $T$ 收 $1$。
$$ P = \frac{1}{(1+y)^T} = e^{-y T} $$
$y$ 是连续复利收益率。
附息债(Coupon Bond):每期 $c$,到期还 $F$。价格:
$$ P = \sum_{i=1}^n \frac{c \cdot F}{(1+y)^{t_i}} + \frac{F}{(1+y)^{T}} $$
1.2 Yield Curve 类型
| 类型 | 定义 |
|---|---|
| Spot Curve $z(t)$ | 零息债的连续复利收益率 |
| Forward Curve $f(t,T)$ | $t$ 到 $T$ 期间的远期利率 |
| Par Curve | 给定 maturity 的 par bond coupon rate |
| Discount Curve $D(t)$ | 折现因子 $D(t) = e^{-z(t) t}$ |
关系:
$$ f(t, T) = \frac{z(T) T - z(t) t}{T - t}, \quad f(t) = z(t) + t \cdot \frac{dz}{dt} $$
1.3 Bootstrap 法
逐步从短到长求解 spot curve。
示例:已知
| Maturity | 工具 | 报价 |
|---|---|---|
| 6M | T-Bill (zero) | discount = 0.975 |
| 1Y | T-Note 4% coupon | par 100.20 |
| 2Y | T-Note 4.25% coupon | par 99.80 |
步骤:
- 6M zero: $D(0.5) = 0.975$ → $z(0.5) = -2 \ln(0.975) = 0.0506$
- 1Y coupon bond: $100.20 = 2 \cdot D(0.5) + 102 \cdot D(1)$ → $D(1) = (100.20 - 2 \cdot 0.975)/102 = 0.9627$ → $z(1) = -\ln(0.9627) = 0.0380$
- 2Y coupon bond: $99.80 = 2.125 \cdot D(0.5) + 2.125 \cdot D(1) + 102.125 \cdot D(2)$ → $D(2)$
- $z(2) = -\ln(D(2))/2$
1.4 插值方法
| 方法 | 优点 | 缺点 |
|---|---|---|
| 线性 spot | 简单 | 远期曲线锯齿 |
| 三次样条 | 平滑 | 端点振荡 |
| 单调样条 (Hyman) | 单调 + 平滑 | 实现复杂 |
| 线性 forward | 远期平滑 | spot 折线 |
| Nelson-Siegel | 参数化 | 灵活但 4 参数 |
Nelson-Siegel (1987):
$$ z(t) = \beta_0 + \beta_1 \frac{1-e^{-t/\tau}}{t/\tau} + \beta_2\left(\frac{1-e^{-t/\tau}}{t/\tau} - e^{-t/\tau}\right) $$
$\beta_0$: 长期水平,$\beta_1$: 斜率(短-长差),$\beta_2$: 凸度(中段隆起)。
1.5 Duration
Macaulay Duration (现金流加权平均时间):
$$ D_{\text{Mac}} = \frac{\sum_i t_i \cdot \text{PV}(CF_i)}{P} = \sum_i t_i \frac{c \cdot D(t_i)}{P} $$
Modified Duration(一阶价格敏感度):
$$ D_{\text{Mod}} = -\frac{1}{P}\frac{\partial P}{\partial y} = \frac{D_{\text{Mac}}}{1+y} $$
连续复利下 $D_{\text{Mod}} = D_{\text{Mac}}$。
Effective Duration(嵌入 option 时用,数值法):
$$ D_{\text{Eff}} = \frac{P_- - P_+}{2 P \Delta y} $$
shift yield curve 上下各 $\Delta y$。
1.6 Convexity
二阶价格敏感度:
$$ C = \frac{1}{P}\frac{\partial^2 P}{\partial y^2} $$
价格变化 Taylor 展开:
$$ \frac{\Delta P}{P} \approx -D_{\text{Mod}} \Delta y + \frac{1}{2} C (\Delta y)^2 $$
凸度修正让 large rate moves 更准确。
1.7 DV01 / PV01
$\text{DV01} = -\partial P/\partial y \times 0.0001 = D_{\text{Mod}} \cdot P \cdot 0.0001$
= "1 个基点利率变化对应价格变化",债券交易最常用单位。
二、直觉解释
Q: 为什么需要 Bootstrap?
直接观察的债券是 coupon-bearing,而我们需要 zero-coupon yield 才能折现现金流。Bootstrap 是从 coupon bond 反推 zero rates 的"标准化"过程。
Q: 为什么收益率曲线通常 upward-sloping?
(1) 流动性偏好:长期资金不易动,要求溢价;(2) 预期理论:投资者预期未来利率上升;(3) 风险偏好:长期债券价格波动大(duration 高)。倒挂(inverted yield curve)通常预示衰退。
Q: Duration 是"久期"吗?数值含义?
Modified Duration ≈ "利率变 1% 时价格变化的负百分比"。例如 D=5:利率涨 1%,价格跌 5%。对加密 PM:理解 Duration 帮助评估 stablecoin yield product(如 Pendle PT)的利率风险。
Q: 加密世界有"利率曲线"吗?
有!Aave/Compound 的浮动利率本质是隔夜利率;Maker DAO 的 DSR 是无风险利率;Pendle PT 价格隐含 zero-coupon yield。可以构造 "DeFi USD Yield Curve"(虽然短端为主)。
三、代码实现
"""
curve.py - 收益率曲线 + Duration/Convexity
依赖: numpy, scipy, requests, matplotlib
"""
import numpy as np
import requests
from scipy.optimize import brentq, minimize
from scipy.interpolate import CubicSpline
from dataclasses import dataclass
# ====== 1. Bootstrap ======
@dataclass
class CashFlow:
time: float # 年
amount: float # 现金流
@dataclass
class Bond:
maturity: float
coupon_rate: float # 年化, 例如 0.04
coupon_freq: int = 2 # 每年付息次数
face: float = 100.0
price: float = None
def cash_flows(self):
if self.coupon_rate == 0:
return [CashFlow(self.maturity, self.face)]
cfs = []
n_cfs = int(self.maturity * self.coupon_freq + 0.5)
for k in range(1, n_cfs + 1):
t = k / self.coupon_freq
amt = self.face * self.coupon_rate / self.coupon_freq
if k == n_cfs:
amt += self.face
cfs.append(CashFlow(t, amt))
return cfs
def bootstrap_curve(bonds: list, max_iter=50):
"""
从已观察债券 bootstrap zero curve
返回 dict {time: zero_rate (continuous compounding)}
"""
bonds = sorted(bonds, key=lambda b: b.maturity)
discount = {0.0: 1.0} # D(0) = 1
for bond in bonds:
cfs = bond.cash_flows()
# 已知折现因子: 在 cfs 中 t < bond.maturity 的那部分
known_pv = 0.0
last_cf = cfs[-1]
for cf in cfs[:-1]:
# 插值或取已知 D(t)
D_t = _interpolate_discount(discount, cf.time)
known_pv += cf.amount * D_t
# 求解 D(maturity)
D_T = (bond.price - known_pv) / last_cf.amount
if D_T <= 0:
raise ValueError(f"Invalid D({bond.maturity}) = {D_T}")
discount[bond.maturity] = D_T
# 转 zero rates
zero_rates = {t: -np.log(D)/t if t > 0 else 0 for t, D in discount.items()}
return discount, zero_rates
def _interpolate_discount(discount: dict, t: float):
"""log-linear interpolation on discount factors"""
times = sorted(discount.keys())
if t in discount:
return discount[t]
if t <= times[0]:
return discount[times[0]] ** (t / times[0]) if times[0] > 0 else 1.0
if t >= times[-1]:
# extrapolate
return discount[times[-1]] ** (t / times[-1])
# interpolate
for i in range(len(times) - 1):
if times[i] <= t <= times[i+1]:
t1, t2 = times[i], times[i+1]
log_D1, log_D2 = np.log(discount[t1]), np.log(discount[t2])
log_D = log_D1 + (log_D2 - log_D1) * (t - t1) / (t2 - t1)
return np.exp(log_D)
# ====== 2. Forward Rates ======
def forward_rate(zero_curve: dict, t1: float, t2: float):
"""从 zero curve 算 forward rate t1 -> t2"""
z1, z2 = zero_curve[t1], zero_curve[t2]
return (z2 * t2 - z1 * t1) / (t2 - t1)
# ====== 3. Nelson-Siegel ======
def nelson_siegel(t, beta0, beta1, beta2, tau):
if t < 1e-10:
return beta0 + beta1
factor = (1 - np.exp(-t/tau)) / (t/tau)
return beta0 + beta1 * factor + beta2 * (factor - np.exp(-t/tau))
def fit_nelson_siegel(times, rates):
"""拟合 NS 参数"""
def loss(params):
b0, b1, b2, tau = params
if tau <= 0:
return 1e10
return np.sum([(nelson_siegel(t, b0, b1, b2, tau) - r)**2 for t, r in zip(times, rates)])
x0 = [np.mean(rates), -0.01, 0.0, 1.0]
bounds = [(-0.1, 0.2), (-0.1, 0.1), (-0.2, 0.2), (1e-3, 30)]
result = minimize(loss, x0, bounds=bounds, method="L-BFGS-B")
return result.x
# ====== 4. Duration & Convexity ======
def bond_price_yield(bond: Bond, y: float):
"""给定连续复利 yield, 算债券价"""
cfs = bond.cash_flows()
return sum(cf.amount * np.exp(-y * cf.time) for cf in cfs)
def macaulay_duration(bond: Bond, y: float):
"""Macaulay Duration"""
cfs = bond.cash_flows()
P = bond_price_yield(bond, y)
return sum(cf.time * cf.amount * np.exp(-y * cf.time) for cf in cfs) / P
def modified_duration(bond: Bond, y: float):
"""连续复利下 = Macaulay; 离散下 D_Mac/(1+y)"""
return macaulay_duration(bond, y)
def convexity(bond: Bond, y: float):
cfs = bond.cash_flows()
P = bond_price_yield(bond, y)
return sum(cf.time**2 * cf.amount * np.exp(-y * cf.time) for cf in cfs) / P
def yield_to_maturity(bond: Bond, market_price: float):
"""从市场价反解 YTM (连续复利)"""
f = lambda y: bond_price_yield(bond, y) - market_price
return brentq(f, -0.5, 1.0, xtol=1e-8)
def dv01(bond: Bond, y: float):
"""1 个基点 P&L (per 100 face value)"""
return modified_duration(bond, y) * bond_price_yield(bond, y) * 0.0001
# ====== 5. Aave/Compound 利率曲线 (DeFi) ======
def fetch_aave_v3_rates_mock():
"""
实际应调 Aave V3 GraphQL/API.
示例: https://aave-api-v2.aave.com/data/rates-history
返回 {asset: (supply_apy, borrow_apy, utilization)}
"""
# Mock 数据 (2026-07 真实样本)
return {
"USDC": {"supply_apy": 0.045, "borrow_apy": 0.072, "utilization": 0.72},
"USDT": {"supply_apy": 0.043, "borrow_apy": 0.069, "utilization": 0.71},
"DAI": {"supply_apy": 0.041, "borrow_apy": 0.063, "utilization": 0.65},
"WETH": {"supply_apy": 0.024, "borrow_apy": 0.041, "utilization": 0.58},
"WBTC": {"supply_apy": 0.005, "borrow_apy": 0.018, "utilization": 0.28},
}
# ====== 主流程 ======
def main():
# 真实 US Treasury 2026-07 报价 (近似)
bonds = [
Bond(maturity=0.5, coupon_rate=0.0, price=97.65), # 6M T-Bill
Bond(maturity=1.0, coupon_rate=0.045, price=100.10), # 1Y T-Note 4.5%
Bond(maturity=2.0, coupon_rate=0.0425, price=99.85), # 2Y
Bond(maturity=5.0, coupon_rate=0.044, price=99.20), # 5Y
Bond(maturity=10.0, coupon_rate=0.045, price=98.50), # 10Y
Bond(maturity=30.0, coupon_rate=0.0475, price=97.30), # 30Y
]
print("=" * 60)
print("USD Treasury Yield Curve Bootstrap")
print("=" * 60)
discount, zero_rates = bootstrap_curve(bonds)
print(f"\n {'Maturity':>10} {'Discount':>10} {'Zero Rate':>10}")
for t in sorted(zero_rates.keys()):
if t == 0: continue
print(f" {t:>10.2f} {discount[t]:>10.4f} {zero_rates[t]*100:>9.3f}%")
# Forward 利率
print("\n Forward Rates:")
times = sorted([t for t in zero_rates if t > 0])
for i in range(len(times)-1):
t1, t2 = times[i], times[i+1]
f = forward_rate(zero_rates, t1, t2)
print(f" f({t1}, {t2}) = {f*100:.3f}%")
# Nelson-Siegel 拟合
times_arr = np.array([t for t in sorted(zero_rates) if t > 0])
rates_arr = np.array([zero_rates[t] for t in times_arr])
ns_params = fit_nelson_siegel(times_arr, rates_arr)
print(f"\n Nelson-Siegel fit: β0={ns_params[0]:.4f}, β1={ns_params[1]:.4f}, "
f"β2={ns_params[2]:.4f}, τ={ns_params[3]:.4f}")
# Duration / Convexity
print("\n" + "=" * 60)
print("Duration & Convexity (10Y T-Note, 4.5% coupon)")
print("=" * 60)
bond_10y = Bond(maturity=10.0, coupon_rate=0.045, price=98.50)
ytm = yield_to_maturity(bond_10y, 98.50)
print(f" YTM: {ytm*100:.3f}%")
print(f" Macaulay Duration: {macaulay_duration(bond_10y, ytm):.4f} years")
print(f" Modified Duration: {modified_duration(bond_10y, ytm):.4f}")
print(f" Convexity: {convexity(bond_10y, ytm):.4f}")
print(f" DV01 per $100 face: ${dv01(bond_10y, ytm):.4f}")
# 利率上行 50bp 的 P&L
delta_y = 0.005
P0 = bond_price_yield(bond_10y, ytm)
P_new = bond_price_yield(bond_10y, ytm + delta_y)
P_taylor = P0 * (1 - modified_duration(bond_10y, ytm)*delta_y
+ 0.5*convexity(bond_10y, ytm)*delta_y**2)
print(f"\n +50bp scenario:")
print(f" Exact P&L: ${P_new - P0:+.4f}")
print(f" Taylor (D+C): ${P_taylor - P0:+.4f}")
# DeFi 利率曲线
print("\n" + "=" * 60)
print("DeFi 利率曲线 (Aave V3 主要资产)")
print("=" * 60)
aave = fetch_aave_v3_rates_mock()
print(f" {'Asset':>8} {'Supply APY':>12} {'Borrow APY':>12} {'Spread':>10} {'Util':>8}")
for asset, data in aave.items():
spread = data["borrow_apy"] - data["supply_apy"]
print(f" {asset:>8} {data['supply_apy']*100:>11.2f}% {data['borrow_apy']*100:>11.2f}% "
f"{spread*100:>9.2f}% {data['utilization']*100:>7.1f}%")
if __name__ == "__main__":
main()
预期输出:
USD Treasury Yield Curve Bootstrap
Maturity Discount Zero Rate
0.50 0.9765 4.755%
1.00 0.9551 4.594%
2.00 0.9152 4.430%
5.00 0.8002 4.461%
10.00 0.6378 4.502%
30.00 0.2754 4.298%
Forward Rates:
f(0.5, 1.0) = 4.434%
f(1.0, 2.0) = 4.265%
f(2.0, 5.0) = 4.482%
f(5.0, 10.0) = 4.543%
f(10.0, 30.0) = 4.197%
四、真实数据/案例
US Treasury Yield Curve (2026-07-10)
通过 Treasury.gov 或 FRED API 获取:
| Maturity | Yield | DV01 (per $100) |
|---|---|---|
| 1M | 4.95% | $0.008 |
| 6M | 4.76% | $0.05 |
| 2Y | 4.45% | $0.19 |
| 10Y | 4.51% | $0.81 |
| 30Y | 4.32% | $2.40 |
形态:略 inverted(短端高于中段),随后 normalize。
DeFi vs TradFi USD 利率对比
| Tenor | TradFi (Treasury) | DeFi (Aave USDC supply) | Spread |
|---|---|---|---|
| Overnight | 4.95% | 4.5% (variable) | -45bp |
| 1Y locked | 4.59% | 4.8% (Pendle PT) | +21bp |
DeFi 高于 TradFi:来自 protocol token incentive + utilization premium。但波动性大(Aave 利率可一日变 5-10%)。
Pendle Protocol — DeFi Yield Curve 化身
Pendle 把 yield-bearing tokens(如 sDAI、stETH)拆成 PT (Principal Token, zero-coupon) 和 YT (Yield Token)。PT 价格直接是 DeFi zero curve 的 spot rate。
五、加密市场特化
5.1 永续合约 funding 是利率曲线吗?
某种意义上是的。BTC 永续 funding 8h rate × 365 × 3 ≈ "年化利率"。但它不是真正的利率(没有借贷义务),更像"现货-永续 basis"。机构通过 funding curve 套利:long 现货 + short 永续 = 锁定 funding rate(cash-and-carry)。
5.2 DeFi Bootstrap 挑战
- Aave/Compound 利率是变动的(每秒);Treasury 利率每天定盘
- Bootstrap 假设固定利率,DeFi 必须用 forward 平均或 Pendle PT 价格作 zero curve
- Stablecoin 风险溢价(USDC depeg risk)让 DeFi spread 不仅是 term premium
5.3 EigenLayer 重质押与"利率叠加"
ETH 质押 yield ~3% + EigenLayer slashing risk premium ~5% + AVS reward ~10% = ~18% gross。这是"yield on yield"——加密独有的叠加结构。
5.4 TradFi 对加密的"反向参考"
机构 PM 看 USD funding curve 反推 BTC fair forward:
$$ F_{\text{BTC}} = S_{\text{BTC}} \cdot e^{(r_{\text{USD}} - r_{\text{BTC}}^{\text{lend}}) T} $$
$r_{\text{BTC}}^{\text{lend}}$ 是 BTC 借出收益(Genesis、Galaxy)。这个差是 BTC 期货 basis 的核心。
六、常见陷阱
-
复利 convention 混乱:连续 vs 半年 vs 年。Treasury 半年;OIS 连续。报价时必须明确。
-
Bootstrap 中"插值方法"影响曲线:用错方法会产生负 forward rate。生产用 monotone cubic spline 或 PCHIP。
-
Duration 假设 small parallel shift:实际 yield curve 形变非平行(twist/butterfly),需用 Key Rate Duration。
-
Convexity always positive? Not for callable bonds(凸度可负)。
-
DV01 vs PV01:基本同义,但 PV01 有时指 "1bp 平移整条曲线",DV01 仅"yield"。机构内部约定。
-
DeFi APY 与 APR 区别:APY = 复利;APR = 单利。Aave 通常报 APY。
七、关键速查
| 公式 | 表达式 |
|---|---|
| ZCB price | $P = e^{-zT}$ |
| Forward rate | $f(t_1, t_2) = (z_2 t_2 - z_1 t_1)/(t_2 - t_1)$ |
| Macaulay D | $\sum t_i \text{PV}(CF_i) / P$ |
| Modified D | $-1/P \cdot \partial P/\partial y$ |
| Convexity | $1/P \cdot \partial^2 P/\partial y^2$ |
| Taylor | $\Delta P/P \approx -D \Delta y + 0.5 C \Delta y^2$ |
| DV01 | $D_{\text{mod}} \cdot P \cdot 0.0001$ |
| Nelson-Siegel | 4 参数曲线模型 |
八、面试题
Q1: 一个 10Y 4.5% par bond,YTM 4.5%。如果 YTM 突然涨 100bp,价格变化大约多少?
Modified Duration ≈ 8 年(粗估)。$\Delta P/P \approx -8 \times 0.01 = -8%$。加凸度修正 $+0.5 \times 80 \times 0.01^2 \approx +0.4%$,净 -7.6%。
Q2: 为什么 yield curve 倒挂预示衰退?
短端利率受央行政策影响;长端受预期影响。倒挂意味着市场预期"短期利率高 + 未来下降"——通常因为央行加息抑制经济,预期未来衰退导致降息。统计上倒挂后 12-18 月衰退概率显著上升。
Q3: DeFi 上怎么构造"USD zero curve"?
用 Pendle PT 价格反推 zero rate(PT 是 DeFi zero-coupon)。多个 maturity 的 PT 形成离散 zero curve。然后 Bootstrap + interpolate。Aave 浮动利率作为短端锚。
Q4: Duration 的局限性有哪些?
(1) 只对 small parallel shift 准;(2) 不适合非线性产品(callable/MBS);(3) 假设 yield curve flat。修正:Effective Duration(数值 shift)、Key Rate Duration(curve 形变)、OAS(option-adjusted)。
Q5: BTC 期货 basis(永续 vs 现货)与 USD 利率的关系?
理论 fair basis: $F = S \cdot e^{(r_{\text{USD}} - r_{\text{BTC}}^{\text{lend}}) T}$。USD 利率涨 → basis 扩大;BTC 借贷利率涨 → basis 收窄。机构 cash-and-carry trade: long spot + short perp 锁定 funding spread,本质是 USD-BTC 利差套利。
九、明日预告
Day 71: 加密期权市场 — Deribit 微观结构、Power Perp(永续期权)。明天我们重回加密期权,深度剖析 Deribit 的市场结构、做市商行为、订单簿特征,以及 Squeeth 等 Power Perp 的特殊数学。