3、数据获取与清洗:数据源选择、字段说明与处理技巧
做量化策略,尤其是市场中性策略,第一步就是搞数据。
很多人觉得策略模型才是核心,其实我告诉你,数据才是地基。地基没打好,上面盖多漂亮的房子都得塌。我自己就吃过这个亏——早年用某平台的数据跑回测,收益曲线漂亮得不行,结果实盘一跑,直接傻眼。后来才发现,是数据里藏了未来函数。
3.1 数据源怎么选?聚宽、Tushare 还是别的?
国内主流的数据源,我接触过不少。简单说说我的看法。
| 数据源 | 优点 | 缺点 | 适合场景 |
|---|---|---|---|
| 聚宽(JoinQuant) | 本地数据齐全,API稳定,文档友好 | 免费版有数据量限制 | 个人研究、小规模回测 |
| Tushare | 数据覆盖面广,社区活跃 | 积分制,高级数据需要付费或贡献 | 需要特殊因子或高频数据时 |
| Wind/Choice | 机构级,数据质量最高 | 贵,个人基本用不起 | 机构实盘、合规要求高的场景 |
| AKShare | 免费开源,更新快 | 稳定性稍差,偶尔断连 | 快速原型验证 |
我个人习惯是:研究阶段用聚宽,因为它的本地数据包可以直接下载,省去很多爬虫的麻烦。如果要做一些特殊的另类数据,比如龙虎榜、大宗交易,我会用 Tushare 补一下。
3.2 数据字段说明:你到底需要哪些列?
很多人一上来就把所有字段都拉下来,几百列数据,看着挺唬人。其实没必要。
做市场中性策略,核心字段其实就这几类:
- 行情数据: open, high, low, close, volume, amount, adj_factor(复权因子)
- 财务数据: pe, pb, eps, roe, total_market_cap, free_float_shares
- 衍生数据: 涨跌幅、换手率、波动率(这些可以自己算)
嗯,这里要注意:复权因子这个字段,很多人会忽略。我见过有人直接用不复权的价格做回测,结果分红除权那天,策略莫名其妙亏了一大笔。其实只要用后复权价格,就能避免这个问题。
3.3 缺失值处理:别直接 dropna()
拿到数据后,第一件事就是检查缺失值。但怎么处理,有讲究。
我曾经犯过一个低级错误:某只股票停牌了几天,数据全是 NaN,我直接 dropna() 把整行删了。结果回测时,这只股票在停牌期间被错误地认为已经平仓,复牌后直接跳空,策略收益全乱了。
正确的做法是分情况讨论:
- 停牌导致的缺失: 用前一天的收盘价填充,同时标记停牌状态
- 新股上市初期: 上市前没有数据,直接剔除这段时间
- 个别字段缺失: 比如某天成交量缺失,可以用前后均值填充
- 财务数据缺失: 用最近一期已披露的数据填充,不要用未来数据
# 一个简单的处理示例
import pandas as pd
# 停牌填充:用前一天收盘价
df['close'].fillna(method='ffill', inplace=True)
# 成交量缺失:用前后5日均值
df['volume'] = df['volume'].fillna(df['volume'].rolling(5, min_periods=1).mean())
# 标记停牌日
df['is_suspended'] = df['close'].isna().astype(int)
3.4 异常值处理:别一刀切
异常值怎么处理?很多人直接 3σ 截断,或者用 IQR 剔除。但我觉得,得先看看异常值是怎么来的。
举个例子:某天某只股票突然涨了 20%,你觉得是异常值吗?不一定。可能是重组复牌,也可能是乌龙指。直接剔除,反而把真实信息丢了。
我自己的处理流程是这样的:
- 先识别: 用 z-score 或 IQR 找出异常点
- 再判断: 看看当天有没有公告、事件
- 最后处理: 如果是数据错误,用前后均值替换;如果是真实事件,保留但标记
# 一个温和的异常值处理方法
def winsorize_series(series, limits=0.01):
"""
用分位数截断,而不是直接剔除
"""
lower = series.quantile(limits)
upper = series.quantile(1 - limits)
return series.clip(lower, upper)
# 应用
df['return'] = winsorize_series(df['return'], limits=0.005)
3.5 数据对齐与频率转换:时间戳是魔鬼
做多因子模型时,最头疼的就是数据对齐。不同数据源的时间戳可能不一样,有的用自然日,有的用交易日,有的甚至用毫秒级时间戳。
我遇到过最坑的一次:财务数据用的是季度末日期(比如 2023-03-31),而行情数据用的是交易日。结果我把财务数据直接 merge 上去,发现 3 月 31 日那天很多股票没有交易数据,merge 出来全是 NaN。
正确的做法是:财务数据要向后对齐到下一个交易日。也就是说,3 月 31 日出的财报,应该对齐到 4 月 1 日(或最近的交易日)。
# 财务数据向后对齐
import pandas as pd
# 假设 trade_dates 是交易日列表
trade_dates = pd.bdate_range('2023-01-01', '2023-12-31')
# 财务数据日期
fin_date = pd.Timestamp('2023-03-31')
# 找到下一个交易日
next_trade_date = trade_dates[trade_dates >= fin_date][0]
print(f"财务数据 {fin_date} 对齐到交易日 {next_trade_date}")
频率转换也是常见需求。比如把日线数据转成周线,或者把 5 分钟数据转成 30 分钟。这里有个原则:降频用聚合,升频用填充。
- 降频(日→周): 收盘价取最后一天,成交量求和,最高价取最大值
- 升频(日→小时): 用前向填充,或者插值(但插值要小心,别引入未来信息)
3.6 本章知识体系图
下面这张图,是我自己整理的数据清洗流程。每次做新策略,我都会按这个步骤走一遍。
数据清洗这件事,说白了就是「慢工出细活」。你花 80% 的时间在数据上,策略反而能跑得稳。我见过太多人急着调参数、换模型,结果数据里一堆坑,最后白忙活一场。
嗯,这一章的内容就到这。数据搞干净了,下一章我们才能放心地算因子。