第十三章:回测框架搭建:历史数据回测、绩效评估指标
做跨品种套利,最怕什么?
怕策略在实盘里跑着跑着就崩了。我见过太多人,拿着一个漂亮的回测曲线就敢上真金白银,结果三个月后亏得连手续费都付不起。说白了,回测框架就是你的策略「试金石」。今天咱们就聊聊,怎么搭一个靠谱的回测系统,以及哪些指标才是真正有用的。
13.1 回测框架的核心设计思路
我个人习惯把回测框架拆成三层:数据层、策略层、评估层。数据层负责喂历史行情,策略层执行交易逻辑,评估层算绩效。这三层各司其职,互不干扰。
嗯,这里要注意一点:回测框架不是越复杂越好。我见过有人把回测系统写得比策略本身还长,结果调试起来痛苦得要命。你想想看,一个简单的跨品种套利策略,核心逻辑可能就几十行代码,回测框架却搞了上千行,这不是本末倒置吗?
核心原则:回测框架应该像一把手术刀,精准、简洁、可复用。不要为了炫技而堆砌功能。
13.2 历史数据回测的完整流程
回测流程其实就五步,但每一步都有坑。我一个个说。
- 数据清洗与对齐——不同品种的交易时间、节假日、除权除息都不一样。我曾经因为没对齐时间戳,回测结果比实盘高了20%,查了三天才发现是数据错位了。
- 信号生成——根据波动率曲面的形态,计算套利信号。比如曲面陡峭时做多近月、做空远月。
- 交易执行模拟——考虑滑点、手续费、冲击成本。别小看这些,它们能吃掉你一半利润。
- 持仓管理——保证金、杠杆、强制平仓线。跨品种套利虽然风险低,但极端行情下照样爆仓。
- 绩效统计——算收益率、夏普比率、最大回撤等。
下面这张图是我自己画的一个回测框架流程图,你可以参考一下:
13.3 绩效评估指标体系
回测跑完了,怎么判断策略好不好?光看收益率可不行。我见过有人年化50%的回测曲线,结果最大回撤40%,这种策略你敢用吗?
下面这几个指标,是我每次回测必看的:
| 指标名称 | 计算公式 | 我的解读 |
|---|---|---|
| 年化收益率 | (最终净值 / 初始净值)^(252/交易天数) - 1 | 别只看这个,它可能是虚高的。我习惯同时看月度收益率分布,如果某个月特别高,那大概率是运气。 |
| 夏普比率 | (年化收益率 - 无风险利率) / 年化波动率 | 跨品种套利一般夏普在1.5以上才算合格。低于1的,说明风险调整后收益不怎么样。 |
| 最大回撤 | 净值曲线从峰值到谷底的最大跌幅 | 这个指标我特别看重。跨品种套利理论上回撤小,但如果超过5%,就要检查是不是逻辑有问题。 |
| 胜率 | 盈利交易次数 / 总交易次数 | 胜率高不代表赚钱。我有个策略胜率只有45%,但盈亏比3:1,照样赚钱。 |
| 盈亏比 | 平均盈利 / 平均亏损 | 跨品种套利一般盈亏比在2以上比较理想。低于1.5的,建议重新审视策略。 |
| 卡玛比率 | 年化收益率 / 最大回撤 | 这个指标比夏普更直观。我一般要求卡玛比率大于3,低于2的坚决不上实盘。 |
我的小技巧:回测时一定要做「滚动回测」。比如用过去3年数据训练参数,然后滚动向前测试1年。这样能避免过拟合。我曾经用全样本回测跑出夏普2.8的策略,滚动回测直接降到1.2,差点被坑。
13.4 代码示例:一个简单的回测框架
下面是我自己写的一个回测框架核心代码,去掉了业务逻辑,只保留骨架。你可以直接拿去改。
import pandas as pd
import numpy as np
class BacktestEngine:
def __init__(self, data, initial_capital=1000000):
self.data = data
self.capital = initial_capital
self.positions = {}
self.trades = []
self.equity_curve = []
def run(self, strategy_func):
"""执行回测"""
for i in range(len(self.data)):
# 获取当前行情
current_data = self.data.iloc[:i+1]
# 调用策略函数生成信号
signal = strategy_func(current_data)
# 执行交易
if signal is not None:
self.execute_trade(signal, current_data.iloc[-1])
# 记录净值
self.equity_curve.append(self.get_net_value(current_data.iloc[-1]))
return self.calculate_performance()
def execute_trade(self, signal, price):
"""模拟交易执行"""
# 这里加入滑点、手续费等
slippage = 0.0001 # 万分之一滑点
commission = 0.0003 # 万分之三手续费
# 实际交易逻辑...
pass
def calculate_performance(self):
"""计算绩效指标"""
equity = pd.Series(self.equity_curve)
returns = equity.pct_change().dropna()
# 年化收益率
annual_return = (equity.iloc[-1] / equity.iloc[0]) ** (252 / len(equity)) - 1
# 夏普比率
sharpe = np.sqrt(252) * returns.mean() / returns.std()
# 最大回撤
peak = equity.expanding().max()
drawdown = (equity - peak) / peak
max_drawdown = drawdown.min()
return {
'annual_return': annual_return,
'sharpe_ratio': sharpe,
'max_drawdown': max_drawdown,
'total_trades': len(self.trades)
}
避坑指南:我曾经在回测里忘了加滑点,结果实盘第一天就亏了2%。记住,回测时滑点至少要设万分之一,如果是流动性差的品种,设万分之三也不过分。另外,手续费别用交易所标准费率,要用你实际能拿到的费率,这差别很大。
13.5 回测中常见的陷阱
做回测这么多年,我踩过的坑能写一本书。这里挑几个最常见的说说:
- 前视偏差——用未来数据预测过去。比如用当天的收盘价来生成当天的交易信号。这错误我犯过,回测曲线漂亮得不像话,实盘直接打回原形。
- 幸存者偏差——只回测现在还活着的品种。那些退市了的、被合并了的,它们的表现往往更差。忽略它们,你的回测结果会虚高。
- 过度优化——为了追求高夏普,把参数调得刚刚好。这种策略换个时间段就废了。我一般要求策略在至少3个不同市场环境下表现稳定,才敢用。
- 忽略交易成本——跨品种套利往往需要频繁调仓,交易成本累积起来很可观。我见过一个策略,不算成本年化15%,算上成本直接变负的。
好了,回测框架这块就聊这么多。记住一句话:回测是用来发现问题的,不是用来证明策略牛逼的。如果你跑完回测发现一堆问题,恭喜你,你离赚钱又近了一步。