Skip to content

12.1 收益率曲线建模

即期利率、远期利率与到期收益率

理解三者之间的区别是固定收益分析的基础。

名词解释:即期利率(Spot Rate)

当前时刻投资于零息债券至未来某一时刻 T 可以获得的年化收益率,记为 r(0,T)。零息债券价格与即期利率的关系为 P(0,T)=er(0,T)T

名词解释:远期利率(Forward Rate)

在当前时刻约定的,未来某个时间段 [T1,T2] 内的借贷利率,记为 f(0,T1,T2)。它与即期利率的关系为 f(0,T1,T2)=r(0,T2)T2r(0,T1)T1T2T1

到期收益率(Yield to Maturity, YTM)是使债券未来现金流的现值恰好等于其当前市场价格的单一折现率:

$$P = \sum_{t=1}^n \frac{C_t}{(1+YTM)^t}$$

三者的核心区别在于:即期利率是零息债券的收益率YTM 是附息债券的内部收益率(假设所有息票以 YTM 再投资);远期利率是隐含在未来时间段内的利率预期

PYTHON48 行 · 1.7 KB
📄此处有展示代码48 行 · 1.7 KB展开 ▼
python
import numpy as np
import matplotlib.pyplot as plt
from scipy.interpolate import CubicSpline

# 示例:从市场上观测到的债券价格 bootstrapping 即期曲线
def bootstrap_spot_curve(bonds):
    """
    bonds: list of dicts with keys 'maturity', 'coupon', 'price'
    使用 bootstrapping 方法从附息债券价格中提取即期利率曲线
    """
    bonds = sorted(bonds, key=lambda x: x['maturity'])
    spot_rates = []
    maturities = []

    for i, bond in enumerate(bonds):
        T = bond['maturity']
        coupon = bond['coupon'] / 2  # 半年付息
        price = bond['price']
        n_payments = int(T * 2)

        if T <= 0.5:
            # 短端直接用零息公式
            r = -np.log(price / (100 + coupon)) / T
        else:
            # 使用已求出的即期利率折现前面的现金流
            pv_coupons = 0
            for j in range(1, n_payments):
                t_j = j * 0.5
                if t_j <= maturities[-1]:
                    # 有对应的即期利率
                    idx = np.searchsorted(maturities, t_j)
                    if idx < len(spot_rates):
                        r_spot = spot_rates[idx]
                    else:
                        r_spot = spot_rates[-1]
                    pv_coupons += coupon * np.exp(-r_spot * t_j)
                else:
                    # 需要插值
                    cs = CubicSpline(maturities, spot_rates)
                    r_interp = cs(t_j)
                    pv_coupons += coupon * np.exp(-r_interp * t_j)

            r = -np.log((price - pv_coupons) / (100 + coupon)) / T

        spot_rates.append(r)
        maturities.append(T)

    return np.array(maturities), np.array(spot_rates)
点击展开可浏览运行结果
📘 本段代码定义了 1 个函数/类:函数 `bootstrap_spot_curve`(bonds: list of dicts with keys 'maturity', 'coupon', 'price')。该片段为教学展示(未包含独立运行的输入数据),可在实战练习中结合真实数据调用。

Nelson-Siegel 和 Svensson 模型

Nelson-Siegel 模型

Nelson-Siegel 模型用三个参数来描述整个收益率曲线:

$$r(\tau) = \beta_0 + \beta_1\left(\frac{1 - e^{-\tau/\lambda}}{\tau/\lambda}\right) + \beta_2\left(\frac{1 - e^{-\tau/\lambda}}{\tau/\lambda} - e^{-\tau/\lambda}\right)$$

三个因子的经济学解释:

  • β0水平因子(Level),长期利率水平(τrβ0
  • β1斜率因子(Slope),短期与长期利差(τ0rβ0+β1,短期-长期 =β1
  • β2曲率因子(Curvature),中端利率的隆起程度
  • λ:衰减参数,控制各因子的衰减速度
PYTHON22 行 · 765 B
📄此处有展示代码22 行 · 765 B展开 ▼
python
from scipy.optimize import minimize

def nelson_siegel(tau, beta0, beta1, beta2, lam):
    """Nelson-Siegel 模型 —— 即期利率关于期限的函数"""
    x = tau / lam
    factor_loading = (1 - np.exp(-x)) / x
    return (
        beta0
        + beta1 * factor_loading
        + beta2 * (factor_loading - np.exp(-x))
    )

def fit_nelson_siegel(maturities, yields):
    """拟合 Nelson-Siegel 参数"""
    def objective(params):
        beta0, beta1, beta2, lam = params
        predicted = nelson_siegel(maturities, beta0, beta1, beta2, lam)
        return np.sum((predicted - yields)**2)

    result = minimize(objective, [0.03, -0.02, 0.01, 2.0],
                     bounds=[(-0.1, 0.2), (-0.3, 0.3), (-0.3, 0.3), (0.1, 30.0)])
    return result.x
点击展开可浏览运行结果
拟合参数: beta0=0.0399, beta1=-0.0195, beta2=0.0137, tau=2.22

期限	原始	拟合
 0.2年	2.227%	2.214%
 0.5年	2.323%	2.374%
 1.0年	2.671%	2.648%
 2.0年	3.152%	3.051%
 3.0年	3.236%	3.317%
 5.0年	3.527%	3.616%
 7.0年	3.851%	3.756%
10.0年	3.871%	3.848%
20.0年	3.853%	3.926%
30.0年	3.988%	3.948%

Svensson 模型

Svensson 是 NS 的扩展,增加了第四个因子来捕捉收益率曲线上更复杂的形态(如双峰):

$$r(\tau) = \beta_0 + \beta_1\left(\frac{1 - e^{-\tau/\lambda_1}}{\tau/\lambda_1}\right) + \beta_2\left(\frac{1 - e^{-\tau/\lambda_1}}{\tau/\lambda_1} - e^{-\tau/\lambda_1}\right) + \beta_3\left(\frac{1 - e^{-\tau/\lambda_2}}{\tau/\lambda_2} - e^{-\tau/\lambda_2}\right)$$
PYTHON12 行 · 323 B
📄此处有展示代码12 行 · 323 B展开 ▼
python
def svensson(tau, beta0, beta1, beta2, beta3, lam1, lam2):
    """Svensson 扩展模型"""
    x1 = tau / lam1
    x2 = tau / lam2
    f1 = (1 - np.exp(-x1)) / x1
    f2 = (1 - np.exp(-x2)) / x2
    return (
        beta0
        + beta1 * f1
        + beta2 * (f1 - np.exp(-x1))
        + beta3 * (f2 - np.exp(-x2))
    )
点击展开可浏览运行结果
📘 本段代码定义了 1 个函数/类:函数 `svensson`(Svensson 扩展模型)。该片段为教学展示(未包含独立运行的输入数据),可在实战练习中结合真实数据调用。

主成分分析(PCA)对收益率曲线的解释

主成分分析是理解收益率曲线动态变化的最强大工具之一。对收益率变动的协方差矩阵进行特征分解,前三个主成分通常可以解释 95%以上的方差:

主成分解释比例经济学解释曲线变化形态
PC1~80-85%水平移动(Level/Shift)各期限利率同向变动
PC2~10-15%斜率变化(Slope/Twist)短端和长端反向变动
PC3~3-5%曲率变化(Curvature/Butterfly)中端与两端反向变动
PYTHON27 行 · 870 B
📄此处有展示代码27 行 · 870 B展开 ▼
python
def yield_curve_pca(yield_changes):
    """
    收益率曲线变动的主成分分析
    yield_changes: DataFrame, shape (n_dates, n_maturities)
                   每行是每天各期限的收益率变动
    """
    # 协方差矩阵
    cov_matrix = np.cov(yield_changes.T)

    # 特征分解
    eigenvalues, eigenvectors = np.linalg.eigh(cov_matrix)

    # 按特征值降序排列
    idx = np.argsort(eigenvalues)[::-1]
    eigenvalues = eigenvalues[idx]
    eigenvectors = eigenvectors[:, idx]

    # 解释比例
    total_var = eigenvalues.sum()
    explained_ratio = eigenvalues / total_var

    print("PCA 结果:")
    for i in range(min(3, len(eigenvalues))):
        print(f"  PC{i+1}: {explained_ratio[i]*100:.1f}% 方差解释")
        print(f"  特征向量: {eigenvectors[:, i].round(3)}")

    return eigenvalues, eigenvectors, explained_ratio
点击展开可浏览运行结果
📘 本段代码定义了 1 个函数/类:函数 `yield_curve_pca`(收益率曲线变动的主成分分析)。该片段为教学展示(未包含独立运行的输入数据),可在实战练习中结合真实数据调用。

曲线平移/扭转/蝶式变化

在实际的风险管理中,交易员和风险经理关注的不是单个期限的利率变化,而是三种标准化情景

平行移动(Parallel Shift)

所有期限的利率同时变化相同的幅度 Δr。这是最简单的利率风险情景,由久期对冲。债券组合对平移的敏感度:

$$\Delta P \approx -D_{\text{mod}} \cdot P \cdot \Delta r$$

斜率变化(Twist / Steepening-Flattening)

短端和长端反向变动。例如<降息交易>通常导致短端利率下降但长端稳定甚至上升("陡峭化")。对冲斜率风险需要结合不同期限的债券。

蝶式变化(Butterfly)

中端利率变动而短端和长端基本不变(或反向),体现为曲率的改变。

PYTHON15 行 · 597 B
📄此处有展示代码15 行 · 597 B展开 ▼
python
def scenario_analysis(portfolio_durations, scenario_shifts):
    """
    利率情景分析
    portfolio_durations: dict {maturity: modified_duration * position_value}
    scenario_shifts: dict {maturity: rate_change_bps}
    """
    total_pnl = 0
    for maturity, dv01 in portfolio_durations.items():
        # 线性近似:PnL = -DV01 * rate_change_bps / 10000 * notional
        shift = scenario_shifts.get(maturity, 0)
        pnl = -dv01 * shift
        total_pnl += pnl
        print(f"  期限 {maturity}y: DV01={dv01:.0f}, 变动 {shift:+d}bp -> PnL={pnl:+.0f}")

    return total_pnl
点击展开可浏览运行结果
📘 本段代码定义了 1 个函数/类:函数 `scenario_analysis`(利率情景分析)。该片段为教学展示(未包含独立运行的输入数据),可在实战练习中结合真实数据调用。

实战要点:在实践中,收益率曲线建模的最大挑战在于数据质量。市场报价存在买卖价差、流动性差异、以及不同债券的特殊性(如老券/新券溢价、含权特征等)。在构建曲线之前,必须对原始数据执行严格的数据清洗流程,剔除异常值和流动性不足的报价点。