第十一章:数据获取与清洗——期权链数据API、数据清洗与对齐、过期合约处理
做期权量化,最头疼的事是什么?
我个人觉得,不是策略逻辑有多复杂,而是数据本身。你想想看,一个波动率曲面模型再漂亮,底层数据是脏的、错的、对不齐的,那出来的信号就是垃圾。
这一章,咱们就聊聊数据获取与清洗。说白了,就是怎么把期权链数据从API里拽下来,怎么洗干净,怎么对齐,以及怎么处理那些过期合约。
11.1 期权链数据API:从哪里拿数据?
我最早做期权交易那会儿,数据源是个大问题。国内几家数据商,接口风格完全不同。有的给CSV,有的给JSON,有的甚至只给Excel。嗯,现在好多了,主流API基本都支持RESTful风格。
常用的期权数据API包括:
- Wind:国内最全,但贵。适合机构。
- 聚宽/米筐:量化平台自带,方便回测。
- 交易所直连:上交所、深交所、中金所。延迟最低,但开发成本高。
- 第三方数据商:如Tushare、AkShare。免费或低价,适合个人。
我个人习惯用AkShare做快速原型验证。它免费,数据覆盖全,而且更新及时。但注意,免费API有频率限制,别在盘中高频调用。
核心要点:选择API时,重点关注三个指标——数据延迟、合约覆盖范围、历史数据长度。做波动率曲面,至少需要过去3年的日频数据。
11.2 数据清洗:脏数据是魔鬼
数据拿到手,第一件事不是建模,是清洗。我在项目中遇到过好几次,因为一个空值导致整个波动率曲面计算崩溃。
常见的脏数据问题:
- 缺失值:某天某个行权价的合约没有成交,价格字段为空。
- 异常值:买卖价差过大,或者价格明显偏离理论值。
- 重复数据:同一合约同一时刻出现多条记录。
- 时间戳错乱:数据时间与交易所时间不一致。
清洗流程我一般这样走:
- 第一步:去重。按合约代码+时间戳去重,保留最新一条。
- 第二步:填充缺失值。对于盘中缺失的报价,用前一笔报价填充,或者用买卖中间价。
- 第三步:过滤异常。比如隐含波动率超过100%或低于5%的,直接剔除。
- 第四步:对齐时间戳。把所有合约的时间统一到同一秒级或毫秒级。
小技巧:清洗时,我习惯保留原始数据副本。万一清洗逻辑有bug,还能回滚重来。别问我怎么知道的——都是教训。
11.3 数据对齐:让不同合约站在同一起跑线
期权链数据有个特点:不同到期日、不同行权价的合约,交易活跃度天差地别。近月平值合约可能每秒都有成交,远月虚值合约可能一天都没几笔。
这就导致一个问题——时间戳对不齐。你拿到的数据,有的合约是10:00:01的报价,有的是10:00:05的报价。直接拿来做曲面拟合,误差会很大。
我的做法是:
- 固定时间切片:比如每分钟取一次快照。所有合约都取该分钟最后一笔成交价。
- 插值对齐:对于活跃合约,用线性插值把价格对齐到同一时刻。
- 剔除冷门合约:如果某个合约一天成交不到10笔,我建议直接扔掉。它带来的噪声远大于信息。
警告:千万不要用不同时间点的数据直接计算波动率曲面。我见过有人把10:00的call和10:05的put放在一起算skew,结果曲面形状完全扭曲。这种错误,犯一次就够了。
11.4 过期合约处理:别让僵尸数据污染你的模型
期权合约是有生命周期的。到期之后,合约就失效了。但很多数据源不会自动删除过期合约,它们只是把价格置为0或者NaN。
如果你不小心把过期合约纳入模型,会出现什么情况?
- 波动率曲面在到期日附近出现奇怪的凹陷或凸起。
- 隐含波动率计算时,时间价值为负,导致结果荒谬。
- 回测时,策略信号被过期合约干扰,产生虚假交易。
处理过期合约,我有一套标准流程:
- 识别到期日:每个合约都有明确的到期日期。从合约代码里可以解析出来,比如“IO2406”就是2024年6月到期。
- 到期日当天处理:到期日当天,合约在收盘后自动失效。我一般在收盘前最后一笔数据之后,就把该合约标记为“待删除”。
- 次日清理:到期日次日,从数据集中彻底移除该合约的所有历史记录。注意,是移除,不是置零。
- 滚动合约:当月合约到期后,自动切换到下月合约作为新的主力合约。
重要:过期合约的处理,一定要在数据预处理阶段完成。不要等到建模时再处理,那时候数据量大了,排查问题会非常痛苦。
11.5 实战:一个完整的数据获取与清洗流程
下面是我常用的一个数据获取与清洗流程的伪代码。实际项目中,我会根据数据源的不同做调整。
# 伪代码:期权链数据获取与清洗流程
def fetch_option_chain(api, symbol, date):
# 1. 获取原始数据
raw_data = api.get_option_chain(symbol, date)
# 2. 去重
clean_data = raw_data.drop_duplicates(subset=['contract', 'timestamp'])
# 3. 填充缺失值
clean_data = clean_data.fillna(method='ffill')
# 4. 过滤异常
clean_data = clean_data[(clean_data['implied_vol'] > 0.05) &
(clean_data['implied_vol'] < 1.0)]
# 5. 对齐时间戳
clean_data = align_timestamps(clean_data, freq='1min')
# 6. 移除过期合约
clean_data = clean_data[clean_data['expiration'] >= date]
return clean_data
这段代码看着简单,但每个步骤背后都有坑。比如fillna用ffill还是bfill,取决于你的数据是盘中的还是盘后的。我一般盘中用ffill,盘后用bfill。
11.6 知识体系总览
为了让你更直观地理解本章的知识结构,我画了一张流程图。它展示了从数据获取到清洗对齐,再到过期合约处理的完整链路。
这张图把整个流程串起来了。你从API拿到原始数据,经过去重、填充、过滤、对齐,最后处理过期合约。每一步都不能跳过,否则最终的数据质量会大打折扣。
个人经验:我曾经在一个项目中,因为偷懒跳过了“对齐时间戳”这一步,结果波动率曲面在盘中出现了奇怪的锯齿状。排查了两天才找到原因。从那以后,我再也不敢省略任何清洗步骤。
好了,数据获取与清洗就聊到这儿。记住一句话:数据质量决定了策略的天花板。花80%的时间在数据上,20%的时间在策略上,这个比例是合理的。