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)
我的小技巧: 数据量大的时候,别一次性拉全市场5000只股票。按行业分批拉取,每批100只,中间sleep 0.5秒,避免被接口封IP。

3.2 获取行业分类数据

行业分类是中性化处理的核心。你想想看,如果不做行业中性化,你算出来的因子可能只是「重仓了某个行业」而已,根本不是选股能力。

国内常用的行业分类标准有:

  • 申万一级行业(28个):最常用,覆盖面广
  • 中信一级行业(29个):机构用得比较多
  • 证监会行业(19个):官方标准,但颗粒度粗

我个人偏好用 申万一级行业。为什么?因为28个行业分类,做回归时自由度够用,又不至于像二级行业那样有100多个类别导致过拟合。

获取行业分类数据,通常有两种方式:

  1. API 接口直接拉:比如聚宽、Tushare 都有行业分类接口
  2. 定期下载 Excel 文件:申万官网每月更新行业成分股列表

代码示例:

# 获取申万行业分类
industry_df = pro.stock_basic(
    fields='ts_code,name,industry'
)

# 查看行业分布
print(industry_df['industry'].value_counts())
注意: 行业分类会动态调整!每年6月和12月申万会调整成分股。我曾经因为用了半年前的行业分类,导致中性化后因子失效了整整两个月。建议每月更新一次行业分类数据。

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'])

清洗环节,我总结了三个必做步骤:

  1. 剔除ST股:ST股交易规则特殊,因子计算会失真
  2. 剔除上市不足60天的新股:新股波动大,数据不稳定
  3. 剔除停牌超过20天的股票:流动性太差,因子信号不可靠
避坑指南: 我曾经在清洗时忘了剔除ST股,结果一个「低波动因子」在ST股上表现特别好——因为ST股跌停板多,波动自然低。但实盘你根本买不进去!从那以后,我每次清洗数据都会先打标签,再统一过滤。

3.4 知识体系总览

下面这张图,把数据准备的整个流程串起来了。你照着这个流程走,基本不会出大问题。

数据准备流程框架 获取行情数据 获取行业分类 数据对齐与清洗 日频数据 · 复权处理 字段:价格/成交量/换手率 申万一级行业(28个) 每月更新成分股 剔除ST股 · 剔除新股 剔除停牌股 · 缺失值处理 输出:对齐后的干净数据集

嗯,数据准备这块儿,说白了就是「磨刀不误砍柴工」。你花80%的时间把数据搞干净,后面20%的时间建模就够了。反过来,数据一团糟,模型再花哨也是白搭。

我个人习惯在数据清洗完后,做一个简单的数据质量报告:统计每只股票的缺失天数、异常值比例、行业覆盖度。这样心里有底,后面出问题了也知道从哪查起。

最后提醒一句: 数据对齐时,记得用 pd.merge_asof 做时间上的向前匹配,而不是简单的等值连接。因为行业分类数据是月频,行情数据是日频,直接 merge 会丢失大量数据。
蓝海数据掘金营,专注资料整理