第三章:数据获取与清洗——股票行情数据的“洗菜”功夫

做量化交易,数据就是你的原材料。原材料不干净,再好的策略也白搭。这一章,咱们聊聊怎么把股票行情数据搞到手,再把它洗得干干净净。

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')
💡 我的建议: 日线数据用 CSV 就够了,分钟线数据一定要用 HDF5。我在项目中试过用 CSV 存分钟线数据,一个文件就 2GB,读取一次要 30 秒。换成 HDF5 后,文件压缩到 300MB,读取只要 2 秒。

3.4 本章知识体系

下面这张图展示了本章的核心逻辑:从数据获取到清洗,再到存储的完整流程。

数据获取与清洗核心流程 数据获取 日线 / 分钟线 数据清洗 缺失值 / 异常值 / 重复值 数据存储 CSV / HDF5 数据源选择 Tushare / AKShare 聚宽 / 米筐 自建数据源 清洗方法 向前填充 / 向后填充 插值法 / 删除法 异常值检测 存储选择 CSV:小数据量 HDF5:大数据量 压缩与索引 核心原则:数据质量决定策略上限,清洗比建模更重要

3.5 避坑指南

最后,分享几个我踩过的坑:

  • 复权问题:日线数据一定要用后复权或前复权。我曾经用未复权数据跑回测,结果分红除权后,策略信号全乱了。
  • 时区问题:分钟线数据的时间戳,一定要统一成北京时间。我遇到过数据源混用 UTC 和 CST 的情况,导致时间错位。
  • 数据对齐:多只股票的数据,时间轴要对齐。停牌的股票要单独处理,不要简单删除。
📌 核心总结: 数据获取要选对源,数据清洗要细心,数据存储要选对格式。这三步做好了,后面的策略开发才能事半功倍。

专注资料整理