3. 数据准备与预处理:ESG评级数据、行情数据、机构持仓数据的获取与清洗

做因子拥挤度监测,最怕什么?

数据还没跑通,模型先崩了。我见过太多团队,模型建得漂漂亮亮,结果一上实盘数据就露馅——全是数据源的问题。所以这一章,咱们把地基打牢。

3.1 数据源的选择与获取

ESG因子拥挤度分析,至少需要三类数据:ESG评级数据行情数据机构持仓数据。缺一个,拥挤度信号就失真。

3.1.1 ESG评级数据

国内主流ESG评级供应商包括:商道融绿、中财绿金、华证、嘉实、万得ESG等。我个人习惯用商道融绿华证两家做交叉验证。为什么?因为不同评级机构对同一家公司的打分可能差出两个标准差——这在拥挤度计算里会直接导致信号翻转。

数据源 覆盖范围 更新频率 获取方式
商道融绿 A股全市场 季度 API / 数据库直连
华证ESG A股全市场 月度 万得终端 / 数据接口
中财绿金 沪深300成分股 半年度 官网下载 / 合作数据商
注意:ESG评级数据存在严重的「幸存者偏差」问题。退市公司、ST公司的评级往往缺失,这会导致拥挤度指标在尾部样本上失真。我曾经在回测中漏掉一批ST股的ESG数据,结果拥挤度信号在2022年四季度突然失效——后来发现是样本选择偏差在作祟。

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个月)
避坑指南:我曾经在时间对齐上栽过跟头。当时直接用「报告期」对齐,没考虑数据实际发布时间。结果2023年一季度的ESG评级数据,我用了2022年年报的机构持仓来匹配——差了整整一个季度。拥挤度信号完全反了。

3.3 数据预处理的核心流程

下面这张图,是我在项目中总结的数据预处理流程。你可以直接拿去用。

ESG因子拥挤度监测 - 数据预处理流程图 ESG评级数据 行情数据 机构持仓数据 缺失值处理 异常值检测 时间对齐 数据合并与对齐 清洗后的面板数据 用于拥挤度计算

流程看起来简单,但每一步都有坑。我建议你按照这个顺序走:

  1. 先清洗ESG数据:因为它是锚点,其他数据都向它对齐
  2. 再清洗行情数据:注意复权、涨跌停、停牌的处理
  3. 最后清洗机构持仓数据:注意持股比例异常、基金合并等问题
  4. 合并数据:以ESG评级发布日为基准,向前取最近一期机构持仓,向后取20个交易日行情

3.4 实战中的几个「坑」

最后分享几个我在项目中踩过的坑,希望能帮你省点时间。

坑一:ESG评级数据的时间戳问题

很多ESG评级数据标注的是「报告期」,而不是「发布日期」。比如2023年一季度的评级,实际可能在2023年5月才发布。如果你用报告期去匹配行情,就会用到未来数据。我建议用发布日期作为时间戳。

坑二:机构持仓数据的滞后性

公募基金季报通常在季度结束后15-20个工作日才披露。也就是说,你看到的2023年Q1持仓数据,实际反映的是2023年3月底的持仓情况。如果你在2023年4月用这个数据算拥挤度,其实已经滞后了一个月。怎么办?我一般会做一个滞后调整,把机构持仓数据向后平移一个季度。

坑三:行情数据的停牌处理

停牌股票的日收益率为0,但这不是真实的交易信号。如果你直接用0填充,会导致相关性计算出现偏差。我建议:停牌期间的数据直接剔除,不参与拥挤度计算。等复牌后再重新纳入。

数据准备这一步,看起来枯燥,但决定了整个模型的成败。我见过太多人把时间花在模型调参上,结果数据源一换,模型就崩了。说白了,数据质量决定了拥挤度信号的上限

下一章,我们会正式进入拥挤度指标的计算。但在此之前,请确保你的数据已经过这三关:缺失值、异常值、时间对齐。否则,后面的工作全是白费。


专注资料整理