23、信号回测框架:向量化回测、事件驱动回测、性能评估

做高频信号回测,说白了就是验证你的策略在历史上能不能赚钱。我见过太多人,策略写得花里胡哨,一回测就露馅。今天咱们聊聊回测框架的两种主流玩法——向量化回测和事件驱动回测,以及怎么评估策略到底行不行。

向量化回测:快,但有点糙

向量化回测,我个人习惯叫它“批量计算”。你把所有历史数据一次性塞进去,用矩阵运算算出结果。速度快得飞起,适合初步筛选信号。

核心思路:用pandas的向量化操作,避免循环。

import pandas as pd
import numpy as np

# 假设我们有一个价格DataFrame
df = pd.DataFrame({
    'close': [100, 102, 101, 103, 105, 104, 106, 108]
})

# 生成信号:收盘价突破5日均线
df['ma5'] = df['close'].rolling(5).mean()
df['signal'] = np.where(df['close'] > df['ma5'], 1, 0)

# 计算每日收益率
df['return'] = df['close'].pct_change()

# 策略收益率 = 信号 * 收益率(假设T+1成交)
df['strategy_return'] = df['signal'].shift(1) * df['return']

# 累计收益
df['cumulative_return'] = (1 + df['strategy_return']).cumprod()

print(df[['close', 'signal', 'strategy_return', 'cumulative_return']])

你看,几行代码就搞定了。但这里有个坑——未来函数。我曾经在项目里用shift(1)没注意,结果信号用了当天的数据,回测曲线漂亮得不像话,实盘直接崩了。嗯,这里要注意:信号必须基于历史数据生成,不能偷看未来。

避坑指南:我曾经用向量化回测做高频策略,发现收益率异常高。排查了半天,原来是pct_change()和shift()的顺序搞反了。记住:先shift信号,再计算收益率。

事件驱动回测:真实,但慢

事件驱动回测,说白了就是模拟真实交易过程。每个tick来了,你都得判断要不要下单、能不能成交、滑点多少。我建议做高频策略时,至少用事件驱动回测验证一遍。

为什么?因为向量化回测假设你总能以收盘价成交,但高频交易里,你看到的价和实际成交价可能差好几个tick。

class EventDrivenBacktest:
    def __init__(self, data, initial_capital=100000):
        self.data = data
        self.capital = initial_capital
        self.position = 0
        self.trades = []
        
    def on_tick(self, tick):
        # 每个tick到来时触发
        signal = self.generate_signal(tick)
        
        if signal == 1 and self.position == 0:
            # 买入,考虑滑点
            buy_price = tick['ask'] * 1.0001  # 0.01%滑点
            shares = self.capital // buy_price
            self.position = shares
            self.capital -= shares * buy_price
            self.trades.append(('buy', tick['timestamp'], buy_price, shares))
            
        elif signal == -1 and self.position > 0:
            # 卖出
            sell_price = tick['bid'] * 0.9999
            self.capital += self.position * sell_price
            self.position = 0
            self.trades.append(('sell', tick['timestamp'], sell_price, self.position))
    
    def generate_signal(self, tick):
        # 这里放你的信号逻辑
        # 比如:买一卖一价差超过阈值
        spread = tick['ask'] - tick['bid']
        if spread > 0.02:
            return 1
        return 0

你看,事件驱动回测里,你得处理订单簿、滑点、成交延迟。我做过一个统计:同样的策略,向量化回测年化收益30%,事件驱动回测只有12%。差距就在滑点和成交概率上。

个人经验:我建议先用向量化回测快速筛选信号,再用事件驱动回测精细验证。别一上来就搞事件驱动,调试起来太费时间。

性能评估:别只看收益率

很多人回测完一看年化50%,就觉得自己要发财了。醒醒,那可能是过拟合。我评估策略时,至少看这几个指标:

指标 含义 我的经验阈值
夏普比率 每单位风险获得的超额收益 高频策略至少 > 2.0
最大回撤 账户从峰值跌到谷底的最大幅度 不超过20%
胜率 盈利交易占比 高频策略40%-60%正常
盈亏比 平均盈利 / 平均亏损 至少 > 1.5
交易次数 总交易笔数 高频策略每天几百到几千笔

我一般用这个函数来算核心指标:

def evaluate_strategy(returns, risk_free_rate=0.02):
    # 年化收益率
    annual_return = returns.mean() * 252 * 100
    
    # 年化波动率
    annual_vol = returns.std() * np.sqrt(252) * 100
    
    # 夏普比率
    sharpe = (returns.mean() * 252 - risk_free_rate) / (returns.std() * np.sqrt(252))
    
    # 最大回撤
    cumulative = (1 + returns).cumprod()
    peak = cumulative.expanding().max()
    drawdown = (cumulative - peak) / peak
    max_drawdown = drawdown.min() * 100
    
    # 胜率
    win_rate = len(returns[returns > 0]) / len(returns[returns != 0])
    
    return {
        '年化收益率': f'{annual_return:.2f}%',
        '年化波动率': f'{annual_vol:.2f}%',
        '夏普比率': f'{sharpe:.2f}',
        '最大回撤': f'{max_drawdown:.2f}%',
        '胜率': f'{win_rate:.2%}'
    }

重要提醒:高频策略的夏普比率容易被高估。因为tick级数据噪声大,收益率序列自相关性强。我习惯用调整后的夏普比率,或者看Calmar比率(年化收益/最大回撤)。

回测框架的核心逻辑

下面这张图,是我做回测时脑子里始终绷着的一根弦:

回测框架核心流程 历史数据输入 信号生成 向量化回测 事件驱动回测 性能评估 策略报告 / 优化迭代

你看,数据进来后,两条路:向量化回测快,适合初步筛选;事件驱动回测慢,但更真实。最后都汇总到性能评估,不行就回去改信号。

我的习惯:先跑向量化回测,看夏普比率和最大回撤。如果夏普 < 1.5,直接放弃,别浪费时间。过了这关,再搞事件驱动回测,加上滑点和手续费。最后用样本外数据验证一遍。

做高频回测,最怕的就是过拟合。我见过有人把参数调到历史数据上完美拟合,结果实盘一周亏光。记住:回测是帮你排除烂策略的,不是帮你找完美策略的。


无相订单流研究社 微信Lucian808555