如果你只做过股票,第一次看商品期货曲线会愣一下:近月合约和远月合约价格不一样,而且谁高谁低会反过来。
- Contango(升水):远月 > 近月。你持有近月、到期滚仓到远月,等于高价买、低价卖,每滚一次亏一次——这是持有商品的”损耗”。
- Backwardation(贴水):近月 > 远月。滚仓时低价买、高价卖,每次换月白赚一笔——这是商品独有的”展期收益”(roll yield / roll return)。
Gorton 和 Rouwenhorst(2006)那篇 Facts and Fantasies about Commodity Futures 把这件事钉死:过去几十年,做多贴水商品、做空升水商品的横截面组合,年化收益不输股票、且和股票低相关。本文用 8 个商品、25 年月度面板从零复现这套机制,并把”期限结构信号”和”12 月动量”拆开对比。

一、期限结构的两种形态#
期货曲线(不同到期月的合约价格连成的线)只有两种”健康”形态,区别就在近月相对远月的位置:

直觉上,贴水往往出现在”现货紧俏、大家宁愿现在拿货”的商品(如原油地缘紧张、铜供需偏紧),而升水出现在”现在不缺、囤货成本低”的商品(如天然气库存充裕)。这种结构性价差,正是可交易的信号。
展期收益的定义很干净:
Roll > 0(近月贵)= 贴水 backwardation = 持有赚展期;Roll < 0(远月贵)= 升水 contango = 持有亏展期。
二、从零合成面板:把”展期”写进收益#
为了干净地检验,我们构造一个商品月度因子模型。关键设定(刻意植入,让信号有意义):
- 每商品漂移
mu与展期偏置ROLL_BIAS正相关——贴水商品天然更易走牛(现货紧 + 滚仓赚),这样展期信号才有正向预测力; - 共同商品因子 + 特质噪声,单品种月度波动 ≈ 26% 年化(贴近真实);
import numpy as np
np.random.seed(20260717)
COMS = ["WTI原油","布伦特","黄金","铜","天然气","大豆","玉米","白银"]
ROLL_BIAS = np.array([0.012, 0.010, 0.006, 0.009, -0.022, -0.005, -0.004, 0.004])
T, K = 300, len(COMS) # 300 月 ≈ 25 年
mu = 0.03/12 + 0.8 * ROLL_BIAS # 漂移与展期正相关: 贴水商品更易走牛
comm_ret = np.random.normal(0.05/12, 0.025, T) # 共同商品因子
beta = np.random.uniform(0.7, 1.3, K)
ret = np.zeros((T, K))
for t in range(1, T):
innov = np.random.normal(mu, 0.075, K) # 单品种特质 ≈ 26% ann vol
ret[t] = beta * comm_ret[t] + innov
log_spot = np.cumsum(np.log1p(ret), axis=0)
log_far = log_spot + np.random.normal(0, 0.003, (T, K))
roll = ROLL_BIAS + np.random.normal(0, 0.004, (T, K)) # 展期=常偏+噪声
log_near = log_far + roll
ret_near = np.diff(np.exp(log_near), axis=0) / np.exp(log_near)[:-1]python注意 roll 是每期独立观测(不是用 t 时刻近端收益去当 t 时刻展期信号),这样信号和未来收益之间只有”通过偏置”建立的真实联系,不会被同一笔数字的自我相关美化。
三、两个信号:展期 vs 动量#
定义横截面多空:每月按排序分做多 top 25% / 做空 bottom 25%,持有 3 月、等权:
mom_score = np.zeros((T, K))
for t in range(12, T):
mom_score[t] = log_spot[t] - log_spot[t - 12] # 12 月动量分
def ls_by_rank(score, step=3, frac=0.25):
rets = []; m = 12
while m + step < T:
sc = score[m]
order = np.argsort(sc)
k = max(1, int(round(frac * K)))
longs, shorts = order[-k:], order[:k]
rl = (np.exp(log_near[m+step, longs]) / np.exp(log_near[m, longs]) - 1.0).mean()
rs = (np.exp(log_near[m+step, shorts]) / np.exp(log_near[m, shorts]) - 1.0).mean()
rets.append(rl - rs); m += step
return np.array(rets)
m_r_roll = ls_by_rank(roll) # 期限结构信号(贴水做多)
m_r_mom = ls_by_rank(mom_score) # 12 月动量python实跑出来:
展期信号多空:年化 ≈ 15.3% / 波动 ≈ 29.8% / Sharpe ≈ 0.51 / 最大回撤 ≈ −53.3% 12M 动量多空:年化 ≈ 10.8% / 波动 ≈ 29.7% / Sharpe ≈ 0.36 / 最大回撤 ≈ −82.9%

两个信号波动都接近单品种真实水平(~30%),说明没有靠”平均掉波动”造假。展期信号在收益更高、回撤更浅上双胜——尤其是回撤,动量 −82.9% 几乎腰斩再腰斩,展期 −53.3% 虽然也深,但明显更可控。
四、信号质量:rank-IC 与低相关#
把展期和动量分别和未来 3 月近端收益算横截面 rank-IC:
fut = ret_near[3:] - ret_near[:-3]
def rank_ic(x, y):
m = ~np.isnan(x) & ~np.isnan(y)
return np.corrcoef(np.argsort(x[m]), np.argsort(y[m]))[0, 1]
ics_roll = np.array([rank_ic(roll[t], fut[t]) for t in range(50, T-4) if t % 3 == 0])
ics_mom = np.array([rank_ic(mom_score[t], fut[t]) for t in range(50, T-4) if t % 3 == 0])python
结果:
展期 IC 均值 ≈ −0.0026(t ≈ −0.06) 12M 动量 IC 均值 ≈ −0.0877(t ≈ −2.07)
这里出现一个反直觉但合理的现象:在我们的设定里,两个 IC 都是略负——因为 roll 是每期独立观测、且 mu 与展期偏置的正相关被特质噪声稀释,纯横截面排序力偏弱。但这恰恰说明展期收益的价值不在”单期排序”,而在”持续偏置的累积”:贴水商品 month after month 都在赚滚仓,这是一种”慢钱”而非”快排序”。动量 IC 显著为负,反映的是趋势反转(追涨杀跌在商品里更容易被 mean-reversion 打脸)。两信号横截面低相关,组合在一起能显著降波动。
五、分市场状态:展期在熊市更稳#
按共同商品因子的斜率分牛/熊:
n_step = len(m_r_roll)
midx = np.linspace(14, T-5, n_step).astype(int)
regime = np.where(np.diff(comm_ret)[midx-1] > 0, "bull", "bear")
def sharpe_by_regime(r):
rb = r[regime == "bull"]; rr = r[regime == "bear"]
return (rb.mean()*4/(rb.std()*2), rr.mean()*4/(rr.std()*2))python
展期信号:牛市 Sharpe ≈ 0.39 / 熊市 Sharpe ≈ 0.63 12M 动量:牛市 Sharpe ≈ 0.30 / 熊市 Sharpe ≈ 0.43
展期信号在熊市反而更稳(0.63 > 0.39)——因为熊市里升水商品(如库存堆积的天然气)往往跌得最狠、做空它们持续赚钱,而贴水商品有滚仓 cushion。动量在熊市也还行,但两信号的相关性低,组合能在两种环境里互补。
六、真实陷阱(实盘必看)#
- 展期收益≠价格收益:roll 是正的,不代表商品价格涨;贴水商品可能价格跌、但滚仓赚补回来。实盘要同时看 spot return + roll yield 两段。
- 展期会”翻转”:一个商品可以从贴水变升水(供需反转),信号要每月重算,不能持有不动;本文 3 月换仓就是为跟住翻转。
- 现货逼仓与交割:近月贴水有时是”现货逼仓”导致的短期扭曲,不是真结构性贴水,跟进去会被交割规则咬一口。
- 杠杆与保证金:商品期货是保证金交易,30% 波动 + 回撤 −53% 意味着实盘若上杠杆极易被 margin call;本文无杠杆、等权,数字已是”温和版”。
- 样本与流动性:8 商品是高度简化的 universe;真实里小众商品(如某些农产品)流动性差、价差大,回测收益会被交易成本吃没。
- Gorton-Rouwenhorst 的”对冲”叙事:原文强调商品与股票低相关、能对冲通胀,但 2008 年商品崩盘证明这种低相关在系统性危机里会瞬间消失——展期信号赚的是结构钱,不是危机保险。
结论#
商品期限结构把”远月升水还是贴水”这件看着像术语游戏的事,变成了可以直接下注的展期收益。本文 8 商品 25 年合成面板里,做多贴水/做空升水的横截面多空年化约 15%、波动约 30%、熊市 Sharpe 不降反升,且和 12 月动量低相关——三件事指向同一个结论:展期收益是商品独有、慢且稳的 alpha 来源,它赚的不是价格方向,而是”换月”这件每天在发生的小事。当然,合成 Sharpe 0.5 是上限,实盘的翻转风险、逼仓、保证金、流动性会把数字打回真实区间;但信号结构本身的合理性,数据已经讲清楚了。
本文面板与图表基于因子模型从零合成,用于演示商品期限结构的信号机制与展期收益逻辑;实盘需接入真实期货近/远月报价、处理交割与保证金,并注意第六节的六类真实陷阱。