1. 幸存者偏差:回测中的隐形杀手

做量化的人,谁没被幸存者偏差坑过?

我入行第三年就栽过一个大跟头。当时跑了一个多因子策略,夏普比高达3.2,回撤不到5%。我兴奋得差点直接上实盘。还好团队里的老大哥拦住了我,他说:「你数据里是不是只有现在还活着的股票?」

我一查,果然。那些退市的、ST的、暴跌90%的股票,全被我过滤掉了。策略在回测里风光无限,真上了实盘,估计一个月就亏光。

幸存者偏差,说白了就是:你只看到了活下来的,却忽略了死掉的。在因子选股里,这个陷阱尤其隐蔽。

什么是幸存者偏差?

想象一下:你统计了100个交易员的业绩,发现平均年化收益30%。你很羡慕,觉得这行真赚钱。但你没看到的是——另外900个亏光离场的人,根本没出现在你的样本里。

回测也是一样。如果你只用当前还在交易的股票做测试,就等于自动排除了那些已经退市、被ST、或者暴跌到无人问津的股票。而这些「死掉」的股票,恰恰是策略最可能亏钱的地方。

核心问题: 幸存者偏差会让你的回测结果系统性偏高。你看到的超额收益,可能只是「幸存者」带来的幻觉。

一个真实的案例

我记得有个朋友,做小市值因子策略。他用的是2023年底的股票池,回测了2015到2023年的数据。结果年化收益25%,最大回撤只有12%。

他觉得自己发现了圣杯。我问他:「你2015年的股票池里,有没有包含那些后来退市的壳公司?」

他愣住了。回去一查,2015年A股有2800只股票,到2023年只剩2500只。那消失的300只里,大部分是小市值公司。他的策略在回测里躲过了这些「炸弹」,但实盘里可不会。

修正之后,他的策略年化收益从25%掉到了9%,最大回撤扩大到35%。这才是真实水平。

如何检测幸存者偏差?

嗯,这里有几个我常用的方法:

  1. 检查股票数量变化:回测期内,股票池数量是否稳定?如果逐年减少,大概率有幸存者偏差。
  2. 对比全样本 vs 幸存样本:分别用「所有曾经存在的股票」和「当前存活的股票」做回测,看差异有多大。
  3. 查看退市股票的表现:如果退市股票在你的策略里普遍亏损,那幸存者偏差的影响就很大。
我的习惯: 每次做回测前,我都会先跑一个「全样本回测」。哪怕数据不全,也要尽量包含退市、ST、停牌的股票。这能帮你看到最坏的情况。

代码示例:如何避免幸存者偏差

下面这段代码,展示了如何用Python构建一个「无幸存者偏差」的股票池。核心思路是:在每一个回测时间点,只使用当时存在的股票

import pandas as pd
import numpy as np
from datetime import datetime

def build_universe_without_survivorship_bias(start_date, end_date):
    """
    构建无幸存者偏差的股票池
    思路:在每个月底,只保留当时在交易的股票
    """
    # 假设我们有一个包含所有股票历史状态的数据集
    # 字段:stock_id, date, is_alive (是否在交易), market_cap, etc.
    
    # 生成每月末的时间序列
    dates = pd.date_range(start=start_date, end=end_date, freq='M')
    
    universe = {}
    for date in dates:
        # 获取该时间点所有存活的股票
        alive_stocks = all_stocks_data[
            (all_stocks_data['date'] == date) & 
            (all_stocks_data['is_alive'] == True)
        ]
        
        # 记录该月的股票池
        universe[date] = alive_stocks['stock_id'].tolist()
        
        # 注意:这里要包含那些即将退市的股票
        # 它们虽然还活着,但可能已经ST或濒临退市
        # 如果你把它们排除,就引入了新的偏差
        
    return universe

# 使用示例
universe = build_universe_without_survivorship_bias('2015-01-01', '2023-12-31')
print(f"2015年1月股票数量: {len(universe['2015-01-31'])}")
print(f"2023年12月股票数量: {len(universe['2023-12-31'])}")
注意: 即使你用了「全样本」,也要小心「未来信息」。比如,你在2015年使用了2023年才上市的新股数据,这同样会引入偏差。正确的做法是:每个时间点只使用该时间点之前已上市且仍在交易的股票。

幸存者偏差的三种常见形式

类型 描述 影响程度
退市偏差 只包含当前存活的股票,忽略已退市的
IPO偏差 回测中使用了尚未上市的股票数据
重组偏差 忽略被借壳、重组后改名的股票

你想想看,这三种偏差叠加在一起,回测结果能有多离谱?我见过一个策略,修正前夏普比2.8,修正后直接变成0.6。说白了,那2.2的夏普比全是偏差贡献的。

避坑指南

我曾经在做一个多因子模型时,发现某个因子在回测里表现特别好。仔细一查,原来这个因子对「小市值+高波动」的股票打分很高,而这些股票里有很多后来退市了。回测时它们被排除在外,因子自然显得很厉害。

后来我养成了几个习惯:

  • 每次回测前,先跑一个「全样本+无未来信息」的基准。如果基准收益明显低于你的策略,那就要警惕了。
  • 对退市股票做单独分析。看看你的策略在退市股票上表现如何。如果普遍亏损,说明策略可能过度依赖「幸存者」。
  • 使用「滚动股票池」。每个时间点只使用当时存在的股票,而不是用一个固定的股票列表。
  • 做压力测试。假设退市率提高一倍,你的策略还能赚钱吗?
一句话总结: 幸存者偏差是回测里最隐蔽、最致命的陷阱。它不会让你的策略失效,但会让你对策略的风险完全误判。修正它,你的回测结果才值得信任。

知识体系图:幸存者偏差的核心逻辑

幸存者偏差:回测中的隐形杀手 幸存者偏差 原因:只看到活着的 影响:回测结果偏高 解决方案:全样本回测 退市股票被排除 IPO数据污染 夏普比虚高 回撤被低估 滚动股票池 退市股票分析 核心原则:每个时间点,只使用当时存在的股票

这张图把幸存者偏差的来龙去脉讲清楚了。你从「原因」出发,顺着「影响」看到后果,最后找到「解决方案」。我个人建议把它打印出来贴在工位上,每次做回测前看一眼。

好了,这一章就到这里。记住:回测里没有幸存者偏差,就像做饭不放盐——看着还行,吃起来全完蛋

专注资料整理