第27章 曲面数据源与清洗:从Bloomberg、Reuters获取数据,异常值处理
做波动率曲面,最头疼的其实不是模型本身。而是——数据。
我见过太多团队,模型跑得飞起,结果一上实盘就崩。为什么?数据源没搞干净。说白了,你喂进去的是垃圾,出来的只能是更垃圾的预测。今天这章,我就把我在Bloomberg和Reuters上踩过的坑,一个一个说清楚。
27.1 数据源的选择:BBG vs. Reuters
先说说两个主流数据源。我个人习惯是:能用BBG就用BBG。为什么?
- Bloomberg(BBG):数据覆盖全,更新快,但贵。一个终端一年几万美金。适合机构。
- Reuters(Refinitiv):便宜一些,但某些小众品种的数据质量参差不齐。我曾在Reuters上抓到过某只港股期权数据,连续三天都是同一个数——明显是没更新。
你想想看,如果数据源本身就有问题,后面的清洗再努力也白搭。所以第一步,选对源。
27.2 数据字段:你到底需要什么?
很多人一上来就拉一堆字段,什么Delta、Gamma、Vega全要。其实没必要。做曲面,核心字段就这几个:
| 字段 | 说明 | 来源 |
|---|---|---|
| Strike | 行权价 | BBG/Reuters |
| Expiry | 到期日 | BBG/Reuters |
| Bid/Ask | 买卖报价 | BBG/Reuters |
| Implied Vol | 隐含波动率 | BBG/Reuters |
| Volume/Open Interest | 成交量/持仓量 | BBG/Reuters |
嗯,这里要注意:隐含波动率这个字段,不同数据源的计算方式可能不一样。BBG用的是标准BS模型,Reuters有时候会用自己调整过的模型。所以如果你同时用两个源,一定要做交叉验证。
27.3 数据清洗:异常值处理
数据拉下来之后,第一件事不是建模,而是清洗。我遇到过最离谱的一次:某只股票的期权数据里,居然有一个行权价是负数。你想想看,行权价怎么可能为负?明显是数据录入错误。
异常值处理,我一般分三步走:
27.3.1 基础过滤
先把明显不合理的数据干掉:
- 行权价 ≤ 0
- 隐含波动率 ≤ 0 或 ≥ 200%
- 买卖价差过大(比如Bid/Ask ratio > 5)
- 成交量 = 0 且 持仓量 = 0
这些规则看起来简单,但能过滤掉至少80%的脏数据。
27.3.2 统计方法
基础过滤之后,再用统计方法揪出那些「看起来正常但实际异常」的数据。我常用的是Z-score和IQR。
# 伪代码示例:Z-score异常检测
import numpy as np
def detect_outliers_zscore(data, threshold=3):
z_scores = np.abs((data - np.mean(data)) / np.std(data))
return data[z_scores > threshold]
# 对隐含波动率做Z-score检测
iv_outliers = detect_outliers_zscore(iv_data)
print(f"发现 {len(iv_outliers)} 个异常波动率数据")
我曾经用这个方法,在Reuters的日元期权数据里抓到了一批「幽灵数据」——那些数据点明明在统计上正常,但实际是某个交易员手动输入的测试数据。Z-score一跑,全现形了。
27.3.3 业务逻辑校验
这一步最容易被忽略。统计上正常的数据,业务上不一定合理。举个例子:
- 深度虚值期权的隐含波动率突然飙升——可能是流动性不足导致的报价失真
- 临近到期的期权,隐含波动率异常平稳——可能是数据源没更新
- 同一到期日、不同行权价的波动率出现「锯齿」状——可能是数据抓取时间不一致
对于这些情况,我一般会加一个平滑性检查:
# 检查同一到期日的波动率是否平滑
def check_smoothness(vol_surface, tolerance=0.1):
for expiry in vol_surface.keys():
strikes = sorted(vol_surface[expiry].keys())
vols = [vol_surface[expiry][k] for k in strikes]
for i in range(1, len(vols)-1):
# 检查相邻点变化是否超过容忍度
if abs(vols[i] - vols[i-1]) > tolerance:
print(f"警告:到期日{expiry},行权价{strikes[i]}附近波动率异常")
27.4 数据对齐:时间戳与到期日
数据清洗完了,还有一个坑:时间对齐。
BBG和Reuters的数据更新时间不一样。BBG是实时推送,Reuters是每5秒刷新一次。如果你同时用两个源,一定要把时间戳对齐到同一个时间点。否则你拿到的曲面,左边是10:00:00的数据,右边是10:00:05的数据,拼在一起就是错的。
我个人的做法是:统一用BBG的时间戳,然后把Reuters的数据做线性插值对齐到BBG的时间点上。
27.5 数据存储:别用Excel
最后说一句:数据存哪里?千万别用Excel。我见过有人把几万条期权数据存在一个Excel文件里,打开一次要5分钟,还经常崩溃。
建议用HDF5或者Parquet格式。这两种格式对时间序列数据支持好,读写快,而且支持压缩。我自己用的是HDF5,配合Pandas的to_hdf()方法,几百万条数据秒级读写。
核心要点总结:
- 数据源选BBG优先,Reuters做补充
- 清洗三步走:基础过滤 → 统计检测 → 业务校验
- 时间对齐是曲面构建的关键,别忽略
- 存储用HDF5或Parquet,别用Excel
我的小技巧:每次拉完数据,先画一张散点图看看。肉眼扫一遍,很多异常值一眼就能看出来。别完全依赖代码,人的直觉有时候比算法管用。
警告:千万不要在数据清洗阶段「过度拟合」。我见过有人把异常值全删了,结果曲面变得异常平滑,但实际交易时完全对不上。保留一些「合理」的异常,反而能反映真实市场情况。