第二章:金融数据基础——股票数据获取、清洗、存储与质量检查
做量化投资,数据就是你的弹药库。弹药不行,枪法再好也白搭。这一章,咱们就聊聊怎么搞到靠谱的股票数据,怎么把它收拾干净,再找个地方好好存起来。
我个人习惯把数据流程分成四步:获取、清洗、存储、检查。每一步都有坑,我踩过不少,今天一并告诉你。
2.1 股票数据获取:tushare vs akshare
国内做量化,绕不开两个库:tushare 和 akshare。它们都是 Python 的金融数据接口,但风格完全不同。
| 对比项 | tushare | akshare |
|---|---|---|
| 数据源 | 自建数据库,稳定 | 爬取公开网站,更新快 |
| 使用门槛 | 需要注册 token,部分接口付费 | 免费,pip install 即用 |
| 数据质量 | 较高,经过清洗 | 依赖源网站,偶有异常 |
| 适合场景 | 生产环境、回测 | 研究、快速验证 |
我建议初学者先用 akshare 上手,零成本。等你要做严肃回测了,再切到 tushare 的 pro 版。
核心原则:数据源不是越全越好,而是越稳越好。宁可少几个字段,也别拿到一堆脏数据。
2.1.1 用 akshare 获取日线数据
举个例子,拿贵州茅台的历史日线。代码很简单:
import akshare as ak
# 获取日线数据
df = ak.stock_zh_a_hist(symbol="600519", period="daily",
start_date="20200101", end_date="20231231")
print(df.head())
返回的字段包括:日期、开盘、收盘、最高、最低、成交量、成交额、振幅、涨跌幅、换手率。够用了。
小技巧:akshare 的接口名经常变。我习惯每次用之前先打印一下 ak.__doc__ 看看最新文档,省得踩坑。
2.1.2 用 tushare 获取数据
tushare 需要先注册拿到 token。我个人习惯把 token 存在环境变量里,不写死在代码中。
import tushare as ts
# 初始化
pro = ts.pro_api('你的token')
# 获取日线
df = pro.daily(ts_code='600519.SH', start_date='20200101',
end_date='20231231')
print(df.head())
tushare 返回的数据更规整,字段命名也统一。比如复权因子、停牌标记这些,akshare 不一定有。
注意:tushare 的 pro 接口有调用频率限制。基础版每分钟最多 200 次。我曾经写了个循环批量下载,结果被限流了半小时...后来老老实实加了 time.sleep。
2.2 数据清洗与预处理
数据拿到手,别急着用。先看看有没有脏数据。我总结了一套标准流程:
- 缺失值处理——检查 NaN,决定填充还是删除
- 异常值检测——比如涨跌幅超过 20% 的,大概率是停牌后复牌
- 数据类型转换——日期转 datetime,价格转 float
- 排序与索引——按日期升序,设置日期为索引
- 复权处理——后复权或前复权,统一口径
2.2.1 缺失值处理
股票数据里,缺失值常见于停牌日。比如某只股票停牌一周,那几天的数据就是 NaN。
# 检查缺失值
print(df.isnull().sum())
# 方法一:删除缺失行
df_clean = df.dropna()
# 方法二:向前填充(用上一个交易日的数据)
df_filled = df.fillna(method='ffill')
我个人习惯用前向填充。因为停牌期间,价格理论上应该延续停牌前的状态。但要注意,如果连续停牌太久,前向填充会失真。
经验之谈:我曾经处理过一只停牌半年的股票,复牌当天跌了 40%。如果用前向填充,那天的数据会完全偏离真实情况。所以,对于停牌超过 20 天的,我建议直接删除那几行。
2.2.2 异常值检测
涨跌幅超过 10% 的,在 A 股里基本就是异常。要么是新股上市首日,要么是 ST 股。我一般用 3-sigma 法则:
# 计算涨跌幅的均值和标准差
mean = df['pct_chg'].mean()
std = df['pct_chg'].std()
# 标记异常
df['is_outlier'] = (abs(df['pct_chg'] - mean) > 3 * std)
嗯,这里要注意:3-sigma 法则假设数据服从正态分布。但股票涨跌幅其实有尖峰厚尾特征。所以我会结合业务逻辑再判断。比如涨跌幅超过 20% 的,直接标记出来人工审核。
2.2.3 复权处理
复权是个大坑。分红、送股、配股都会导致价格跳空。如果不复权,你的回测结果会严重失真。
tushare 和 akshare 都提供了复权因子。我个人习惯用后复权,因为后复权的价格序列是连续的,适合做技术分析。
# 使用 tushare 获取复权因子
df_adj = pro.adj_factor(ts_code='600519.SH')
# 计算后复权价格
df['close_adj'] = df['close'] * df_adj['adj_factor']
避坑指南:我曾经直接用前复权数据做回测,结果发现策略收益被高估了。原因是前复权会改变历史价格,导致买入信号提前出现。后来我改用后复权,回测结果才合理。
2.3 数据存储:CSV、HDF5 与数据库
数据清洗完了,得找个地方存起来。三种主流方式,各有优劣。
| 存储方式 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| CSV | 简单、通用、可读 | 慢、无压缩、不支持索引 | 小数据量、临时分析 |
| HDF5 | 快、支持压缩、支持索引 | 二进制、不易查看 | 中等数据量、回测 |
| 数据库 | 支持并发、查询灵活 | 部署复杂、维护成本高 | 生产环境、团队协作 |
我个人建议:研究阶段用 HDF5,生产环境用数据库。CSV 只用来做数据交换。
2.3.1 存储为 CSV
df.to_csv('600519_daily.csv', index=False)
简单粗暴。但如果你有 5000 只股票,每只存一个 CSV,管理起来会疯掉。
2.3.2 存储为 HDF5
HDF5 支持分层存储。我习惯按股票代码分组,每个组里存不同频率的数据。
import pandas as pd
# 存储
with pd.HDFStore('stock_data.h5') as store:
store.put('600519/daily', df, format='table')
store.put('000001/daily', df2, format='table')
# 读取
with pd.HDFStore('stock_data.h5') as store:
df = store.get('600519/daily')
性能对比:同样 100 万行数据,CSV 读取需要 3 秒,HDF5 只需要 0.3 秒。回测时,这个差距会放大到分钟级别。
2.3.3 存储到数据库
我用得最多的是 SQLite,轻量级,零配置。团队协作时用 PostgreSQL。
import sqlite3
# 连接数据库
conn = sqlite3.connect('stock.db')
# 写入
df.to_sql('daily_600519', conn, if_exists='replace', index=False)
# 读取
df = pd.read_sql('SELECT * FROM daily_600519 WHERE date > "2023-01-01"', conn)
注意:数据库写入时,一定要设置索引。否则每次查询都是全表扫描,慢得你想哭。我建议用 (date, ts_code) 作为联合索引。
2.4 数据质量检查
数据存好了,不代表就万事大吉。我每次跑回测前,都会做一轮质量检查。这是血的教训换来的。
2.4.1 检查清单
- 时间连续性:交易日是否完整?有没有跳空?
- 价格合理性:开盘价、最高价、最低价、收盘价是否满足 OHLC 关系?
- 成交量非负:成交量不能为负数或零(停牌除外)
- 涨跌幅范围:A 股正常涨跌幅不超过 ±10%,ST 股不超过 ±5%
- 复权一致性:复权后的价格序列是否单调?
2.4.2 自动化检查脚本
我写了一个简单的检查函数,每次加载数据后自动跑一遍。
def check_data_quality(df):
issues = []
# 检查 OHLC 关系
if not (df['high'] >= df['low']).all():
issues.append('最高价低于最低价')
if not (df['high'] >= df['close']).all():
issues.append('最高价低于收盘价')
# 检查成交量
if (df['volume'] < 0).any():
issues.append('存在负成交量')
# 检查涨跌幅
if (abs(df['pct_chg']) > 10).any():
issues.append('存在异常涨跌幅')
if issues:
print('数据质量问题:')
for issue in issues:
print(f' - {issue}')
else:
print('数据质量检查通过')
return issues
经验之谈:我曾经用了一个看似完美的数据集,回测年化收益 30%。结果一查,发现数据里混入了未来信息——某天的收盘价被错误地填充了第二天的数据。从那以后,我每次回测前都会做一次未来数据检测:检查是否有任何特征用到了未来的信息。
2.5 本章知识体系
下面这张图,概括了本章的核心逻辑。从数据源到最终可用数据,每一步都有对应的处理手段。
你看,整个流程是环环相扣的。数据源选错了,后面再怎么清洗也救不回来。存储方式选错了,回测效率会大打折扣。质量检查没做好,策略结果可能就是一场幻觉。
我个人习惯把这套流程写成脚本,每天开盘前自动跑一遍。数据有问题,系统会发邮件提醒我。这样我就能安心睡觉,不用半夜爬起来检查数据。
最后说一句:数据是量化投资的基石。花 80% 的时间在数据上,剩下 20% 的时间建模,这是值得的。别像我刚开始那样,拿到数据就急着跑策略,结果被数据坑得体无完肤。