第十五章:基差交易的策略回测框架
回测,说白了就是让策略在历史数据里跑一遍。我见过太多人,回测做得花里胡哨,一上实盘就崩。为什么?因为回测框架本身就有问题。今天咱们就聊聊,怎么搭一个靠谱的基差交易回测系统。
一、回测系统搭建:别小看这步
我个人习惯,回测系统分三层:数据层、策略层、执行层。别搞太复杂,够用就行。
核心原则:回测不是越精细越好,而是越接近真实交易越好。
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 如何避免过拟合
我总结了几个实用方法:
- 交叉验证:把数据分成多段,轮流做训练集和测试集。别只用一段数据来回测。
- 参数敏感性分析:改变参数,看绩效是否稳定。如果参数稍微一动就崩,那策略有问题。
- 样本外测试:留出最后20%的数据不做任何优化,只做最终验证。
- 简化策略:能用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行代码,但能跑出结果。后来才逐步加入滑点、手续费、保证金这些。
五、实战中的几个坑
最后,分享几个我在实战中踩过的坑:
- 幸存者偏差:回测时用了现在的合约列表,但历史上有些合约已经退市了。这会导致回测结果偏乐观。
- 前视偏差:不小心用了未来数据。比如用当天的收盘价计算信号,但信号应该在开盘时生成。
- 过度优化:为了追求高夏普,把参数调得刚刚好。结果换个时间段就失效。
记住:回测的目的是发现策略的缺陷,而不是证明策略有多好。如果你回测结果完美无缺,那大概率是哪里出了问题。
好了,回测框架就聊到这儿。框架搭好了,下一步就是跑数据、调参数、反复验证。这个过程很枯燥,但值得。毕竟,实盘的钱不是大风刮来的。