数据层搭建:数据源选择、清洗与存储
做量化回测,第一步就是搞定数据。这事儿听起来简单,但坑特别多。
我刚开始做回测框架时,觉得数据嘛,找个接口一拉不就完了?结果跑出来的策略曲线漂亮得不像话,实盘一测直接傻眼。后来才发现,是数据源本身就有问题。
所以这一章,咱们把数据层拆开揉碎了讲。说白了,就是三件事:去哪拿数据、怎么洗干净、存成什么格式。
一、数据源选择:Tushare vs JoinQuant
国内做量化,绕不开这两个平台。我个人的习惯是:回测用Tushare,实盘监控用JoinQuant。为什么?
| 对比维度 | Tushare | JoinQuant(聚宽) |
|---|---|---|
| 数据覆盖 | 股票、基金、期货、期权、宏观经济 | 股票、基金、期货、指数 |
| 更新频率 | 日频免费,分钟/ tick 需积分 | 日频免费,分钟级需付费 |
| 接口风格 | RESTful API,返回 DataFrame | 本地 SDK,类似数据库查询 |
| 稳定性 | 偶尔限流,需加延时 | 相对稳定,但免费额度有限 |
| 我的推荐 | 研究、回测首选 | 实盘、策略监控首选 |
嗯,这里要注意:千万别在回测代码里直接实时拉数据。我见过有人每次回测都去调Tushare接口,结果网络一波动,回测结果就变了。数据必须本地化。
核心原则:回测数据必须是一次性下载、反复使用的。网络请求只发生在数据更新时。
二、数据清洗:那些年我踩过的坑
数据拿到手,你以为就能直接用?太天真了。
我曾经在回测一个动量策略时,发现某天收益率突然跳了20%。查了半天,原来是那天股票除权,价格没复权。你想想看,一个除权事件就能让你的策略看起来像神一样,或者像屎一样。
2.1 必须处理的脏数据
- 缺失值:停牌日、节假日、新股上市前。我一般用前向填充(ffill),或者直接剔除。
- 异常值:比如某只股票突然涨了1000%,大概率是数据错误。用3倍标准差或分位数法过滤。
- 复权问题:这是最大的坑。回测必须用后复权数据,否则除权除息日会出现价格断层。
- 未来数据:比如当天的收盘价被用来计算当天的指标。这属于逻辑错误,但数据清洗时也要检查时间戳。
避坑指南:我曾经在清洗时忘了处理停牌日,结果策略在停牌期间「凭空」交易了。回测收益高得离谱,实盘根本不可能。所以,停牌日的数据要么填充为前一日收盘价,要么标记为不可交易。
2.2 清洗流程示例
我一般按这个顺序来:
- 检查时间索引是否连续,补全缺失的交易日
- 处理缺失值(ffill / dropna)
- 复权处理(用Tushare的
adj_factor字段) - 剔除ST股、退市股(如果你不需要的话)
- 检查极端值(比如涨跌幅超过20%的,标记出来人工复核)
import pandas as pd
import tushare as ts
# 拉取数据
df = ts.pro_bar(ts_code='000001.SZ', adj='qfq', start_date='20200101', end_date='20231231')
# 清洗步骤
df = df.set_index('trade_date')
df.index = pd.to_datetime(df.index)
# 1. 补全交易日
all_dates = pd.date_range(start=df.index.min(), end=df.index.max(), freq='B')
df = df.reindex(all_dates)
# 2. 前向填充
df['close'] = df['close'].ffill()
df['open'] = df['open'].ffill()
# 3. 剔除异常
df = df[df['close'] < df['close'].quantile(0.99)] # 简单粗暴
print(f"清洗后数据量: {len(df)} 行")
小技巧:清洗完数据后,一定要做一次「回测一致性检查」。就是拿清洗后的数据,手动算几个已知的指标(比如某天的涨跌幅),跟原始数据对比。这一步能发现很多隐蔽的错误。
三、数据存储:HDF5 还是 CSV?
这个问题我纠结了很久。刚开始图省事,全用CSV。结果数据量一上来,回测加载就要半分钟。后来换了HDF5,速度提升明显。
3.1 两种格式的对比
| 特性 | CSV | HDF5 |
|---|---|---|
| 可读性 | 高,记事本就能打开 | 低,需要专用工具 |
| 读写速度 | 慢(尤其大文件) | 快(二进制格式) |
| 压缩率 | 无压缩 | 支持压缩(可减小50%体积) |
| 跨语言 | 通用 | Python/Pandas 生态最佳 |
| 我的选择 | 小数据量、调试用 | 生产环境、大数据量 |
我个人建议:日常研究用CSV,正式回测框架用HDF5。为什么?因为HDF5支持按索引快速切片,比如只读取某只股票某段时间的数据,速度比CSV快几十倍。
3.2 HDF5 存储示例
import pandas as pd
# 假设 df 是清洗好的数据,包含多只股票
# 按股票代码分组存储
with pd.HDFStore('market_data.h5', mode='w') as store:
for code, group in df.groupby('ts_code'):
store.put(f'stock/{code}', group, format='table', data_columns=True)
print(f"已存储: {code}")
# 读取时只需指定 key
df_000001 = pd.read_hdf('market_data.h5', key='stock/000001.SZ')
注意:HDF5 在多进程写入时可能会锁文件。如果你需要并行更新数据,建议用 Parquet 格式(Pandas 也支持)。不过对于个人回测框架,HDF5 完全够用。
四、本章知识体系图
下面这张图,是我自己梳理的数据层搭建流程。你可以把它当作一个检查清单:
这张图其实就概括了本章的全部内容。你照着这个流程走,数据层基本不会出大问题。
五、一些心里话
数据层是整个回测框架的地基。地基不稳,上面盖的房子再漂亮也是危房。
我见过太多人花大量时间调策略参数,却不愿意花一天时间把数据清洗干净。结果就是,策略在回测里跑得风生水起,实盘一塌糊涂。
所以,我的建议是:在数据层上多花点时间,绝对值得。把数据源、清洗流程、存储格式都固定下来,后面写策略的时候,你只需要关心逻辑本身。
最后一个小提醒:每次更新数据后,记得跑一遍清洗脚本。别偷懒,数据源那边偶尔会出幺蛾子。我曾经就因为Tushare某天数据接口返回了空值,没检查就直接用了,结果回测结果偏了3%。
公众号:蓝海数据掘金营,微信deep3321