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())
注意: AKShare的接口偶尔会变。我记得有一次,我写好的脚本跑了一个月,突然报错了。查了半天,发现是接口名从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)} 条')
核心原则: 清洗数据时,宁可少一条,不要多一条脏数据。我曾经因为没剔除一个异常值,导致整个回测结果偏差了5%。从那以后,我每次清洗完都会手动检查几个关键日期。

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 知识体系总览

下面这张图,是我自己总结的数据获取与清洗流程。你可以把它当作一个检查清单。

期权数据获取与清洗流程 数据源选择 Tushare(需token) AKShare(免费) 数据获取 数据清洗 去重 填充缺失 剔除异常 过滤非交易日 数据对齐

这张图把整个流程串起来了。从选择数据源开始,到获取数据,再到清洗和对齐。每一步都有对应的代码实现。你照着这个流程走,基本不会出大问题。

4.7 避坑指南

最后,分享几个我踩过的坑:

  • 合约代码会变: 不要硬编码合约代码,用接口动态获取
  • 数据频率不一致: Tushare和AKShare的分钟线定义不同,对齐时要注意
  • 复权问题: 期权不需要复权,但要注意分红除息的影响
  • 数据缓存: 每次回测都重新拉数据太慢,建议存到本地CSV或数据库
我曾经踩过的坑: 有一次做回测,发现策略在某个时间段表现特别好。后来一查,原来是那段时间的数据里混入了非交易日,导致收益率计算错误。从那以后,我每次清洗完都会用交易日历再过滤一遍。

数据获取与清洗,说白了就是为策略回测打好地基。地基不稳,楼盖得再高也是白搭。希望这一章的内容能帮你少走一些弯路。


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