第十五章:基差交易的策略回测框架

回测,说白了就是让策略在历史数据里跑一遍。我见过太多人,回测做得花里胡哨,一上实盘就崩。为什么?因为回测框架本身就有问题。今天咱们就聊聊,怎么搭一个靠谱的基差交易回测系统。

一、回测系统搭建:别小看这步

我个人习惯,回测系统分三层:数据层、策略层、执行层。别搞太复杂,够用就行。

核心原则:回测不是越精细越好,而是越接近真实交易越好。

1.1 数据层

基差交易需要两类数据:现货价格和期货价格。注意,这里有个坑——数据对齐。现货和期货的交易时间不一样,你直接用收盘价算基差,会出问题。

# 一个简单的数据对齐示例
import pandas as pd

def align_futures_spot(futures_df, spot_df):
    # 只取共同交易时段
    common_index = futures_df.index.intersection(spot_df.index)
    aligned = pd.DataFrame({
        'futures': futures_df.loc[common_index, 'close'],
        'spot': spot_df.loc[common_index, 'close']
    })
    # 计算基差
    aligned['basis'] = aligned['futures'] - aligned['spot']
    return aligned

小技巧:我建议用分钟级数据做回测,日线数据太粗糙,容易忽略盘中波动。当然,数据量会大很多,但值得。

1.2 策略层

策略层就是你的交易逻辑。基差交易常见的逻辑有:均值回归、趋势跟踪、套利价差。我个人偏爱均值回归,因为基差这东西,长期看确实会收敛。

class BasisTradingStrategy:
    def __init__(self, z_entry=2.0, z_exit=0.5):
        self.z_entry = z_entry  # 开仓阈值
        self.z_exit = z_exit    # 平仓阈值
        
    def generate_signals(self, basis_series):
        # 计算Z-score
        mean = basis_series.rolling(20).mean()
        std = basis_series.rolling(20).std()
        z_score = (basis_series - mean) / std
        
        # 生成信号
        signals = pd.Series(0, index=basis_series.index)
        signals[z_score > self.z_entry] = -1  # 做空基差
        signals[z_score < -self.z_entry] = 1  # 做多基差
        signals[abs(z_score) < self.z_exit] = 0  # 平仓
        
        return signals

1.3 执行层

执行层模拟真实交易。这里要考虑滑点、手续费、保证金。很多人回测赚钱,实盘亏钱,就是因为忽略了这些细节。

注意:基差交易通常涉及杠杆,回测时一定要考虑保证金占用和强制平仓风险。我曾经有个策略,回测年化50%,实盘第一周就爆仓了——因为没算保证金比例变化。

二、绩效评估指标:别只看收益率

回测做完了,怎么评价策略好不好?很多人只看年化收益率,这是大忌。我一般看这几个指标:

指标 计算公式 说明
夏普比率 (策略收益率 - 无风险利率) / 波动率 衡量风险调整后收益,>1算不错
最大回撤 峰值到谷值的最大跌幅 基差交易建议控制在15%以内
胜率 盈利交易次数 / 总交易次数 基差交易胜率通常不高,但盈亏比大
盈亏比 平均盈利 / 平均亏损 建议>2,否则很难赚钱
卡玛比率 年化收益率 / 最大回撤 我比较看重这个,反映收益质量

我的经验:基差交易策略,夏普比率能到1.5以上就算优秀了。别追求3.0、5.0那种,大概率是过拟合。

三、过拟合问题处理:回测最大的敌人

过拟合,说白了就是策略在历史数据上表现太好,但一到未来就失效。我见过最夸张的案例:有人回测年化200%,实盘亏了80%。

3.1 过拟合的常见表现

  • 参数稍微一改,绩效就大幅波动
  • 不同时间段回测结果差异巨大
  • 策略逻辑复杂,用了太多条件判断
  • 样本外测试表现远差于样本内

3.2 如何避免过拟合

我总结了几个实用方法:

  1. 交叉验证:把数据分成多段,轮流做训练集和测试集。别只用一段数据来回测。
  2. 参数敏感性分析:改变参数,看绩效是否稳定。如果参数稍微一动就崩,那策略有问题。
  3. 样本外测试:留出最后20%的数据不做任何优化,只做最终验证。
  4. 简化策略:能用3个参数解决的问题,别用10个。奥卡姆剃刀在量化里同样适用。
# 参数敏感性分析示例
def parameter_sensitivity(strategy, param_range):
    results = []
    for param in param_range:
        # 修改策略参数
        strategy.set_param(param)
        # 运行回测
        perf = strategy.backtest()
        results.append({
            'param': param,
            'sharpe': perf['sharpe'],
            'max_drawdown': perf['max_drawdown']
        })
    return pd.DataFrame(results)

# 如果结果波动太大,说明策略不稳定

避坑指南:我曾经犯过一个错误——用全量数据优化参数,结果策略在样本外表现极差。后来我学乖了,一定留出样本外数据,而且样本外测试只做一次,不做二次优化。

四、回测框架的核心逻辑

下面这张图,是我自己总结的回测框架核心流程。你看一眼,基本就明白整体架构了。

数据层 现货/期货价格对齐 策略层 信号生成/仓位管理 执行层 滑点/手续费模拟 绩效评估 夏普/回撤/胜率/盈亏比 过拟合检测 交叉验证/参数敏感性分析 反馈优化 基差交易回测框架核心流程

嗯,这张图基本把回测框架的闭环讲清楚了。数据进来,策略跑,执行模拟,绩效评估,最后检测过拟合。如果过拟合严重,就回去调整策略,再跑一遍。

我的建议:别想着一步到位。先搭一个最简单的框架,跑通流程,再慢慢加细节。我刚开始做回测时,第一版只有50行代码,但能跑出结果。后来才逐步加入滑点、手续费、保证金这些。

五、实战中的几个坑

最后,分享几个我在实战中踩过的坑:

  • 幸存者偏差:回测时用了现在的合约列表,但历史上有些合约已经退市了。这会导致回测结果偏乐观。
  • 前视偏差:不小心用了未来数据。比如用当天的收盘价计算信号,但信号应该在开盘时生成。
  • 过度优化:为了追求高夏普,把参数调得刚刚好。结果换个时间段就失效。

记住:回测的目的是发现策略的缺陷,而不是证明策略有多好。如果你回测结果完美无缺,那大概率是哪里出了问题。

好了,回测框架就聊到这儿。框架搭好了,下一步就是跑数据、调参数、反复验证。这个过程很枯燥,但值得。毕竟,实盘的钱不是大风刮来的。

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