第十一章:数据获取与清洗——期权链数据API、数据清洗与对齐、过期合约处理

做期权量化,最头疼的事是什么?

我个人觉得,不是策略逻辑有多复杂,而是数据本身。你想想看,一个波动率曲面模型再漂亮,底层数据是脏的、错的、对不齐的,那出来的信号就是垃圾。

这一章,咱们就聊聊数据获取与清洗。说白了,就是怎么把期权链数据从API里拽下来,怎么洗干净,怎么对齐,以及怎么处理那些过期合约。

11.1 期权链数据API:从哪里拿数据?

我最早做期权交易那会儿,数据源是个大问题。国内几家数据商,接口风格完全不同。有的给CSV,有的给JSON,有的甚至只给Excel。嗯,现在好多了,主流API基本都支持RESTful风格。

常用的期权数据API包括:

  • Wind:国内最全,但贵。适合机构。
  • 聚宽/米筐:量化平台自带,方便回测。
  • 交易所直连:上交所、深交所、中金所。延迟最低,但开发成本高。
  • 第三方数据商:如Tushare、AkShare。免费或低价,适合个人。

我个人习惯用AkShare做快速原型验证。它免费,数据覆盖全,而且更新及时。但注意,免费API有频率限制,别在盘中高频调用。

核心要点:选择API时,重点关注三个指标——数据延迟、合约覆盖范围、历史数据长度。做波动率曲面,至少需要过去3年的日频数据。

11.2 数据清洗:脏数据是魔鬼

数据拿到手,第一件事不是建模,是清洗。我在项目中遇到过好几次,因为一个空值导致整个波动率曲面计算崩溃。

常见的脏数据问题:

  1. 缺失值:某天某个行权价的合约没有成交,价格字段为空。
  2. 异常值:买卖价差过大,或者价格明显偏离理论值。
  3. 重复数据:同一合约同一时刻出现多条记录。
  4. 时间戳错乱:数据时间与交易所时间不一致。

清洗流程我一般这样走:

  • 第一步:去重。按合约代码+时间戳去重,保留最新一条。
  • 第二步:填充缺失值。对于盘中缺失的报价,用前一笔报价填充,或者用买卖中间价。
  • 第三步:过滤异常。比如隐含波动率超过100%或低于5%的,直接剔除。
  • 第四步:对齐时间戳。把所有合约的时间统一到同一秒级或毫秒级。

小技巧:清洗时,我习惯保留原始数据副本。万一清洗逻辑有bug,还能回滚重来。别问我怎么知道的——都是教训。

11.3 数据对齐:让不同合约站在同一起跑线

期权链数据有个特点:不同到期日、不同行权价的合约,交易活跃度天差地别。近月平值合约可能每秒都有成交,远月虚值合约可能一天都没几笔。

这就导致一个问题——时间戳对不齐。你拿到的数据,有的合约是10:00:01的报价,有的是10:00:05的报价。直接拿来做曲面拟合,误差会很大。

我的做法是:

  • 固定时间切片:比如每分钟取一次快照。所有合约都取该分钟最后一笔成交价。
  • 插值对齐:对于活跃合约,用线性插值把价格对齐到同一时刻。
  • 剔除冷门合约:如果某个合约一天成交不到10笔,我建议直接扔掉。它带来的噪声远大于信息。

警告:千万不要用不同时间点的数据直接计算波动率曲面。我见过有人把10:00的call和10:05的put放在一起算skew,结果曲面形状完全扭曲。这种错误,犯一次就够了。

11.4 过期合约处理:别让僵尸数据污染你的模型

期权合约是有生命周期的。到期之后,合约就失效了。但很多数据源不会自动删除过期合约,它们只是把价格置为0或者NaN。

如果你不小心把过期合约纳入模型,会出现什么情况?

  • 波动率曲面在到期日附近出现奇怪的凹陷或凸起。
  • 隐含波动率计算时,时间价值为负,导致结果荒谬。
  • 回测时,策略信号被过期合约干扰,产生虚假交易。

处理过期合约,我有一套标准流程:

  1. 识别到期日:每个合约都有明确的到期日期。从合约代码里可以解析出来,比如“IO2406”就是2024年6月到期。
  2. 到期日当天处理:到期日当天,合约在收盘后自动失效。我一般在收盘前最后一笔数据之后,就把该合约标记为“待删除”。
  3. 次日清理:到期日次日,从数据集中彻底移除该合约的所有历史记录。注意,是移除,不是置零。
  4. 滚动合约:当月合约到期后,自动切换到下月合约作为新的主力合约。

重要:过期合约的处理,一定要在数据预处理阶段完成。不要等到建模时再处理,那时候数据量大了,排查问题会非常痛苦。

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%的时间在策略上,这个比例是合理的。

公众号:蓝海资料掘金营,微信deep3321