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%,你觉得是异常值吗?不一定。可能是重组复牌,也可能是乌龙指。直接剔除,反而把真实信息丢了。

我自己的处理流程是这样的:

  1. 先识别: 用 z-score 或 IQR 找出异常点
  2. 再判断: 看看当天有没有公告、事件
  3. 最后处理: 如果是数据错误,用前后均值替换;如果是真实事件,保留但标记
# 一个温和的异常值处理方法
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σ 截断处理收益率数据,结果把很多真实的极端行情(比如 2015 年股灾)给截没了。后来回测出来的策略,在极端行情下表现极差。所以,异常值处理要「温和」,别太狠。

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 本章知识体系图

下面这张图,是我自己整理的数据清洗流程。每次做新策略,我都会按这个步骤走一遍。

数据获取与清洗核心流程 数据源选择 字段筛选与理解 缺失值处理 异常值处理 数据对齐与频率转换 关键提醒 • 数据源选1-2个即可 • 字段宁缺毋滥 • 缺失值分情况处理 • 异常值温和截断 • 时间戳必须对齐 • 降频聚合,升频填充 • 每次清洗后画图检查 • 保留原始数据备份 • 避免未来函数 • 停牌数据单独标记 • 财务数据向后对齐 • 复权因子必须用

数据清洗这件事,说白了就是「慢工出细活」。你花 80% 的时间在数据上,策略反而能跑得稳。我见过太多人急着调参数、换模型,结果数据里一堆坑,最后白忙活一场。

嗯,这一章的内容就到这。数据搞干净了,下一章我们才能放心地算因子。


蓝海数据掘金营,专注资料整理