第27章:商品指数套利:ETF与期货指数的基差交易,展期收益捕获
各位同学,今天我们来聊一个很有意思的话题——商品指数套利。说白了,就是利用ETF和期货指数之间的价差来赚钱。我刚开始接触这个策略时,觉得它挺绕的,但一旦搞懂了背后的逻辑,你会发现它其实是个很稳定的收益来源。
什么是商品指数套利?
先问大家一个问题:你买商品ETF和直接买商品期货,最终收益一样吗?
答案是否定的。原因在于,ETF持有的是现货或近月合约,而期货指数反映的是远月合约的价格。这两者之间天然存在一个价差,我们称之为基差。
商品指数套利,就是利用这个基差来做文章。当ETF价格相对于期货指数被低估时,我们买入ETF、做空期货指数;反之则反向操作。听起来简单,但实际操作中坑不少。
核心逻辑: ETF价格 = 期货指数价格 + 基差。我们的目标就是捕捉基差的回归。
展期收益——真正的利润来源
这里我要重点讲一个概念:展期收益。你可能听过这个词,但未必真正理解它的威力。
商品期货合约是有到期日的。ETF为了保持对指数的跟踪,需要不断将即将到期的合约「展期」到下一个月份。这个展期过程,就会产生收益或亏损。
举个例子:假设当前近月合约价格是100元,远月合约价格是95元(这叫期货贴水,也叫backwardation)。ETF卖出近月、买入远月,每手就能赚5元的价差。这就是展期收益。
反过来,如果远月比近月贵(期货升水,contango),展期就会产生亏损。
我的经验: 我个人习惯在贴水结构明显的商品上做多ETF、做空期货指数。比如原油、铜这些品种,经常出现深度贴水,展期收益相当可观。
基差交易的三种模式
在实际操作中,我总结出三种常见的交易模式:
- 统计套利模式:基于历史基差的均值回归特性。当基差偏离到2个标准差以上时入场。
- 事件驱动模式:利用展期日、交割日等特殊时间点的基差异常波动。
- 趋势跟踪模式:当商品处于明显的贴水或升水结构时,顺势持有头寸。
这三种模式各有优劣。我个人比较喜欢第一种,因为它有明确的入场和出场信号,回测起来也相对可靠。
实战中的避坑指南
嗯,这里我要说几个我曾经踩过的坑:
避坑1:流动性陷阱
我曾经在某个小众商品ETF上做套利,结果发现期货指数流动性很好,但ETF成交量极低。一买就涨,一卖就跌,滑点吃掉了我所有利润。所以,做之前一定要检查ETF的日均成交额,低于1000万的尽量别碰。
避坑2:跟踪误差
ETF的净值并不总是等于指数价格。管理费、分红、申购赎回机制都会造成跟踪误差。我建议用ETF的IOPV(实时参考净值)而不是市价来做计算。
避坑3:展期时间不匹配
ETF的展期时间通常是固定的(比如每月第5个交易日),而期货指数的展期时间可能不同。这会导致基差出现短期异常。我的做法是:在展期前后3天内降低仓位。
代码实现:基差计算与信号生成
下面我给出一个简单的Python实现,用于计算基差并生成交易信号。
import pandas as pd
import numpy as np
import yfinance as yf
# 获取ETF和期货指数数据
def get_data(etf_ticker, futures_ticker, start_date, end_date):
etf = yf.download(etf_ticker, start=start_date, end=end_date)['Adj Close']
futures = yf.download(futures_ticker, start=start_date, end=end_date)['Adj Close']
return pd.DataFrame({'ETF': etf, 'Futures': futures})
# 计算基差
def calc_basis(df):
df['Basis'] = df['ETF'] - df['Futures']
df['Basis_Pct'] = df['Basis'] / df['Futures'] * 100
return df
# 生成交易信号
def generate_signals(df, entry_z=2.0, exit_z=0.5):
mean = df['Basis_Pct'].rolling(60).mean()
std = df['Basis_Pct'].rolling(60).std()
df['Z_Score'] = (df['Basis_Pct'] - mean) / std
df['Signal'] = 0
# 当Z-score超过2时做空基差(卖出ETF,买入期货)
df.loc[df['Z_Score'] > entry_z, 'Signal'] = -1
# 当Z-score低于-2时做多基差(买入ETF,卖出期货)
df.loc[df['Z_Score'] < -entry_z, 'Signal'] = 1
# 回归到0.5以内时平仓
df.loc[abs(df['Z_Score']) < exit_z, 'Signal'] = 0
return df
# 回测
def backtest(df, initial_capital=100000):
df['Position'] = df['Signal'].shift(1)
df['Return'] = df['Position'] * df['Basis'].pct_change()
df['Equity'] = initial_capital * (1 + df['Return']).cumprod()
return df
# 运行示例
data = get_data('GSG', 'SPGSCI', '2020-01-01', '2023-12-31')
data = calc_basis(data)
data = generate_signals(data)
results = backtest(data)
print(f"最终净值: {results['Equity'].iloc[-1]:.2f}")
print(f"年化收益率: {((results['Equity'].iloc[-1]/100000)**(1/3)-1)*100:.2f}%")
代码说明: 这里我用的是GSG(iShares商品ETF)和SPGSCI(标普高盛商品指数)作为示例。实际交易中,你需要根据具体品种调整参数。Z-score的窗口期我习惯用60天,但不同品种可能需要优化。
展期收益的量化计算
展期收益到底能贡献多少?我们来算一笔账。
| 品种 | 平均贴水率 | 展期频率 | 年化展期收益 |
|---|---|---|---|
| 原油 | -3.5% | 每月 | 约4.2% |
| 铜 | -2.1% | 每月 | 约2.5% |
| 黄金 | +0.8% | 每月 | 约-1.0% |
| 玉米 | -1.2% | 每季 | 约1.4% |
你看,原油的展期收益是最高的,但波动也大。黄金是升水结构,做多ETF反而会亏损展期收益。所以选品种很重要。
知识体系框架
为了让大家更直观地理解本章的知识结构,我画了一张图:
这张图把本章的核心逻辑串起来了。从基差出发,到展期收益,再到三种交易模式,最后落到实战要点和代码实现。你想想看,是不是一目了然?
最后说几句
商品指数套利这个策略,说白了就是赚「结构」的钱。只要贴水结构在,展期收益就在。但要注意,市场结构是会变化的。2020年原油暴跌时,WTI原油期货一度出现深度贴水,但随后迅速转为升水。如果你死扛着不做调整,可能会亏得很惨。
我个人习惯是:每周末检查一次持仓品种的期限结构,如果发现贴水率收窄到1%以下,就减仓一半。如果转为升水,直接清仓。这个纪律帮我躲过了好几次大坑。
好了,关于商品指数套利的内容就讲到这里。代码部分大家回去跑一跑,有问题随时交流。