第4章:金融数据获取——从Tushare到AkShare的实战之路
做量化交易,数据就是你的弹药库。没有干净的数据,再牛的策略也是白搭。
这一章,我带你搞定有色金属期货的数据获取、清洗和存储。说白了,就是让你知道数据从哪来、怎么处理、存哪去。
4.1 数据源选择:Tushare vs AkShare
国内做期货量化,主流就两个库:Tushare和AkShare。我两个都用过,说说我的感受。
| 对比项 | Tushare | AkShare |
|---|---|---|
| 数据覆盖 | 全面,含基本面 | 更广,含另类数据 |
| 获取方式 | 需注册Token | 免费,部分需积分 |
| 速度 | 较快(付费更快) | 中等 |
| 文档 | 清晰,社区活跃 | 更新快,但略乱 |
我的建议:新手先用AkShare上手,免费且数据够用。等你要做高频或深度研究时,再考虑Tushare的付费接口。我在项目中遇到过,AkShare偶尔会因网站改版导致接口失效,所以记得加异常处理。
4.2 实战:用AkShare获取沪铜期货数据
直接上代码。我习惯先写一个通用的获取函数,方便后续复用。
import akshare as ak
import pandas as pd
from datetime import datetime, timedelta
def get_futures_data(symbol='CU', start_date='20200101', end_date=None):
"""
获取期货主力连续合约数据
symbol: 合约代码,如'CU'为沪铜
"""
if end_date is None:
end_date = datetime.now().strftime('%Y%m%d')
try:
# 获取主力连续合约
df = ak.futures_main_sina(symbol=symbol)
# 筛选日期范围
df = df[(df['date'] >= start_date) & (df['date'] <= end_date)]
return df
except Exception as e:
print(f"数据获取失败: {e}")
return None
# 获取沪铜近3年数据
cu_data = get_futures_data('CU', '20210101')
print(cu_data.head())
嗯,这里要注意:AkShare返回的数据列名是中文的,我建议你统一转成英文,方便后续处理。
4.3 数据清洗与预处理
拿到原始数据后,你会发现各种问题。我总结了三类常见坑:
4.3.1 处理缺失值
期货数据偶尔会缺一天,比如节假日或者数据源抽风。我的处理原则是:
- 连续缺失不超过3天:用前向填充(ffill)
- 超过3天:直接删除,别硬补
- 开盘价/收盘价缺失:必须删除,不能填充
# 缺失值处理
def handle_missing(df):
# 检查缺失比例
missing_ratio = df.isnull().sum() / len(df)
print(f"缺失比例:\n{missing_ratio}")
# 对价格列严格处理
price_cols = ['open', 'high', 'low', 'close']
df = df.dropna(subset=price_cols)
# 对成交量等用前向填充
df['volume'] = df['volume'].ffill()
df['open_interest'] = df['open_interest'].ffill()
return df
避坑指南:我曾经因为没检查缺失值,直接跑策略,结果回测曲线漂亮得不像话。后来发现是某个月数据全空,策略自动用前一天数据填充,造成了假信号。从那以后,我每次拿到数据第一件事就是画缺失值热力图。
4.3.2 异常值检测
期货市场偶尔会有极端行情,但有些异常是数据错误。我常用的方法是:
- 3σ原则:超过均值±3倍标准差的数据标记为异常
- IQR方法:用四分位距检测,更稳健
- 涨跌停检查:超过涨跌停板的价格直接剔除
def detect_outliers(df, col='close', method='iqr'):
if method == 'iqr':
Q1 = df[col].quantile(0.25)
Q3 = df[col].quantile(0.75)
IQR = Q3 - Q1
lower = Q1 - 1.5 * IQR
upper = Q3 + 1.5 * IQR
outliers = df[(df[col] < lower) | (df[col] > upper)]
print(f"检测到 {len(outliers)} 个异常值")
return outliers
return None
4.3.3 复权处理
期货和股票不一样,没有除权除息,但有合约换月。主力连续合约在换月时会有跳空,必须处理。
我常用的方法是:
- 前复权:以当前合约为基准,调整历史数据
- 后复权:以最早合约为基准,调整后续数据
- 等比复权:按比例调整,保持收益率不变
def adjust_futures_price(df, method='forward'):
"""
期货复权处理
method: 'forward' 前复权, 'backward' 后复权
"""
df = df.copy()
# 计算换月跳空比例
df['gap_ratio'] = df['close'] / df['close'].shift(1)
if method == 'forward':
# 以最新数据为基准
base_price = df['close'].iloc[-1]
df['adj_close'] = base_price / (df['gap_ratio'].iloc[::-1].cumprod().iloc[::-1])
else:
# 以最早数据为基准
base_price = df['close'].iloc[0]
df['adj_close'] = base_price * df['gap_ratio'].cumprod()
return df
核心要点:复权后的数据只能用于策略回测,不能用于实盘交易。实盘时,你交易的是当前主力合约,不是复权后的虚拟价格。
4.4 数据存储方案
数据存哪?我根据使用场景分三种:
| 存储方式 | 适用场景 | 优缺点 |
|---|---|---|
| CSV | 小数据量、快速验证 | 简单直观,但读写慢 |
| HDF5 | 中等数据量、频繁读写 | 压缩率高,支持快速切片 |
| 数据库(MySQL/PostgreSQL) | 大数据量、多用户协作 | 支持复杂查询,但部署麻烦 |
我个人习惯:
- 日常研究用HDF5,pandas直接读写,方便
- 生产环境用数据库,配合SQL做数据校验
- CSV只用来做数据交换,比如给同事发数据
# HDF5存储示例
def save_to_hdf5(df, filename='futures_data.h5', key='cu'):
df.to_hdf(filename, key=key, mode='a',
format='table', data_columns=True)
print(f"数据已保存至 {filename}")
# 读取
def load_from_hdf5(filename='futures_data.h5', key='cu'):
return pd.read_hdf(filename, key=key)
# 数据库存储(以MySQL为例)
def save_to_mysql(df, table_name='futures_cu'):
from sqlalchemy import create_engine
engine = create_engine('mysql+pymysql://user:pass@localhost/db')
df.to_sql(table_name, engine, if_exists='replace', index=False)
print(f"数据已写入MySQL表 {table_name}")
4.5 本章知识体系
下面这张图,是我做数据流程时画的,帮你理清思路:
我的工作流:每天早上开盘前,先跑一遍数据获取脚本,自动更新到HDF5文件。然后做一次简单的数据质量检查——看看有没有缺失、有没有异常跳空。整个过程不到5分钟,但能避免很多坑。
好了,数据获取和清洗这块就这些。记住一个原则:垃圾进,垃圾出。数据质量决定了策略的上限,别在这步偷懒。