第三章:数据获取与清洗——股票行情数据的“洗菜”功夫
做量化交易,数据就是你的原材料。原材料不干净,再好的策略也白搭。这一章,咱们聊聊怎么把股票行情数据搞到手,再把它洗得干干净净。
3.1 股票行情数据获取:日线与分钟线
我个人习惯把行情数据分成两类:日线数据和分钟线数据。日线数据用于中长线策略,分钟线数据用于短线或高频策略。
3.1.1 日线数据获取
日线数据相对好拿。常用的数据源有:
- Tushare:国内老牌金融数据接口,免费版够用,但有限流。
- AKShare:开源免费,数据源多,但稳定性一般。
- Wind / 聚宽:付费数据源,质量高,适合机构。
我建议新手先用 Tushare 或 AKShare 练手。代码很简单:
import tushare as ts
# 设置 token(去官网注册获取)
ts.set_token('your_token_here')
pro = ts.pro_api()
# 获取平安银行日线数据
df = pro.daily(ts_code='000001.SZ', start_date='20230101', end_date='20231231')
print(df.head())
嗯,这里要注意:Tushare 的免费版每分钟只能请求 200 次。如果你要批量获取几千只股票,得加个 sleep 控制节奏。我在项目中遇到过因为没加限流,直接被封 IP 的尴尬事。
3.1.2 分钟线数据获取
分钟线数据就麻烦多了。为什么?因为数据量大,存储成本高。
常用的分钟线数据源:
- 聚宽(JoinQuant):提供 1 分钟、5 分钟、30 分钟等粒度数据。
- 米筐(RiceQuant):类似聚宽,数据质量不错。
- 自建数据源:通过券商接口或 Level-2 行情自行采集。
举个例子,用聚宽获取分钟线数据:
from jqdatasdk import *
# 登录聚宽账号
auth('your_account', 'your_password')
# 获取平安银行 2023年1月的5分钟线数据
df = get_price('000001.XSHE', start_date='2023-01-01', end_date='2023-01-31',
frequency='5min', fields=['open', 'close', 'high', 'low', 'volume'])
print(df.head())
你想想看,一分钟线数据一天就有 240 条,一年就是 5 万多条。如果存 3000 只股票,数据量轻松上亿条。所以,分钟线数据的存储和清洗,比日线数据要讲究得多。
3.2 数据清洗与缺失值处理
数据拿到手,第一件事不是分析,而是清洗。我见过太多人直接拿原始数据跑策略,结果回测漂亮,实盘一塌糊涂。说白了,数据里藏着各种坑。
3.2.1 常见的数据问题
| 问题类型 | 表现 | 原因 |
|---|---|---|
| 缺失值 | 某天或某分钟没有数据 | 停牌、休市、数据源故障 |
| 异常值 | 价格突然暴涨暴跌 | 数据录入错误、复权未处理 |
| 重复值 | 同一时间戳出现多条数据 | 数据源重复推送 |
| 时间戳错乱 | 数据顺序不对 | 时区问题、数据源排序错误 |
3.2.2 缺失值处理方法
缺失值是最常见的问题。我的处理原则是:能不删就不删,能补就补。
常用的补全方法:
- 向前填充(ffill):用上一个非缺失值填充。适合停牌数据。
- 向后填充(bfill):用下一个非缺失值填充。适合数据源延迟。
- 插值法:线性插值或多项式插值。适合分钟线数据。
- 直接删除:如果缺失比例超过 20%,建议直接删除该股票或该时间段。
举个例子:
import pandas as pd
# 假设 df 是日线数据,包含缺失值
# 向前填充停牌日的数据
df['close'] = df['close'].fillna(method='ffill')
# 如果开盘价也缺失,用收盘价替代
df['open'] = df['open'].fillna(df['close'])
# 删除仍然缺失的行(比如刚上市第一天)
df = df.dropna(subset=['close', 'volume'])
我曾经在项目中遇到过一个坑:某只股票停牌了 3 个月,复牌后直接跌停。如果用向前填充,那 3 个月的数据看起来一切正常,但实际上价格已经天差地别。所以,停牌超过 10 天的数据,我建议标记出来,不要简单填充。
3.3 数据存储:CSV vs HDF5
数据清洗完了,得存起来。选哪种存储方式?这取决于你的数据量和使用场景。
3.3.1 CSV 格式
CSV 是最简单的存储方式。优点是人眼可读,方便调试。缺点是读写慢,占用空间大。
适合场景:
- 数据量小(少于 100 万行)
- 需要人工查看或分享数据
- 临时存储
代码示例:
# 保存为 CSV
df.to_csv('stock_data.csv', index=False)
# 读取 CSV
df = pd.read_csv('stock_data.csv', parse_dates=['trade_date'])
3.3.2 HDF5 格式
HDF5 是二进制格式,读写速度快,支持压缩,占用空间小。缺点是不方便直接查看。
适合场景:
- 数据量大(超过 100 万行)
- 需要频繁读写
- 分钟线数据存储
代码示例:
# 保存为 HDF5
df.to_hdf('stock_data.h5', key='daily_data', mode='w', complevel=9)
# 读取 HDF5
df = pd.read_hdf('stock_data.h5', key='daily_data')
3.4 本章知识体系
下面这张图展示了本章的核心逻辑:从数据获取到清洗,再到存储的完整流程。
3.5 避坑指南
最后,分享几个我踩过的坑:
- 复权问题:日线数据一定要用后复权或前复权。我曾经用未复权数据跑回测,结果分红除权后,策略信号全乱了。
- 时区问题:分钟线数据的时间戳,一定要统一成北京时间。我遇到过数据源混用 UTC 和 CST 的情况,导致时间错位。
- 数据对齐:多只股票的数据,时间轴要对齐。停牌的股票要单独处理,不要简单删除。