第十一章:数据获取与清洗:如何从交易所/数据商获取期权链数据,数据清洗与预处理
做Gamma交易,最怕什么?
不是模型算错了,而是你拿到的数据本身就是错的。
我见过太多交易员,花大把时间优化策略,结果回头一看——原始数据里藏着几十个异常点。嗯,那感觉就像你精心搭建的沙堡,地基却是烂的。
这一章,我们就来聊聊期权链数据的获取与清洗。说白了,就是怎么把交易所或数据商给的“毛坯房”数据,装修成能直接用的“精装房”。
11.1 数据源的选择:交易所 vs 数据商
我个人习惯把数据源分成两类:官方渠道和第三方渠道。
| 数据源类型 | 代表 | 优点 | 缺点 |
|---|---|---|---|
| 交易所直连 | CBOE、上交所、大商所 | 数据最原始、无延迟、无二次加工 | 接口复杂、费用高、需要专线 |
| 专业数据商 | Bloomberg、Reuters、Wind | 接口友好、附带清洗、历史数据全 | 贵、有二次加工可能引入偏差 |
| 开源/免费源 | Yahoo Finance、Quandl | 免费、快速上手 | 数据质量参差不齐、延迟高 |
你想想看,如果拿免费数据做回测,发现年化收益30%,兴冲冲上实盘——结果数据源一换,收益直接腰斩。这种坑,我踩过不止一次。
11.2 期权链数据的核心字段
不管从哪个源拿数据,期权链的核心字段都差不多。我列一个标准结构:
# 标准期权链数据字段
字段列表:
- trade_date: 交易日期
- symbol: 标的代码
- option_code: 期权合约代码
- strike: 行权价
- expiration: 到期日
- call_put: 看涨/看跌标识
- open: 开盘价
- high: 最高价
- low: 最低价
- close: 收盘价
- volume: 成交量
- open_interest: 持仓量
- implied_vol: 隐含波动率
- delta, gamma, theta, vega: 希腊字母
- underlying_price: 标的价格
- days_to_expiry: 剩余天数
重点注意: 隐含波动率这个字段,不同数据商的计算方法可能不同。有的用BS模型,有的用二叉树。我建议自己算一遍,或者至少确认数据商的计算方式。
11.3 数据获取实战:从交易所API拉数据
以国内中金所为例,我写一个简单的数据获取流程。注意,这只是演示逻辑,实际接口需要申请权限。
import requests
import pandas as pd
from datetime import datetime
def fetch_option_chain(symbol, date):
"""
从交易所获取期权链数据
实际接口地址和参数需要替换
"""
url = f"https://api.exchange.com/option/chain"
params = {
"symbol": symbol,
"date": date,
"type": "all"
}
# 这里需要处理认证
headers = {
"Authorization": "Bearer YOUR_API_KEY"
}
response = requests.get(url, params=params, headers=headers)
if response.status_code == 200:
data = response.json()
df = pd.DataFrame(data['data'])
return df
else:
print(f"请求失败: {response.status_code}")
return None
# 使用示例
df_raw = fetch_option_chain("IO", "2024-01-15")
print(f"获取到 {len(df_raw)} 条期权数据")
11.4 数据清洗:从脏数据到干净数据
数据清洗,说白了就是“去伪存真”。我总结了一套标准流程:
11.4.1 缺失值处理
期权数据里,缺失值很常见。比如某天某个深度虚值期权没有成交,价格就是空的。
# 缺失值处理策略
def clean_missing_values(df):
# 1. 删除全部为空的列
df = df.dropna(axis=1, how='all')
# 2. 对于价格字段,用前向填充
price_cols = ['open', 'high', 'low', 'close']
df[price_cols] = df[price_cols].fillna(method='ffill')
# 3. 对于成交量,空值填0
df['volume'] = df['volume'].fillna(0)
# 4. 对于隐含波动率,用同一行权价附近的均值填充
df['implied_vol'] = df.groupby('strike')['implied_vol'].transform(
lambda x: x.fillna(x.mean())
)
return df
11.4.2 异常值检测
我个人习惯用“3-sigma”法则加“业务逻辑”双重过滤。
def detect_outliers(df):
# 业务逻辑过滤
# 1. 价格不能为负
df = df[df['close'] >= 0]
# 2. 隐含波动率不能为负,且不能超过500%
df = df[(df['implied_vol'] >= 0) & (df['implied_vol'] <= 5.0)]
# 3. 成交量不能为负
df = df[df['volume'] >= 0]
# 统计方法过滤
# 对隐含波动率做3-sigma过滤
mean_vol = df['implied_vol'].mean()
std_vol = df['implied_vol'].std()
df = df[(df['implied_vol'] >= mean_vol - 3*std_vol) &
(df['implied_vol'] <= mean_vol + 3*std_vol)]
return df
11.4.3 数据对齐与插值
做Gamma交易,我们需要完整的波动率曲面。但实际数据往往有缺失,比如某些行权价没有报价。
def interpolate_vol_surface(df):
"""
对波动率曲面进行插值
使用三次样条插值
"""
from scipy.interpolate import griddata
# 提取已知点
points = df[['strike', 'days_to_expiry']].values
values = df['implied_vol'].values
# 生成完整的网格
strikes = np.linspace(df['strike'].min(), df['strike'].max(), 50)
expiries = np.linspace(df['days_to_expiry'].min(), df['days_to_expiry'].max(), 20)
grid_x, grid_y = np.meshgrid(strikes, expiries)
# 插值
grid_z = griddata(points, values, (grid_x, grid_y), method='cubic')
return grid_x, grid_y, grid_z
11.5 数据预处理:为Gamma交易做准备
数据清洗干净后,还需要做预处理,才能用于Gamma交易策略。
11.5.1 计算希腊字母
如果数据商没有提供希腊字母,或者你想用自己的模型算,可以这样:
from scipy.stats import norm
import numpy as np
def calculate_greeks(S, K, T, r, sigma, option_type):
"""
计算期权希腊字母
S: 标的价格
K: 行权价
T: 剩余时间(年)
r: 无风险利率
sigma: 隐含波动率
option_type: 'call' 或 'put'
"""
d1 = (np.log(S/K) + (r + 0.5*sigma**2)*T) / (sigma*np.sqrt(T))
d2 = d1 - sigma*np.sqrt(T)
if option_type == 'call':
delta = norm.cdf(d1)
gamma = norm.pdf(d1) / (S * sigma * np.sqrt(T))
else:
delta = -norm.cdf(-d1)
gamma = norm.pdf(d1) / (S * sigma * np.sqrt(T))
return delta, gamma
11.5.2 构建波动率曲面
这是Gamma交易的核心输入。我习惯把数据整理成矩阵形式:
def build_vol_surface(df):
"""
构建波动率曲面矩阵
行:行权价
列:到期时间
值:隐含波动率
"""
# 透视表
vol_surface = df.pivot_table(
values='implied_vol',
index='strike',
columns='days_to_expiry',
aggfunc='mean'
)
# 排序
vol_surface = vol_surface.sort_index()
vol_surface = vol_surface[sorted(vol_surface.columns)]
return vol_surface
核心要点: 波动率曲面的质量,直接决定了Gamma交易的效果。我建议每次构建完曲面后,都做一次可视化检查。如果曲面出现不合理的“尖刺”或“凹陷”,说明数据清洗还有问题。
11.6 数据质量检查清单
最后,我分享一个自己用了多年的检查清单:
- 完整性检查: 每个交易日的数据是否完整?有没有跳空?
- 一致性检查: 同一标的的看涨和看跌期权,平价附近的隐含波动率是否接近?
- 合理性检查: 深度实值期权的隐含波动率是否异常低?深度虚值的是否异常高?
- 时间序列检查: 同一合约的隐含波动率是否出现不合理的跳跃?
- 套利检查: 是否存在明显的套利机会?如果有,可能是数据错误。
数据获取与清洗,听起来很枯燥,但它是所有量化交易的基石。你想想看,如果连数据都是错的,再牛的模型又有什么用?
我个人习惯把数据清洗的代码封装成一个独立的模块,每次跑策略前先跑一遍清洗流程。虽然麻烦,但能省掉后面90%的debug时间。
数据清洗这件事,做一次不难,难的是每次都做、每次都做对。我建议你把清洗流程写成自动化脚本,每天开盘前自动跑一遍。这样,你就能把精力放在真正重要的事情上——交易决策。
记住一句话:垃圾进,垃圾出。数据质量,就是你的策略生命线。