3、数据准备与清洗:数据源选择、清洗流程与频率统一
做行业因子分析,最磨人的其实不是模型有多复杂,而是数据本身。
我见过太多人,上来就调包跑回归,结果跑出来一堆假信号。为什么?数据没洗干净。说白了,因子研究里,80%的坑都埋在数据准备阶段。今天我就把这部分掰开揉碎了讲清楚。
3.1 数据源选择:Wind、Bloomberg、Tushare
选数据源,看你的场景和预算。我个人习惯把数据源分成三类:
| 数据源 | 适用场景 | 成本 | 数据质量 |
|---|---|---|---|
| Wind | 国内机构、公募私募 | 高(年费几万起) | ★★★★★ |
| Bloomberg | 海外市场、跨境策略 | 极高(年费十几万起) | ★★★★★ |
| Tushare | 个人研究、回测验证 | 免费/低 | ★★★★ |
Wind 是国内量化圈的标配。它的API接口成熟,数据覆盖全,财务数据、行情数据、行业分类都有。我在项目中遇到最多的情况是——用Wind拉数据时,要注意它的复权因子是后复权还是前复权。嗯,这里有个坑:如果你做行业因子,建议用后复权,因为行业轮动看的是相对收益,后复权能保留真实的涨跌幅结构。
Bloomberg 做海外市场绕不开。它的字段命名和Wind完全不同,比如PE在Wind里是pe_ttm,在Bloomberg里是PE_RATIO。我刚开始做跨境策略时,被这个命名差异坑过好几次。建议你提前建一个字段映射表,省得来回查。
Tushare 是个人研究者的福音。免费版能拿到日线行情、基础财务数据,够用了。但要注意,Tushare的行业分类更新有时会滞后,尤其是申万行业分类调整的时候。我曾经在回测中发现因子突然失效,查了半天,原来是行业分类没更新,股票被分错了组。
3.2 数据清洗流程:去重、缺失值、异常值
数据清洗,说白了就是「把脏数据变成干净数据」。我总结了一个三步走流程:
3.2.1 去重
重复数据比你想象的更常见。比如同一个交易日,某只股票在Wind里出现了两条记录——一条是正常交易,一条是盘后大宗交易。如果你不处理,因子计算就会出错。
我的做法是:
- 按
股票代码 + 交易日期做唯一键去重 - 如果有多条,保留最后一条(通常是最新的)
- 如果字段不一致(比如一条有PE,一条没有),合并取非空值
# 去重示例(Python伪代码)
df = df.drop_duplicates(subset=['stock_code', 'trade_date'], keep='last')
3.2.2 缺失值处理
缺失值是个大问题。行业因子数据里,缺失值通常来自:停牌、新股上市、财务数据未更新。
我一般分情况处理:
- 停牌导致的缺失: 用前一个交易日的值填充(forward fill)。因为停牌期间因子值理论上不变。
- 新股导致的缺失: 上市前没有数据,直接删除。不要填充,否则会引入偏差。
- 财务数据未更新: 用最近一期财报数据填充。比如Q3财报没出,就用Q2的。
3.2.3 异常值处理
异常值,说白了就是那些「离谱」的数据。比如某只股票的市盈率突然变成10000倍,或者市值突然翻了一百倍。
我常用的方法:
- MAD法(中位数绝对偏差): 比3σ更稳健,适合金融数据。因为金融数据本身就有厚尾特征,3σ会把很多正常值误杀。
- 分位数截断: 上下各截掉1%或5%。简单粗暴,但有效。
# MAD法异常值处理示例
import numpy as np
def mad_outlier_removal(series, threshold=5):
median = np.median(series)
mad = np.median(np.abs(series - median))
modified_z_scores = 0.6745 * (series - median) / mad
return series[np.abs(modified_z_scores) < threshold]
你想想看,为什么用MAD而不是标准差?因为标准差对极端值太敏感了。一个极端值就能把均值拉偏,然后3σ法反而检测不到它。MAD用的是中位数,稳得很。
3.3 数据对齐与频率统一
这是最容易被忽视的一步。行业因子分析里,数据来源不同,频率也不同。比如:
- 行情数据:日频
- 财务数据:季频
- 行业分类:月频(有时会调整)
如果不做对齐,你算出来的因子值就是错的。举个例子:你用今天的行情数据,配上三个月前的财务数据,算出来的PE能准吗?
我的对齐原则:
- 低频向高频对齐: 财务数据是季频,就把它填充到每个交易日。比如Q1的财报数据,从Q1结束后的第一个交易日开始使用,直到Q2财报出来。
- 避免未来信息: 这是大忌。财务数据有滞后性,Q1财报通常在4月底才出。你不能在4月之前就用Q1的数据。我习惯在数据里加一个
report_date字段,确保只用已发布的数据。
# 数据对齐示例
# 假设df_daily是日频行情,df_quarterly是季频财务数据
# 对齐逻辑:将财务数据按报告日期对齐到每个交易日
df_aligned = df_daily.merge(df_quarterly, on='stock_code', how='left')
df_aligned = df_aligned[df_aligned['trade_date'] >= df_aligned['report_date']]
嗯,说到这里,我想起一个真实案例。有个团队做行业动量因子,回测效果特别好,年化超额20%以上。结果实盘一跑,直接亏了。查了半天,发现是数据对齐出了问题——他们用当天的收盘价去算因子,但财务数据用的是当季的,而财报其实还没发布。说白了,就是用了未来数据。回测里赚的钱,全是假的。
所以,数据准备这一步,再怎么强调都不为过。你想想看,如果地基没打好,上面盖的房子再漂亮,也是危楼。
好了,数据准备与清洗这部分就讲到这里。记住一句话:干净的数据是因子有效性的前提。别急着跑模型,先把数据搞明白。