数据层搭建:数据源选择、清洗与存储

做量化回测,第一步就是搞定数据。这事儿听起来简单,但坑特别多。

我刚开始做回测框架时,觉得数据嘛,找个接口一拉不就完了?结果跑出来的策略曲线漂亮得不像话,实盘一测直接傻眼。后来才发现,是数据源本身就有问题。

所以这一章,咱们把数据层拆开揉碎了讲。说白了,就是三件事:去哪拿数据、怎么洗干净、存成什么格式

一、数据源选择:Tushare vs JoinQuant

国内做量化,绕不开这两个平台。我个人的习惯是:回测用Tushare,实盘监控用JoinQuant。为什么?

对比维度 Tushare JoinQuant(聚宽)
数据覆盖 股票、基金、期货、期权、宏观经济 股票、基金、期货、指数
更新频率 日频免费,分钟/ tick 需积分 日频免费,分钟级需付费
接口风格 RESTful API,返回 DataFrame 本地 SDK,类似数据库查询
稳定性 偶尔限流,需加延时 相对稳定,但免费额度有限
我的推荐 研究、回测首选 实盘、策略监控首选

嗯,这里要注意:千万别在回测代码里直接实时拉数据。我见过有人每次回测都去调Tushare接口,结果网络一波动,回测结果就变了。数据必须本地化。

核心原则:回测数据必须是一次性下载、反复使用的。网络请求只发生在数据更新时。

二、数据清洗:那些年我踩过的坑

数据拿到手,你以为就能直接用?太天真了。

我曾经在回测一个动量策略时,发现某天收益率突然跳了20%。查了半天,原来是那天股票除权,价格没复权。你想想看,一个除权事件就能让你的策略看起来像神一样,或者像屎一样。

2.1 必须处理的脏数据

  • 缺失值:停牌日、节假日、新股上市前。我一般用前向填充(ffill),或者直接剔除。
  • 异常值:比如某只股票突然涨了1000%,大概率是数据错误。用3倍标准差或分位数法过滤。
  • 复权问题:这是最大的坑。回测必须用后复权数据,否则除权除息日会出现价格断层。
  • 未来数据:比如当天的收盘价被用来计算当天的指标。这属于逻辑错误,但数据清洗时也要检查时间戳。

避坑指南:我曾经在清洗时忘了处理停牌日,结果策略在停牌期间「凭空」交易了。回测收益高得离谱,实盘根本不可能。所以,停牌日的数据要么填充为前一日收盘价,要么标记为不可交易。

2.2 清洗流程示例

我一般按这个顺序来:

  1. 检查时间索引是否连续,补全缺失的交易日
  2. 处理缺失值(ffill / dropna)
  3. 复权处理(用Tushare的 adj_factor 字段)
  4. 剔除ST股、退市股(如果你不需要的话)
  5. 检查极端值(比如涨跌幅超过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(回测研究) JoinQuant(实盘监控) 数据清洗 缺失值 → 异常值 → 复权 → 未来数据检查 数据存储 CSV(调试/小数据) ↔ HDF5(生产/大数据)

这张图其实就概括了本章的全部内容。你照着这个流程走,数据层基本不会出大问题。

五、一些心里话

数据层是整个回测框架的地基。地基不稳,上面盖的房子再漂亮也是危房。

我见过太多人花大量时间调策略参数,却不愿意花一天时间把数据清洗干净。结果就是,策略在回测里跑得风生水起,实盘一塌糊涂。

所以,我的建议是:在数据层上多花点时间,绝对值得。把数据源、清洗流程、存储格式都固定下来,后面写策略的时候,你只需要关心逻辑本身。

最后一个小提醒:每次更新数据后,记得跑一遍清洗脚本。别偷懒,数据源那边偶尔会出幺蛾子。我曾经就因为Tushare某天数据接口返回了空值,没检查就直接用了,结果回测结果偏了3%。


公众号:蓝海数据掘金营,微信deep3321