第一章 数据模块设计:行情数据获取、数据清洗与对齐、数据存储方案

做量化回测,第一关就是数据。我见过太多人策略写得漂亮,结果数据一塌糊涂,回测曲线美如画,实盘直接崩。说白了,数据模块是整个回测系统的地基,地基不稳,上面盖什么都白搭。

这一章,咱们就聊聊行情数据怎么拿、怎么洗、怎么存。我会把我在实战中踩过的坑、总结的经验,一股脑倒出来。

1.1 行情数据获取:从哪里来,怎么拿

行情数据来源,无非就几种:

  • 交易所官方接口:比如上交所、深交所的Level-2行情。数据最准,但门槛高,要申请牌照。
  • 第三方数据商:Wind、聚宽、Tushare、AKShare。我早期做回测时,用的就是Tushare,免费、够用。
  • 券商内部数据:如果你在机构,一般有内部数据库,直接拉就行。

我个人习惯,先用免费数据源做原型验证,等策略稳定了,再换付费数据。为什么呢?因为免费数据偶尔会有缺失或延迟,但做回测初期,够用了。

小提示: 别一上来就追求毫秒级精度。日线数据做策略验证,完全够用。高频数据那是另一套玩法,后面章节会讲。

举个例子,用Python拉取A股日线数据:

import tushare as ts

# 设置token(去tushare官网注册)
pro = ts.pro_api('你的token')

# 获取平安银行日线数据
df = pro.daily(ts_code='000001.SZ', start_date='20200101', end_date='20231231')
print(df.head())

嗯,这里要注意:免费接口有频率限制,比如每分钟最多请求200次。我曾经写了个循环批量拉数据,结果被限流了,卡了半小时。后来加了个time.sleep(0.3),问题解决。

1.2 数据清洗与对齐:脏数据是回测的隐形杀手

数据拿到手,别急着用。先看看有没有坑。

常见问题:

  • 缺失值:某天停牌,数据直接空着。你想想看,如果回测时没处理,策略会以为那天没交易,结果算出来的收益率全是错的。
  • 异常值:比如某只股票突然涨了1000%,一看是除权除息没处理。我遇到过,回测曲线直接飞上天,吓我一跳。
  • 时间戳不一致:不同数据源的时间格式不同,有的用'2023-01-01',有的用'20230101'。不对齐的话,合并数据时会乱套。

清洗流程:

  1. 去重:检查是否有重复行,有的话删掉。
  2. 填充缺失值:停牌日的数据,用前一天的收盘价填充。或者直接删除,看你的策略需求。
  3. 处理异常值:用3σ原则或IQR方法,把明显离谱的数据标记出来,人工复核。
  4. 复权处理:股票分红送股后,价格会跳空。必须做前复权或后复权,否则回测失真。
避坑指南: 我曾经用未复权的数据跑回测,策略看起来年化50%,实盘直接亏成狗。后来才发现,分红导致的跳空被策略误判为「买入信号」。从那以后,我每次回测前必做复权处理。

数据对齐,说白了就是让不同品种的数据在时间轴上对齐。比如你要同时交易茅台和五粮液,它们的交易日可能不完全重合。茅台停牌那天,五粮液还在交易。这时候,要么把茅台那天的数据填充为NaN,要么直接剔除那天。

代码示例:

import pandas as pd

# 假设df1是茅台日线,df2是五粮液日线
# 按日期对齐,缺失值用前向填充
df_aligned = pd.merge(df1, df2, on='date', how='outer', suffixes=('_maotai', '_wuliangye'))
df_aligned = df_aligned.sort_values('date').fillna(method='ffill')

1.3 数据存储方案:HDF5 vs Parquet

数据清洗完了,得存起来。存成CSV?别闹,几百万行数据,CSV读写慢得要命,而且不支持压缩。

我推荐两种格式:HDF5Parquet。它们各有千秋。

特性 HDF5 Parquet
读写速度 快,尤其适合大量小文件 更快,列式存储,查询部分列时极快
压缩率 中等 高,Snappy压缩后体积小很多
跨语言支持 Python、C++、Java 几乎所有大数据框架都支持
适用场景 单机回测、小团队 分布式系统、大数据量

我个人习惯:单机回测用HDF5,因为pandas直接支持,读写方便。生产环境用Parquet,因为要对接Spark、Flink这些大数据框架。

HDF5存储示例:

import pandas as pd

# 存储为HDF5
df.to_hdf('stock_data.h5', key='daily', mode='w', complevel=9)

# 读取
df = pd.read_hdf('stock_data.h5', key='daily')

Parquet存储示例:

# 存储为Parquet
df.to_parquet('stock_data.parquet', compression='snappy')

# 读取
df = pd.read_parquet('stock_data.parquet')
重要: 无论用哪种格式,一定要做数据校验。存完之后读一遍,检查行数、列数、时间范围是否一致。我吃过亏,存完发现数据少了一半,原来是写入时内存不够,部分数据没写进去。

1.4 本章知识体系图

下面这张图,概括了数据模块的核心流程。你可以把它当作一个检查清单,每次做数据准备时,对着走一遍。

数据模块核心流程 数据获取 交易所 / 第三方 / 券商 数据清洗 去重 / 填充 / 复权 数据对齐 时间轴对齐 数据存储 HDF5 / Parquet 数据校验 行数 / 列数 / 时间范围 数据校验不通过时,返回数据获取或清洗步骤重新处理 数据模块是整个回测系统的地基

这张图里,我特意把「数据校验」画成了一个循环。为什么?因为数据问题往往不是一次就能解决的。你校验发现行数不对,回去查,可能是数据源漏了某天,也可能是清洗时误删了。反复迭代几次,数据才能用。

好了,数据模块的核心内容就这些。记住一句话:数据质量决定回测可信度。别嫌麻烦,把数据搞干净了,后面的策略开发才能安心。


专注资料整理