4、宏观因子的数据获取与清洗:数据源选择、频率对齐与异常处理

做宏观因子驱动策略,最头疼的往往不是模型本身,而是数据。我见过太多人花三个月搭了个漂亮的模型,结果因为数据源选错了、频率没对齐,回测曲线漂亮得不像话,一上实盘就崩。说白了,数据是地基,地基不稳,楼盖得再高也是危房。

这一章,咱们就聊聊数据获取与清洗的那些坑。我会把我在实战中踩过的雷、总结的经验,一股脑倒出来。

4.1 数据源选择:Wind、Bloomberg、CEIC,怎么选?

先说说三大主流数据源。我个人习惯是:国内用Wind,跨境用Bloomberg,宏观长序列用CEIC。但这只是大原则,具体还得看场景。

数据源 优势 劣势 我的使用场景
Wind 国内数据最全,API友好,更新快 跨境数据少,历史数据有时不连续 A股因子、国内宏观指标
Bloomberg 全球数据标准,衍生品数据强 贵,学习曲线陡 跨境宏观、外汇、商品
CEIC 宏观长序列王者,历史数据完整 微观数据弱,更新频率低 GDP、CPI等长周期研究
一个小技巧: 我一般会同时订阅Wind和Bloomberg。Wind用来做日常因子计算,Bloomberg用来做交叉验证。有一次我发现Wind的某国CPI数据跟Bloomberg差了0.3个百分点,查了半天才发现是Wind用了季调前数据,Bloomberg用了季调后。这种坑,你不对比根本不知道。

4.2 数据频率对齐:日频、周频、月频,怎么统一?

数据频率对齐,是宏观因子建模里最容易被忽视的环节。你想想看,GDP是季度数据,CPI是月度数据,利率是日频数据,股票收益率是日频数据。这些数据频率不一样,怎么放到一个模型里?

我的做法是:统一降频到最低频率。比如你要做月度再平衡,那就把所有数据都转成月频。

具体怎么转?我总结了一套规则:

  • 流量数据(如GDP、工业增加值): 取季度内最后一个月的数据,或者取季度平均值。我个人习惯用最后一个月,因为反应更快。
  • 存量数据(如货币供应量M2): 直接取月末值。
  • 价格数据(如CPI、PPI): 取月度值,不需要额外处理。
  • 收益率数据(如股票指数): 取月内日收益率的累乘,或者直接取月末值计算月收益率。
注意: 千万不要用插值法把低频数据转成高频!我曾经见过有人把季度GDP用线性插值转成月度数据,然后拿去跟日频收益率做回归。结果呢?伪回归!因为插值引入了未来信息。你想想看,季度GDP的月度插值,其实是用到了整个季度的信息,这在回测里就是未来函数。

代码实现上,我一般用pandas的resample方法。举个例子:

import pandas as pd

# 假设df是日频数据,index是日期
# 转成月频,取月末值
monthly_df = df.resample('M').last()

# 如果是收益率,需要累乘
monthly_return = (1 + df).resample('M').prod() - 1

# 如果是季度数据,转成月频(取季度末月)
quarterly_to_monthly = df.resample('M').ffill()  # 前向填充
避坑指南: 我曾经在做一个全球宏观因子模型时,把美国GDP数据(季度)和中国PMI数据(月度)放在一起。结果发现美国GDP的发布时间滞后了两个月,而PMI是当月发布。这就导致数据的时间戳对不上。后来我学乖了,所有数据都按发布日对齐,而不是按数据所属期对齐。这一点,很多人会忽略。

4.3 异常值处理:别让一个极端值毁了你的模型

宏观数据里,异常值太常见了。比如2020年3月的VIX指数,比如2008年10月的信用利差。这些极端值,到底是该保留还是该剔除?

我的原则是:先判断异常值的性质,再决定处理方法

  • 数据录入错误: 比如某个数据点突然跳了10个标准差,明显是录入错误。这种直接剔除或用前后值填充。
  • 真实极端事件: 比如金融危机、疫情。这种要保留,但可以做缩尾处理(Winsorization)。
  • 结构性变化: 比如某个国家改了统计口径。这种需要分段处理,或者用断点回归。

具体操作上,我常用两种方法:

  1. MAD法(中位数绝对偏差): 比标准差法更稳健,不受极端值影响。
  2. 分位数法: 比如把超过99%分位数的值替换成99%分位数。
import numpy as np

def winsorize_series(series, limits=[0.01, 0.01]):
    """
    缩尾处理:把超出上下限的值替换为分位数
    """
    lower = series.quantile(limits[0])
    upper = series.quantile(1 - limits[1])
    return series.clip(lower, upper)

def mad_outlier(series, threshold=3):
    """
    MAD法检测异常值
    """
    median = series.median()
    mad = np.median(np.abs(series - median))
    modified_z_scores = 0.6745 * (series - median) / mad
    return np.abs(modified_z_scores) > threshold
注意: 缩尾处理要谨慎。我记得有一次做利率因子,把2008年10月的极端值缩尾了,结果模型在危机期间的预测能力大幅下降。后来我意识到,对于宏观因子来说,极端事件往往是最有信息量的。所以我现在会保留极端值,但会在模型里加入一个哑变量来标记异常时期。

4.4 缺失值填充:能不用插值就不用插值

缺失值处理,是数据清洗里最考验功力的环节。宏观数据缺失的原因五花八门:节假日、数据发布延迟、统计口径变更、数据源缺失……

我的处理优先级是:

  1. 前向填充(ffill): 适用于低频数据,比如月度数据缺失,用上个月的值填充。这是最保守的方法。
  2. 后向填充(bfill): 适用于数据发布延迟的情况。比如某个月的数据晚了两周发布,可以用下个月的值回填。
  3. 线性插值: 只适用于缺失值较少且数据趋势明显的情况。比如日频数据缺失一两天。
  4. 模型预测填充: 用其他相关因子来预测缺失值。比如用PMI预测GDP。这个方法最复杂,但效果最好。
避坑指南: 我曾经在做一个新兴市场宏观因子时,发现某国的CPI数据连续缺失了三个月。我一开始用线性插值填充,结果模型跑出来效果很差。后来查了新闻才知道,那三个月该国改了CPI的统计口径,数据是断层的。线性插值把断层前后的数据连起来了,反而引入了错误信息。所以,缺失值填充前,一定要先搞清楚缺失的原因

代码实现上,pandas的fillna方法基本够用:

# 前向填充
df.fillna(method='ffill', inplace=True)

# 后向填充
df.fillna(method='bfill', inplace=True)

# 线性插值
df.interpolate(method='linear', inplace=True)

# 更高级的:用其他因子做回归预测
from sklearn.linear_model import LinearRegression
# 假设X是其他因子,y是缺失的因子
model = LinearRegression()
model.fit(X_train, y_train)
y_pred = model.predict(X_test)

4.5 知识体系总览

说了这么多,咱们用一张图来总结一下本章的核心逻辑。这张图是我在做项目时自己画的,后来成了团队的标准流程。

宏观因子数据获取与清洗流程 数据源选择 Wind / Bloomberg / CEIC 频率对齐 统一降频到最低频率 异常值处理 MAD法 / 缩尾处理 缺失值填充 ffill / 插值 / 模型预测 交叉验证 多数据源对比校验 输出清洗后数据 用于因子计算与建模 核心原则:先判断异常性质,再决定处理方法 能不用插值就不用插值,能保留极端值就保留 关键:数据按发布日对齐,而非数据所属期对齐 缺失值填充前,先搞清楚缺失原因

这张图看起来简单,但每一步都有坑。数据源选错了,后面再怎么清洗都是白搭。频率没对齐,模型跑出来全是伪相关。异常值处理不当,一个极端值就能把整个模型带偏。缺失值填充太随意,等于给模型喂了毒药。

嗯,这一章的内容就到这里。数据清洗是个细致活,急不得。下一章咱们聊聊因子计算的具体方法,到时候会用到今天清洗好的数据。


公众号:蓝海资料掘金营,微信deep3321