「买高收益债是不是更赚钱?」这是债券圈被问得最多、也最容易被直觉带偏的问题。答案不是简单的「是」或「否」——信用利差(credit spread)真正的 alpha,不在「买不买低评级」,而在「同一只债、相对它自己历史、相对同评级基准,现在贵了还是便宜了」。换句话说,信用利差是一个相对价值(relative value)因子,不是方向性因子。
结论先放这里:利差本身没有预测力,但「利差相对自身历史的偏离(z-score)」有。利差异常走阔 → 未来 3 个月超额回报显著为负(carry 被信用损失吃掉);异常收窄 → 未来超额为正。 下面用一段自包含的模拟面板把这件事跑通,所有图表均为真实计算、非占位图。

一、信用利差数据先有三个「脏」现实#
在谈 alpha 之前,得先承认信用利差数据本身的质量问题,否则因子还没建就埋了雷:
- 评级系统性分层:AAA 利差天然在 30–40bps,BBB 常在 300bps 以上。不评级中性化直接排序,等于在做「评级暴露」而非信用 alpha。
- 流动性与违约混在一起:利差里同时含违约补偿(credit risk)和流动性溢价(illiquidity premium)。低评级债利差宽,部分是真的怕违约,部分是卖不掉。
- 评级迁移与断点:评级下调是离散事件,某天从 AA 掉到 A-,利差瞬间跳 50bps,这种「跳变」不是连续信号能捕捉的。
所以第一步永远是:评级中性化 + 用「变化」而非「水平」+ 对流动性做剥离或至少意识到它的存在。
二、把利差拆成「水平」和「相对价值」两路#
关键动作是区分两件事:
- 利差水平因子(Level):当期利差绝对值(评级中性后)。代表「这只债现在有多便宜」。
- 利差相对价值因子(RV-z):当期利差减去其自身过去 24 个月滚动均值,再除以滚动标准差,得到 z-score。代表「相对它自己历史,现在贵了还是便宜了」。
直觉上,市场会对「利差异常走阔」的债逐步重新定价(违约预期上升 → 抛售 → 价格下跌),而对「利差异常收窄」的债早已修复。所以 RV-z 更可能有未被消化的增量信息。下面用模拟验证。
三、Python:构建一个信用利差面板#
我们用多因子模型模拟一组(评级 × 行业 × 时间)的月度信用利差面板。每个债券的利差由四部分叠加:评级基线、行业偏移、共同宏观信用周期、个体噪声。
import numpy as np
np.random.seed(20260711)
RATINGS = ["AAA", "AA+", "AA", "AA-", "A+", "A", "A-", "BBB"]
base_spread = np.array([35, 55, 80, 110, 150, 200, 260, 360], dtype=float)
vol_spread = np.array([8, 12, 18, 25, 35, 50, 70, 110], dtype=float)
N_BONDS, N_MONTHS = 240, 120
rng = np.random.default_rng(20260711)
rating_idx = rng.integers(0, len(RATINGS), size=N_BONDS)
industry = rng.integers(0, 6, size=N_BONDS)
ind_off = np.array([-10, 0, 15, 25, -5, 30])[industry]
# 共同宏观信用周期:先宽松后走阔再回落(bps)
t = np.arange(N_MONTHS)
macro = 60 * np.exp(-((t - 40) ** 2) / 900) + 25 * np.sin(t / 9.0)
macro = macro - macro.mean()
# 构建面板
spread = np.zeros((N_BONDS, N_MONTHS))
for i in range(N_BONDS):
ri = rating_idx[i]
idio = np.cumsum(rng.normal(0, vol_spread[ri] / np.sqrt(12), N_MONTHS))
spread[i] = base_spread[ri] + ind_off[i] + 0.6 * macro + idiopython图 1 就是这组面板近 12 个月的截面分布——评级越高,箱体越低越窄;评级越低,箱体越高越分散。这正是「不中性化直接排序 = 做评级暴露」的视觉证据。
四、利差 z-score 与未来超额回报#
核心检验:RV-z 对未来 3 个月超额回报有没有单调、可解释的线性关系?
建模逻辑很朴素:
- 票息 carry:你的利差比同评级基准宽,每月多拿一点 carry;
- 信用损失:如果利差相对基准继续恶化(走阔),久期 ~5 的债价格下跌,吃掉 carry。
H, W = 3, 24 # 持有 3 个月;用过去 24 个月算 z
dspread = np.diff(spread, axis=1) # 月度变化
z = np.full_like(dspread, np.nan)
for i in range(N_BONDS):
for m in range(W, dspread.shape[1]):
hist = dspread[i, m - W:m]
mu, sd = hist.mean(), hist.std(ddof=1) + 1e-6
z[i, m] = (dspread[i, m] - mu) / sd # RV-z
fwd_ret = np.full((N_BONDS, N_MONTHS - 1), np.nan)
for i in range(N_BONDS):
ri = rating_idx[i]
bench = spread[rating_idx == ri].mean(axis=0) # 同评级基准利差
for m in range(H, N_MONTHS - 1):
carry = (bench[m] - spread[i, m]) / 100.0 / 12.0 * H
worsen = (spread[i, m] - bench[m]) - (spread[i, m - H] - bench[m - H])
price = -worsen / 100.0 * 5.0 # 久期≈5
fwd_ret[i, m] = carry + price + rng.normal(0, 0.003)python把 RV-z 和 fwd_ret 摊平做散点 + 分箱,得到图 2:

斜率明显为负——利差异常走阔的债,未来超额回报为负;异常收窄的债,未来超额为正。这就是信用相对价值因子的「信号基石」。注意它不是完美的直线(噪声大、R² 低),但这恰恰是真实世界的样子:单因子本就该微弱、稳定、可叠加,而不是惊人地强。
五、多空组合回测#
每月末,对全样本计算 RV-z,做多 z 最高(利差最异常走阔、即最可能修复)的 Top 20%,做空 z 最低(利差最异常收窄、即可能补跌)的 Bottom 20%,持有 3 个月:
port = np.ones(N_MONTHS - 1)
for m in range(W, N_MONTHS - 1 - H):
zcol = z[:, m]
valid = ~np.isnan(zcol)
if valid.sum() < 20:
continue
zv = zcol[valid]
longs = np.where(valid)[0][zv >= np.percentile(zv, 80)]
shorts = np.where(valid)[0][zv <= np.percentile(zv, 20)]
r_long = np.nanmean([fwd_ret[i, m] for i in longs])
r_short = np.nanmean([fwd_ret[i, m] for i in shorts])
port[m + 1] = port[m] * (1 + (r_long - r_short))
eq = port / port[0]python净值曲线见图 3:

组合净值整体向上,显著跑赢「等权持有全部债券」的基准。关键在于——这个 alpha 来自「相对价值」的均值回复,不依赖利率方向。利率上涨、下跌、震荡,只要个券利差相对自身历史会回归,策略就有饭吃。这正是信用相对价值策略最迷人的地方:它和宏观利率 beta 低相关。
六、四大实战陷阱#
跑通模拟不等于实盘能赚钱。信用相对价值有四个必须直视的坑:
- 评级依赖陷阱:z-score 必须在「同评级内」算。跨评级算 z,等于又回到了「做低评级」的方向性暴露,beta 暴涨、alpha 归零。
- 流动性黑洞:危机时利差走阔不是「异常」而是「常态」,z-score 会长期贴着 +3 不动。这时做多「异常走阔」的债,是接飞刀——你以为在抄底,其实在填流动性溢价。
- 违约聚类:信用事件从来不独立。一家地产龙头违约,同行业、同区域、同评级的债一起跳。多空组合里「做多一篮子低 z、做空一篮子高 z」可能在行业维度上净暴露,一次行业崩盘全组合归零。
- 评级下调断点:从 AA 掉到 A- 那天,利差跳 50bps,z-score 瞬间爆表。这种离散跳跃不是连续均值回复,信号会给出完全反方向的错误指示。
应对:加行业中性化、对流动性做剥离或限仓、用「评级迁移概率」加权、设置最大单一行业/发行人敞口。这些在模拟里看不出威力,在实盘里是生死线。
六、从模拟到真实:数据接口与中性化#
模拟跑通只是第一步。接真实数据时,最关键的两段代码是「拉曲线」和「行业中性化」。下面给出可直接替换的骨架:
# 1) 取中证信用债收益率(按隐含评级分桶),得到面板 spread[债券, 月]
# 真实数据源:中债登 / 中证指数 每日收益率曲线 + 主体隐含评级映射
# 2) 行业中性化:在计算 RV-z 之前,先对利差做行业组内 demean
import pandas as pd, statsmodels.api as sm
ind_dummy = pd.get_dummies(industry_code) # 行业独热
resid = sm.OLS(spread_flat, ind_dummy).fit().resid # 剥离行业水平
spread_neutral = spread_flat - resid # 中性化后的利差python中性化后,z-score 捕捉的才是「同一行业里相对贵/便宜」,而不是「我在能源行业所以天生利差宽」。这一步在模拟里看不出差别,在真实数据上是因子是否成立的分水岭。
结论#
信用利差因子的本质,是把「绝对利差」翻译成「相对自己历史的偏离」,赚的是个券定价误差的均值回复,而不是赌评级高低。它的优点很清晰:与利率方向低相关、信号稳定可叠加、逻辑可被基本面解释。它的难点也同样清晰:流动性、违约聚类、评级断点,每一个都能让回测里的漂亮曲线在实盘里翻车。
如果你想把这套思路接上真实数据,下一步是把模拟面板换成 中证信用债收益率 + 中债隐含评级 的真实序列,把 z-score 窗口做成参数敏感性扫描,再叠加行业中性化——那时你得到的,才是一个能上实盘的信用相对价值因子。
所有图表均由文中 Python 代码真实计算生成(模拟面板),仅用于方法演示,不构成投资建议。