第七章 回测框架搭建:向量化回测引擎实现、事件驱动回测引擎实现、滑点与手续费模拟
做基差交易,回测是绕不开的一关。
我见过太多人,策略逻辑写得天花乱坠,一上实盘就亏得底掉。为什么?因为回测框架没搭好。说白了,回测就是你的策略在历史数据上的「模拟飞行」。飞得好,不代表真能上天;飞得不好,那肯定摔得惨。
这一章,咱们就聊聊怎么搭一个靠谱的回测框架。我会把向量化引擎和事件驱动引擎都讲透,再聊聊滑点和手续费那些坑。嗯,都是我在实战中踩过的雷。
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 回测框架的完整架构
说了这么多,咱们来看看一个完整的回测框架长什么样。我画了一张图,把核心模块和流程都标出来了。
这个架构图,说白了就是四个层次:数据进来,策略决策,执行交易,评估结果。每一层都有它的坑,咱们前面都聊过了。
最后说一句:回测框架搭好了,只是第一步。真正考验人的,是怎么解读回测结果。我见过有人回测收益30%,兴奋得睡不着觉。结果仔细一看,最大回撤40%——这策略你敢用吗?
嗯,回测的事,咱们今天就聊到这儿。记住一句话:回测是工具,不是信仰。