- 原理
什么是套利?
套利是指在买入或卖出一种金融资产的同时卖出或买入另一种相关的金融资产从中利用价差获得套利的过程。
什么是跨品种套利?
当两个合约有很强的相关性时,可能存在相似的变动关系,两种合约之间的价差会维持在一定的水平上。当市场出现变化时,两种合约之间的价差会偏离均衡水平。此时,可以买入其中一份合约同时卖出其中一份合约,当价差恢复到正常水平时平仓,获取收益。
以大商所玉米和淀粉为例,合约分别为c1801和cs1801。二者之间相关性为0.7333,价差处于相对稳定合理区间。如图所示。
二者价差整体处于250-350之间。当价差偏离此区间时,可以进行跨品种套利。
跨品种套利有以下几个特点:
1.套利的两种资产必须有一定的相关性。2.两种合约标的不同,到期时间相同。3.两种资产之间的价差呈现一定规律。
怎样确定合约之间有相关性?
最常用的方法是利用EG两步法对两个序列做协整检验,判断两个序列是否平稳。只有单整阶数相同,二者才有可能存在一定的关系。
以大豆和豆粕为例,选取其在2017年1月1日至2018年1月1日的主力合约价格时间序列,利用statsmodels包进行协整检验。
检验结果为:焦炭的t = -1.7886,1%置信区间的临界值为-3.4576,说明该序列在99%的置信水平下平稳。焦煤的t = -2.0500,1%置信区间的临界值为-3.4576,说明该序列在99%的置信水平下平稳。
因此,二者都为平稳序列。
利用OLS回归检残差序列是否平稳,残差的t=-2.3214,临界值为-3.4577,说明残差平稳。因此,可以认为二者之间存在一定的关系。
回归后的残差图如下:
对残差进行ks检验,检验结果p=0,说明残差分布为正态分布。
策略设计
传统利用价差进行跨品种套利的方法是计算出均值和方差,设定开仓、平仓和止损阈值。当新的价格达到阈值时,进行相应的开仓和平仓操作。
应该怎样确定均值?
均值的选取主要有两种方法,第一种方法是固定均值。先按历史价格计算相应的阈值(比如利用2017年2月-2017年6月的数据计算阈值,在2019年7月进行套利),再用最新价差进行比较,会发现前后均值差异很大。如图所示。
因此,常用变动的均值设定阈值。即用过去N天两个标的之间差值的均值和方差。
- 策略思路
第一步:选择相关性较高的两个合约,本例选择大商所的焦炭和焦煤。第二步:以过去30个的1d频率bar的均值正负0.75个标准差作为开仓阈值,以正负2个标准差作为止损阈值。第三步:最新价差上穿上界时做空价差,回归到均值附近平仓;下穿下界时做多价差,回归到均值附近平仓。设定止损点,触发止损点则全部平仓。
- 策略代码
"""
关注: Ctp接口量化
"""
from _ctp import *
from Config import Config
import numpy as np
class Arbitrage_Strategy(Strategy):
def __init__(self):
super().__init__()
# 选择的两个合约
self.symbol_lsit = ['j1901', 'jm1901']
# 订阅历史数据
self.bar_time = BarType.Min #订阅K线周期 秒级 BarType.Time3 Time5 Time15 Time30 分钟级 BarType.Min , Min3 、 Min5 、 Min15 、 Min30 、 Min60
self.StrategyType = StrategyType.Bar #策略类型 StrategyType.Renko StrategyType.Bar StrategyType.Tick
def on_trade(self, trade):
print(trade)
def on_tick(self, tick=None):
print(tick.InstrumentID,tick.LastPrice)
def on_bar(self, tick=None, Bar=None):
# 数据提取
symbol = tick.InstrumentID #合约代码
print(self.GetData(symbol)) # 获取k历史数据
j_close = self.GetData(self.symbol_lsit[0])
jm_close = self.GetData(self.symbol_lsit[1])
# 提取最新价差
new_price = j_close[-1] - jm_close[-1]
# 计算历史价差,上下限,止损点
spread_history = j_close[:-2] - jm_close[:-2]
self.spread_history_mean = np.mean(spread_history)
self.spread_history_std = np.std(spread_history)
self.up = self.spread_history_mean + 0.75 * self.spread_history_std
self.down = self.spread_history_mean - 0.75 * self.spread_history_std
self.up_stoppoint = self.spread_history_mean + 2 * self.spread_history_std
self.down_stoppoint = self.spread_history_mean - 2 * self.spread_history_std
# 查持仓
print(self.Get_Position(self.symbol_lsit[0])) # 返回多条持仓
print(self.Get_Position(self.symbol_lsit[1])) # 返回多条持仓
position_j_long = self.GetPosition(self.symbol_lsit[0],"Long") # 返回一条持仓
position_j_short = self.GetPosition(self.symbol_lsit[0],"Short") # 返回一条持仓
position_jm_long = self.GetPosition(self.symbol_lsit[1],"Long") # 返回一条持仓
position_jm_short = self.GetPosition(self.symbol_lsit[1],"Short") # 返回一条持仓
# 设计买卖信号
# 设计开仓信号
if not position_jm_short and not position_jm_long:
if new_price > self.up:
print('做空价差组合')
self.send(self.symbol_lsit[0], DirectionType.Sell, OffsetType.Open, j_close[-1], 1, OrderType.Limit) # # OffsetType.Open 开仓, OffsetType.Close 平仓, OffsetType.CloseToday 平今 , OffsetType.CloseYesterday 平昨
self.send(self.symbol_lsit[1], DirectionType.Buy, OffsetType.Open, jm_close[-1], 1, OrderType.Limit) # # OrderType.FOK """全部完成,否则撤销""" OrderType.FAK """部分成交,剩余撤销""" OrderType.Market 市价 OrderType.Limit 限价
if new_price < self.down:
print('做多价差组合')
self.send(self.symbol_lsit[0], DirectionType.Buy, OffsetType.Open, j_close[-1], 1, OrderType.Limit) # # OffsetType.Open 开仓, OffsetType.Close 平仓, OffsetType.CloseToday 平今 , OffsetType.CloseYesterday 平昨
self.send(self.symbol_lsit[1], DirectionType.Sell, OffsetType.Open, jm_close[-1], 1, OrderType.Limit) # # OrderType.FOK """全部完成,否则撤销""" OrderType.FAK """部分成交,剩余撤销""" OrderType.Market 市价 OrderType.Limit 限价
# 设计平仓信号
# 持jm多仓时
if position_jm_long:
if new_price >= self.spread_history_mean:
# 价差回归到均值水平时,平仓
print('价差回归到均衡水平,平仓')
self.send(self.symbol_lsit[0], DirectionType.Sell, OffsetType.Close, j_close[-1], position_j_long["总持仓"], OrderType.Limit) # OffsetType.Close 已优化 适应 上期所 平今 平昨 的区别
self.send(self.symbol_lsit[1], DirectionType.Buy, OffsetType.Close, jm_close, position_jm_long["总持仓"], OrderType.Limit)
if new_price < self.down_stoppoint:
# 价差达到止损位,平仓止损
print('价差超过止损点,平仓止损')
self.send(self.symbol_lsit[0], DirectionType.Sell, OffsetType.Close, j_close[-1], position_j_long["总持仓"], OrderType.Limit) # OffsetType.Close 已优化 适应 上期所 平今 平昨 的区别
self.send(self.symbol_lsit[1], DirectionType.Buy, OffsetType.Close, jm_close[-1], position_jm_long["总持仓"], OrderType.Limit)
# 持jm空仓时
if position_jm_short:
if new_price <= self.spread_history_mean:
# 价差回归到均值水平时,平仓
print('价差回归到均衡水平,平仓')
self.send(self.symbol_lsit[0], DirectionType.Buy, OffsetType.Close, j_close[-1], position_j_short["总持仓"], OrderType.Limit) # OffsetType.Close 已优化 适应 上期所 平今 平昨 的区别
self.send(self.symbol_lsit[1], DirectionType.Sell, OffsetType.Close, jm_close[-1], position_jm_short["总持仓"], OrderType.Limit)
if new_price > self.up_stoppoint:
# 价差达到止损位,平仓止损
print('价差超过止损点,平仓止损')
self.send(self.symbol_lsit[0], DirectionType.Buy, OffsetType.Close, j_close[-1], position_j_short["总持仓"], OrderType.Limit) # OffsetType.Close 已优化 适应 上期所 平今 平昨 的区别
self.send(self.symbol_lsit[1], DirectionType.Sell, OffsetType.Close, jm_close[-1], position_jm_short["总持仓"], OrderType.Limit)
if __name__ == '__main__':
t = CTP(Arbitrage_Strategy())
t.Login(Config)
注:此策略只用于学习、交流、演示,不构成任何投资建议。