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的复权因子居然有差异,后来发现是复权算法不同。所以,跨数据源做因子时,一定要统一数据源,别混着用。
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 数据清洗与预处理
数据拿到手,第一件事不是建模,是清洗。你想想看,原始数据里什么妖魔鬼怪都有:缺失值、异常值、停牌、除权除息、数据跳空……
我一般按以下步骤来:
- 缺失值处理:先看缺失比例。如果某只股票缺失超过20%,直接剔除。如果只是零星缺失,用前向填充(ffill)或插值法。
- 异常值检测:比如某天收益率突然涨了50%,明显是数据错误。我习惯用3倍标准差法或MAD(中位数绝对偏差)来识别。
- 复权处理:股票有分红送股,必须用后复权或前复权。我个人偏好前复权,因为历史价格更连续。
- 停牌处理:停牌期间价格不变,但因子值会失真。我一般把停牌超过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数据用线性插值变成月度数据。注意,这种方法会引入“未来信息”,回测时一定要小心。
- 事件驱动对齐:以某个事件(比如财报发布日)为基准,对齐前后窗口的数据。这种方法最准确,但实现起来最复杂。
举个例子,假设你有日频的股票收益率和月频的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 知识体系总览
下面这张图,是我自己总结的风险因子数据源处理流程。你可以把它当作一个检查清单:
这张图把整个流程串起来了。从数据获取开始,到清洗,再到频率对齐,最后得到一张干净、对齐的因子面板数据。每一步都有坑,但只要你按这个流程走,基本不会出大问题。
好了,关于数据源的部分就聊到这里。记住一句话:数据质量决定了模型的天花板。别急着上模型,先把数据搞干净。