3、数据准备:获取股票行情数据、获取行业分类数据、数据对齐与清洗
好,咱们进入实战环节。数据准备这一步,说白了就是给模型「喂饭」。饭要是馊了,模型再牛也白搭。我在做多因子模型初期,就吃过数据没对齐的亏——回测曲线漂亮得不行,实盘一跑直接崩。后来才发现,是行情数据和行业分类数据差了整整一天。
所以这一节,咱们把数据准备的三个核心环节拆开揉碎讲清楚。你跟着我的节奏走,保证不踩坑。
3.1 获取股票行情数据
行情数据是因子计算的原材料。我个人习惯用 日频数据,因为多因子模型换仓周期通常是月频或周频,日频足够用了。当然,如果你做高频策略,那就得用分钟级数据,但那是另一个话题了。
获取行情数据,核心字段就这几个:
| 字段名 | 说明 | 数据来源 |
|---|---|---|
| trade_date | 交易日期 | 交易所日历 |
| stock_code | 股票代码(如 000001.SZ) | Wind / Tushare / 聚宽 |
| close_price | 复权收盘价(后复权) | 行情接口 |
| volume | 成交量(股) | 行情接口 |
| turnover_rate | 换手率 | 衍生计算 |
这里有个关键点:一定要用复权价格。为什么?因为分红送股会导致价格跳空,你不复权的话,计算收益率会失真。我曾经用未复权数据算了一个「高股息因子」,结果回测收益高得离谱——后来发现是除权日价格暴跌导致的假信号。
代码示例(用 Python 获取数据):
import pandas as pd
import tushare as ts
# 初始化接口
pro = ts.pro_api('your_token')
# 获取平安银行 2023年行情数据
df = pro.daily(
ts_code='000001.SZ',
start_date='20230101',
end_date='20231231',
fields='trade_date,open,high,low,close,vol'
)
# 后复权处理(简化版)
df['adj_close'] = df['close'] * (1 + df['vol'] / df['vol'].shift(1)).fillna(1)
3.2 获取行业分类数据
行业分类是中性化处理的核心。你想想看,如果不做行业中性化,你算出来的因子可能只是「重仓了某个行业」而已,根本不是选股能力。
国内常用的行业分类标准有:
- 申万一级行业(28个):最常用,覆盖面广
- 中信一级行业(29个):机构用得比较多
- 证监会行业(19个):官方标准,但颗粒度粗
我个人偏好用 申万一级行业。为什么?因为28个行业分类,做回归时自由度够用,又不至于像二级行业那样有100多个类别导致过拟合。
获取行业分类数据,通常有两种方式:
- API 接口直接拉:比如聚宽、Tushare 都有行业分类接口
- 定期下载 Excel 文件:申万官网每月更新行业成分股列表
代码示例:
# 获取申万行业分类
industry_df = pro.stock_basic(
fields='ts_code,name,industry'
)
# 查看行业分布
print(industry_df['industry'].value_counts())
3.3 数据对齐与清洗
数据对齐,说白了就是让行情数据和行业分类数据在同一个时间点上「对上号」。这一步做不好,后面所有分析都是垃圾。
对齐的核心逻辑:
- 时间对齐:行情数据是日频,行业分类是月频。以行情日期为基准,向前匹配最近的行业分类
- 股票对齐:确保行情数据里的每只股票,在行业分类表里都有对应的行业
- 缺失值处理:新股上市前5天没有行业分类?直接剔除
我常用的对齐方法:
# 假设行情数据为 price_df,行业分类为 industry_df
# 按股票代码合并
merged_df = price_df.merge(
industry_df[['ts_code', 'industry']],
on='ts_code',
how='left'
)
# 检查缺失行业
missing = merged_df[merged_df['industry'].isna()]
print(f"缺失行业的股票数: {len(missing)}")
# 剔除缺失数据
merged_df = merged_df.dropna(subset=['industry'])
清洗环节,我总结了三个必做步骤:
- 剔除ST股:ST股交易规则特殊,因子计算会失真
- 剔除上市不足60天的新股:新股波动大,数据不稳定
- 剔除停牌超过20天的股票:流动性太差,因子信号不可靠
3.4 知识体系总览
下面这张图,把数据准备的整个流程串起来了。你照着这个流程走,基本不会出大问题。
嗯,数据准备这块儿,说白了就是「磨刀不误砍柴工」。你花80%的时间把数据搞干净,后面20%的时间建模就够了。反过来,数据一团糟,模型再花哨也是白搭。
我个人习惯在数据清洗完后,做一个简单的数据质量报告:统计每只股票的缺失天数、异常值比例、行业覆盖度。这样心里有底,后面出问题了也知道从哪查起。
pd.merge_asof 做时间上的向前匹配,而不是简单的等值连接。因为行业分类数据是月频,行情数据是日频,直接 merge 会丢失大量数据。