因子数据准备:数据源选择与清洗对齐
做因子评价,第一步就是搞定数据。说实话,这一步最容易被忽视,但也是坑最多的地方。我见过太多人花大把时间调模型,结果数据源就有问题,最后全白干。今天咱们就把数据准备这块掰开揉碎了讲清楚。
一、数据源选择:行情、财务、另类数据
数据源的选择,说白了就是决定你因子质量的起点。我个人习惯把数据分成三大类:
1. 行情数据
这是最基础的数据,包括开盘价、收盘价、最高价、最低价、成交量、成交额等。来源主要有:
- Wind / 聚宽 / Tushare:国内主流,数据质量还行,但要注意复权问题
- Yahoo Finance / Alpha Vantage:海外市场,免费但有时延迟
- 交易所直连:最可靠,但成本高
⚠️ 避坑指南:我曾经用某免费接口拿A股数据,结果发现后复权价格在分红日前后对不上,导致因子计算全错。后来我改用Wind的复权因子自己算,才彻底解决。
2. 财务数据
财务数据是基本面因子的命根子。包括:
- 资产负债表(总资产、负债、股东权益)
- 利润表(营收、净利润、扣非净利润)
- 现金流量表(经营现金流、投资现金流)
- 衍生指标(ROE、ROA、毛利率等)
这里有个关键点:财务数据有滞后性。年报截止日是4月底,半年报是8月底,季报是次月月底。你拿到的数据其实是「过去时」。
💡 我的经验:做因子回测时,一定要用「已披露」的数据,而不是「实际截止日」的数据。否则你会用到未来信息,回测结果虚高。我踩过这个坑,后来加了个「披露日期」字段才解决。
3. 另类数据
这是近年越来越火的方向。包括:
- 舆情数据(新闻情感、社交媒体热度)
- 供应链数据(上下游关系、订单量)
- 卫星数据(停车场车流量、工厂烟囱排放)
- 电商数据(销量、价格变动)
另类数据的价值在于「信息差」。但清洗难度也最大,我建议先从结构化数据入手,别一上来就搞NLP。
二、数据清洗与对齐
数据拿到手,第一件事不是算因子,而是清洗。你想想看,脏数据进去,垃圾因子出来,这不是白忙活吗?
1. 常见数据问题
| 问题类型 | 表现 | 处理方法 |
|---|---|---|
| 缺失值 | 某天某股票无行情 | 前向填充 / 插值 / 删除 |
| 异常值 | 价格突然跳涨100倍 | 3σ截断 / 分位数截断 |
| 重复值 | 同一时间戳多条记录 | 去重(保留最新) |
| 停牌数据 | 连续多日无交易 | 标记为NaN,不参与计算 |
2. 数据对齐——最容易被忽略的环节
不同数据源的时间粒度不一样。行情数据是日频,财务数据是季频,另类数据可能是分钟级。怎么对齐?
我的做法是:统一以交易日为基准。具体来说:
- 行情数据:直接按日期对齐
- 财务数据:取最新披露值,前向填充到每个交易日
- 另类数据:按日聚合(均值/中位数/最后值)
🔑 核心原则:永远不要用未来信息填充过去。比如财务数据,只能用「截至当前日期已披露」的数据,不能用「实际截止日」的数据。
3. 代码示例:数据清洗与对齐
import pandas as pd
import numpy as np
# 读取行情数据
price_df = pd.read_parquet('daily_price.parquet')
# 读取财务数据
fund_df = pd.read_parquet('financial_data.parquet')
# 清洗:去除停牌日(成交量为0)
price_df = price_df[price_df['volume'] > 0]
# 清洗:异常值处理(3σ截断)
def winsorize(series, limits=0.01):
lower = series.quantile(limits)
upper = series.quantile(1 - limits)
return series.clip(lower, upper)
price_df['close'] = price_df.groupby('date')['close'].transform(winsorize)
# 对齐:财务数据前向填充到每个交易日
# 假设fund_df有'date'和'stock_code'列
fund_df = fund_df.sort_values(['stock_code', 'date'])
fund_df = fund_df.groupby('stock_code').apply(
lambda x: x.set_index('date').reindex(
price_df[price_df['stock_code'] == x.name]['date'].unique(),
method='ffill'
).reset_index()
)
# 合并
merged_df = pd.merge(price_df, fund_df, on=['date', 'stock_code'], how='left')
三、数据存储方案:HDF5 vs Parquet
数据量大了以后,CSV根本扛不住。我建议直接上列式存储格式。目前主流就两个:HDF5和Parquet。
1. HDF5
- 优点:支持分层结构,可以存多个DataFrame在一个文件里
- 缺点:文件容易损坏,跨语言支持一般
- 适用场景:单机研究,数据量在10GB以内
2. Parquet
- 优点:压缩率高,读取速度快,跨语言支持好(Python/R/Java都能读)
- 缺点:不支持增量写入,每次写入都要重写整个文件
- 适用场景:生产环境,大数据量,需要与其他系统交互
💡 我的选择:个人研究用HDF5,因为方便。生产环境用Parquet,因为稳定。如果你刚开始做,我建议直接上Parquet,省得后面迁移。
3. 存储结构设计
我习惯按「因子类别 + 时间分区」来组织数据:
data/
├── raw/
│ ├── price/
│ │ ├── 2023-01.parquet
│ │ ├── 2023-02.parquet
│ │ └── ...
│ ├── financial/
│ │ ├── 2023Q1.parquet
│ │ └── ...
│ └── alternative/
│ └── sentiment.parquet
├── cleaned/
│ └── aligned_data.parquet
└── factors/
├── momentum.parquet
├── value.parquet
└── quality.parquet
4. 代码示例:读写Parquet
import pandas as pd
# 写入
df.to_parquet('factor_data.parquet',
compression='snappy',
partition_cols=['date'])
# 读取
df = pd.read_parquet('factor_data.parquet',
filters=[('date', '>=', '2023-01-01')])
⚠️ 注意:Parquet的partition_cols参数很实用,按日期分区后,读取特定时间段的数据会快很多。但别分太细,按月分区就够了,按天分区文件太多反而慢。
知识体系总览
下面这张图概括了本章的核心逻辑:
数据准备这件事,说白了就是「磨刀不误砍柴工」。我见过太多人急着算因子,结果数据没对齐,回测结果漂亮得一塌糊涂,实盘一跑就露馅。嗯,这里要记住:数据质量决定了因子上限,模型只是逼近这个上限的手段。
我个人习惯在数据清洗上花至少30%的时间。别嫌多,这步省了,后面全是坑。