跨品种套利 (Cross-Variety Arbitrage)

ZaynPei Lv6

跨品种套利 (Cross-Variety Arbitrage) 是一种利用相关品种之间的价格差异进行交易以获取利润的策略。它基于这样一个假设:在有效市场中,相关品种的价格关系应该是稳定且可预测的。当这种关系出现偏离时,套利者可以通过买入和卖出相关品种来获取利润。

它属于统计套利 (Statistical Arbitrage) 的范畴,其核心是一种均值回归 (Mean Reversion) 的信念,但它交易的不是单一资产的价格,而是两种相关资产之间的价差 (Spread)。

前提:经济关联性

策略成功的基石是选择的两个品种之间必须有强烈的、稳定的经济关联。例如:

  • 焦炭(j) vs. 焦煤(jm) : 焦煤是生产焦炭的原材料。它们的成本和需求端紧密相连,因此价格走势高度相关。

  • 玉米(c) vs. 淀粉(cs): 玉米是生产淀粉的原材料。

  • 大豆 vs. 豆粕: 大豆是生产豆粕的原材料。

这种内在的经济联系,使得它们的价差在长期来看,会像一根“橡皮筋”,围绕一个均衡水平(均值)上下波动。

套利机会的产生

由于短期市场情绪、供需冲击或流动性问题,这根“橡皮筋”有时会被过度拉伸(价差过大)或过度压缩(价差过小)。

套利策略的目标,就是在“橡皮筋”被拉伸到极限时,做空价差(卖出高价的,买入低价的),赌它会收缩;在被压缩到极限时,做多价差(买入低价的,卖出高价的),赌它会扩张。

如何验证相关性存在

最常用的方法是利用EG两步法对两个序列做协整检验,判断两个序列是否平稳。只有单整阶数相同,二者才有可能存在一定的关系。

平稳性 (Stationarity): 一个时间序列如果是平稳的,意味着它的均值和方差不随时间改变。它的走势图看起来像是在一条水平线上下随机波动。 协整的直观理解: 两个单独看起来“不平稳”(像随机游走,没有回归均值的趋势)的资产价格,如果它们是协整的,就意味着它们之间存在一个神奇的长期均衡关系, 此时他们的价差(Spread)是平稳的

策略逻辑

第一步:定义价差和统计通道

  • 价差 (Spread): Spread = Price(DCE.j1901) - Price(DCE.jm1901)。

  • 历史数据窗口: 使用过去30个交易日的数据作为计算统计量的基础。

  • 通道中轨 (均衡值): 过去30日价差的均值 mean。

  • 开仓边界 (触发线): 在中轨的上下方各设置一个边界,宽度为 0.75 * 标准差。

    • 上轨: Up_Open = mean + 0.75 * std

    • 下轨: Down_Open = mean - 0.75 * std

  • 止损边界 (风控线): 在中轨的上下方设置更宽的边界,宽度为 2.0 * 标准差。

    • 上轨止损: Up_Stop = mean + 2.0 * std

    • 下轨止损: Down_Stop = mean - 2.0 * std

第二步:开仓逻辑

  • 做空价差: 如果当前实时价差向上突破了上轨开仓线 (Up_Open),意味着价差异常偏高。执行操作:卖出焦炭(j),买入焦煤(jm)。

  • 做多价差: 如果当前实时价差向下突破了下轨开仓线 (Down_Open),意味着价差异常偏低。执行操作:买入焦炭(j),卖出焦煤(jm)。

第三步:平仓逻辑

  • 止盈平仓: 无论持有多头还是空头价差头寸,当价差回归到中轨线(均值)时,认为套利目标达成,平掉所有头寸,锁定利润。

  • 止损平仓:

    • 如果持有多头价差头寸,但价差没有回归,反而继续下跌并跌破了下轨止损线 (Down_Stop),立即平仓,防止亏损扩大。

    • 如果持有空头价差头寸,但价差没有回归,反而继续上涨并涨破了上轨止损线 (Up_Stop),立即平仓。

代码示例

下面的代码示例展示了如何实现一个简单的跨品种套利策略:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
# coding=utf-8
from __future__ import print_function, absolute_import, unicode_literals
from gm.api import *
import numpy as np
def init(context):
# 选择的两个合约
context.symbol = ['DCE.j1901', 'DCE.jm1901']
# 订阅历史数据
subscribe(symbols=context.symbol,frequency='1d',count=11,wait_group=True)
def on_bar(context, bars):
# 数据提取
j_close = context.data(symbol=context.symbol[0],frequency='1d',fields='close',count=31).values
jm_close = context.data(symbol=context.symbol[1],frequency='1d',fields='close',count=31).values
# 提取最新价差
new_price = j_close[-1] - jm_close[-1]
# 计算历史价差,上下限,止损点
spread_history = j_close[:-2] - jm_close[:-2]
context.spread_history_mean = np.mean(spread_history)
context.spread_history_std = np.std(spread_history)
context.up = context.spread_history_mean + 0.75 * context.spread_history_std
context.down = context.spread_history_mean - 0.75 * context.spread_history_std
context.up_stoppoint = context.spread_history_mean + 2 * context.spread_history_std
context.down_stoppoint = context.spread_history_mean - 2 * context.spread_history_std
# 查持仓
position_jm_long = context.account().position(symbol=context.symbol[0],side=1)
position_jm_short = context.account().position(symbol=context.symbol[0],side=2)
# 设计买卖信号
# 设计开仓信号
if not position_jm_short and not position_jm_long:
if new_price > context.up:
print('做空价差组合')
order_volume(symbol=context.symbol[0],side=OrderSide_Sell,volume=1,order_type=OrderType_Market,position_effect=1)
order_volume(symbol=context.symbol[1], side=OrderSide_Buy, volume=1, order_type=OrderType_Market, position_effect=PositionEffect_Open)
if new_price < context.down:
print('做多价差组合')
order_volume(symbol=context.symbol[0], side=OrderSide_Buy, volume=1, order_type=OrderType_Market, position_effect=PositionEffect_Open)
order_volume(symbol=context.symbol[1], side=OrderSide_Sell, volume=1, order_type=OrderType_Market, position_effect=PositionEffect_Open)
# 设计平仓信号
# 持jm多仓时
if position_jm_long:
if new_price >= context.spread_history_mean:
# 价差回归到均值水平时,平仓
print('价差回归到均衡水平,平仓')
order_volume(symbol=context.symbol[0], side=OrderSide_Sell, volume=1, order_type=OrderType_Market, position_effect=PositionEffect_Close)
order_volume(symbol=context.symbol[1], side=OrderSide_Buy, volume=1, order_type=OrderType_Market, position_effect=PositionEffect_Close)
if new_price < context.down_stoppoint:
# 价差达到止损位,平仓止损
print('价差超过止损点,平仓止损')
order_volume(symbol=context.symbol[0], side=OrderSide_Sell, volume=1, order_type=OrderType_Market, position_effect=PositionEffect_Close)
order_volume(symbol=context.symbol[1], side=OrderSide_Buy, volume=1, order_type=OrderType_Market, position_effect=PositionEffect_Close)
# 持jm空仓时
if position_jm_short:
if new_price <= context.spread_history_mean:
# 价差回归到均值水平时,平仓
print('价差回归到均衡水平,平仓')
order_volume(symbol=context.symbol[0], side=OrderSide_Buy, volume=1, order_type=OrderType_Market, position_effect=PositionEffect_Close)
order_volume(symbol=context.symbol[1], side=OrderSide_Sell, volume=1, order_type=OrderType_Market, position_effect=PositionEffect_Close)
if new_price > context.up_stoppoint:
# 价差达到止损位,平仓止损
print('价差超过止损点,平仓止损')
order_volume(symbol=context.symbol[0], side=OrderSide_Buy, volume=1, order_type=OrderType_Market, position_effect=PositionEffect_Close)
order_volume(symbol=context.symbol[1], side=OrderSide_Sell, volume=1, order_type=OrderType_Market, position_effect=PositionEffect_Close)
if __name__ == '__main__':
'''
strategy_id策略ID,由系统生成
filename文件名,请与本文件名保持一致
mode实时模式:MODE_LIVE回测模式:MODE_BACKTEST
token绑定计算机的ID,可在系统设置-密钥管理中生成
backtest_start_time回测开始时间
backtest_end_time回测结束时间
backtest_adjust股票复权方式不复权:ADJUST_NONE前复权:ADJUST_PREV后复权:ADJUST_POST
backtest_initial_cash回测初始资金
backtest_commission_ratio回测佣金比例
backtest_slippage_ratio回测滑点比例
'''
run(strategy_id='strategy_id',
filename='main.py',
mode=MODE_BACKTEST,
token='token',
backtest_start_time='2018-02-01 08:00:00',
backtest_end_time='2018-12-31 16:00:00',
backtest_adjust=ADJUST_PREV,
backtest_initial_cash=2000000,
backtest_commission_ratio=0.0001,
backtest_slippage_ratio=0.0001)