1. 程式人生 > >【策略研究】跨品種價差套利策略(附原始碼)

【策略研究】跨品種價差套利策略(附原始碼)

跨品種價差套利簡介

套利原理

​ 通俗地講,就是兩個合約相關性很好,突然市場出了一個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個單位的收益回報

  • 策略收益曲線相當穩定,最大回撤極小,缺點是交易次數少,很長時間無交易。

  1. # coding=utf-8
  2. from __future__ import print_function, absolute_import, unicode_literals
  3. from gm.api import*
  4. import numpy as np
  5. '''
  6. 本策略根據計算滾動的.過去的30個1min的bar的均值正負2個標準差得到布林線
  7. 並在最新價差上穿上軌來做空價差,下穿下軌來做多價差
  8. 並在迴歸至上下軌水平內的時候平倉
  9. 回測資料為:SHFE.rb1801和SHFE.hc1801的1min資料
  10. 回測時間為:2017-09-01 08:00:00到2017-10-01 16:00:00
  11. '''
  12. def init(context):
  13. # 進行套利的品種
  14. context.goods =['SHFE.rb1801','SHFE.hc1801']
  15. # 訂閱行情
  16. subscribe(symbols=context.goods, frequency='60s', count=31, wait_group=True)
  17. def on_bar(context, bars):
  18. # 獲取兩個品種的時間序列
  19. data_rb = context.data(symbol=context.goods[0], frequency='60s', count=31, fields='close')
  20. close_rb = data_rb.values
  21. data_hc = context.data(symbol=context.goods[1], frequency='60s', count=31, fields='close')
  22. close_hc = data_hc.values
  23. # 計算價差
  24. spread = close_rb[:-1]- close_hc[:-1]
  25. # 計算布林帶的上下軌
  26. up = np.mean(spread)+2* np.std(spread)
  27. down = np.mean(spread)-2* np.std(spread)
  28. # 計算最新價差
  29. spread_now = close_rb[-1]- close_hc[-1]
  30. # 無交易時若價差上(下)穿布林帶上(下)軌則做空(多)價差
  31. position_rb_long = context.account().position(symbol=context.goods[0], side=PositionSide_Long)
  32. position_rb_short = context.account().position(symbol=context.goods[0], side=PositionSide_Short)
  33. ifnot position_rb_long andnot position_rb_short:
  34. if spread_now > up:
  35. order_target_volume(symbol=context.goods[0], volume=1, order_type=OrderType_Market,
  36. position_side=PositionSide_Short)
  37. print(context.goods[0],'以市價單開空倉一手')
  38. order_target_volume(symbol=context.goods[1], volume=1, order_type=OrderType_Market,
  39. position_side=PositionSide_Long)
  40. print(context.goods[1],'以市價單開多倉一手')
  41. if spread_now < down:
  42. order_target_volume(symbol=context.goods[0], volume=1, order_type=OrderType_Market,
  43. position_side=PositionSide_Long)
  44. print(context.goods[0],'以市價單開多倉一手')
  45. order_target_volume(symbol=context.goods[1], volume=1, order_type=OrderType_Market,
  46. position_side=PositionSide_Short)
  47. print(context.goods[1],'以市價單開空倉一手')
  48. # 價差迴歸時平倉
  49. elif position_rb_short:
  50. if spread_now <= up:
  51. order_close_all()
  52. print('價格迴歸,平所有倉位')
  53. # 跌破下軌反向開倉
  54. if spread_now < down:
  55. order_target_volume(symbol=context.goods[0], volume=1, order_type=OrderType_Market,
  56. position_side=PositionSide_Long)
  57. print(context.goods[0],'以市價單開多倉一手')
  58. order_target_volume(symbol=context.goods[1], volume=1, order_type=OrderType_Market,
  59. position_side=PositionSide_Short)
  60. print(context.goods[1],'以市價單開空倉一手')
  61. elif position_rb_long:
  62. if spread_now >= down:
  63. order_close_all()
  64. print('價格迴歸,平所有倉位')
  65. # 漲破上軌反向開倉
  66. if spread_now > up:
  67. order_target_volume(symbol=context.goods[0], volume=1, order_type=OrderType_Market,
  68. position_side=PositionSide_Short)
  69. print(context.goods[0],'以市價單開空倉一手')
  70. order_target_volume(symbol=context.goods[1], volume=1, order_type=OrderType_Market,
  71. position_side=PositionSide_Long)
  72. print(context.goods[1],'以市價單開多倉一手')
  73. if __name__ =='__main__':
  74. '''
  75. strategy_id策略ID,由系統生成
  76. filename檔名,請與本檔名保持一致
  77. mode實時模式:MODE_LIVE回測模式:MODE_BACKTEST
  78. token繫結計算機的ID,可在系統設定-金鑰管理中生成
  79. backtest_start_time回測開始時間
  80. backtest_end_time回測結束時間
  81. backtest_adjust股票復權方式不復權:ADJUST_NONE前復權:ADJUST_PREV後復權:ADJUST_POST
  82. backtest_initial_cash回測初始資金
  83. backtest_commission_ratio回測佣金比例
  84. backtest_slippage_ratio回測滑點比例
  85. '''
  86. run(strategy_id='strategy_id',
  87. filename='main.py',
  88. mode=MODE_BACKTEST,
  89. token='token_id',
  90. backtest_start_time='2017-09-01 08:00:00',
  91. backtest_end_time='2017-10-01 16:00:00',
  92. backtest_adjust=ADJUST_PREV,
  93. backtest_initial_cash=500000,
  94. backtest_commission_ratio=0.0001,
  95. backtest_slippage_ratio=0.0001)

文章來源:掘金量化交易平臺,轉載請註明出處!

----------------------------------------------------------------------------------------------------------------------------------------------------------

更多經典股票/期貨量化策略原始碼檢視: