回测框架核心概念:事件驱动 vs 向量化回测、回测的偏差来源

做量化回测这么多年,我见过太多人一上来就写回测代码,结果跑出来的曲线漂亮得不行,实盘一上就傻眼。说白了,很多问题都出在没搞懂回测框架的核心概念上。今天咱们就聊聊两个最基础的东西:回测引擎的两种工作模式,以及那些让你亏钱的偏差来源。

一、事件驱动 vs 向量化回测

这两种模式,我习惯把它们比作「点菜」和「自助餐」。你想想看,点菜是一道一道上,每道菜都有先后顺序;自助餐是一次性把所有菜都摆出来,你想吃啥拿啥。回测也是这个道理。

1. 向量化回测

向量化回测,说白了就是一次性处理所有数据。你把历史行情数据全部加载进来,用矩阵运算一次性算出所有信号和持仓。这种方式快,真的快。我在做高频策略初筛时,经常用向量化回测,几秒钟就能跑完十年数据。

核心特点:

  • 一次性处理全部数据,速度快
  • 适合策略初筛和参数优化
  • 无法处理复杂的订单逻辑
  • 容易忽略交易细节
# 向量化回测示例
import pandas as pd
import numpy as np

def vectorized_backtest(data):
    # 一次性计算信号
    data['signal'] = np.where(data['close'] > data['ma20'], 1, -1)
    # 一次性计算持仓
    data['position'] = data['signal'].shift(1)
    # 一次性计算收益
    data['returns'] = data['position'] * data['close'].pct_change()
    return data['returns'].cumsum()

嗯,这里要注意。向量化回测有个大坑——它假设你可以在任意时刻以任意价格成交。我在项目中遇到过,一个看起来年化50%的策略,加上滑点和手续费后直接变成负收益。为什么会这样?因为向量化回测忽略了市场微观结构。

2. 事件驱动回测

事件驱动回测就不同了。它模拟真实交易环境,一根K线一根K线地处理。每来一根新K线,就触发一次事件,然后决定要不要交易。这种方式慢,但真实。

我的建议:做高频策略或者需要精细控制订单执行的策略,一定要用事件驱动。我曾经用向量化回测跑一个统计套利策略,结果实盘时因为订单执行延迟,套利窗口早就关闭了。

# 事件驱动回测框架核心结构
class EventDrivenBacktest:
    def __init__(self, data):
        self.data = data
        self.current_index = 0
        self.position = 0
        self.cash = 1000000
        
    def on_bar(self, bar):
        # 每根K线触发一次
        signal = self.generate_signal(bar)
        if signal == 1 and self.position == 0:
            self.buy(bar.close)
        elif signal == -1 and self.position > 0:
            self.sell(bar.close)
            
    def run(self):
        for i in range(len(self.data)):
            bar = self.data.iloc[i]
            self.on_bar(bar)
            self.current_index = i

3. 怎么选?

对比维度 向量化回测 事件驱动回测
执行速度 极快 较慢
模拟真实度
订单管理 不支持 支持
适用场景 策略初筛、参数优化 实盘模拟、精细策略
代码复杂度

我个人习惯是:先用向量化回测快速筛选策略,选出有潜力的再用事件驱动回测做精细验证。这样既保证了效率,又不会漏掉细节。

二、回测的偏差来源

这部分内容,我建议你拿个小本本记下来。因为很多人在回测上栽跟头,都是因为这些偏差。我自己就吃过不少亏。

1. 前视偏差

前视偏差,也叫未来函数。说白了,就是用未来的数据去预测过去。听起来很蠢对吧?但实际操作中特别容易犯。

我曾经犯过的错:写一个均线策略时,用了当天的收盘价来计算均线,然后用这个均线去判断当天的买卖信号。结果回测曲线漂亮得不像话,实盘直接崩了。为什么?因为收盘价出来的时候,当天已经结束了,你根本没法用这个价格去交易。

常见的几种前视偏差:

  • 价格前视:用未来价格计算指标
  • 信息前视:用未来发布的数据做决策
  • 幸存者偏差:只考虑存活下来的股票
# 前视偏差示例
def wrong_ma_strategy(data):
    # 错误:用当天收盘价计算均线,然后判断当天信号
    data['ma5'] = data['close'].rolling(5).mean()
    data['signal'] = np.where(data['close'] > data['ma5'], 1, -1)
    # 正确做法:用前一天的数据计算
    data['ma5'] = data['close'].shift(1).rolling(5).mean()
    data['signal'] = np.where(data['close'] > data['ma5'], 1, -1)

2. 幸存者偏差

幸存者偏差是个老生常谈的问题。你想想看,如果你只回测现在还活着的股票,那些退市的、暴跌的都被你忽略了,结果当然好看。但实盘时,你很可能就买到了那些「幸存者」的反面。

避坑指南:回测时一定要包含已经退市的股票。我见过一个策略,回测十年年化30%,结果实盘第一年就亏了20%。后来一查,原来回测时只用了沪深300的当前成分股,那些被踢出去的垃圾股全被忽略了。

3. 其他常见偏差

偏差类型 说明 如何避免
生存偏差 只考虑存活标的 使用全量历史数据
选择偏差 选择特定时间段 覆盖牛熊周期
过拟合偏差 参数过度优化 交叉验证、样本外测试
交易成本偏差 忽略滑点和手续费 合理估算交易成本
流动性偏差 假设无限流动性 加入成交量限制

4. 如何系统性地避免偏差?

我总结了一套流程,每次做回测前都会过一遍:

  1. 数据清洗:检查是否有未来数据泄露
  2. 时间对齐:确保所有数据时间戳一致
  3. 成本估算:加入合理的滑点和手续费
  4. 样本外测试:留一部分数据做验证
  5. 蒙特卡洛模拟:随机扰动参数看稳定性

一个小技巧:我习惯在回测代码里加一个「偏差检查」函数,每次跑完回测自动输出一份偏差报告。这样能快速定位问题,不用手动一个个排查。

三、核心知识体系

下面这张图,是我自己总结的回测框架核心知识体系。你可以把它当成一个检查清单,每次做回测前对照着看一遍。

回测框架核心知识体系 回测引擎 偏差来源 向量化回测 事件驱动回测 速度快,精度低 速度慢,精度高 策略初筛 精细验证 前视偏差 幸存者偏差 过拟合偏差 交易成本偏差 选择偏差 流动性偏差 最佳实践:向量化初筛 → 事件驱动验证 → 偏差检查 核心原则:先快后慢,先粗后细,先验后证

嗯,这张图基本把今天的内容串起来了。左边是回测引擎的两种模式,右边是各种偏差来源,底部是最佳实践。你每次做回测前,可以对照这张图检查一遍,能省不少事。

最后提醒一句:回测只是工具,不是圣杯。再漂亮的回测曲线,上了实盘都可能变样。我见过太多人把回测当成了预测,结果亏得底裤都不剩。记住,回测的目的是发现策略的缺陷,而不是证明策略有多牛。

蓝海数据掘金营,专注资料整理