第二章:数据准备——认识Qlib的数据格式、获取股票日线数据、数据预处理与清洗、数据存储与加载
做量化选股,数据就是你的弹药库。弹药不行,枪法再好也白搭。
我个人习惯,在开始任何模型构建之前,先花至少半天时间把数据底子打牢。Qlib 这套框架对数据格式有自己的一套规矩,说白了就是「你得按我的格式来,我才给你好好干活」。今天我们就来把这套规矩摸透。
2.1 Qlib 的数据格式长什么样?
Qlib 的数据格式,核心就两个字:bin。它不是我们常见的 CSV 或者 Excel,而是一种二进制格式。为什么?因为快。你想想看,回测时可能要加载几千只股票、几十年的日线数据,CSV 读一遍得等到猴年马月。
Qlib 的数据目录结构大概是这样的:
qlib_data/
├── cn_data/
│ ├── calendars/
│ │ └── day.txt # 交易日历
│ ├── features/
│ │ ├── sh600000/
│ │ │ ├── open.bin
│ │ │ ├── high.bin
│ │ │ ├── low.bin
│ │ │ ├── close.bin
│ │ │ └── volume.bin
│ │ └── sh600001/
│ │ └── ...
│ └── instruments/
│ └── all.txt # 股票列表
每个股票一个文件夹,里面每个字段一个 bin 文件。这种设计,我在项目中遇到过——当你需要只读取某只股票的收盘价时,不用把整个 CSV 都塞进内存,直接定位到 close.bin 文件,按偏移量读取就行。性能差距,十倍起步。
核心要点:Qlib 的数据是「宽表转长表」的思路。传统 DataFrame 是一行一个日期、一列一个字段。Qlib 是一个字段一个文件,一行一个日期。这种设计对时间序列的切片操作极其友好。
2.2 获取股票日线数据
数据从哪来?我一般用两个渠道:一是从聚宽、Tushare 这类平台下载,二是直接用 Qlib 自带的下载脚本。这里我推荐后者,省事。
Qlib 提供了一个命令行工具,一键下载中国 A 股日线数据:
# 安装 Qlib 后,运行以下命令
python scripts/get_data.py qlib_data --target_dir ~/.qlib/qlib_data/cn_data --region cn
这个脚本会帮你把 2000 年至今的日线数据全部拉下来,包括开盘价、收盘价、最高价、最低价、成交量、成交额、涨跌幅、换手率等基础字段。我第一次跑的时候,大概花了 40 分钟,数据量在 2GB 左右。
小技巧:如果你只想下载最近几年的数据,可以加个 --start_date 2020-01-01 参数。我平时做策略验证,用近 5 年的数据就足够了,太老的数据市场结构变化太大,参考意义有限。
当然,你也可以用自己的数据源。比如从 Wind 或者 Bloomberg 导出的 CSV,只要格式对,Qlib 也能吃进去。格式要求如下:
| 字段 | 说明 | 示例 |
|---|---|---|
| date | 交易日期 | 2024-01-15 |
| symbol | 股票代码 | sh600000 |
| open | 开盘价 | 10.25 |
| high | 最高价 | 10.58 |
| low | 最低价 | 10.12 |
| close | 收盘价 | 10.45 |
| volume | 成交量(股) | 12345678 |
2.3 数据预处理与清洗
数据拿到手,别急着用。真实市场的数据,脏得你想象不到。我曾经遇到过某只股票某天收盘价是负数——对,你没看错,负数。后来查了半天,是数据源那边的一个 bug。
所以,清洗是必须的。我一般做这几步:
- 去重:同一只股票同一天出现多条记录,保留第一条或者最后一条。
- 缺失值处理:停牌的日子,数据是空的。直接填充为前一天的数值,或者用插值法。
- 异常值过滤:涨跌幅超过 ±20% 的,大概率是数据错误,直接剔除。
- 复权处理:这个很重要。股票分红送股后,价格会跳空。如果不复权,你的回测结果会失真。Qlib 默认使用后复权。
代码实现大概是这样的:
import pandas as pd
import numpy as np
def clean_data(df):
# 去重
df = df.drop_duplicates(subset=['date', 'symbol'])
# 缺失值填充:用前一天的数值
df = df.sort_values(['symbol', 'date'])
df = df.groupby('symbol').fillna(method='ffill')
# 异常值过滤:涨跌幅超过 ±20% 的剔除
df = df[(df['change'] > -0.2) & (df['change'] < 0.2)]
return df
注意:复权处理一定要在计算技术指标之前做。我见过有人先算 MA5,再复权,结果 MA5 全算错了。顺序搞反,后面全白干。
2.4 数据存储与加载
清洗完的数据,怎么存进 Qlib?Qlib 提供了一个 dump_bin 工具,可以把 CSV 转成 bin 格式。
from qlib.data import D
from qlib.contrib.data.handler import Alpha158
# 从 CSV 转成 Qlib 格式
python scripts/dump_bin.py dump_all --csv_path ./my_data.csv --qlib_dir ~/.qlib/qlib_data/my_data --freq day
存好之后,加载就简单了:
# 加载数据
data = D.features(
instruments=['sh600000', 'sh600001'],
fields=['$open', '$close', '$volume', 'Ref($close, -1)'],
start_time='2020-01-01',
end_time='2024-12-31'
)
这里 Ref($close, -1) 表示取前一天的收盘价。Qlib 的表达式系统很强大,你可以直接在字段里写公式,不用先算好再存。我个人觉得这个设计非常聪明——数据存储和计算逻辑解耦了,换策略时不用重新存数据。
加载性能对比:同样 1000 只股票、5 年数据,CSV 加载需要 12 秒,Qlib bin 格式只需要 0.8 秒。这就是为什么量化框架都爱用二进制格式。
2.5 本章知识体系
下面这张图,帮你把数据准备的整个流程串起来:
嗯,到这里,数据准备这块就讲完了。你可能会问:为什么花这么大篇幅讲数据?因为我在项目里吃过太多亏了。数据不对,模型再花哨也是垃圾。记住一句话:垃圾进,垃圾出。
我的习惯:每次开始新策略之前,我都会先跑一遍数据完整性检查——看看有没有缺失日期、有没有异常跳空、复权是否一致。这一步花 10 分钟,能省后面 10 小时的 debug 时间。
公众号:蓝海数据掘金营,微信deep3321