3. 金融市场数据基础:股票数据获取与预处理
做量化投资,第一步就是搞定数据。没有数据,再牛的策略也是空中楼阁。今天我们就来聊聊怎么拿到股票数据,怎么把它洗干净,以及怎么用时间序列的思维去理解它。
我个人习惯把数据获取比作「做饭前的买菜」。菜买得不好,厨艺再高也白搭。数据也一样,源头脏了,后面分析全是错的。
3.1 数据获取:tushare vs akshare
国内做量化,绕不开两个库:tushare 和 akshare。它们都是免费的开源库,但定位不太一样。
| 对比项 | tushare | akshare |
|---|---|---|
| 数据源 | 自建数据库,稳定 | 爬取公开网站,更新快 |
| 注册门槛 | 需要 token,部分接口收费 | 无需注册,直接调用 |
| 数据质量 | 高,经过清洗 | 中等,偶有异常值 |
| 适合场景 | 正式策略回测 | 快速验证、研究 |
我刚开始做量化时,图省事直接用 akshare。结果有一次回测收益特别高,兴奋了半天,后来发现是某天的复权数据算错了。嗯,从那以后,正式策略我都用 tushare。
3.1.1 用 tushare 获取日线数据
import tushare as ts
# 设置 token(需要去官网注册)
ts.set_token('你的token')
pro = ts.pro_api()
# 获取平安银行 2023年日线数据
df = pro.daily(
ts_code='000001.SZ',
start_date='20230101',
end_date='20231231'
)
print(df.head())
pro.daily(adj='qfq') 即可。
3.1.2 用 akshare 快速获取数据
import akshare as ak
# 获取上证指数历史数据
df = ak.stock_zh_index_daily(symbol="sh000001")
print(df.head())
akshare 的好处是快,不用注册。但你要自己做好数据校验。我曾经发现某只股票在 akshare 里突然多了一天「涨停」数据,其实是网站爬虫抓错了。
3.2 数据清洗与预处理
数据拿到手,别急着用。先看看有没有坑。我总结了一套「三步清洗法」:
- 缺失值处理:股票停牌日会有 NaN,直接 drop 还是 fill?
- 异常值检测:涨跌幅超过 20% 的,大概率是数据错误。
- 格式统一:日期列转 datetime,价格列转 float。
3.2.1 实战:清洗平安银行数据
import pandas as pd
import numpy as np
# 假设 df 是从 tushare 获取的原始数据
df = df.sort_values('trade_date') # 按日期排序
# 1. 检查缺失值
print(df.isnull().sum())
# 2. 处理缺失:前向填充(停牌日沿用前一日数据)
df['close'] = df['close'].fillna(method='ffill')
# 3. 异常值检测:涨跌幅超过 ±10% 的标记为异常
df['pct_chg'] = df['close'].pct_change()
df.loc[df['pct_chg'].abs() > 0.1, 'close'] = np.nan
df['close'] = df['close'].fillna(method='ffill')
# 4. 日期格式统一
df['trade_date'] = pd.to_datetime(df['trade_date'])
df.set_index('trade_date', inplace=True)
3.3 时间序列数据结构
量化数据本质上就是时间序列。每一行是一个时间点,每一列是一个特征。理解这个结构,是后面所有分析的基础。
说白了,时间序列数据就三个要素:
- 时间戳:唯一标识每一行
- 观测值:价格、成交量等
- 频率:日频、分钟频、Tick 级
3.3.1 为什么频率很重要?
你想想看,日频数据和分钟频数据,处理方式完全不同。日频数据可以简单用 pct_change() 算收益率,但分钟频数据要考虑隔夜跳空。
我做过一个高频策略,用 5 分钟数据算动量。结果发现每天开盘第一根 K 线的收益率特别大,差点把策略带偏。后来才意识到,那是隔夜信息的集中释放,不是真正的动量。
3.3.2 构建标准时间序列 DataFrame
# 标准的时间序列 DataFrame
# 索引是时间,列是特征
ts_df = pd.DataFrame({
'open': [10.0, 10.1, 10.2],
'high': [10.3, 10.4, 10.5],
'low': [9.9, 10.0, 10.1],
'close': [10.2, 10.3, 10.4],
'volume': [10000, 12000, 11000]
}, index=pd.date_range('2023-01-01', periods=3, freq='D'))
# 检查时间索引是否连续
print(ts_df.index.is_monotonic_increasing)
3.4 本章知识体系
下面这张图,是我自己总结的数据处理流程。每次做新策略前,我都会对照着走一遍。
这张图我贴在了工位上。每次数据出问题,我就回来看看是哪一步没做好。你想想看,如果数据获取这一步就错了,后面再花哨的分析都是白费。
df.info() 和 df.describe() 快速扫一眼。如果发现最大值是 99999 这种明显异常,赶紧回去查源头。
好了,数据基础就聊到这里。记住一句话:数据质量决定策略上限。后面我们会基于这些干净数据,开始真正的因子挖掘。
公众号:蓝海数据掘金营,微信deep3321