4. 数据获取与清洗:从Tushare/AKShare获取期权行情数据,数据清洗与对齐
做期权策略开发,最头疼的事是什么?
我个人觉得,不是策略逻辑有多复杂,而是数据本身。你想想看,策略再牛,数据是脏的、乱的、对不齐的,那结果就是废的。我在项目里踩过这个坑,回测跑出来年化收益50%,结果发现是数据里混了非交易日——嗯,从那以后,我对数据清洗这件事就特别较真。
4.1 数据源的选择:Tushare vs AKShare
国内做期权回测,主流的数据源就两个:Tushare和AKShare。我两个都用过,说说我的感受。
| 对比项 | Tushare | AKShare |
|---|---|---|
| 数据覆盖 | 50ETF、300ETF期权,历史数据全 | 50ETF、300ETF、商品期权,覆盖面更广 |
| 获取方式 | 需要token,有积分限制 | 开源免费,无需token |
| 数据频率 | 日线、分钟线 | 日线为主,部分合约有分钟线 |
| 稳定性 | 高,有付费保障 | 中等,偶尔接口变动 |
我的建议是:如果做高频或者对数据质量要求极高,可以考虑Tushare付费版。如果是做策略原型验证,AKShare完全够用,而且免费。
4.2 从Tushare获取期权行情
先看Tushare怎么拿数据。我习惯用opt_daily接口,直接拿日线行情。
import tushare as ts
# 初始化,需要你的token
pro = ts.pro_api('你的token')
# 获取50ETF期权日线数据
# 参数说明:ts_code是合约代码,trade_date是交易日期
df = pro.opt_daily(
ts_code='10002211.SH', # 合约代码
trade_date='20240115', # 交易日期
fields='ts_code,trade_date,open,high,low,close,pre_close,vol,amount,oi'
)
print(df.head())
fields参数可以只选需要的字段。结果每次拉一堆没用的数据,传输慢还占内存。后来就只拉close, vol, oi这三个核心字段,速度快多了。
但这里有个坑——Tushare的期权合约代码是动态变化的。比如50ETF期权,每个月都会新挂合约,旧合约退市。你如果写死代码,下个月就跑不通了。
我的做法是:先拉合约列表,再循环拉行情。
# 先获取所有可用的期权合约
df_list = pro.opt_basic(exchange='SSE', fields='ts_code,name,list_date,delist_date')
# 筛选出当前交易的合约
active_contracts = df_list[df_list['delist_date'].isna()]
# 循环拉取每个合约的行情
all_data = []
for code in active_contracts['ts_code'].head(10): # 先试10个
df = pro.opt_daily(ts_code=code, start_date='20240101', end_date='20240131')
all_data.append(df)
print(f'已获取 {code} 的数据')
4.3 从AKShare获取期权行情
AKShare的接口更直接,不需要token。但要注意,它的字段命名和Tushare不太一样。
import akshare as ak
# 获取50ETF期权实时行情
option_50etf = ak.option_50etf_hist(
symbol="10002211", # 合约代码
start_date="20240101",
end_date="20240131"
)
print(option_50etf.head())
option_50etf_hist改成了option_50etf_hist_sina。所以,建议定期检查一下文档。
4.4 数据清洗:脏数据怎么处理?
数据拿到手,别急着用。先看看有没有脏数据。我总结了几种常见情况:
- 缺失值: 某天没有交易,或者数据没更新
- 异常值: 价格突然跳空,或者成交量异常大
- 重复值: 同一天同一个合约出现两条记录
- 非交易日: 周末或节假日的数据混进来了
我的清洗流程是这样的:
import pandas as pd
import numpy as np
def clean_option_data(df):
"""
期权数据清洗函数
"""
# 1. 去重
df = df.drop_duplicates(subset=['trade_date', 'ts_code'])
# 2. 处理缺失值
# 如果某天close为空,用前一天的close填充
df['close'] = df['close'].fillna(method='ffill')
# 3. 剔除异常值
# 价格不能为负,成交量不能为负
df = df[(df['close'] > 0) & (df['vol'] >= 0)]
# 4. 剔除非交易日
# 用交易日历过滤
trade_cal = pro.trade_cal(exchange='SSE', start_date='20240101', end_date='20240131')
trade_dates = trade_cal[trade_cal['is_open'] == 1]['cal_date'].tolist()
df = df[df['trade_date'].isin(trade_dates)]
return df
# 使用
df_clean = clean_option_data(df_raw)
print(f'清洗前: {len(df_raw)} 条, 清洗后: {len(df_clean)} 条')
4.5 数据对齐:不同合约怎么对齐到同一时间轴?
做期权策略,经常需要同时分析多个合约。比如,你想看不同行权价的期权在同一个时间点的表现。这时候就需要数据对齐。
对齐的核心思路:以交易日为索引,把不同合约的数据拼到同一张表里。
# 假设我们有三个合约的数据
contracts = ['10002211', '10002212', '10002213']
all_data = {}
for code in contracts:
df = pro.opt_daily(ts_code=code, start_date='20240101', end_date='20240131')
df = df[['trade_date', 'close']]
df.columns = ['trade_date', f'close_{code}']
all_data[code] = df
# 以交易日为索引,合并所有合约
merged = all_data[contracts[0]]
for code in contracts[1:]:
merged = pd.merge(merged, all_data[code], on='trade_date', how='outer')
# 按日期排序
merged = merged.sort_values('trade_date').reset_index(drop=True)
print(merged.head())
这里有个细节:how='outer'表示保留所有日期。如果某个合约在某天没有数据,对应位置就是NaN。你可以选择填充或者删除。
4.6 知识体系总览
下面这张图,是我自己总结的数据获取与清洗流程。你可以把它当作一个检查清单。
这张图把整个流程串起来了。从选择数据源开始,到获取数据,再到清洗和对齐。每一步都有对应的代码实现。你照着这个流程走,基本不会出大问题。
4.7 避坑指南
最后,分享几个我踩过的坑:
- 合约代码会变: 不要硬编码合约代码,用接口动态获取
- 数据频率不一致: Tushare和AKShare的分钟线定义不同,对齐时要注意
- 复权问题: 期权不需要复权,但要注意分红除息的影响
- 数据缓存: 每次回测都重新拉数据太慢,建议存到本地CSV或数据库
数据获取与清洗,说白了就是为策略回测打好地基。地基不稳,楼盖得再高也是白搭。希望这一章的内容能帮你少走一些弯路。
公众号:蓝海资料掘金营,微信deep3321