第二十九讲:曲面回测框架——历史数据回放、策略绩效评估、稳健性检验

各位同学,今天我们来聊聊回测。说实话,我见过太多人把回测当成「跑个代码看曲线」的简单活儿。但真正做过实盘的人都知道——回测框架设计得好不好,直接决定了你是在「验证策略」还是在「自欺欺人」。

曲面策略的回测,比普通期权策略要复杂一个量级。为什么?因为曲面本身是动态的,你不仅要判断方向,还要判断曲面的形态变化。嗯,这里我们先理清核心逻辑。

一、回测框架的核心架构

我个人习惯把曲面回测框架拆成三层:

  • 数据层:历史曲面数据、标的价格、无风险利率
  • 策略层:信号生成、交易执行、仓位管理
  • 评估层:绩效指标、风险度量、稳健性检验

说白了,就是「喂数据→跑策略→看结果」三步走。但每一步都有坑。

核心原则:回测必须模拟真实交易环境。包括交易成本、滑点、流动性限制。我曾经见过一个策略回测年化50%,实盘直接亏20%——就是因为没考虑曲面报价的买卖价差。

下面这张图是我自己常用的回测流程,你可以参考一下:

曲面回测框架流程图 数据层 历史曲面数据 标的价格 / 利率 策略层 信号生成 → 交易执行 仓位管理 评估层 绩效指标 稳健性检验 历史数据回放循环 数据预处理 清洗 / 插值 / 对齐 信号生成 曲面形态 / 期限结构 绩效评估 夏普 / 最大回撤 / 胜率 稳健性检验

二、历史数据回放——别让「未来信息」污染了你的回测

这是回测里最容易犯的错误,没有之一。我刚开始做曲面策略时,就踩过这个坑——用当天的数据去预测当天的信号,结果回测曲线漂亮得像假的一样。嗯,它确实是假的。

正确的做法是:滚动窗口回放。每个交易日,你只能用截止到前一个交易日的数据来生成信号。

我的经验:建议用「事件驱动」的方式来回放。每次只推进一个时间步,更新曲面数据,然后判断是否触发交易信号。别用向量化回测,那玩意儿在曲面策略里容易出bug。

下面是一个简单的回放框架示例:

class SurfaceBacktestEngine:
    def __init__(self, surface_data, spot_data, r=0.03):
        self.surface_data = surface_data  # 历史曲面数据
        self.spot_data = spot_data        # 标的价格序列
        self.r = r                        # 无风险利率
        self.portfolio = Portfolio()      # 投资组合
        self.trades = []                  # 交易记录

    def run(self, strategy_func):
        """滚动回放主循环"""
        for i in range(1, len(self.surface_data)):
            # 只使用到 i-1 的数据
            current_surface = self.surface_data[:i]
            current_spot = self.spot_data[:i]

            # 生成信号
            signal = strategy_func(current_surface, current_spot)

            # 执行交易(假设在 i 时刻以收盘价成交)
            if signal is not None:
                self.execute_trade(signal, i)

            # 每日盯市
            self.portfolio.mark_to_market(
                self.surface_data[i],
                self.spot_data[i]
            )

    def execute_trade(self, signal, idx):
        """执行交易,考虑滑点和交易成本"""
        # 这里要模拟真实的成交价格
        fill_price = self.estimate_fill_price(signal, idx)
        self.trades.append({
            'time': idx,
            'signal': signal,
            'fill_price': fill_price,
            'cost': self.calc_transaction_cost(signal)
        })
        self.portfolio.update(signal, fill_price)

三、策略绩效评估——别只看收益率曲线

很多人回测完只看两个指标:总收益率和最大回撤。这远远不够。曲面策略的特殊性在于,它的风险暴露是动态变化的。

我个人习惯用下面这套指标体系:

指标类别 具体指标 说明
收益类 年化收益率、累计收益率 基础指标,但别单独看
风险类 年化波动率、最大回撤、VaR 曲面策略的波动率往往不恒定
风险调整类 夏普比率、卡玛比率、索提诺比率 夏普比率在期权策略里可能失真
曲面特定类 Vega暴露、Gamma暴露、期限结构暴露 这是曲面策略独有的风险维度
交易统计类 胜率、盈亏比、平均持仓周期 帮助理解策略的交易逻辑

注意:曲面策略的夏普比率容易被「虚高」。为什么?因为期权策略的收益分布往往是非正态的,有肥尾特征。我建议同时看索提诺比率,它只考虑下行波动。

举个例子,我曾经回测一个曲面套利策略,夏普比率高达2.8,但索提诺比率只有1.2。后来发现,这个策略在大部分时间赚小钱,但偶尔会大亏——这就是典型的「卖期权」特征。

四、稳健性检验——别让你的策略「过拟合」

这是整个回测框架里最重要的一环,也是最容易被忽视的。说白了,稳健性检验就是回答一个问题:你的策略是发现了真正的市场规律,还是只是拟合了历史噪音?

我常用的稳健性检验方法有四种:

  1. 参数敏感性分析:改变策略参数(比如阈值、窗口长度),看绩效是否稳定。如果参数稍微一变,绩效就大幅波动,那这个策略基本不可用。
  2. 不同市场环境测试:把回测期分成牛市、熊市、震荡市、高波动市、低波动市,分别看绩效。曲面策略在不同市场环境下的表现差异可能很大。
  3. 蒙特卡洛模拟:对历史数据进行重采样,生成多条「合成历史」,然后跑策略。如果大部分合成路径上策略都赚钱,那说明策略有统计显著性。
  4. 样本外测试:把数据分成训练集和测试集。在训练集上优化参数,在测试集上验证。这是最基础的稳健性检验,但很多人连这一步都不做。

避坑指南:我曾经犯过一个错误——在回测时用了全样本数据来优化参数,然后说「这个策略很稳健」。结果实盘时市场风格一变,策略直接失效。后来我学乖了,每次回测都留出最后20%的数据做样本外测试。

下面是一个参数敏感性分析的代码示例:

def parameter_sensitivity_test(strategy_func, param_grid):
    """
    对策略参数进行网格搜索,分析绩效稳定性
    """
    results = []
    for param_name, param_values in param_grid.items():
        for value in param_values:
            # 设置参数
            setattr(strategy_func, param_name, value)

            # 运行回测
            engine = SurfaceBacktestEngine(surface_data, spot_data)
            engine.run(strategy_func)

            # 记录绩效
            perf = engine.calc_performance_metrics()
            results.append({
                'param': param_name,
                'value': value,
                'sharpe': perf['sharpe_ratio'],
                'max_drawdown': perf['max_drawdown'],
                'win_rate': perf['win_rate']
            })

    # 分析参数变化对绩效的影响
    for param_name in param_grid.keys():
        param_results = [r for r in results if r['param'] == param_name]
        sharpe_values = [r['sharpe'] for r in param_results]

        # 计算夏普比率的标准差
        sharpe_std = np.std(sharpe_values)
        if sharpe_std > 0.5:
            print(f"警告:参数 {param_name} 对绩效影响较大,策略可能不稳定")
        else:
            print(f"参数 {param_name} 对绩效影响较小,策略较稳健")

    return results

五、实战中的几个关键细节

最后,分享几个我在实战中总结的经验:

  • 交易成本别设太低:曲面策略通常涉及多个期权合约,交易成本会显著影响净收益。我一般按每张合约2-5元来估算,再加上滑点。
  • 注意数据频率:曲面数据通常是日频的,但日内波动可能很大。如果你的策略是日频调仓,那没问题。如果是高频策略,需要更高频的曲面数据——但说实话,个人投资者很难拿到高质量的日内曲面数据。
  • 别忽略流动性:深度虚值或深度实值的期权流动性很差。回测时如果用了这些合约,实盘可能根本成交不了。我建议在回测中过滤掉流动性不足的合约。
  • 滚动展期要模拟真实:曲面策略经常需要展期(比如从近月合约滚动到远月)。展期时的价差和成本,一定要在回测中模拟出来。

我的习惯:每次回测完,我都会手动检查几笔交易的成交价格是否合理。比如,某个信号触发了,但当时的市场深度是否足够?如果不够,这笔交易在实盘里可能无法按回测价格成交。这种「人工抽检」虽然费时间,但能发现很多自动化回测发现不了的问题。

好了,关于曲面回测框架的核心内容就这些。记住,回测不是为了证明你的策略有多牛,而是为了发现你的策略在什么情况下会亏钱。把亏钱的情况想清楚了,实盘才能活得久。


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