第七章 回测框架搭建:向量化回测引擎实现、事件驱动回测引擎实现、滑点与手续费模拟

做基差交易,回测是绕不开的一关。

我见过太多人,策略逻辑写得天花乱坠,一上实盘就亏得底掉。为什么?因为回测框架没搭好。说白了,回测就是你的策略在历史数据上的「模拟飞行」。飞得好,不代表真能上天;飞得不好,那肯定摔得惨。

这一章,咱们就聊聊怎么搭一个靠谱的回测框架。我会把向量化引擎和事件驱动引擎都讲透,再聊聊滑点和手续费那些坑。嗯,都是我在实战中踩过的雷。

7.1 向量化回测引擎:快,但别太信它

向量化回测,说白了就是「批量计算」。你把历史数据全塞进去,一次性算出所有信号和收益。速度快得飞起,几秒钟就能跑完几年的数据。

我个人习惯用向量化引擎做初步筛选。比如,我要测试一个简单的跨期套利策略——当近月合约比远月合约便宜超过1%时开仓。用向量化引擎,几行代码就搞定。

import pandas as pd
import numpy as np

def vectorized_backtest(df, spread_threshold=0.01):
    # df 包含近月价格和远月价格
    df['spread'] = (df['near_price'] - df['far_price']) / df['far_price']
    df['signal'] = 0
    df.loc[df['spread'] < -spread_threshold, 'signal'] = 1  # 做多价差
    df.loc[df['spread'] > spread_threshold, 'signal'] = -1 # 做空价差
    
    # 计算每日收益
    df['daily_return'] = df['signal'].shift(1) * df['spread'].diff()
    df['cumulative_return'] = (1 + df['daily_return']).cumprod()
    
    return df

你看,就这么简单。但我要提醒你——向量化回测有个大坑:它假设所有交易都能在收盘价成交。现实中,你根本做不到。

⚠️ 我曾经踩过的坑:

有一次,我用向量化引擎跑了一个螺纹钢的跨期套利策略,年化收益高达40%。我兴奋得差点直接上实盘。还好多留了个心眼,用事件驱动引擎重新跑了一遍——结果年化收益只有12%。差距在哪?就在成交细节上。

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

事件驱动引擎,就是模拟真实交易环境。每一笔Tick数据进来,引擎都要判断:要不要开仓?要不要平仓?能不能成交?

它慢,但真实。你想想看,实盘交易不就是这样的吗?

事件驱动引擎的核心,是一个事件循环。我一般这么设计:

class EventDrivenBacktest:
    def __init__(self, data_handler, strategy, portfolio):
        self.data_handler = data_handler
        self.strategy = strategy
        self.portfolio = portfolio
        self.events = []
        
    def run(self):
        while True:
            # 1. 获取下一个数据事件
            event = self.data_handler.get_next_event()
            if event is None:
                break
                
            # 2. 策略根据事件生成信号
            signal = self.strategy.on_event(event)
            
            # 3. 如果有信号,生成订单
            if signal:
                order = self.portfolio.generate_order(signal)
                self.events.append(order)
                
            # 4. 模拟撮合
            fill_event = self.match_engine.match(order, event)
            
            # 5. 更新投资组合
            self.portfolio.update(fill_event)

这个框架看起来简单,但每个环节都有讲究。比如数据处理器,你要考虑是处理Tick数据还是分钟数据。我个人建议,做基差交易至少用分钟数据,Tick数据太吵了。

7.3 滑点模拟:别让理想主义害了你

滑点,就是你的预期成交价和实际成交价之间的差距。做基差交易,滑点尤其要命——因为价差本身就不大,滑点一吃,利润就没了。

我常用的滑点模型有三种:

模型类型 实现方式 适用场景
固定滑点 每笔交易固定扣除N个最小变动价位 流动性极好的品种(如股指期货)
比例滑点 按成交金额的固定比例扣除 流动性一般的品种
基于买卖价差的滑点 用历史买卖价差数据模拟 最真实,但数据难获取

💡 我的经验:做基差交易回测时,滑点至少设2个最小变动价位。别问我为什么,问就是吃过亏。有一次我设了1个最小变动价位的滑点,回测结果漂亮得很。结果实盘第一天,连续三笔交易都没吃到价差,全被滑点吃掉了。

7.4 手续费模拟:蚊子腿也是肉

手续费看起来不多,但基差交易是高频交易,一天可能来回好几次。累积下来,手续费能吃掉你20%的利润。

我一般这样模拟手续费:

def calculate_commission(trade_value, commission_rate=0.0003, min_commission=5):
    """
    计算手续费
    trade_value: 交易金额
    commission_rate: 费率,默认万分之三
    min_commission: 最低手续费,默认5元
    """
    commission = trade_value * commission_rate
    return max(commission, min_commission)

注意,不同品种的手续费结构不一样。股指期货是固定金额,商品期货是比例收费。还有,别忘了交易所的「平今仓」优惠——有些品种平今仓手续费是免的。

🔑 核心要点:

  • 向量化引擎适合快速筛选,但别用它做最终决策
  • 事件驱动引擎更真实,但要注意性能优化
  • 滑点至少设2个最小变动价位
  • 手续费要按品种特性分别设置
  • 回测结果至少要打八折,才是你实盘可能拿到的收益

7.5 回测框架的完整架构

说了这么多,咱们来看看一个完整的回测框架长什么样。我画了一张图,把核心模块和流程都标出来了。

基差交易回测框架架构 数据层 历史Tick数据 | 分钟数据 | 日线数据 | 合约信息 策略层 信号生成 | 仓位管理 | 风险控制 | 订单管理 执行层 滑点模拟 | 手续费计算 | 撮合引擎 | 成交记录 评估层 收益分析 | 风险指标 | 回撤统计 | 夏普比率

这个架构图,说白了就是四个层次:数据进来,策略决策,执行交易,评估结果。每一层都有它的坑,咱们前面都聊过了。

最后说一句:回测框架搭好了,只是第一步。真正考验人的,是怎么解读回测结果。我见过有人回测收益30%,兴奋得睡不着觉。结果仔细一看,最大回撤40%——这策略你敢用吗?

嗯,回测的事,咱们今天就聊到这儿。记住一句话:回测是工具,不是信仰。

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