做债券数据活的人应该都有过这段经历月初拿到一张几百行的持债清单要求补全中债估值收益率、修正久期、票面利率、待偿期限还得按主体评级筛一遍。最早我靠Wind终端一只一只点开复制粘贴到Excel做完差不多小半天眼睛都是花的。后来把Wind给的另外两条路摸熟了——Excel插件和Python的WindPy接口——同一张表压缩到五分钟。这篇文章没有官方文档那么死板就是我在债券分析里被这些工具救过、也坑过之后整理出来的完整玩法。不管你是债券交易员、信评、风控还是量化开发只要日常工作离不开Wind数据和Excel这篇文章都值得收藏。我会先把Excel插件端的函数用法讲透再一步步把Python交互跑通最后给出债券场景下的完整脚本和踩坑记录。1. 债券工作里的两个“Wind入口”Excel插件和Python各管哪一段1.1 Excel插件解决“轻量查询”Python解决“批量与自动化”很多刚接触Wind的人有个误区以为Excel插件和Python接口只能选一个。实际这两套东西在债券工作流里分工完全是互补的。Excel插件最大的优势是“所见即所得”。你在单元格里敲一行WSS(210205.IB,中债估值收益率,)马上看到结果鼠标下拉还能批量填充。对于临时查一两只券、核对一个数、做张给领导看的交互报表这个效率是Python比不了的。它适合的场景是量不大、逻辑不复杂、需要人工干预和确认的数据操作。Python接口WindPy则适合重活累活。几百只债券一起取数Excel公式往下拖的时候每一格都在发请求文件卡得鼠标都挪不动这时候用Python一次w.wss调用把几百个代码的数据全部拉下来转成DataFrame清洗、筛选、计算再回填Excel速度和稳定性完全不在一个量级。债券分析和股票不太一样你可能需要每天跟估值报表、收益率曲线、利差数据打交道这些要么是批量截面要么是长时间序列天生适合脚本处理。所以我给你的第一个建议查数、核数用Excel插件跑表、建模型用Python两条腿走路。1.2 同一个Wind账号数据口径是否一致实务中大家最担心的问题就是Excel插件里取出来的数和Python取出来的是不是一样我的经验是底层数据源同一套绝大多数情况下口径一致。但有一个隐藏风险某些字段在Excel端有默认参数在Python端可能也需要显式传参数值你不传的时候可能会得到不太一样的值。举个例子收益率类字段经常涉及“精度”“计算方式”“是否含税”等参数评级字段可能涉及“债项评级”和“主体评级”。如果你在Excel里用的字段名到Python里原封不动照搬有时候会踩到参数默认值的坑。所以我做关键报表时有一个固定习惯重要数据先用终端手工核一次再分别用Excel插件和Python各取一次三者对上之后再把这个字段名单固化在脚本里。不要盲目信任某个字段名Wind的指标显示名在不同版本里偶尔会调整脚里最好加一步数据校验。2. 先用Excel插件把估值表拉直债券场景最常用的W函数2.1 WSS、WSD、WDS、WSQ选哪个Wind Excel插件对外暴露了一组W开头的函数。很多新手拿到就乱用拉出来的数据张冠李戴。其实选函数很简单核心就是判断你要的是“截面”还是“时序”。函数用途返回形态债券场景WSS多个代码的某一时点指标一行一个代码取最新估值收益率、久期、评级WSD单代码的历史时间序列一行一个日期取某只债或某条曲线的历史收益率WDS多代码在历史某一天的截面数据一行一个代码取历史估值日所有券的估值WSQ实时行情快照一行一个代码银行间/交易所行情盯盘WST日内分钟/ tick数据一行一个时间点基本不用于债券日常债券分析里最常碰到的还是WSS和WSD。比如我要知道“210205.IB今天的中债估值收益率和修正久期”用WSS我要看“过去一年这只券的估值收益率走势”用WSD。WDS常用于做历史截面回看比如“6月30日这天这批债券分别估值多少”。2.2 一个债券估值模板的搭建过程假设你手里有一批债券代码要在Excel里快速建一张估值表。打开Wind插件后可以先在“公式生成器”里搜索字段名把标准字段名复制出来避免手打错误。然后在单元格里这样写A2: 210205.IB B2: WSS(A2,中债估值收益率,) C2: WSS(A2,中债估值修正久期,) D2: WSS(A2,票面利率,) E2: WSS(A2,待偿期限,)如果有多只券就把A列往下拉成代码列表B到E列的公式一并下拉填充。这里有个实操细节下拉之前一定要把公式里的代码引用改成绝对引用比如WSS($A2,中债估值收益率,)或者直接用Excel表格对象Table的结构化引用否则下拉后行号会错位轻则取错券重则整列都是#N/A。我建模板的时候还会顺手在最上面加一行“数据日期”然后用WDS来做历史切换。只要改日期整张表自动回到那一天这个在月底回溯、历史估值对比时特别好用。2.3 Excel插件的两个“反直觉”坑第一个坑WSS返回的很多数值在Excel里会被当成文本。你用SUM、AVERAGE统计时会发现结果不对单元格左上角可能有个绿色三角。处理办法是给公式包一层数学运算比如WSS($A2,中债估值修正久期,)*1或者在外面套VALUE()。不建议只靠“分列”功能去改因为公式刷新后又会变回文本。第二个坑整张表全是WSS公式时只要改一个无关单元格Excel都可能在后台重新请求一遍Wind数据文件会越用越卡。更麻烦的是如果你把工作簿发给没有Wind终端的人对方打开后单元格全变成#NAME?或#VALUE!。所以给外部人员发报表之前一定要把含公式的区域复制粘贴成数值。如果是自己日常用建议加一个“刷新开关”让公式前面用一个单元格布尔值控制比如IF($G$1,WSS($A2,中债估值收益率,),—)只有需要刷新时才打开开关否则填—占位。这招能让大表格流畅很多。3. Python接管数据WindPy的安装、连接与三板斧3.1 安装WindPy并完成第一次连接WindPy是Wind官方提供的Python接口不是第三方爬虫数据合法性和稳定性都有保障。安装方式现在很省事打开Wind终端在菜单里找到“帮助”或“API接口”区域下载对应Python版本的安装包或者直接在终端里搜索“WindPy”按提示pip安装。安装完成后代码里这样启动from WindPy import w import pandas as pd w.start() print(w.isconnected())如果输出True说明已经连上终端了。有几个前提条件必须是Windows系统且本机装了Wind终端。连接前Wind终端要保持登录状态不一定要有行情界面但后台进程得活着。Python版本最好在Wind官方支持矩阵内别一上来装最新版Python有的版本WindPy还没适配。第一次跑通我用了五分钟大部分时间都花在确认Python版本和Wind终端版本是不是匹配上。如果你import WindPy就报错先去检查这个版本匹配关系比反复重装库有效得多。3.2 wsd、wss、wsq返回对象的解析套路WindPy里最常用的三个接口对应Excel端的WSS、WSD、WSQw.wss(codes, fields, options)取截面多代码多字段一次返回。w.wsd(code, field, beginTime, endTime, options)取单代码单字段的历史序列。w.wsq(codes, fields, options)取实时行情。每个接口返回的都是一个对象关键属性就这几个ErrorCode0表示成功、Codes代码列表、Fields字段列表、Times日期列表、Data数据矩阵。新手最容易犯的错是拿到返回对象后盯着它看半天不知道下一步怎么处理。其实把它转成pandas结构就行了。from WindPy import w import pandas as pd w.start() # 截面数据多只债券、多个字段 codes [210205.IB, 220001.IB, 019742.SH] fields 中债估值收益率,中债估值修正久期,票面利率,待偿期限 res w.wss(codes, fields, tradeDate20240920) if res.ErrorCode 0: df pd.DataFrame(res.Data, indexres.Codes, columnsres.Fields) print(df) else: print(ErrorCode:, res.ErrorCode, res.Data)这段代码是债券估值表最核心的骨架。res.Data是一个二维列表行为代码、列为字段所以直接用它构造DataFrame索引设成债券代码列名设成字段名就行。历史序列的转法也很固定res w.wsd(210205.IB, 中债估值收益率, 2024-01-01, 2024-12-31, fillPrevious) if res.ErrorCode 0: s pd.Series(res.Data[0], indexpd.to_datetime(res.Times), nameres.Fields[0]) print(s.head())fillPrevious这个参数在债券历史序列里很实用因为非交易日太多不填的话序列会有大量空值画图不好看计算也会出问题。3.3 把Wind数据转成Pandas DataFrame的两个实用函数为了避免每次写代码都重复处理返回对象我把这几个接口封装成了小函数放在本地工具模块里。你完全可以直接抄走用from WindPy import w import pandas as pd w.start() def wss_to_df(codes, fields, options): 多代码、多字段截面数据返回DataFrame res w.wss(codes, fields, options) if res.ErrorCode ! 0: raise RuntimeError(fWind error: {res.ErrorCode}) return pd.DataFrame(res.Data, indexres.Codes, columnsres.Fields) def wsd_to_series(code, field, start, end, options): 单代码、单字段历史序列返回Series res w.wsd(code, field, start, end, options) if res.ErrorCode ! 0: raise RuntimeError(fWind error: {res.ErrorCode}) return pd.Series(res.Data[0], indexpd.to_datetime(res.Times), nameres.Fields[0]) def wsd_to_df(codes, field, start, end, options): 多代码、单字段历史序列返回宽表DataFrame行为日期列为代码 res w.wsd(codes, field, start, end, options) if res.ErrorCode ! 0: raise RuntimeError(fWind error: {res.ErrorCode}) df pd.DataFrame(res.Data).T df.index pd.to_datetime(res.Times) df.columns res.Codes return df注意w.wsd其实也支持一次传多个代码返回的res.Data里每个元素是对应一只券的序列。wsd_to_df就是利用这个特性把多只券的同字段历史数据拼成宽表做利差分析和曲线走势对比很顺手。4. 债券实战批量拉取估值指标并回填Excel的完整脚本4.1 案例需求筛选“久期在2到4年、主体评级较好”的信用债光讲函数太抽象我拿一个真实场景串一遍。假设你手上有一堆信用债代码领导想要一张表只要待偿期限在2到4年、中债估值修正久期在2到4年、主体评级在AA及以上的券同时把估值收益率、净价、票面利率全列出来。放到以前我会在Excel里拉几百行WSS公式然后手动筛。现在Python几十行搞定。顺便说一句这个选券逻辑看着简单却是组合免疫、久期匹配这些策略里最常做的基础动作。4.2 脚本分步拆解代码清单、参数说明、输出效果from WindPy import w import pandas as pd w.start() codes [ 210205.IB, 220001.IB, 220005.IB, 019742.SH, 143295.SH, 102280023.IB, 102280156.IB, ] fields 中债估值收益率,中债估值修正久期,中债估值净价,票面利率,待偿期限,主体评级 res w.wss(codes, fields, tradeDate20240920) if res.ErrorCode ! 0: print(取数失败ErrorCode:, res.ErrorCode) else: df pd.DataFrame(res.Data, indexres.Codes, columnsres.Fields) print(原始数据) print(df) # 数值型字段转成数字避免后面比较时报错 for col in [中债估值收益率, 中债估值修正久期, 中债估值净价, 票面利率, 待偿期限]: df[col] pd.to_numeric(df[col], errorscoerce) # 评级字段先看看实际返回了什么别急着筛 print(评级字段枚举值, df[主体评级].unique()) rating_mask df[主体评级].astype(str).str.contains(AA\\, regexTrue, naFalse) maturity_mask (df[待偿期限] 2) (df[待偿期限] 4) duration_mask (df[中债估值修正久期] 2) (df[中债估值修正久期] 4) result df[rating_mask maturity_mask duration_mask] print(筛选结果) print(result)有几处需要特别说明代码后缀不能省。债券在Wind里的代码体系比股票复杂银行间用.IB上交所用.SH深交所用.SZ。同一个券在不同市场可能对应不同代码写错后缀取不到数或者取到另一只券。pd.to_numeric(errorscoerce)是必须的。Wind返回的有些数值列可能是对象类型直接和数字比较会报错强制转成数字后转不了的会自动变成NaN后面再做缺失处理。主体评级字段我习惯先打印unique()看一次。因为现实中可能是“AAA”“AA”“AA 中债”也可能带了空格或后缀。用str.contains(AA\\, regexTrue)匹配能避开“AAA被AA误匹配”的问题因为正则里的\\表示匹配加号本身不会把AAA匹配进去。4.3 用Python拼接Excel报告把结果回填到模板表数据算完最自然的需求是落成Excel报表。如果你只要一个简单的结果文件pandas一行就行result.to_excel(bond_screen_result.xlsx, sheet_name筛选结果)但实际工作中领导可能已经给了你一个固定格式的Excel模板里面有表头、配色、批注。这时候别让pandas直接生成新文件更好的做法是用openpyxl读取模板把结果填进去。from openpyxl import load_workbook wb load_workbook(模板.xlsx) ws wb[估值表] # 从模板第2行开始写1表头 for i, (code, row) in enumerate(result.iterrows(), start2): ws.cell(rowi, column1, valuecode) ws.cell(rowi, column2, valuerow[中债估值收益率]) ws.cell(rowi, column3, valuerow[中债估值修正久期]) ws.cell(rowi, column4, valuerow[中债估值净价]) ws.cell(rowi, column5, valuerow[票面利率]) ws.cell(rowi, column6, valuerow[待偿期限]) wb.save(估值表_已更新.xlsx)这样保留模板格式只更新业务数据领导收到后也挑不出毛病。如果你做的是一份周报建议把这段代码放进一个main()函数每周五下午跑一次自动生成带日期的文件名比如bond_report_20240920.xlsx省得手动改名。5. 高频踩坑记录从返回码到缺失值从性能到权限5.1 先检查ErrorCode再谈数据准确性WindPy里所有接口的返回对象都有一个ErrorCode。我见过很多同事写脚本时不看这个值直接拿res.Data去做计算结果数据是空的或错的找了半天才发现是权限或参数问题。我的习惯是每一个接口调用后第一时间判断ErrorCode ! 0就抛异常或记录日志。负数错误码的排查顺序也有讲究先看w.isconnected()是不是True不是就重启连接再看债券代码后缀是否正确比如.IB写成了.SH然后看字段名是否被Wind改名了最后才怀疑是权限不足。按这个方法排查90%的问题能在两分钟内定位。5.2 缺失值、停牌、NaN债券数据和股票数据不太一样股票数据有停牌、涨跌停、不交易的日子缺失值特别多。债券虽然也有非交易日但估值数据通常是中债登或中证估值机构提供的相对连续。不过这不代表不会出现NaN比如新债还没上市、老债已经摘牌、某只券当天没有估值Wind返回的值可能是None、nan或者空字符串。我的处理原则是数值型字段统一走pd.to_numeric(errorscoerce)字符串型字段缺失时用fillna()筛选逻辑里有NaN的券直接剔除绝不拿NaN去比大小。另外一个细节如果是从wsd拿历史收益率序列缺数据的非交易日不要用dropna直接删会让时间轴变得支离破碎建议用fillPrevious参数或者先把时间序列重采样到标准交易日历上。5.3 批量请求性能优化一次API调用代替几百次循环新手最容易写出的低效代码是在循环里一次次调wss# 千万别这么写 for code in codes: res w.wss(code, 中债估值收益率, ) # ...几百只债券就发几百次请求速度慢不说还容易触发Wind终端的风控或超时。正确做法是把所有代码一次性传给w.wss一次调用出全部结果。如果你确实要分段拉取比如代码量太大也尽量控制在每批200到300只批与批之间sleep(0.5)给终端一点喘息时间。我实测下来几百只债券的截面数据一次调用几秒钟就能返回而循环方式可能要好几分钟差距非常明显。5.4 权限与登录态脚本半夜跑挂了的真实案例之前帮同事搭过一个定时任务每天晚上十一点自动拉第二天的估值数据方便早上开盘前看。脚本逻辑没问题但连着跑了两天都挂。排查发现问题出在Wind终端到了晚上自动进入锁屏状态或者被安全策略踢出了登录态导致WindPy连不上。这个坑很隐蔽因为人坐在工位上时终端是正常登录的压根想不到后台进程会掉线。后来我在脚本开头加了两道保险第一步调用w.start()后立即检查w.isconnected()连不上就直接发邮件告警第二步在拉数失败时重试三次仍然失败就保留上次数据文件并标记数据日期为“前值”。另外给跑定时任务的那台电脑设置好电源计划别让系统自动休眠终端进程也要在任务计划里做“保持运行”的配置。还有一类权限问题某些字段需要特定模块权限比如中债估值全价、银行间质押式回购利率等。用普通账号拉这些字段返回码会直接报权限类错误。处理办法是找负责Wind账号管理的同事申请对应权限别自己折腾。6. 我的工作流建议哪种场景该用Excel哪种该切Python6.1 一张表说清楚分工使用场景推荐工具原因临时查一只债的估值和久期Excel插件WSS快、直观改个代码立刻刷新做几十只以内的小范围分析Excel插件公式透视表交互方便周报/月报几百只债券批量估值Python WindPy一次拉取、清洗、回填稳定高效构建收益率曲线、利差时序Python wsdpandas处理时间序列最顺手给领导演示的交互式报表Excel插件透视表非技术同事也能自己筛选结论很简单凡是会重复执行的、数据量大的、要拼接多张表的都往Python迁移凡是临时的、交互的、需要人反复看的留在Excel里。6.2 从Excel迁移到Python的最小成本路径很多债券研究员不是不会Python而是担心迁移过程太折腾。我建议不要想着一周内把所有报表全切过去而是找一个频率最高、最耗时的表先试点。比如你现在每周都要做“信用债估值跟踪表”那就把它的取数逻辑抽出来用Python跑通输出结果和原来的Excel手动表对比两周确认无误后再替换。替换时也保留原来的Excel模板只把数据源从公式换成Python回填的数值。这样迁移的好处是业务侧的人看到的还是同一个文件只是背后的数据生产流程变了。等你在一个场景上积累了信心再慢慢把其他重复性报表迁过来压力会小很多。6.3 一点个人经验工具是手段不是目的最后说点真话。工具再顺也只是把脏活累活自动化并不意味着数据直接可用。我在实际项目里踩过更隐蔽的坑Excel模板里公式套公式某天字段改名导致整列偏差Python脚本因为评级字段返回了一种我没见过的新写法筛选条件悄悄出了问题。所以我现在养成了两个习惯分享给你。一是每版数据出来抽三到五只券和Wind终端人工核对一遍尤其是第一次使用新字段名的时候。二是把取数日期、字段版本、筛选条件都记录在最终报表的备注里这样过两个月回看还能知道当时这张表是怎么算出来的。工具的组合拳打好了能帮你省出大量时间但省出来的时间不该只用来做更多表而是用来理解数字背后业务在发生什么。毕竟在债券这个市场里数据和风险的距离往往比你想象的要近。