8. 回测框架搭建:向量化回测引擎设计,考虑交易成本与滑点
回测,说白了就是让策略在历史数据上跑一遍。但这里有个坑——很多人跑出来的曲线漂亮得不行,实盘一上就崩。为什么?因为没把交易成本和滑点算进去。
我个人习惯,回测框架一定要从第一天就考虑这两个东西。否则你看到的收益率,全是幻觉。
8.1 向量化 vs 事件驱动:选哪个?
回测引擎分两种:向量化和事件驱动。我刚开始做的时候,两种都写过。后来发现,对于基差套利这种策略,向量化完全够用,而且快得多。
向量化回测的核心思想:用数组运算代替循环。你想想看,一次算完所有信号,一次算完所有持仓,一次算完所有收益。速度比事件驱动快几个数量级。
但要注意,向量化有个前提——你的策略逻辑必须能用矩阵运算表达。基差套利恰好满足这个条件。
- 向量化:一次性处理所有数据,速度快,适合批量策略验证
- 事件驱动:逐笔模拟,更真实,但慢,适合高频或复杂逻辑
8.2 交易成本怎么算?
交易成本不只是手续费。我见过有人只算万二的佣金,结果实盘亏得底掉。真正的交易成本包括三块:
| 成本类型 | 说明 | 典型值(商品期货) |
|---|---|---|
| 手续费 | 交易所+期货公司收取 | 万分之0.5 ~ 万分之3 |
| 滑点 | 实际成交价与信号价的偏差 | 0.5 ~ 2个tick |
| 冲击成本 | 大单对市场价格的推动 | 视流动性而定 |
嗯,这里要注意。滑点是最容易被低估的。我在项目中遇到过,明明信号价差是10个点,结果一开仓,实际成交价差只有8个点。两个点就这么没了。
8.3 向量化回测引擎的核心设计
直接上代码吧。这是我常用的一个简化版向量化回测框架。注意看交易成本和滑点是怎么嵌入的。
import numpy as np
import pandas as pd
class VectorizedBacktester:
def __init__(self, data, signal_func,
fee_rate=0.0003, # 手续费率
slippage=1, # 滑点(tick数)
tick_size=1): # 最小变动价位
self.data = data
self.signal_func = signal_func
self.fee_rate = fee_rate
self.slippage = slippage
self.tick_size = tick_size
def run(self):
# 生成信号(向量化)
signals = self.signal_func(self.data)
# 计算持仓(考虑滑点后的实际成交价)
# 开仓价 = 信号价 + 滑点 * 方向
entry_price = self.data['close'] + \
self.slippage * self.tick_size * signals
# 计算交易成本
trade_cost = entry_price * self.fee_rate * abs(signals.diff())
# 计算收益(考虑成本)
returns = signals.shift(1) * \
(self.data['close'].pct_change()) - trade_cost
# 累计收益
cumulative_returns = (1 + returns).cumprod()
return cumulative_returns
你看,核心就几行。但关键在 entry_price 那行——滑点直接加在成交价上。方向为正(做多)就加,为负(做空)就减。这才是真实成交价。
8.4 滑点模型:别用固定值
固定滑点虽然简单,但不够真实。我后来改成了动态滑点模型——根据流动性来调整。
def dynamic_slippage(volume, avg_volume, base_slippage=1):
"""
动态滑点模型
volume: 当前成交量
avg_volume: 平均成交量
base_slippage: 基础滑点(tick数)
"""
# 流动性越差,滑点越大
liquidity_ratio = volume / avg_volume
if liquidity_ratio < 0.5:
return base_slippage * 2
elif liquidity_ratio < 1.0:
return base_slippage * 1.5
else:
return base_slippage
为什么会这样?你想想看,流动性差的时候,你的单子很难找到对手盘。我曾经在某个冷门合约上吃过亏,回测时滑点设1个tick,实盘直接滑了3个tick。从那以后,我再也不敢用固定滑点了。
8.5 完整的回测流程
一个完整的向量化回测,应该包含以下步骤:
- 数据准备:清洗、对齐、处理缺失值
- 信号生成:计算基差、生成开平仓信号
- 成本计算:手续费 + 滑点 + 冲击成本
- 收益计算:考虑成本后的净值曲线
- 绩效评估:夏普比率、最大回撤、胜率等
shift(1) 把信号往后挪一天。
8.6 绩效评估指标
跑完回测,不能只看收益率曲线。我一般会看这几个指标:
| 指标 | 公式 | 合格线 |
|---|---|---|
| 年化收益率 | 累计收益^(252/天数) - 1 | > 10% |
| 夏普比率 | (收益率 - 无风险利率) / 波动率 | > 1.5 |
| 最大回撤 | 峰值到谷底的最大跌幅 | < 15% |
| 胜率 | 盈利交易次数 / 总交易次数 | > 50% |
注意,这些指标都是在扣除交易成本之后算的。如果扣除成本后夏普比率还能到1.5以上,这个策略才值得上实盘。
8.7 回测的常见陷阱
最后,说几个我踩过的坑:
- 前视偏差:用未来数据生成信号。比如用当天的收盘价算当天的信号,这是作弊。
- 幸存者偏差:只回测现在还活着的合约。退市的合约也要包含进去。
- 过度优化:参数调得刚刚好,换段时间就失效。我一般会做样本外测试。
好了,向量化回测框架的核心就这些。说白了,就是要把交易成本和滑点当成策略的一部分来设计。你想想看,如果回测时都不考虑这些,实盘怎么可能赚钱?
框架搭好了,下一步就是往里面填策略逻辑。记住,回测的目的是发现bug,不是证明策略牛逼。能发现问题的回测,才是好回测。