第三章 因子数据获取:从零搭建你的数据管道

做量化投资,有一句老话叫「垃圾进,垃圾出」。

你策略再牛,模型再花哨,数据源出了问题,一切都是白搭。我刚开始做因子研究那会儿,就吃过这个亏——花了三周跑出来的回测曲线漂亮得不行,结果发现是复权数据没处理好。嗯,那感觉,就像考试抄答案抄错了行。

今天咱们就聊聊,怎么把数据这块地基打牢。

3.1 数据源的选择:Tushare vs Wind

国内做量化,绕不开两个数据源:Tushare 和 Wind。

我个人习惯是这么分的:

维度 Tushare Wind
行情数据 免费版够用,Pro版更稳 收费,但数据质量极高
财务数据 有,但更新有延迟 实时,且字段极其丰富
上手难度 低,文档清晰 中,需要装插件
适合场景 个人研究、小规模回测 机构级、实盘、高频

说白了,如果你是自己做研究,Tushare 完全够用。但如果你在机构里干活,Wind 几乎是标配。我在项目中遇到过不少团队,两边都接,Tushare 做快速验证,Wind 做最终确认。

3.2 使用 Tushare 获取股票行情数据

先说说 Tushare。这东西上手很简单,注册个账号,拿到 token,就能开始拉了。

核心要点:行情数据一定要用「后复权」或「前复权」。原始价格会因分红送股产生跳空,直接拿来算因子会出大问题。

下面是我常用的代码模板:

import tushare as ts

# 初始化
pro = ts.pro_api('你的token')

# 获取日线行情(后复权)
df = pro.daily(
    ts_code='000001.SZ',
    start_date='20200101',
    end_date='20231231',
    adj='qfq'  # 前复权
)

# 我习惯再加个复权因子,方便后续处理
adj_factor = pro.adj_factor(ts_code='000001.SZ')
print(df.head())

这里有个坑,我曾经踩过——Tushare 的免费版有调用频率限制,每秒最多 5 次。如果你要拉全市场 5000 只股票,直接循环会报错。我的解决办法是加个 sleep:

import time

for code in stock_list:
    df = pro.daily(ts_code=code, ...)
    time.sleep(0.2)  # 每秒最多5次,0.2秒一次刚好
    # 存数据...

小技巧:建议把原始数据存到本地 CSV 或数据库里,别每次都重新拉。我一般每天收盘后跑一次增量更新,这样既省 token 额度,又不会因为网络问题中断。

3.3 使用 Wind 获取财务数据

Wind 的财务数据,说实话,比 Tushare 全得多。比如商誉、研发费用、递延所得税这些细项,Tushare 要么没有,要么更新慢。

Wind 的 Python 接口叫 WindPy,用法是这样的:

from WindPy import w

w.start()

# 获取利润表数据
data = w.wss(
    "000001.SZ, 000002.SZ",  # 股票代码
    "roe, eps, grossprofitmargin",  # 指标
    "rptType=1;year=2023"  # 年报
)

print(data.Data)

你想想看,Wind 的 wss 函数一次能拉几百只股票,效率极高。但要注意,财务数据是按报告期发布的,不是每天都有。我一般会在财报季(4月、8月、10月)集中更新一次。

警告:Wind 的财务数据有「实际值」和「预测值」之分。回测时千万别用未来数据!我见过有人用 Q2 的财报数据去回测 Q1 的行情,结果回测曲线漂亮得离谱——因为数据泄露了。

3.4 数据清洗与预处理:80% 的时间花在这里

数据拿到手,别急着算因子。先做清洗。

我总结了一套标准流程,你直接照着做就行:

  1. 去重——同一个交易日出现两条数据?保留第一条。
  2. 处理缺失值——财务数据常有 NaN,我习惯用前向填充(ffill)。
  3. 异常值检测——比如市盈率突然变成 10000,明显是数据错误。
  4. 对齐时间戳——行情数据和财务数据的日期格式要统一。

举个例子,处理缺失值:

import pandas as pd

# 假设 df 是财务数据,index 是日期
df = df.fillna(method='ffill')  # 用上一个有效值填充

# 如果开头还有缺失,用下一个有效值回填
df = df.fillna(method='bfill')

为什么会这样?因为财务数据不是每天发布的。比如 2023 年年报可能在 2024 年 4 月才出,那 2023 年 1 月到 2024 年 3 月这段时间,你只能用上一年的数据。

避坑指南:我曾经在清洗时忘了处理「停牌日」的数据。股票停牌期间,行情数据是空的,但财务数据还在。如果不剔除停牌日,算出来的因子值会严重失真。我的做法是:先标记停牌日,然后在合并数据时直接 drop 掉。

3.5 数据合并:把行情和财务捏在一起

行情数据是日频的,财务数据是季频的(甚至年频)。怎么合并?

我的做法是:以行情数据为主表,用 merge 把财务数据「贴」上去。

# 行情数据:daily_prices,包含 date, stock_code, close
# 财务数据:financials,包含 report_date, stock_code, roe

# 先把财务数据的报告期对齐到交易日
# 比如 2023-12-31 的报告,在 2024-04-30 之后才可用
financials['available_date'] = financials['report_date'] + pd.DateOffset(months=4)

# 合并
merged = pd.merge_asof(
    daily_prices.sort_values('date'),
    financials.sort_values('available_date'),
    left_on='date',
    right_on='available_date',
    by='stock_code',
    direction='backward'
)

这里用了 merge_asof,它能自动找到「最接近的」财务数据。比如今天是 2024-05-15,它会匹配到 2023 年年报(如果 2024 年一季报还没出的话)。

提示:财务数据的「可用日期」很关键。A 股规定,年报必须在次年 4 月底前发布,一季报在 4 月底,中报在 8 月底,三季报在 10 月底。我一般会加 1 个月的缓冲期,确保数据真的可用。

3.6 本章知识体系总览

下面这张图,是我做数据管道时的核心思路。你照着这个流程走,基本不会出错。

因子数据获取与处理流程 数据源 获取方式 数据清洗 Tushare(行情) Wind(财务) API 调用 本地缓存 去重、填充 异常值处理 最终输出:干净、对齐的因子数据集

数据获取这件事,说白了就是「耐心活」。你只要把流程标准化,每次跑脚本就行了。我现在的习惯是:每天收盘后自动跑一次增量更新,周末做一次全量校验。这样既不会漏数据,也不会因为某次网络中断导致整个管道崩掉。

嗯,数据准备好了,下一步就是算因子了。不过那是下一章的事,咱们先把数据管道的根基打牢。


公众号:蓝海资料掘金营,微信deep3321