跨期套利策略(附:源码)

跨期套利策略简介

什么是跨期套利?

​ 跨期套利**是套利交易中最普遍的一种,是股指期货的跨期套利(Calendar Spread Arbitrage)即为在同一交易所进行同一指数、但不同交割月份的套利活动。

​ 跨期套利**是利用同一商品但不同交割月份之间正常价格差距出现异常变化时进行对冲而获利的,又可分为牛市套利(bull spread)和熊市套利(bear spread)两种形式。例如在进行金属牛市套利时,交易所买入近期交割月份的金属合约,同时卖出远期交割月份的金属合约,希望近期合约价格上涨幅度大于远期合约价格的上帐幅度;而熊市套利则相反,即卖出近期交割月份合约,买入远期交割月份合约,并期望远期合约价格下跌幅度小于近期合约的价格下跌幅度。

什么是价差套利?

​价差套利**的前提是做出商品期货品种间同一月份的价格之间的价差,并且画出价差的时间序列图,分析价差,寻找合理的价差范围,超出合理的价差变动范围时如何进行操作。

协整性检验

下图为两个时间序列(“RB1701”,“RB1705”)。

​ !走势

​ 可以看出,两只股票具有同涨同跌的规律,长期以来两只股票的价差比较平稳,当价差变大或变小时,会有某种“力量”使它回归均值,我们的策略也正基于此。但仅凭肉眼观测,肯定是不严谨的,这就需要协整性检验。如果两个股票具有强协整性,那么无论它们中途怎么走的,它们前进的方向总是一样的。

什么是平稳性?

​ 提到协整性,就不得不提平稳性。在数学中,平稳随机过程(Stationary random process)或者严平稳随机过程(Strictly-sense stationary random process),又称狭义平稳过程,是在固定时间和位置的概率分布与所有时间和位置的概率分布相同的随机过程:即随机过程的统计特性不随时间的推移而变化。这样,数学期望和方差这些参数也不随时间和位置变化。平稳在理论上有严平稳和宽平稳两种,在实际应用上宽平稳使用较多。宽平稳的数学定义为:对于时间序列 ​,若对任意的​,满足:

E(Y_t)=E(Y_{t+m})

cov(Y_t,Y_{t+k})=cov(Y_{t+k},Y_{t+k+m})

则称时间序列​ 是宽平稳的。

​ 平稳性(stationarity)是一个序列在时间推移中保持稳定不变的性质,或许您对于上图的“价差平稳”不认可,我们将价差做了一阶差分,可以看出,它始终是围绕着一个长期均值(0)在波动。

什么是整性?

如果某一非平稳序列​能够经过​次差分后变成平稳序列,就称该序列为​阶整性,也成为单整,记为 X_t \sim I(d)

什么是协整性?

协整性是指若两个或多个非平稳的变量序列,其某个线性组合后的序列呈平稳性。

假设两个非平稳时间序列 Yt,Xt,且有Yt \sim I(d) 和 X(t)\sim I(b)。如果存在某一参数向量(1-\beta),使得:Xt 且有Yt \sim I(d)X(t)\sim I(b)。如果存在某一参数向量 (1-\beta),使得:

[Y_t-\beta X_t]\sim I(d-b)

其中,​b为正整数,​(1-\beta)为协整向量,​\beta为协整系数。

那么 Yt,Xt,且有Yt \sim I(d) 和 X(t)\sim I(b)。如果存在某一参数向量(1-\beta),使得:Xt 之间存在协整,记为:

Y_t,X_t \sim CI(d,b) ,X_t~CI(d,b)b)

如果​d-b=0,那么:

有:

意味着回归方程

有意义,因为这时​惟一存在。同时,时间序列,​在时间上的实际变动与长期均衡轨道的偏差​是收敛的。就是说,即使在某一时点上,,​的运动方向相互背离,当它们之间存在的长期规律或内在力量迫使它们趋向一致。即​服从均值为0,方差为1的正态分布。因而判定​和​之间存在长期均衡关系。

​ 协整概念是一个强有力的概念。因为协整允许我们刻画两个或多个序列之间的平衡或平稳关系。对于每一个序列单独来说可能是非平稳的,这些序列的矩,如均值、方差或协方差随时间而变化,而这些时间序列的线性组合序列却可能有不随时间变化的性质。

什么是单位根检验?

​ 常用的时间序列数据单整和协整阶数的检验,是所谓的单位根检验。

DF(Dickey-Fuller)检验

​ 考虑时间序列Yt,Xt,且有Yt \sim I(d) 和 X(t)\sim I(b)。如果存在某一参数向量(1-\beta),使得:​,有一阶自回归:

其中, \rho为代估参数,ε_tε_tε_t,ε_t 为白噪声。

如果​\rho =1,则Yt,Xt,且有Yt \sim I(d) 和 X(t)\sim I(b)。如果存在某一参数向量(1-\beta),使得:​ 为随机游走过程;如果\left | \rho \right |<1​,则​Yt,Xt,且有Yt \sim I(d) 和 X(t)\sim I(b)。如果存在某一参数向量(1-\beta),使得:​为0阶平稳过程。基于此,1979年迪基和富勒提出检验​ 检验。称DF检验,其回归方程为:

$DY_t=\delta Y_{t-1}+ε_t$ 

其中,\delta​为待估参数, DY_t=Y_{t-1}-Y_{t}

具体做法是用最小二乘法求出上式中的​,并检验其负性:

H_0 : \delta=0$

H_0  : \delta<0

如果接受 H_0,意味着 .如果拒绝H_0,接受H_1,则

ADF(Augmented Dickey-Fuller)检验

如果 DYt,Xt,且有Yt \sim I(d) 和 X(t)\sim I(b)。如果存在某一参数向量(1-\beta),使得:存在自回归,考虑截距 \delta_0,趋势项 \theta_t,则有ADF检验方程:

为什么需要检验协整性?

​ 如果我们不经检验而求得​,​之间的某种关系,如​。由于这种关系本身可能不存在,得到这样的关系纯属偶然。因而用此关系式所得到的分析,判断和预测结果会不真实乃至荒谬。

怎样检验协整性?

​ 本策略采用“Engle-Granger两步协整检验法”,

​ Engle-Granger两步协整检验法用普通最小二乘法估计这些变量之间的平稳关系系数,然后用单位根检验来检验残差,如果序列是平稳的,则不存在单位根, 否则就会存在单位根。ADF检验的原假设是存在单位根,因此如果得到的统计量显著小于3个置信度(1%,5%,10%)的临界统计值时,说明是拒绝原假设的。

我们以两个序列Yt,Xt,且有Yt \sim I(d) 和 X(t)\sim I(b)。如果存在某一参数向量(1-\beta),使得:​和Xt​为例,在检验协整性之前,首先要对序列的单整性进行检验,只有当两个序列单整阶数相同时,才有可能存在协整关系。

在​Yt,Xt,且有Yt \sim I(d) 和 X(t)\sim I(b)。如果存在某一参数向量(1-\beta),使得:Xt​具有相同单整阶数,通过单整性检验之后,我们用最小二乘法估计模型:

并计算相应的残差序列:

然后,检验残差序列的平稳性:

利用ADF检验法,检验在上述估计下得到的回归方程的残差​是否平稳(如果​Yt,Xt,且有Yt \sim I(d) 和 X(t)\sim I(b)。如果存在某一参数向量(1-\beta),使得:和​Xt不是协整的,则他们的任意组合都是非平稳的,因此残差e_t​将是非平稳的)。也就是说,我们检验残差​e_t的非平稳的假设,就是检验Yt,Xt,且有Yt \sim I(d) 和 X(t)\sim I(b)。如果存在某一参数向量(1-\beta),使得:​和​Xt不是协整的假设。

跨期套利策略实现(基于掘金量化平台)

策略思想

• 首先获取套利(RB1801,RB1805)标的价格序列。

• 根据​两步法(1、序列同阶单整 2、​残差平稳)判断序列具有协整关系之后(若无协整关系则全平仓位不进行操作)。

• 通过计算两个真实价格序列回归残差的1.5个标准差上下轨,并在价差突破上轨的时候做空价差,价差突破下轨的时候做多价差并在回归至标准差水平内的时候平仓。

策略主要步骤实现

订阅数据

subscribe(symbols=symbols, frequency='1d', count=31, wait_group=True)

订阅数据需要在定义init函数里面设置,并调用subscribe函数,这里注意,我们需要通过计算前三十根bars来作为开平仓的标准,并在当前bar上做出开平仓操作,所以需要获取31根bar:

symbols 需要设置订阅的标的代码。

frequency需设置订阅数据的周期级别,这里设置1d 表示以一天为周期。

count需要设置获取的bar的数量

数据获取

data_rb = context.data(symbol=symbol, frequency='1d', count=31, fields='close')

订阅数据之后,需要获取已经订阅的数据来进行操作,这时需调用context.data函数:

symbols 需要设置订阅的标的代码。

frequency需设置订阅数据的周期级别,这里设置1d表示以一天为周期。

count需要设置获取的bar的数量

fields需要设置返回值的种类

协整性检验

def cointegration_test(series01, series02):
    urt_rb1801 = ts.adfuller(np.array(series01), 1)[1]
    urt_rb1805 = ts.adfuller(np.array(series02), 1)[1]
    print (ts.adfuller(np.array(series01), 1))
    # 同时平稳或不平稳则差分再次检验
    if (urt_rb1801 > 0.1 and urt_rb1805 > 0.1) or (urt_rb1801 < 0.1 and urt_rb1805 < 0.1):
        urt_diff_rb1801 = ts.adfuller(np.diff(np.array(series01)), 1)[1]
        urt_diff_rb1805 = ts.adfuller(np.diff(np.array(series02), 1))[1]
        # 同时差分平稳进行OLS回归的残差平稳检验
        if urt_diff_rb1801 < 0.1 and urt_diff_rb1805 < 0.1:
            matrix = np.vstack([series02, np.ones(len(series02))]).T
            beta, c = np.linalg.lstsq(matrix, series01)[0]
            resid = series01 - beta * series02 - c
            if ts.adfuller(np.array(resid), 1)[1] > 0.1:
                result = 0.0
            else:
                result = 1.0
            return beta, c, resid, result

        else:
            result = 0.0
            return 0.0, 0.0, 0.0, result

    else:
        result = 0.0
        return 0.0, 0.0, 0.0, result

协整性检验需要我们自己定义函数(如以上代码),此函数需要输入两个序列,返回值为回归方程中的各项参数及两序列是否具有协整性的结果。

策略回测分析

回测报告

分析

​ 我们选取了2017年7月至2017年10月作为回测周期,“RB1801”与“RB1805”作为标的合约,价差均值周期设为30,可以看出:

• 胜率(具有盈利的平仓次数与总平仓次数之比)达到了50%。

• 卡玛比率(年化收益率与历史最大回撤之比)是使用最大回撤率来衡量风险。采用最大回撤率来衡量风险,关注的是最极端的情况。卡玛比率越高表示策略承受每单位最大损失获得的报酬越高。在这里卡玛比率超过了16.02。

• 夏普比率(年化收益率减无风险收益率的差收益波动率之比)超过2.76,也即承受1单位的风险,会有超过2.5个单位的收益回报

• 策略收益曲线相当稳定,适合稳定型投资者,最大回撤极小,缺点是交易次数少,很长时间无交易。

附:跨期套利策略源码

# coding=utf-8
from __future__ import print_function, absolute_import, unicode_literals
 
import numpy as np
from gm.api import *
 
try:
    import statsmodels.tsa.stattools as ts
except:
    print('请安装statsmodels库')
 
'''
本策略基于掘金量化交易平台 网址:www.myquant.cn
 
本策略根据EG两步法(1.序列同阶单整2.OLS残差平稳)判断序列具有协整关系之后(若无协整关系则全平仓位不进行操作)
通过计算两个真实价格序列回归残差的0.9个标准差上下轨,并在价差突破上轨的时候做空价差,价差突破下轨的时候做多价差
并在回归至标准差水平内的时候平仓
回测数据为:SHFE.rb1801和SHFE.rb1805的1min数据
回测时间为:2017-09-25 08:00:00到2017-10-01 15:00:00
'''
 
 
# 协整检验的函数
def cointegration_test(series01, series02):
    urt_rb1801 = ts.adfuller(np.array(series01), 1)[1]
    urt_rb1805 = ts.adfuller(np.array(series02), 1)[1]
    print (ts.adfuller(np.array(series01), 1))
    # 同时平稳或不平稳则差分再次检验
    if (urt_rb1801 > 0.1 and urt_rb1805 > 0.1) or (urt_rb1801 < 0.1 and urt_rb1805 < 0.1):
        urt_diff_rb1801 = ts.adfuller(np.diff(np.array(series01)), 1)[1]
        urt_diff_rb1805 = ts.adfuller(np.diff(np.array(series02), 1))[1]
        # 同时差分平稳进行OLS回归的残差平稳检验
        if urt_diff_rb1801 < 0.1 and urt_diff_rb1805 < 0.1:
            matrix = np.vstack([series02, np.ones(len(series02))]).T
            beta, c = np.linalg.lstsq(matrix, series01)[0]
            resid = series01 - beta * series02 - c
            if ts.adfuller(np.array(resid), 1)[1] > 0.1:
                result = 0.0
            else:
                result = 1.0
            return beta, c, resid, result
 
        else:
            result = 0.0
            return 0.0, 0.0, 0.0, result
 
    else:
        result = 0.0
        return 0.0, 0.0, 0.0, result
 
 
def init(context):
    context.goods = ['SHFE.rb1801', 'SHFE.rb1805']
    # 订阅品种
    subscribe(symbols=context.goods, frequency='1d', count=31, wait_group=True)
 
 
def on_bar(context, bars):
    # 获取过去800个60s的收盘价数据
    close_01 = context.data(symbol=context.goods[0], frequency='1d', count=31, fields='close')['close'].values
    close_02 = context.data(symbol=context.goods[1], frequency='1d', count=31, fields='close')['close'].values
    print (len(close_01),len(close_02))
    # 展示两个价格序列的协整检验的结果
    beta, c, resid, result = cointegration_test(close_01, close_02)
    # 如果返回协整检验不通过的结果则全平仓位等待
    if not result:
        print('协整检验不通过,全平所有仓位')
        order_close_all()
        return
 
    # 计算残差的标准差上下轨
    mean = np.mean(resid)
    up = mean + 1.5 * np.std(resid)
    down = mean - 1.5 * np.std(resid)
    # 计算新残差
    resid_new = close_01[-1] - beta * close_02[-1] - c
    # 获取rb1801的多空仓位
    position_01_long = context.account().position(symbol=context.goods[0], side=PositionSide_Long)
    position_01_short = context.account().position(symbol=context.goods[0], side=PositionSide_Short)
    if not position_01_long and not position_01_short :
        # 上穿上轨时做空新残差
        if resid_new > up:
            order_target_volume(symbol=context.goods[0], volume=50, order_type=OrderType_Market,
                                position_side=PositionSide_Short)
            print(context.goods[0] + '以市价单开空仓1手')
            order_target_volume(symbol=context.goods[1], volume=50, order_type=OrderType_Market,
                                position_side=PositionSide_Long)
            print(context.goods[1] + '以市价单开多仓1手')
        # 下穿下轨时做多新残差
        if resid_new < down:
            order_target_volume(symbol=context.goods[0], volume=50, order_type=OrderType_Market,
                                position_side=PositionSide_Long)
            print(context.goods[0], '以市价单开多仓1手')
            order_target_volume(symbol=context.goods[1], volume=50, order_type=OrderType_Market,
                                position_side=PositionSide_Short)
            print(context.goods[1], '以市价单开空仓1手')
    # 新残差回归时平仓
    elif position_01_short:
        if resid_new <= up:
            order_close_all()
            print('价格回归,平掉所有仓位')
        # 突破下轨反向开仓
        if resid_new < down:
            order_target_volume(symbol=context.goods[0], volume=50, order_type=OrderType_Market,
                                position_side=PositionSide_Long)
            print(context.goods[0], '以市价单开多仓1手')
            order_target_volume(symbol=context.goods[1], volume=50, order_type=OrderType_Market,
                                position_side=PositionSide_Short)
            print(context.goods[1], '以市价单开空仓1手')
    elif position_01_long:
        if resid_new >= down:
            order_close_all()
            print('价格回归,平所有仓位')
        # 突破上轨反向开仓
        if resid_new > up:
            order_target_volume(symbol=context.goods[0], volume=50, order_type=OrderType_Market,
                                position_side=PositionSide_Short)
            print(context.goods[0], '以市价单开空仓1手')
            order_target_volume(symbol=context.goods[1], volume=50, order_type=OrderType_Market,
                                position_side=PositionSide_Long)
            print(context.goods[1], '以市价单开多仓1手')
 
 
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='23b0ef26-d355-11e7-9259-9cd21ef04ea9',
        filename='cointeration.py',
        mode=MODE_BACKTEST,
        token='c395247a76e8a5caeee699d668d6f550213bc418',
        backtest_start_time='2017-07-01 08:00:00',
        backtest_end_time='2017-10-01 16:00:00',
        backtest_adjust=ADJUST_PREV,
        backtest_initial_cash=500000,
        backtest_commission_ratio=0.0001,
        backtest_slippage_ratio=0.0001)

来源:掘金量化  myquant.cn

相关阅读: | 量化交易  | 期货模拟交易 | python量化交易 | 股票数据  | 量化交易策略 | 机器学习算法  | 多因子选股 |  

                   | 双均线策略 |  网格交易法 |  海龟交易法  |  跨期套利  | 行业轮动  | 指数增强  | 跨品种套利 | 日内交易 | 

  • 6
    点赞
  • 80
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值