第二十八节:基差与回测框架——基差策略的回测设计、数据清洗与绩效评估

做基差交易这么多年,我越来越觉得,策略好不好,回测说了算。但回测这东西,说白了就是个双刃剑。你设计得不好,它骗你;你数据没洗干净,它坑你;你评估指标选错了,它误导你。今天咱们就聊聊,怎么把这个回测框架搭扎实。

一、基差策略的回测设计

回测设计,我个人的习惯是分三步走:定义信号、构建组合、执行模拟。每一步都有坑,咱们一个一个说。

1.1 信号定义:基差怎么变成交易指令?

基差策略的核心信号,无非就是基差的绝对水平、相对变化、或者统计上的异常值。举个例子:

  • 绝对基差阈值:当基差大于某个值(比如+200元/吨)时做空基差,小于某个值(比如-200元/吨)时做多基差。
  • 基差动量:基差连续N日扩大或收窄,触发入场。
  • 统计套利信号:基差偏离历史均值超过2倍标准差,做回归。

我在项目中遇到过一个问题:用绝对阈值做回测,结果发现大部分信号都集中在交割月前两周。为什么?因为那时候基差波动最大。后来我加了时间过滤条件——只交易非交割月的前两个月,信号质量明显提升。

关键点:信号定义要结合合约的生命周期。近月合约和远月合约的基差行为完全不同,别混为一谈。

1.2 组合构建:多空腿怎么配?

基差交易通常涉及两个腿:现货(或近月期货)和远月期货。回测时,你得明确:

  • 头寸比例:是1:1对冲,还是根据贝塔调整?
  • 换月规则:合约到期前多少天换仓?换仓成本怎么算?
  • 资金管理:每笔交易用多少保证金?杠杆倍数怎么设?

嗯,这里要注意:很多新手回测时忽略换月成本,结果实盘一跑,收益直接腰斩。我建议在回测中显式记录每次换月的滑点和手续费,哪怕只是估算,也比没有强。

1.3 执行模拟:滑点与流动性

回测里最骗人的就是“理想成交”。你想想看,基差策略往往交易的是非主力合约,流动性差得很。我见过一个策略,回测年化30%,实盘一跑只有5%。原因就是回测时没考虑滑点。

我的做法是:根据合约的日均成交量,动态设置滑点。比如成交量低于5000手的合约,滑点设为1个tick;低于1000手的,设为3个tick。这样回测结果才靠谱。

小技巧:回测时可以用“成交比例”来模拟流动性——假设你的订单只能吃掉盘口10%的深度,超出部分按下一个价位成交。

二、基差数据的清洗与处理

数据清洗,说白了就是“垃圾进,垃圾出”。基差数据尤其脏,因为涉及两个价格序列,任何一个出问题,基差就歪了。

2.1 常见的数据问题

问题类型 表现 原因
缺失值 某天基差为NaN 节假日、数据源中断
异常跳点 基差突然偏离正常范围10倍 数据录入错误、涨跌停
合约换月断层 基差在换月日出现跳跃 新旧合约价格不连续
时间戳不对齐 现货和期货数据时间不同步 数据源采集频率不一致

我曾经遇到过一个坑:某品种的现货数据是收盘价,期货数据是15:00的结算价,两者时间戳差了15分钟。结果基差序列里每天都有个“假波动”。后来我强制对齐到同一时间戳,问题才解决。

2.2 清洗流程:我的标准做法

  1. 去重与排序:按时间升序排列,删除重复行。
  2. 缺失值处理:连续缺失不超过3天,用线性插值;超过3天,直接删除该段。
  3. 异常值检测:用3倍标准差或百分位法(比如0.5%~99.5%)过滤。
  4. 换月调整:在换月日,用“后复权”方法调整基差序列,避免断层。
  5. 时间对齐:将现货和期货数据统一到同一时间频率(比如日频)。
警告:不要用“前复权”处理基差数据!前复权会改变历史基差的绝对水平,导致回测信号失真。一定要用后复权,或者干脆不做复权,只保留原始基差。

2.3 代码示例:基差数据清洗

import pandas as pd
import numpy as np

def clean_basis_data(df, price_col='close', basis_col='basis'):
    """
    基差数据清洗函数
    df: 包含日期、现货价格、期货价格的DataFrame
    """
    # 1. 去重排序
    df = df.sort_values('date').drop_duplicates(subset='date')
    
    # 2. 计算基差
    df[basis_col] = df['spot_price'] - df['futures_price']
    
    # 3. 缺失值处理(线性插值,最多3天)
    df[basis_col] = df[basis_col].interpolate(method='linear', limit=3)
    
    # 4. 异常值过滤(3倍标准差)
    mean = df[basis_col].mean()
    std = df[basis_col].std()
    df = df[(df[basis_col] > mean - 3*std) & (df[basis_col] < mean + 3*std)]
    
    # 5. 换月调整(后复权示例)
    # 假设换月日为 roll_date,调整因子为 roll_adjust
    # df['basis_adj'] = df[basis_col] - roll_adjust
    
    return df

三、基差策略的绩效评估

绩效评估,不能只看年化收益率。我见过太多人拿一个夏普比率2.0的策略当宝贝,结果实盘亏成狗。为什么?因为回测里的夏普比率是“纸面夏普”,没考虑实盘的各种摩擦。

3.1 核心指标:哪些必须看?

指标 计算公式 我的关注点
年化收益率 (总收益率+1)^(252/N) - 1 低于10%的策略,我基本不看
夏普比率 (年化收益 - 无风险利率) / 年化波动 大于1.5才算及格
最大回撤 峰值到谷值的最大跌幅 超过20%的策略,需要谨慎
卡玛比率 年化收益 / 最大回撤 大于2.0才算优秀
胜率 盈利交易次数 / 总交易次数 基差策略胜率通常在40%-60%
盈亏比 平均盈利 / 平均亏损 大于1.5才值得做

我个人习惯,还会加一个“资金曲线平滑度”的指标——说白了就是看最大回撤的持续时间。如果一个策略回撤了6个月还没回来,那它的逻辑可能已经失效了。

3.2 回测中的“幸存者偏差”陷阱

做基差回测,最容易犯的错误就是只回测活下来的合约。你想想看,如果一个品种的某个合约在交割前被强行平仓了,那它的基差数据就不完整。如果你只保留完整数据的合约,那回测结果就会偏乐观。

我的做法是:回测时包含所有合约,包括那些提前退市的。如果某个合约的数据不完整,就把它当作“亏损交易”计入回测。这样虽然保守,但更接近真实。

核心观点:回测不是为了证明策略有多牛,而是为了找出策略在什么情况下会死。所以,多想想“最坏情况”,少看看“最佳曲线”。

3.3 绩效归因:赚钱到底靠什么?

基差策略的收益来源,无非三个:

  • 基差回归收益:基差从偏离回归到均值,赚的是价差。
  • 展期收益:换月时,由于远近月价差结构带来的收益。
  • 方向性收益:现货或期货单边波动带来的意外收益(通常不是策略本意)。

我建议在绩效评估中,把这三部分拆开来看。怎么拆?很简单:

  • 基差回归收益 = 基差变化 × 头寸方向
  • 展期收益 = 每次换月时的价差变化
  • 方向性收益 = 总收益 - 基差回归收益 - 展期收益

如果发现方向性收益占比超过30%,那你的策略其实是个“伪基差策略”,本质是在赌方向。这时候就要小心了。

四、知识体系总览

下面这张图,是我对本章内容的总结。你可以把它当作一个检查清单,做回测时对照着看,能少踩不少坑。

基差策略回测框架 回测设计 数据清洗 绩效评估 子模块 • 信号定义:阈值/动量/统计套利 • 组合构建:多空比例/换月规则 • 执行模拟:滑点/流动性/手续费 子模块 • 缺失值处理:插值/删除 • 异常值检测:标准差/百分位 • 换月调整:后复权/时间对齐 子模块 • 核心指标:夏普/回撤/卡玛 • 幸存者偏差:包含退市合约 • 绩效归因:回归/展期/方向 核心原则 数据干净是底线,滑点真实是良心,归因清晰是水平 注:三个模块相互独立,但任何一个模块出问题,整个回测结果都不可信

好了,关于基差策略的回测框架,咱们就聊到这儿。数据清洗、回测设计、绩效评估,这三块缺一不可。下次你再跑回测的时候,不妨对照着这张图,一个一个检查过去。相信我,能帮你省下不少冤枉钱。

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