因子数据准备:数据源选择与清洗对齐

做因子评价,第一步就是搞定数据。说实话,这一步最容易被忽视,但也是坑最多的地方。我见过太多人花大把时间调模型,结果数据源就有问题,最后全白干。今天咱们就把数据准备这块掰开揉碎了讲清楚。

一、数据源选择:行情、财务、另类数据

数据源的选择,说白了就是决定你因子质量的起点。我个人习惯把数据分成三大类:

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参数很实用,按日期分区后,读取特定时间段的数据会快很多。但别分太细,按月分区就够了,按天分区文件太多反而慢。

知识体系总览

下面这张图概括了本章的核心逻辑:

因子数据准备知识体系 数据源选择 行情数据 财务数据 另类数据 数据清洗 缺失值处理 异常值截断 重复值去重 数据对齐 时间基准统一 前向填充 避免未来信息 数据存储方案 HDF5:分层存储,适合单机研究 Parquet:列式存储,适合生产环境 按时间分区,压缩存储 核心原则 数据质量 > 模型复杂度 | 避免未来信息 | 统一时间基准

数据准备这件事,说白了就是「磨刀不误砍柴工」。我见过太多人急着算因子,结果数据没对齐,回测结果漂亮得一塌糊涂,实盘一跑就露馅。嗯,这里要记住:数据质量决定了因子上限,模型只是逼近这个上限的手段。

我个人习惯在数据清洗上花至少30%的时间。别嫌多,这步省了,后面全是坑。

专注资料整理