第三章 信用利差数据获取:从原始数据到标准化利差

做信用债交易,最怕什么?

不是市场波动,而是你手里的数据是错的。

我见过太多交易员,拿着Wind上拉下来的利差数据直接做策略,结果回测漂亮得不行,实盘一跑就崩。为什么?因为数据源没对齐,利差计算口径五花八门。

这一章,咱们就聊聊怎么把信用利差数据搞干净。说白了,就是三个步骤:数据获取 → 数据清洗 → 利差计算与标准化

核心观点:信用利差数据的质量,直接决定策略的生死。别在数据上偷懒,否则市场会教你做人。

信用利差数据体系 数据获取 数据清洗 利差计算与标准化 Wind接口 聚宽/JQData 彭博API 缺失值处理 异常值检测 期限对齐 OAS计算 Z-Spread 标准化处理 输出:标准化信用利差时间序列

3.1 数据获取:三个主流渠道怎么选?

做信用利差分析,数据源是第一步。我个人习惯用Wind做主力,聚宽做回测验证,彭博用来做跨境品种的交叉核对。说说各自的坑和优势。

3.1.1 Wind接口:国内债市标配

Wind在信用债数据上覆盖面最全。中债估值、中证估值、交易所成交数据,基本都能拉得到。

我常用的Wind Python接口代码长这样:

from WindPy import w
w.start()

# 拉取某只信用债的中债估值收益率
data = w.wsd("101754001.IB", 
             "ytm_b", 
             "2024-01-01", 
             "2024-12-31", 
             "returnType=1")
print(data.Data[0])

小技巧:Wind的wsd接口默认返回的是收盘价数据。如果你要拿中债估值,记得加参数returnType=1。我之前没加这个参数,拉了一周的数据全是错的,气得我直接重跑。

3.1.2 聚宽JQData:回测党的最爱

聚宽的好处是数据格式干净,不用自己清洗太多。但信用债覆盖不如Wind全,尤其是一些私募债和次级债。

from jqdatasdk import *
auth('你的账号', '你的密码')

# 获取某只信用债的历史行情
df = get_price("101754001.XSHG", 
               start_date="2024-01-01", 
               end_date="2024-12-31", 
               frequency="daily")
print(df.head())

注意:聚宽的债券代码后缀是.XSHG或.XSHE,别和Wind的.IB搞混了。我有个同事就是代码写混了,回测跑了三天才发现数据全是空的。

3.1.3 彭博API:跨境品种必备

做中资美元债或者点心债,彭博是绕不开的。彭博的API叫blpapi,用起来稍微复杂点,但数据质量确实高。

import blpapi
from blpapi import Session

session = Session()
session.start()

# 请求某只中资美元债的OAS数据
refData = session.refData("XS1234567890 CORP", 
                          ["OAS_SPREAD", "YLD_YTM_MID"])
print(refData)

说实话,彭博的API文档写得真不咋地。我第一次用的时候,光连接session就折腾了两小时。后来发现是环境变量没配好。

3.2 数据清洗:别让脏数据毁了你的策略

数据拉下来之后,别急着算利差。先做清洗。我总结了三步走:

  1. 缺失值处理:债券数据经常有缺失,尤其是节假日或者流动性差的券。
  2. 异常值检测:比如收益率突然跳升100bp,可能是数据错误,也可能是真的违约风险爆发。
  3. 期限对齐:不同债券的剩余期限不同,直接比收益率没意义。

举个例子,我曾经处理过一只城投债的数据,某天收益率从4.5%直接跳到15%。我第一反应是数据错了,后来一查,那天确实有负面舆情。嗯,这种异常值就不能简单剔除,得结合事件分析。

import pandas as pd
import numpy as np

# 假设df是原始数据,包含date, bond_code, ytm三列
df = pd.read_csv("credit_bond_data.csv")

# 第一步:删除ytm为空的记录
df = df.dropna(subset=["ytm"])

# 第二步:用3-sigma法则检测异常值
mean_ytm = df["ytm"].mean()
std_ytm = df["ytm"].std()
df = df[(df["ytm"] > mean_ytm - 3*std_ytm) & 
        (df["ytm"] < mean_ytm + 3*std_ytm)]

# 第三步:按剩余期限分组,做期限对齐
df["tenor_bucket"] = pd.cut(df["remaining_term"], 
                            bins=[0, 1, 3, 5, 7, 10, 30],
                            labels=["0-1Y", "1-3Y", "3-5Y", 
                                    "5-7Y", "7-10Y", "10Y+"])

关键点:数据清洗不是机械地删数据。你得知道哪些数据是「真异常」,哪些是「假信号」。我一般会保留异常值单独分析,而不是一刀切删掉。

3.3 利差计算:从收益率到信用利差

信用利差的计算,说白了就是「债券收益率 - 无风险利率」。但问题来了:无风险利率用哪个?国债收益率?国开债收益率?还是利率互换?

我个人习惯用同期限国开债收益率作为基准。为什么?因为国开债流动性好,而且和信用债的税收待遇更接近。用国债的话,还要考虑税收调整,麻烦。

3.3.1 简单利差 vs OAS

最简单的利差就是直接减:

# 简单利差计算
df["simple_spread"] = df["ytm"] - df["benchmark_yield"]

但这样做有个问题:它没考虑债券的含权条款。比如一个含回售权的债券,它的收益率本身就比普通债低。这时候就要用OAS(期权调整利差)

OAS的计算比较复杂,一般用Wind或者彭博的接口直接拿:

# 用Wind拿OAS
oas_data = w.wsd("101754001.IB", 
                 "oas_spread", 
                 "2024-01-01", 
                 "2024-12-31")
print(oas_data.Data[0])

3.3.2 Z-Spread:另一种视角

Z-Spread是假设利率期限结构是平坦的,然后算出来的利差。它和OAS的区别在于:OAS考虑了利率波动对期权价值的影响,Z-Spread没考虑。

做交易的时候,我一般两个都看。如果OAS和Z-Spread差距很大,说明这只债的含权条款价值很高,需要仔细分析。

3.4 标准化处理:让不同债券的利差可以比较

不同评级、不同期限的债券,利差本身没有可比性。比如一个3年期AA+城投债利差150bp,一个5年期AA产业债利差200bp,哪个更贵?

这时候就需要标准化。我常用的方法有两种:

  • Z-score标准化:将利差减去均值,除以标准差。这样不同债券的利差就变成了「偏离历史均值的标准差倍数」。
  • 百分位数排名:看当前利差处于历史分布的什么位置。比如当前利差处于95%分位数,说明很贵了。
# Z-score标准化
df["z_score"] = (df["simple_spread"] - df["simple_spread"].mean()) / df["simple_spread"].std()

# 百分位数排名
df["percentile"] = df["simple_spread"].rank(pct=True)

我的习惯:做交易信号的时候,我更喜欢用百分位数。因为Z-score对极端值太敏感,而百分位数更稳健。比如某只债的利差突然飙到历史最高,Z-score可能变成5,但百分位数还是100%,更容易理解。

3.5 实战中的避坑指南

最后,分享几个我踩过的坑:

  • 数据频率不一致:Wind的估值数据是日频,但成交数据可能是分钟级。合并的时候一定要对齐时间戳。
  • 复权问题:债券有付息,如果不做复权处理,收益率曲线会跳来跳去。我一般用中债的净价收益率,省心。
  • 节假日处理:中国债市有调休,有时候周六也交易。直接用自然日做时间序列会出问题。我习惯用交易日历对齐。

曾经踩过的坑:有一次我做跨市场套利策略,用了Wind的国债收益率和聚宽的公司债收益率。结果发现两个数据源的基准利率差了10bp。后来一查,Wind用的是中债估值,聚宽用的是中证估值。两个估值体系不一样!从那以后,我所有数据都只用同一个数据源。

好了,数据获取、清洗、计算、标准化,这一套流程走下来,你手里的信用利差数据才算真正能用。下一章咱们聊聊怎么用这些数据构建交易信号。不过在那之前,我建议你先把自己手上的数据跑一遍,看看有没有我提到的那些坑。


专注资料整理