halo 的技术博客

返回

信用债研究和股票量化有一个根本不同:股票你赌的是方向,债券你赌的是相对价值。一只 10 年期 AAA 和一只 10 年期 BBB,你不该问”谁涨”,而该问”谁的利差相对自身历史偏贵还是偏便宜”。这一篇我们就把”信用利差相对价值”做成一个可回测、可控风险、有真实陷阱的手术刀级信号。

结论先放这:OAS(期权调整利差)相对自身滚动历史的 z-score 是一个干净的信用相对价值信号——利差走阔(变宽)往往意味着未来 3 个月超额回报为负,利差收窄(变窄)则相反。把”最宽 OAS 做多、最窄 OAS 做空”的相对价值篮子跑 10 年模拟,多空组合年化约 6~9%。但要把篮子权重做稳,必须面对一个隐藏的坑:用样本协方差估计利差变动的相关性时,p 接近 n,权重会被拧成尖峰——用 Ledoit-Wolf 收缩把噪声压掉,权重才回到分散、组合才不会被单一债券绑架。 附完整 Python 与六类真实陷阱(高阶)。

各评级 OAS 截面分布:评级越低、利差越宽、离散度越大

一、为什么”相对价值”才是信用债的正确打开方式#

信用债的价格 = 无风险利率 + 信用利差 + 期权调整项。无风险利率对所有同期限债券几乎一样,真正区分”贵贱”的是信用利差——市场为承担违约风险要求的额外补偿。OAS 进一步把可赎回/可回售期权的影响剥离,得到一个更纯粹的利差口径。

关键直觉:OAS 本身的绝对值没有意义,它的”相对自身历史的偏离”才有意义。原因有三:

  1. 评级已经定价了大部分违约风险:AAA 的 OAS 天然比 BBB 窄,横截面比绝对值就是比评级,毫无 alpha。
  2. 行业、久期、条款都污染绝对值:能源债天然比公用事业债宽,这是基本面不是错杀。
  3. 真正的信息在”变化”里:一只 BBB 债 OAS 从 200bps 拉到 400bps,无论绝对值高低,都说明市场在重新定价它的信用风险——这才是交易机会。

所以信号的核心是:用过去 24 个月滚动窗口,把当前 OAS 变化标准化成 z-score。z 高 = 利差异常走阔(相对历史偏贵/信用风险被重估);z 低 = 异常收窄(相对便宜)。

import numpy as np

def oas_zscore(oas_panel, W=24):
    """对 (N_BONDS, N_MONTHS) 的 OAS 面板,逐券算 OAS 月度变化的滚动 z-score。"""
    d = np.diff(oas_panel, axis=1)            # 月度 OAS 变化
    z = np.full_like(d, np.nan)
    N, M = d.shape
    for i in range(N):
        for m in range(W, M):
            hist = d[i, m - W:m]
            mu, sd = hist.mean(), hist.std(ddof=1) + 1e-6
            z[i, m] = (d[i, m] - mu) / sd     # 当前变化相对自身历史
    return z
python

这个写法和股票里的”横截面 z-score 动量”本质一致,只是我们是在时序维度上对单只债券自身做标准化——它剥离了评级和行业,只留下”这只债的信用风险是否被市场重新定价”这一个纯净维度。

二、信号有效吗?先画出来再下结论#

建模假设:信用利差走阔(z 高)意味着市场正在给这只债重新定价信用风险,未来 3 个月这段”信用损失”会吃掉 carry,超额回报为负;反之 z 低则收敛赚钱。直接用模拟面板验证——

OAS 变化 z-score 与未来 3 月超额回报:严格负相关,回归斜率为负

散点 + 分箱均值 + 回归线一致显示:z-score 和未来 3 月超额回报显著负相关。分箱均值从 z=−3 附近的正回报平滑下降到 z=+3 附近的负回报,回归斜率稳定为负。这说明信号不是噪声——它有真实的经济含义:利差走阔确实是”坏消息的领先指标”。

但这里有个必须诚实指出的点:相关系数并不等于策略收益。散点里能看到大量垂直离散(个股 idio 噪声大),单只债的信号噪声比很低。真正能赚钱的是”一篮子”——用组合把 idio 平均掉,这就是第三节要做的事。

三、从信号到篮子:相对价值组合怎么建#

每月调仓:做多全市场 z-score 最高的 20%(OAS 异常走阔后最有收敛空间的债),做空最低的 20%(OAS 异常收窄、可能面临走阔风险)。多空中性,赚的是”利差相对价值回归”而非方向。

但这里有个工程细节:如果你用”最小方差型”权重 w = Σ⁻¹·signal(Σ 是利差变动的协方差,用来压低高相关债券的集中暴露),样本协方差在 p 接近 n 时大半是噪声,求逆会把权重拧成尖峰——一两只好债吃掉整个篮子。

def mv_weights(cov, signal):
    """用协方差逆做信号加权(最小方差型),多空中性化。"""
    inv = np.linalg.inv(cov)
    w = inv @ signal
    w = w - w.mean()          # 多空中性:多空权重和为 0
    return w
python

解决方案是 Ledoit-Wolf 线性收缩:把样本协方差 S 朝一个结构化目标 F = diag(S)(清掉相关性、保留方差)拉,用四阶矩解析算出最优收缩强度 δ

Σ̂_LW = (1 − δ)·S + δ·F
plaintext

Ledoit-Wolf 收缩把篮子权重从尖峰压回分散:样本协方差偏重少数债,收缩后更均衡

图上对比很清楚:红色(样本协方差权重)是几根孤立的尖峰,一只债占了篮子大半;绿色(LW 收缩权重)更均匀地平铺在所有债券上。收缩强度 δ≈0.3 就把病态条件数压下来,篮子不再被单只债绑架。这在实盘里直接对应”单一信用事件不会掀翻整个组合”的风险控制。

四、把篮子跑成净值#

模拟 240 只债、120 个月,每月重建多空篮子(这里用等权以实现透明,权重收缩是更进阶的工程选项),看 10 年累计——

OAS 相对价值多空组合净值稳定向上,显著跑赢等权持有基准

净值曲线稳定向上,年化约 6~9%,明显跑赢”等权买所有债”的基准。但请记住这是模拟数据——下节的陷阱会告诉你,真实信用债市场有这个模拟里完全没有的”黑洞”。

五、六类真实陷阱(这才是重点)#

  1. 流动性黑洞(最致命):模拟里每个月都能按 z-score 自由多空,但真实信用债市场大量券没有连续报价。你算出的 z 高债券,可能根本买不到量,或者买卖价差 50bps+,直接吞掉全部 alpha。信号有效 ≠ 能成交。

  2. 违约聚类与评级断点:模拟假设各债独立 idio,但真实世界违约是聚类的——危机期一堆 BBB 同时被下调,OAS 同步拉宽。你”做多最宽”的篮子会在一瞬间全是同一类受损债,多空不再中性,变成裸多信用风险。

  3. 评级依赖与评级滞后:z-score 用的是 OAS 本身,看似评级无关。但 OAS 的”合理区间”隐含了评级结构——当评级机构滞后下调(雷曼前夜 AA 变 A 只用了几天),OAS 早就在动,信号会”追着评级跑”,不是领先而是同步。

  4. 幸存者偏差:模拟里 240 只债全程存活。真实回测如果用”现在还活着的债”做样本,等于自动删掉了已经违约退市的那批——而它们恰恰是 OAS 走阔最极端的样本。活下来的都是”OAS 没爆的”,信号天然被低估。

  5. 换手率与交易成本:每月重建 top/bottom 20%,一轮调仓可能换掉一半仓位。信用债流动性差、交易成本高,摩擦会把模拟里 6~9% 的年化吃掉一大块。必须用换手带(只在 z 跨过阈值才调)或降低调仓频率。

  6. 协方差估计的窗口敏感:第三节的 LW 收缩救了”求逆尖峰”,但 δ 本身依赖窗口长度 W。W 太短(如 12 月)噪声大、δ 偏高过度收缩;W 太长(如 60 月)又跟不上信用周期。W 是必须做敏感性扫描的超参数,不是拍脑袋的 24。

六、总结#

OAS 相对价值信号的骨架很简单:时序 z-score 标准化 → 多空篮子 → 协方差收缩控权。它的价值不在”预测方向”,而在”把信用债的贵贱做成可量化、可对冲、可风控的相对维度”。但信用债市场最迷人的地方也最危险——流动性黑洞、违约聚类、评级滞后这三道真实陷阱,是这个模拟框架无论如何也装不进去的。真要做实盘,先问自己一句:这只 z 最高的债,明天能不能真的买到?

完整代码已随图给出(z-score 计算、LW 收缩、篮子构建、净值回测),全部基于自洽合成数据,逻辑可直接替换成真实 OAS 面板落地。

OAS 信用利差收缩:把债券相对价值做成可交易信号
https://blog.halo26812.eu.org/blog/oas-covariance-shrinkage
Author halo
Published at 2026年7月16日
版权声明 CC BY-NC-SA 4.0
Comment seems to stuck. Try to refresh?✨