2、风险因子数据源:数据获取渠道、清洗与对齐

做量化建模,尤其是市场风险因子模型,有一句老话叫“垃圾进,垃圾出”。

模型再漂亮,数学再复杂,数据源出了问题,结果就是废纸一张。我见过太多团队,花三个月搭了个漂亮的因子模型,最后发现数据频率没对齐,回测全是幻觉。嗯,今天我们就来聊聊数据源这件事。

2.1 主流数据获取渠道

目前业内常用的数据源,说白了就三家:Bloomberg、Reuters(路透)、Wind。国内做A股,Wind是标配;做全球市场,Bloomberg和Reuters绕不开。

数据源 覆盖市场 数据频率 接口方式 成本
Bloomberg 全球股票、债券、商品、外汇 Tick、分钟、日、周、月 Bloomberg API (BLPAPI) 高(终端+API费用)
Reuters (Refinitiv) 全球市场,尤其外汇和固定收益 Tick、分钟、日 Eikon API / RDP 中高
Wind A股、港股、债券、期货 分钟、日、周、月 WindPy (Python接口) 中等

我个人习惯是:做A股因子研究,Wind就够了。但如果你要建模全球宏观风险因子,比如美元指数、美债收益率、VIX这些,Bloomberg的数据质量和覆盖度确实更好。我在项目中遇到过一个问题——同一只股票,Wind和Bloomberg的复权因子居然有差异,后来发现是复权算法不同。所以,跨数据源做因子时,一定要统一数据源,别混着用。

小技巧: 如果你用Wind,建议用WindPy的w.wsd()函数拉日频数据,速度比Excel插件快很多。代码示例:
import pandas as pd
from WindPy import w

w.start()

# 获取沪深300指数日频数据
data = w.wsd("000300.SH", "close, pct_chg", "2023-01-01", "2023-12-31", "")
df = pd.DataFrame(data.Data, index=data.Fields, columns=data.Times).T
print(df.head())

2.2 数据清洗与预处理

数据拿到手,第一件事不是建模,是清洗。你想想看,原始数据里什么妖魔鬼怪都有:缺失值、异常值、停牌、除权除息、数据跳空……

我一般按以下步骤来:

  1. 缺失值处理:先看缺失比例。如果某只股票缺失超过20%,直接剔除。如果只是零星缺失,用前向填充(ffill)或插值法。
  2. 异常值检测:比如某天收益率突然涨了50%,明显是数据错误。我习惯用3倍标准差法或MAD(中位数绝对偏差)来识别。
  3. 复权处理:股票有分红送股,必须用后复权或前复权。我个人偏好前复权,因为历史价格更连续。
  4. 停牌处理:停牌期间价格不变,但因子值会失真。我一般把停牌超过5天的数据标记为NaN,或者用行业均值填充。
避坑指南: 我曾经在回测时发现一个因子表现特别好,后来一查,是因为某只股票停牌期间因子值没更新,导致回测时“提前知道”了未来信息。嗯,从那以后,我每次清洗数据都会专门检查停牌标记。

下面是一个简单的清洗函数示例:

def clean_price_data(df, price_col='close', method='ffill', outlier_std=3):
    # 缺失值处理
    df[price_col] = df[price_col].fillna(method=method)
    
    # 异常值处理(收益率异常)
    returns = df[price_col].pct_change()
    mean = returns.mean()
    std = returns.std()
    outlier_mask = (returns > mean + outlier_std * std) | (returns < mean - outlier_std * std)
    df.loc[outlier_mask, price_col] = None
    df[price_col] = df[price_col].fillna(method=method)
    
    return df

2.3 数据频率与对齐问题

这是最容易踩坑的地方。你想想看,一个因子模型里,可能有日频的股票收益率、周频的宏观经济数据、月频的财务报表数据。频率不一样,怎么对齐?

我常用的方法有三种:

  • 降频对齐:把所有数据统一到最低频率。比如日频股票数据降采样到周频,取每周最后一个交易日的数据。优点是简单,缺点是损失信息。
  • 升频对齐:把低频数据插值到高频。比如把季度GDP数据用线性插值变成月度数据。注意,这种方法会引入“未来信息”,回测时一定要小心。
  • 事件驱动对齐:以某个事件(比如财报发布日)为基准,对齐前后窗口的数据。这种方法最准确,但实现起来最复杂。
警告: 千万别把不同频率的数据直接放在一起跑回归!我见过有人把日频收益率和月频GDP数据直接做时间序列回归,结果自相关严重,模型完全失效。一定要先对齐时间戳。

举个例子,假设你有日频的股票收益率和月频的CPI数据:

# 日频数据
daily_returns = pd.DataFrame({
    'date': pd.date_range('2023-01-01', '2023-12-31', freq='D'),
    'return': np.random.randn(365)
})

# 月频数据
monthly_cpi = pd.DataFrame({
    'date': pd.date_range('2023-01-01', '2023-12-31', freq='M'),
    'cpi': np.random.randn(12) * 0.5 + 2.0
})

# 降频对齐:取每月最后一个交易日的收益率
daily_returns['month'] = daily_returns['date'].dt.to_period('M')
monthly_returns = daily_returns.groupby('month').last().reset_index()
monthly_returns['date'] = monthly_returns['month'].dt.to_timestamp()

# 合并
aligned_data = pd.merge(monthly_returns, monthly_cpi, on='date', how='left')
print(aligned_data.head())

这里要注意,groupby().last()取的是每月最后一个交易日,不是自然月的最后一天。如果某个月最后一天是周末,股票没有交易,那就要往前推。我一般用pd.merge_asof()来做最近邻对齐,更稳健。

个人经验: 做多因子模型时,我建议把所有因子统一到日频。低频因子(比如估值、动量)用最新值填充,高频因子(比如波动率)用滚动窗口计算。这样既保留了信息,又避免了频率混乱。

2.4 知识体系总览

下面这张图,是我自己总结的风险因子数据源处理流程。你可以把它当作一个检查清单:

风险因子数据源处理流程 数据获取 Bloomberg / Reuters / Wind 数据清洗 缺失值 / 异常值 / 复权 频率对齐 降频 / 升频 / 事件 降频对齐 日频→周频/月频 升频对齐 月频→日频(插值) 事件驱动对齐 财报发布日 / 分红日 对齐后的因子面板数据

这张图把整个流程串起来了。从数据获取开始,到清洗,再到频率对齐,最后得到一张干净、对齐的因子面板数据。每一步都有坑,但只要你按这个流程走,基本不会出大问题。

好了,关于数据源的部分就聊到这里。记住一句话:数据质量决定了模型的天花板。别急着上模型,先把数据搞干净。


专注资料整理