第三章 因子数据获取:从零搭建你的数据管道
做量化投资,有一句老话叫「垃圾进,垃圾出」。
你策略再牛,模型再花哨,数据源出了问题,一切都是白搭。我刚开始做因子研究那会儿,就吃过这个亏——花了三周跑出来的回测曲线漂亮得不行,结果发现是复权数据没处理好。嗯,那感觉,就像考试抄答案抄错了行。
今天咱们就聊聊,怎么把数据这块地基打牢。
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% 的时间花在这里
数据拿到手,别急着算因子。先做清洗。
我总结了一套标准流程,你直接照着做就行:
- 去重——同一个交易日出现两条数据?保留第一条。
- 处理缺失值——财务数据常有 NaN,我习惯用前向填充(ffill)。
- 异常值检测——比如市盈率突然变成 10000,明显是数据错误。
- 对齐时间戳——行情数据和财务数据的日期格式要统一。
举个例子,处理缺失值:
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 本章知识体系总览
下面这张图,是我做数据管道时的核心思路。你照着这个流程走,基本不会出错。
数据获取这件事,说白了就是「耐心活」。你只要把流程标准化,每次跑脚本就行了。我现在的习惯是:每天收盘后自动跑一次增量更新,周末做一次全量校验。这样既不会漏数据,也不会因为某次网络中断导致整个管道崩掉。
嗯,数据准备好了,下一步就是算因子了。不过那是下一章的事,咱们先把数据管道的根基打牢。
公众号:蓝海资料掘金营,微信deep3321