第十三章:回测框架搭建:历史数据回测、绩效评估指标

做跨品种套利,最怕什么?

怕策略在实盘里跑着跑着就崩了。我见过太多人,拿着一个漂亮的回测曲线就敢上真金白银,结果三个月后亏得连手续费都付不起。说白了,回测框架就是你的策略「试金石」。今天咱们就聊聊,怎么搭一个靠谱的回测系统,以及哪些指标才是真正有用的。

13.1 回测框架的核心设计思路

我个人习惯把回测框架拆成三层:数据层、策略层、评估层。数据层负责喂历史行情,策略层执行交易逻辑,评估层算绩效。这三层各司其职,互不干扰。

嗯,这里要注意一点:回测框架不是越复杂越好。我见过有人把回测系统写得比策略本身还长,结果调试起来痛苦得要命。你想想看,一个简单的跨品种套利策略,核心逻辑可能就几十行代码,回测框架却搞了上千行,这不是本末倒置吗?

核心原则:回测框架应该像一把手术刀,精准、简洁、可复用。不要为了炫技而堆砌功能。

13.2 历史数据回测的完整流程

回测流程其实就五步,但每一步都有坑。我一个个说。

  1. 数据清洗与对齐——不同品种的交易时间、节假日、除权除息都不一样。我曾经因为没对齐时间戳,回测结果比实盘高了20%,查了三天才发现是数据错位了。
  2. 信号生成——根据波动率曲面的形态,计算套利信号。比如曲面陡峭时做多近月、做空远月。
  3. 交易执行模拟——考虑滑点、手续费、冲击成本。别小看这些,它们能吃掉你一半利润。
  4. 持仓管理——保证金、杠杆、强制平仓线。跨品种套利虽然风险低,但极端行情下照样爆仓。
  5. 绩效统计——算收益率、夏普比率、最大回撤等。

下面这张图是我自己画的一个回测框架流程图,你可以参考一下:

数据层 历史行情 · 清洗对齐 策略层 信号生成 · 交易执行 评估层 绩效统计 · 归因分析 迭代优化 回测执行详细流程 数据加载 信号计算 模拟交易 绩效统计 参数调优 · 滚动回测

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%,算上成本直接变负的。

好了,回测框架这块就聊这么多。记住一句话:回测是用来发现问题的,不是用来证明策略牛逼的。如果你跑完回测发现一堆问题,恭喜你,你离赚钱又近了一步。


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