3. 数据准备与清洗:归因分析所需的数据源

做归因分析,说白了就是给策略的收益算一笔「明白账」。但账本好不好,全看原始数据干不干净。我见过太多人,模型建得花里胡哨,结果数据一塌糊涂,最后归因出来的结论全是错的。

嗯,这一章我们就来聊聊,归因分析到底需要哪些数据,以及怎么把这些数据收拾得服服帖帖。

3.1 归因分析的四大数据源

我个人习惯把归因分析的数据源分成四类。少一个,分析就不完整。

数据源 核心字段 频率要求 常见问题
持仓数据 证券代码、持仓数量、成本价、市值 日频或更高 除权除息未调整、停牌数据缺失
交易数据 成交时间、价格、数量、买卖方向 逐笔或分钟级 撤单记录干扰、成交价与行情价不一致
基准数据 基准指数点位、成分股权重 日频 指数编制规则变更、权重数据滞后
因子数据 因子值、因子暴露、因子收益率 日频或月频 幸存者偏差、前视偏差

你想想看,如果持仓数据里少了停牌股的复牌信息,那归因出来的行业配置收益可能直接偏掉几个基点。我在项目中遇到过一回,就是因为某只停牌股复牌后连续涨停,但持仓数据没更新,结果归因出来的超额收益全算到了选股上——这明显不合理。

3.2 数据清洗的实战技巧

数据清洗这件事,听起来枯燥,但做不好真的会翻车。我总结了几个高频坑点,你直接拿去用。

3.2.1 持仓数据的除权除息处理

这是最容易被忽略的。股票分红送股后,持仓成本如果不做复权处理,那归因出来的收益全是错的。

# 一个简单的复权处理逻辑
def adjust_holdings(holdings_df, corporate_actions_df):
    """
    holdings_df: 持仓数据,包含证券代码、持仓数量、成本价
    corporate_actions_df: 公司行为数据,包含除权除息日、送股比例、分红金额
    """
    for idx, action in corporate_actions_df.iterrows():
        code = action['证券代码']
        ex_date = action['除权除息日']
        
        # 找到该股票在除权日之前的持仓
        mask = (holdings_df['证券代码'] == code) & (holdings_df['日期'] < ex_date)
        
        # 调整持仓数量(送股)
        holdings_df.loc[mask, '持仓数量'] *= (1 + action['送股比例'])
        
        # 调整成本价(分红+送股)
        holdings_df.loc[mask, '成本价'] = (
            holdings_df.loc[mask, '成本价'] - action['分红金额']
        ) / (1 + action['送股比例'])
    
    return holdings_df
⚠️ 我曾经踩过的坑: 有些数据源会把除权除息日当天的持仓数据直接按新价格更新,但成本价没变。这样算出来的持仓收益会虚高。一定要用「前复权」的方式统一处理。

3.2.2 交易数据的时间对齐

交易数据和行情数据的时间戳往往不一致。比如交易所的逐笔成交是微秒级的,但行情快照是3秒一张。怎么对齐?

我的做法是:以行情快照时间为基准,把交易数据聚合到最近的快照时间点上

# 交易数据对齐到行情快照
def align_trades_to_snapshot(trades_df, snapshot_times):
    """
    trades_df: 逐笔交易数据,包含成交时间、价格、数量
    snapshot_times: 行情快照时间列表(如每3秒一个)
    """
    # 使用pd.cut将成交时间分桶到最近的快照时间
    trades_df['snapshot_time'] = pd.cut(
        trades_df['成交时间'],
        bins=snapshot_times,
        labels=snapshot_times[:-1],
        right=False
    )
    
    # 按快照时间聚合
    aligned = trades_df.groupby('snapshot_time').agg({
        '成交价格': 'vwap',  # 成交量加权均价
        '成交数量': 'sum'
    }).reset_index()
    
    return aligned

说白了,就是别让微秒级的交易数据去匹配秒级的行情数据,否则你会多出一堆「幽灵交易」。

3.2.3 基准数据的权重对齐

基准指数的成分股权重是归因分析的核心输入。但很多数据源给的权重是「日末权重」,而你的持仓可能是盘中调整的。这里有个小技巧:用前一天的权重匹配当天的持仓

💡 个人经验: 我习惯在每天收盘后,先拉取当天的基准成分股权重(一般是中证指数公司下午5点后发布),然后和当天的持仓做匹配。这样第二天开盘前就能跑出归因结果。

3.3 数据对齐的核心逻辑

数据对齐这件事,我画了一张图帮你理解。你看完就明白了。

归因分析数据对齐流程 持仓数据 交易数据 基准数据 因子数据 数据清洗:去重、除权除息、时间戳标准化、缺失值处理 数据对齐:按日期/时间戳合并,统一频率(日频/分钟频) 对齐后的归因分析数据集 包含:持仓权重、交易成本、基准收益、因子暴露 Brinson / 因子归因模型输入

你看,整个流程其实就三步:清洗 → 对齐 → 输出。但每一步都有细节。

3.4 实战中的避坑指南

我直接给你列几个最常见的坑,你写代码的时候对照着检查。

  • 幸存者偏差:因子数据里只包含当前还在的股票,退市的、被ST的全没了。这样算出来的因子收益率会偏高。我曾经因为这个原因,回测出来的因子IC值高达0.15,实盘直接腰斩。
  • 前视偏差:用未来的数据去计算当天的因子值。比如用当天的收盘价去算前一天的因子暴露。嗯,这个坑我见过太多人踩了。
  • 停牌处理:停牌期间没有交易数据,但持仓数据还在。如果不做特殊处理,归因出来的行业配置收益会失真。我的做法是:停牌期间用「前收盘价」计算持仓市值,不纳入交易归因。
  • 分红再投资:基准指数通常假设分红再投资,但你的持仓可能没有。如果不做调整,基准收益和持仓收益的基准点就不一致。

核心原则: 数据清洗不是越复杂越好,而是「够用就好」。你只需要保证:

  1. 所有数据的时间戳是统一的(比如都用北京时间)
  2. 所有价格都是复权后的(前复权或后复权,保持一致)
  3. 所有权重都是归一化的(加起来等于1)
  4. 所有缺失值都有明确的处理策略(填充、删除、还是标记)

说实话,数据准备这部分占了我整个归因分析项目60%的时间。但一旦把数据管道搭好了,后面的模型验证、结果解读就快多了。你想想看,如果数据本身就有问题,那再牛的归因模型也救不了你。

好了,这一章的内容就到这里。数据准备好了,下一章我们就可以开始真正的归因计算了。


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