【策略研究】跨品種價差套利策略(附原始碼)
跨品種價差套利簡介
套利原理
通俗地講,就是兩個合約相關性很好,突然市場出了一個bug,破壞了兩個合約之間的平衡狀態,進場套利;等待市場回覆,平倉出場。即均值回覆思想。
價差套利
價差套利的前提是做出商品期貨品種間同一月份的價格之間的價差,並且畫出價差的時間序列圖,分析價差,尋找合理的價差範圍,超出合理的價差變動範圍時如何進行操作。
套利標的的選取
我們選取同為黑鋼產業的主要產品,熱軋卷板與螺紋鋼作為套利標的。
首先,從價格走勢上來看,二者價格趨勢上一致性非常高,基本處於同漲同跌局面,如下圖所示,2014年至2017年熱軋卷板與螺紋鋼價格指數,相關性達到0.924。二者成本從鐵礦石到鋼坯這個階段幾乎一樣,只是在最終的軋製成材階段出現了分化。
其次,二者價格的季節性也表現出較高的一致性。熱軋卷板和螺紋鋼的年內高點普遍出現在4月,次高點出現在7月,而10月不管是熱軋卷板還是螺紋鋼都傾向於出現年內低點。在較一致的季節性特點下,二者的價格強弱也有一定的規律可循。一般情況下,1、2月熱軋卷板的表現會略強於螺紋鋼,但是之後二者比價會逐步走低,也就是螺紋鋼相對於熱軋卷板會逐步變強。而這種強弱關係一般到10月會達到一個極致,之後熱軋卷板會再度重新變得相對強一點。
為了防止未來函式的引入,在這裡我們選取“HC1701”與“RB1701”作為套利觀察合約
圖示分為兩部分,上圖為2016-04-01至2016-12-31的兩標的合約日線收盤價的資料,經計算,相關性係數為0.988,下圖為兩者價差的走勢,上下界分別為均值加減兩倍標準差。
可以看出價差在上下界之外會再次迴歸,這就成為了我們的交易機會,不過,均值週期需要縮短,以增加交易次數。另外,我們用2017-04-01至2017-12-31的“HC1801”與“RB1801”作為標的合約,進行回測。
跨品種價差套利策略實現(基於掘金量化平臺)
策略思想
-
獲取過去的30個交易日的bar的均值正負2個標準差得到上下界。
-
用最新價差來判斷開倉方向,上穿上軌來做空價差,下穿下軌來做多價差。
-
迴歸至上下軌水平內的時候平倉。
策略主要步驟實現
訂閱資料
subscribe(symbols=symbols, frequency='1d', count=31, wait_group =True)
(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(symbol=symbol, frequency='1d', count=31, fields='close')
訂閱資料之後,需要獲取已經訂閱的資料來進行操作,這時需呼叫context.data
函式:
-
symbols
需要設定訂閱的標的程式碼。 -
frequency
需設定訂閱資料的週期級別,這裡設定1d
表示以一天為週期。 -
count
需要設定獲取的bar的數量 -
fields
需要設定返回值的種類
策略回測分析
回測報告
分析
我們選取了2017年4月至2017年11月作為回測週期,“HC1801”與“RB1801”作為標的合約,價差均值週期設為30,可以看出:
-
勝率(具有盈利的平倉次數與總平倉次數之比)達到了56.25%。
-
卡瑪比率(年化收益率與歷史最大回撤之比)是使用最大回撤率來衡量風險。採用最大回撤率來衡量風險,關注的是最極端的情況。卡瑪比率越高表示策略承受每單位最大損失獲得的報酬越高。在這裡卡瑪比率超過了4。
-
夏普比率(年化收益率減無風險收益率的差收益波動率之比)超過1.5,也即承受1單位的風險,會有超過1.5個單位的收益回報
-
策略收益曲線相當穩定,最大回撤極小,缺點是交易次數少,很長時間無交易。
# coding=utf-8
from __future__ import print_function, absolute_import, unicode_literals
from gm.api import*
import numpy as np
'''
本策略根據計算滾動的.過去的30個1min的bar的均值正負2個標準差得到布林線
並在最新價差上穿上軌來做空價差,下穿下軌來做多價差
並在迴歸至上下軌水平內的時候平倉
回測資料為:SHFE.rb1801和SHFE.hc1801的1min資料
回測時間為:2017-09-01 08:00:00到2017-10-01 16:00:00
'''
def init(context):
# 進行套利的品種
context.goods =['SHFE.rb1801','SHFE.hc1801']
# 訂閱行情
subscribe(symbols=context.goods, frequency='60s', count=31, wait_group=True)
def on_bar(context, bars):
# 獲取兩個品種的時間序列
data_rb = context.data(symbol=context.goods[0], frequency='60s', count=31, fields='close')
close_rb = data_rb.values
data_hc = context.data(symbol=context.goods[1], frequency='60s', count=31, fields='close')
close_hc = data_hc.values
# 計算價差
spread = close_rb[:-1]- close_hc[:-1]
# 計算布林帶的上下軌
up = np.mean(spread)+2* np.std(spread)
down = np.mean(spread)-2* np.std(spread)
# 計算最新價差
spread_now = close_rb[-1]- close_hc[-1]
# 無交易時若價差上(下)穿布林帶上(下)軌則做空(多)價差
position_rb_long = context.account().position(symbol=context.goods[0], side=PositionSide_Long)
position_rb_short = context.account().position(symbol=context.goods[0], side=PositionSide_Short)
ifnot position_rb_long andnot position_rb_short:
if spread_now > up:
order_target_volume(symbol=context.goods[0], volume=1, order_type=OrderType_Market,
position_side=PositionSide_Short)
print(context.goods[0],'以市價單開空倉一手')
order_target_volume(symbol=context.goods[1], volume=1, order_type=OrderType_Market,
position_side=PositionSide_Long)
print(context.goods[1],'以市價單開多倉一手')
if spread_now < down:
order_target_volume(symbol=context.goods[0], volume=1, order_type=OrderType_Market,
position_side=PositionSide_Long)
print(context.goods[0],'以市價單開多倉一手')
order_target_volume(symbol=context.goods[1], volume=1, order_type=OrderType_Market,
position_side=PositionSide_Short)
print(context.goods[1],'以市價單開空倉一手')
# 價差迴歸時平倉
elif position_rb_short:
if spread_now <= up:
order_close_all()
print('價格迴歸,平所有倉位')
# 跌破下軌反向開倉
if spread_now < down:
order_target_volume(symbol=context.goods[0], volume=1, order_type=OrderType_Market,
position_side=PositionSide_Long)
print(context.goods[0],'以市價單開多倉一手')
order_target_volume(symbol=context.goods[1], volume=1, order_type=OrderType_Market,
position_side=PositionSide_Short)
print(context.goods[1],'以市價單開空倉一手')
elif position_rb_long:
if spread_now >= down:
order_close_all()
print('價格迴歸,平所有倉位')
# 漲破上軌反向開倉
if spread_now > up:
order_target_volume(symbol=context.goods[0], volume=1, order_type=OrderType_Market,
position_side=PositionSide_Short)
print(context.goods[0],'以市價單開空倉一手')
order_target_volume(symbol=context.goods[1], volume=1, order_type=OrderType_Market,
position_side=PositionSide_Long)
print(context.goods[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='strategy_id',
filename='main.py',
mode=MODE_BACKTEST,
token='token_id',
backtest_start_time='2017-09-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)
文章來源:掘金量化交易平臺,轉載請註明出處!
----------------------------------------------------------------------------------------------------------------------------------------------------------
更多經典股票/期貨量化策略原始碼檢視: