第四章:数据获取与清洗——从交易所拿到能用的期权数据

做期权交易的朋友都知道,波动率曲面这东西,看着高大上,其实第一步就卡死很多人——数据拿不到,或者拿到的数据根本没法用。我刚开始做期权量化的时候,就吃过这个亏。从交易所拉了一堆原始数据,兴冲冲跑模型,结果算出来的波动率曲面跟心电图似的,完全没法看。

后来我才明白,数据清洗和预处理,才是整个波动率曲面构建中最耗时、也最关键的环节。今天我就把这几年踩过的坑,总结成一套标准流程,分享给你。

核心观点: 波动率曲面的精度,90%取决于数据质量。模型再牛,数据是垃圾,结果就是垃圾。

4.1 从交易所获取期权行情数据

国内期权市场,主要就是上交所的50ETF期权、300ETF期权,还有中金所的股指期权。我个人习惯用上交所的数据,因为流动性最好,曲面也最平滑。

获取数据的方式,我推荐两种:

  • 直接通过交易所API:上交所的MDGW(行情网关)可以拿到实时数据,但门槛高,需要券商资质。
  • 通过数据服务商:比如万得、聚宽、Tushare。我早期用Tushare,免费版够用,但要注意频率限制。

这里给个简单的Tushare获取代码示例:

import tushare as ts

# 设置token
pro = ts.pro_api('你的token')

# 获取50ETF期权日线数据
df = pro.opt_daily(
    ts_code='10002211.SH',  # 期权合约代码
    start_date='20240101',
    end_date='20240131',
    fields='ts_code,trade_date,pre_settle,open,high,low,close,settle,volume,amount,oi'
)

print(df.head())

小技巧: 我建议一次拉取不要超过3个月的数据,否则容易超时。分批次拉取,然后合并,更稳定。

4.2 数据清洗与预处理

数据拿到手,第一件事不是算波动率,而是清洗。我曾经有一次,模型跑出来曲面全是负的,查了半天,原来是某天的结算价数据错了,少了一个小数点。

清洗流程我总结为三步:

  1. 去重与排序:按交易日期和合约代码排序,删除重复行。
  2. 异常值处理:检查价格、成交量、持仓量是否合理。比如,期权价格不能为负,成交量不能为0(除非是深度虚值且无交易)。
  3. 缺失值填充:对于缺失的结算价,我习惯用前一天的结算价填充,或者用当日开盘价替代。但要注意,如果连续缺失超过3天,建议直接删除该合约。

看个例子:

# 清洗示例
import pandas as pd

# 假设df是原始数据
df = df.drop_duplicates(subset=['ts_code', 'trade_date'])
df = df.sort_values(['ts_code', 'trade_date'])

# 异常值过滤
df = df[(df['close'] > 0) & (df['volume'] >= 0)]

# 缺失值填充(前向填充)
df['settle'] = df.groupby('ts_code')['settle'].ffill()

# 删除连续缺失超过3天的合约
df = df.groupby('ts_code').filter(lambda x: x['settle'].isna().sum() <= 3)

注意: 千万不要用均值填充期权数据!期权价格是非线性的,均值填充会严重扭曲波动率曲面。我见过有人这么干,结果曲面形状完全变了。

4.3 到期日与行权价标准化

这一步是构建波动率曲面的前提。不同合约的到期日和行权价都不一样,必须统一成标准格式。

到期日标准化:

  • 将到期日转换为剩余天数(或年化时间)。我习惯用实际天数/365,简单直接。
  • 注意:国内期权到期日是每个月的第四个星期三。这个日期不是固定的,需要从合约代码中解析。

行权价标准化:

  • 将行权价转换为在值程度(Moneyness),即行权价/标的价格。
  • 或者用Delta值来标准化,我个人更推荐Delta,因为它能反映期权的真实风险暴露。

代码实现:

# 标准化示例
import numpy as np

# 假设有标的价格S
S = 2.500  # 50ETF当日价格

# 计算剩余天数
df['maturity_date'] = pd.to_datetime(df['ts_code'].str[4:8] + df['ts_code'].str[8:10], format='%y%m%d')
df['trade_date'] = pd.to_datetime(df['trade_date'])
df['days_to_expiry'] = (df['maturity_date'] - df['trade_date']).dt.days
df['time_to_expiry'] = df['days_to_expiry'] / 365.0

# 计算在值程度
df['moneyness'] = df['strike'] / S

# 或者用Delta(需要先计算隐含波动率,这里简化)
# df['delta'] = ...

关键点: 标准化后的数据,才能用于后续的曲面插值。我一般会生成一个DataFrame,包含:trade_date, time_to_expiry, moneyness, implied_vol, call_put_flag。这样后续建模就方便了。

4.4 知识体系总览

为了让你更直观地理解整个流程,我画了一张图。这张图把数据获取、清洗、标准化串起来了,你一看就明白。

数据获取与清洗流程 数据获取 交易所API / 数据服务商 数据清洗 去重 → 异常值 → 缺失值 标准化 到期日 → 在值程度 获取方式 • 实时API(MDGW) • 历史数据(Tushare) • 数据服务商(万得) 清洗要点 • 价格不能为负 • 成交量合理 • 缺失≤3天可填充 标准化字段 • time_to_expiry • moneyness • delta(可选) 输出:标准化期权数据表 用于波动率曲面构建

我的经验: 标准化这一步,很多人会忽略合约代码的解析。国内期权合约代码有固定格式,比如10002211.SH,前四位是年份和月份,后四位是行权价。我建议写一个解析函数,一次性搞定。

好了,数据清洗和标准化就讲到这里。记住,数据质量决定曲面精度。下一章我们会用这些清洗好的数据,开始构建波动率曲面。到时候你会发现,前面花的时间都是值得的。

蓝海数据掘金营,专注资料整理