3. 数据准备与预处理:ESG评级数据、行情数据、机构持仓数据的获取与清洗
做因子拥挤度监测,最怕什么?
数据还没跑通,模型先崩了。我见过太多团队,模型建得漂漂亮亮,结果一上实盘数据就露馅——全是数据源的问题。所以这一章,咱们把地基打牢。
3.1 数据源的选择与获取
ESG因子拥挤度分析,至少需要三类数据:ESG评级数据、行情数据、机构持仓数据。缺一个,拥挤度信号就失真。
3.1.1 ESG评级数据
国内主流ESG评级供应商包括:商道融绿、中财绿金、华证、嘉实、万得ESG等。我个人习惯用商道融绿和华证两家做交叉验证。为什么?因为不同评级机构对同一家公司的打分可能差出两个标准差——这在拥挤度计算里会直接导致信号翻转。
| 数据源 | 覆盖范围 | 更新频率 | 获取方式 |
|---|---|---|---|
| 商道融绿 | A股全市场 | 季度 | API / 数据库直连 |
| 华证ESG | A股全市场 | 月度 | 万得终端 / 数据接口 |
| 中财绿金 | 沪深300成分股 | 半年度 | 官网下载 / 合作数据商 |
3.1.2 行情数据
行情数据相对成熟。我建议用日频复权价格,不要用原始价格。复权方式选「后复权」,这样计算收益率时不会因为分红送股产生断点。
数据频率上:
- 日频数据:用于计算短期拥挤度(如20日滚动相关性)
- 周频数据:用于中期拥挤度(如52周动量拥挤)
- 月频数据:用于长期拥挤度(如因子暴露变化)
嗯,这里要注意:如果你用日频数据做拥挤度,一定要做非流动性调整。小盘股的日收益率波动极大,直接算相关性会得到一堆假信号。
3.1.3 机构持仓数据
机构持仓数据是拥挤度监测的核心输入。为什么?因为拥挤度的本质就是「大家都在买同一个东西」。机构持仓数据能直接告诉我们:哪些股票被机构集中持有。
数据来源:
- 公募基金季报:每季度披露前十大重仓股
- 上市公司年报/半年报:披露前十大流通股东
- 交易所龙虎榜:日频的机构买卖数据(但覆盖不全)
我个人建议以公募基金季报为主。为什么?因为公募基金是A股最大的机构投资者,而且季报数据相对规范、容易清洗。
3.2 数据清洗的「三座大山」
数据拿到手,别急着算因子。先过三关:缺失值处理、异常值检测、时间对齐。
3.2.1 缺失值处理
ESG评级数据的缺失率通常很高。我统计过,A股全市场每年约有15%-20%的股票没有ESG评级。怎么办?
- 方法一:行业均值填充。用同行业、同市值分位的公司评级均值填充。我个人觉得这是最稳妥的做法。
- 方法二:前向填充。如果某公司上季度有评级、本季度缺失,用上季度数据填充。但要注意:评级变化通常不会太大,但一旦发生重大事件(如环保处罚),前向填充会严重失真。
- 方法三:直接剔除。如果缺失率超过30%,我建议直接剔除该股票。否则拥挤度指标会被大量填充数据「稀释」。
3.2.2 异常值检测
行情数据里的异常值,最常见的是「涨停/跌停」导致的收益率截断。机构持仓数据里,异常值往往是「持股比例超过100%」——这通常是数据合并错误。
我的清洗流程:
# 伪代码示例
def clean_data(df):
# 1. 剔除持股比例 > 100% 的记录
df = df[df['hold_pct'] <= 1.0]
# 2. 剔除日收益率绝对值 > 20% 的记录(A股涨跌停限制)
df = df[abs(df['daily_ret']) <= 0.2]
# 3. 剔除ESG评级为负值或大于100的记录
df = df[(df['esg_score'] >= 0) & (df['esg_score'] <= 100)]
# 4. 标记并剔除连续缺失超过3期的股票
df['missing_count'] = df.groupby('stock_id')['esg_score'].transform(
lambda x: x.isna().rolling(3).sum()
)
df = df[df['missing_count'] < 3]
return df
你想想看,如果持仓数据里出现持股比例120%,那后面算出来的拥挤度能信吗?肯定不能。
3.2.3 时间对齐
三类数据的频率不同:ESG评级是季度数据,行情是日频数据,机构持仓是季度数据。怎么对齐?
我的做法是:以ESG评级数据的发布日为锚点。因为拥挤度监测的核心是「ESG因子」,其他数据都向它对齐。
- 行情数据:取ESG评级发布日前后20个交易日的日频数据
- 机构持仓数据:取最近一期季报数据(注意:季报有滞后,通常滞后1-2个月)
3.3 数据预处理的核心流程
下面这张图,是我在项目中总结的数据预处理流程。你可以直接拿去用。
流程看起来简单,但每一步都有坑。我建议你按照这个顺序走:
- 先清洗ESG数据:因为它是锚点,其他数据都向它对齐
- 再清洗行情数据:注意复权、涨跌停、停牌的处理
- 最后清洗机构持仓数据:注意持股比例异常、基金合并等问题
- 合并数据:以ESG评级发布日为基准,向前取最近一期机构持仓,向后取20个交易日行情
3.4 实战中的几个「坑」
最后分享几个我在项目中踩过的坑,希望能帮你省点时间。
坑一:ESG评级数据的时间戳问题
很多ESG评级数据标注的是「报告期」,而不是「发布日期」。比如2023年一季度的评级,实际可能在2023年5月才发布。如果你用报告期去匹配行情,就会用到未来数据。我建议用发布日期作为时间戳。
坑二:机构持仓数据的滞后性
公募基金季报通常在季度结束后15-20个工作日才披露。也就是说,你看到的2023年Q1持仓数据,实际反映的是2023年3月底的持仓情况。如果你在2023年4月用这个数据算拥挤度,其实已经滞后了一个月。怎么办?我一般会做一个滞后调整,把机构持仓数据向后平移一个季度。
坑三:行情数据的停牌处理
停牌股票的日收益率为0,但这不是真实的交易信号。如果你直接用0填充,会导致相关性计算出现偏差。我建议:停牌期间的数据直接剔除,不参与拥挤度计算。等复牌后再重新纳入。
数据准备这一步,看起来枯燥,但决定了整个模型的成败。我见过太多人把时间花在模型调参上,结果数据源一换,模型就崩了。说白了,数据质量决定了拥挤度信号的上限。
下一章,我们会正式进入拥挤度指标的计算。但在此之前,请确保你的数据已经过这三关:缺失值、异常值、时间对齐。否则,后面的工作全是白费。