4、复权数据处理:前复权与后复权原理、除权除息数据修正、复权因子计算

做量化交易,最怕什么?

怕数据有坑。

而「复权」就是最大的坑之一。我刚开始做回测时,拿到的股票数据直接扔进策略,结果收益率曲线漂亮得不像话。后来一查,原来是没处理复权,把分红送股当成了白捡的钱。嗯,那叫一个尴尬。

4.1 为什么需要复权?

股票会分红、送股、配股。这些操作会导致股价在除权除息日出现「断崖式下跌」。比如一只股票100块,每10股派10元,除息日开盘价就变成99块。你想想看,这1块钱的缺口不是市场波动,是钱真的到你账户里了。

但如果你直接用原始价格做回测,策略会认为股价跌了1%,触发止损。这就完全失真了。

复权的目的:消除分红送股对价格序列的「非市场性扰动」,让价格曲线连续可分析。

核心原则:复权只改变价格,不改变收益率。无论前复权还是后复权,你计算出的收益率应该完全一致。

4.2 前复权 vs 后复权

这两个概念,说白了就是「调整基准日」不同。

类型 调整方式 当前价格 历史价格 适用场景
前复权 调整历史价格,保持最新价格不变 真实价格 被调低 回测、技术分析
后复权 调整历史价格,保持上市首日价格不变 被调高 真实价格 长期趋势分析

我个人习惯用前复权做回测。为什么?因为前复权下,最新价格就是真实成交价,你看到的K线形态、均线系统都和实盘一致。后复权的话,当前价格可能被放大到几百甚至上千块,看着怪别扭的。

我的经验:做日内策略或短线策略,必须用前复权。做十年以上的长周期趋势分析,后复权更合适,因为它保留了历史价格的「真实感」。

4.3 除权除息数据修正

要算复权,先得搞清楚「除权除息」到底发生了什么。我遇到过不少新手,直接把分红金额加到股价上,结果算出来的复权因子全是错的。

除权除息主要分三种情况:

  • 现金分红:股价减去每股分红。比如10派5元,每股除息0.5元。
  • 送股/转增:股价按比例下调。比如10送10,股价直接腰斩。
  • 配股:比较复杂,需要按配股价和配股比例加权平均。

修正公式其实很简单:

除权除息参考价 = (前收盘价 - 每股现金分红 + 配股价 × 配股比例) / (1 + 送股比例 + 转增比例 + 配股比例)

举个例子:某股票前收盘20元,10派5元(每股0.5元),10送5(送股比例0.5),没有配股。

除权参考价 = (20 - 0.5) / (1 + 0.5) = 19.5 / 1.5 = 13元

你看,从20跌到13,不是市场跌了35%,是公司把一部分价值变成了现金和股票给了你。

注意:有些数据源提供的「复权因子」是累计值,有些是单次值。我曾经因为混用两种格式,导致回测结果差了20%。一定要看清楚文档说明。

4.4 复权因子计算

复权因子,说白了就是一个「乘数」。你拿着这个乘数去乘原始价格,就能得到复权后的价格。

计算步骤:

  1. 从上市首日或某个基准日开始,逐日计算复权因子。
  2. 遇到除权除息日,根据除权比例更新因子。
  3. 非除权日,因子保持不变。

前复权因子的计算公式:

复权因子(t) = 复权因子(t-1) × (1 + 送股比例 + 转增比例 + 配股比例) / (前收盘价 - 每股现金分红 + 配股价 × 配股比例) × 前收盘价

嗯,看着有点绕。我一般直接用Python算:

import pandas as pd

def calc_forward_adj_factor(df):
    """
    df: 包含日期、收盘价、每股分红、送股比例、配股比例、配股价
    返回前复权因子序列
    """
    df = df.sort_values('date', ascending=False)  # 从最新往最早算
    factor = [1.0]
    
    for i in range(1, len(df)):
        prev_close = df.iloc[i-1]['close']
        cash_div = df.iloc[i]['cash_div']
        stock_div = df.iloc[i]['stock_div']
        rights_ratio = df.iloc[i]['rights_ratio']
        rights_price = df.iloc[i]['rights_price']
        
        # 除权除息日调整
        if cash_div > 0 or stock_div > 0 or rights_ratio > 0:
            adj_factor = (prev_close - cash_div + rights_price * rights_ratio) / \
                         (prev_close * (1 + stock_div + rights_ratio))
            factor.append(factor[-1] * adj_factor)
        else:
            factor.append(factor[-1])
    
    df['adj_factor'] = factor[::-1]  # 反转回时间正序
    df['adj_close'] = df['close'] * df['adj_factor']
    return df
避坑指南:我曾经在计算复权因子时,忘了处理「连续除权」的情况。比如某股票一周内既分红又送股,因子要连续乘,不能只算一次。这个bug让我排查了整整两天。

4.5 复权数据的实战要点

做量化这几年,我总结了几条关于复权的铁律:

  • 回测必须用前复权:否则你的买入卖出信号全是错的。
  • 不要用复权数据算分红收益率:复权后的价格已经包含了分红影响,再用它算股息率会重复计算。
  • 多数据源交叉验证:不同数据商的复权算法可能有细微差异。我习惯同时拉两个数据源,对比复权因子是否一致。
  • 新股上市前5日不参与复权:有些数据商会把上市首日的涨跌幅也纳入复权计算,这会导致因子失真。
重要提醒:如果你用后复权数据做回测,记得把当前价格也换成后复权价格。否则你看到的「当前价格」和「历史价格」不在同一个坐标系下,策略逻辑会完全错乱。

4.6 知识体系总览

下面这张图,是我自己梳理的复权数据处理全流程。每次做数据清洗时,我都会对照着检查一遍。

复权数据处理知识体系 原始行情数据 除权除息事件识别 现金分红 送股/转增 配股 复权因子计算 前复权 / 后复权 价格序列

这张图把复权处理的流程串起来了。从原始数据出发,先识别除权除息事件,然后根据事件类型分别处理,最后计算出复权因子,生成前复权或后复权价格序列。每一步都有坑,每一步都需要验证。

好了,复权这块就聊到这儿。记住一句话:不复权的回测,都是耍流氓


公众号:蓝海数据掘金营,微信deep3321