1、內容概括
目前國內商品期貨套利模式主要包括產業(yè)鏈套利、跨期套利、內外盤套利和期現套利。下面的內容將講述商品期貨產業(yè)鏈套利模型,參考于東方證券《衍生品系列研究之五-商品期貨套利策略實證》,根據其產業(yè)鏈價值構造邏輯,撰寫策略,并進行實測。這篇研報的理論基礎是產業(yè)鏈價值穩(wěn)定,且利潤回復性強,可以進行反向操作期貨合約,獲取產業(yè)鏈利潤均值回復的交易性機會。
2、鋼廠利潤產業(yè)鏈關系
目前國內商品期貨套利模式主要包括產業(yè)鏈套利、跨期套利、內外盤套利和期現套利。這里我們針對黑色產業(yè)鏈期貨品種進行研究,利用產業(yè)鏈關系進行鋼廠利潤套利,涉及螺紋鋼、鐵礦、焦炭品種。煉鋼工藝中影響總成本的主要因素是原料成本,即鐵礦石、焦炭成本。根據研報內容,我們獲知制造鋼材時鐵礦石與焦炭的消耗可以通過如下方式進行計算:
螺紋鋼期貨價格 = 1.6×鐵礦石期貨價格 + 0.5×焦炭期貨價格 + 其他成本
上述等式是無套利的情形,而市場上的期貨價格是波動的,上述等式在實際的市場中是不等的。如果從價差的變動來看,上述等式左右兩邊的價差可以理解為鋼廠煉鋼的利潤,那么價差的波動就是鋼廠利潤的波動,因此追隨鋼廠利潤波動的模式就是鋼廠利潤套利的模式,在實際操作中,我們用指數合約代替實際價格
鋼廠利潤=1×螺紋鋼指數合約價格-1.6×鐵礦石指數合約價格-0.5×焦炭指數合約價格-其他成本
關于鋼廠煉鋼利潤波動的邏輯,參考研報內容:如果煉鋼利潤過高,鐵礦和焦炭價格會跟漲,擠壓煉鋼利潤;煉鋼利潤過低,鋼材價格回升。我們可以看到鋼廠利潤波動的邏輯性較強,基于此,當鋼廠利潤達到高位時,可以做空利潤,即做空螺紋鋼做多鐵礦石焦炭,當鋼廠利潤處于底位時,可以做多利潤,即做多螺紋鋼做空鐵礦石焦炭。
3、策略模型構建
一般的套利做法是設置固定的價差值進行套利,在價格偏離價差平均水平時進行多空操作,下面是通過期貨指數繪制的鋼廠利潤曲線
從上面的圖中我們發(fā)現價格并沒有一個穩(wěn)定的回復價格,即價差的分布并不對稱,這樣的序列顯然不適合用傳統(tǒng)的回復套利方法,在本報告中我們采用類似布林通道的策略思路,比如當價差超越長期或者短期均值一定標準差之時,可以認為此時的價差水平偏高,因此我們做空價格相對高的期貨,做多價格相對低的期貨,而當二者的價差回歸到一個長期或短期均值的時候同時對二者進行平倉。這樣策略獲得價差回復的收益。
我們對鋼廠利潤波動設計策略進行套利,考慮到不同的期貨品種上市時間不一樣,加上初始統(tǒng)計需要一定的初始數據長度,三個品種中,鐵礦石期貨是最晚,于 2013 年 10 月 18 日上市交易,考慮到計算均值需要一定的數據,我們統(tǒng)一將策略設置為2014 年 1 月 1 日開始回測。
價差序列下穿上軌,利潤沖高回落進行回復,策略空螺紋鋼、多焦煤焦炭;價差序列上穿下軌,利潤過低回復上升,策略多螺紋鋼、空焦煤焦炭。
研報中模型具體設置為
開倉條件:價差在10日均值加1倍標準差和1.2倍標準差之間,有回歸趨勢開倉。
平倉條件:回歸到10日均值進行平倉。
止損:設置的止損為5%,止損后10天內不開倉。
在該示例中,在回測過程中我們設置不同的開倉標準以及止損等條件,發(fā)現以下設置更為合適
開倉條件:價差在15日均值加1.8倍標準差之間,有回歸趨勢開倉。
止損:設置的止損位3%,止損后10天內不開倉
手續(xù)費:萬分之1雙邊
策略代碼
# 導入函數庫
from jqdata import *
import numpy as np
## 初始化函數,設定基準等等
def initialize(context):
# 設定銀華日利作為基準
set_benchmark('511880.XSHG')
#設置日志輸出級別
log.set_level('order', 'error')
set_parameter(context)
### 期貨相關設定 ###
# 設定賬戶為金融賬戶
set_subportfolios([SubPortfolioConfig(cash=context.portfolio.starting_cash, type='futures')])
# 期貨類每筆交易時的手續(xù)費是:買入時萬分之1,賣出時萬分之1,平今倉為萬分之1
set_order_cost(OrderCost(open_commission=0.0001, close_commission=0.0001,close_today_commission=0.0001), type='index_futures')
#獲取可操作資金
g.init_cash = context.portfolio.starting_cash
#主力合約記錄
g.main_rb = get_dominant_future('RB', date=context.current_dt)
g.main_i = get_dominant_future('I', date=context.current_dt)
g.main_j = get_dominant_future('J', date=context.current_dt)
# 設定保證金比例
set_option('futures_margin_rate', 0.10)
# 設置滑點(單邊萬5,雙邊千1)
set_slippage(PriceRelatedSlippage(0.00),type='future')
# 開盤時運行
run_daily( market_open, time='open', reference_security='RB8888.XSGE')
# 收盤后運行
run_daily( after_market_close, time='after_close', reference_security='RB8888.XSGE')
# 參數設置函數
def set_parameter(context):
#利潤回歸模型
g.ma = 15
g.up_std = 1.8
g.down_std=1.8
g.state = 0
#利潤系數
#參考研報內容 鋼廠利潤公式 鋼廠利潤= 1*螺紋鋼期貨價格- 1.6*鐵礦石期貨價格+0.5*焦炭期貨價格+其他成本
g.x=1
g.y=1.6
g.z=0.5
#風控部分
g.risk_days = 10
g.tot_values = [context.portfolio.starting_cash]*g.risk_days
g.maxdown = 0.03 #最大回撤設置
## 開盤時運行函數
def market_open(context):
#風控部分
#觸發(fā)風控規(guī)則10天內保持空倉
if g.risk_days < 10:
#清空持倉
hold_future_s = context.portfolio.short_positions.keys()
hold_future_l = context.portfolio.long_positions.keys()
#對合約標的全部清空
if len(hold_future_s)>0:
print '觸發(fā)風控平空倉'
for future_s in hold_future_s:
order_target_value(future_s,0,side='short')
if len(hold_future_l)>0:
print '觸發(fā)風控平多倉'
for future_l in hold_future_l:
order_target_value(future_l,0,side='long')
else:
#運行風控函數
risk = rolling_risk(context) #滾動計算策略回撤
if risk:
pass
#未觸發(fā)風控邏輯執(zhí)行交易邏輯
else:
trade(context)
g.risk_days += 1 #風控天數累加
#交易主體部分
def trade(context):
#獲取幾個標的的指數價格序列
price_df = history(50,security_list=['RB8888.XSGE','I8888.XDCE','J8888.XDCE','JM8888.XDCE','RB9999.XSGE','I9999.XDCE','J9999.XDCE','JM9999.XDCE'])
#獲取鋼廠利潤
se = g.x*price_df['RB8888.XSGE'] - g.y*price_df['I8888.XDCE'] - g.z*price_df['J8888.XDCE']
#資金比例
#由三者的系數推出資金權重比例
a,b,c = g.y*g.z,g.x*g.z,g.y*g.z
#初始資金的十分之一
cash = g.init_cash*0.1
#資金分配
cash_i = (b/(a+b+c))*cash
cash_j = (c/(a+b+c))*cash
cash_rb = (a/(a+b+c))*cash
#獲取交易信號
trade_signal = get_signal(se.values)
#根據交易信號進行交易
#獲取標的的主力合約
main_rb = get_dominant_future('RB', date=context.current_dt)
main_i = get_dominant_future('I', date=context.current_dt)
main_j = get_dominant_future('J', date=context.current_dt)
#獲取當前持倉的合約
hold_future_s = context.portfolio.short_positions.keys()
hold_future_l = context.portfolio.long_positions.keys()
#交易部分
if trade_signal > 1: #下穿上軌
print '觸發(fā)交易信號:空螺紋鋼、多鐵礦石、焦煤'
#對非主力合約的空倉標的全部清空
if len(hold_future_s)>0:
for future_s in hold_future_s:
if future_s != main_rb:
order_target_value(future_s,0,side='short')
#做空螺紋鋼主力
order_target_value(main_rb,cash_rb, side='short')
#對非主力合約的多倉的標的全部清空
if len(hold_future_l)>0:
for future_l in hold_future_l:
if (future_l != main_i) and (future_l != main_j):
order_target_value(future_l,0,side='long')
#做多鐵礦石、焦煤主力
order_target_value(main_i,cash_i, side='long')
order_target_value(main_j,cash_j, side='long')
elif trade_signal < -1: #上穿下軌
print '觸發(fā)交易信號:多螺紋鋼、空鐵礦石、焦煤'
#對非主力合約的多倉的標的全部清空
if len(hold_future_l)>0:
for future_l in hold_future_l:
if future_l != main_rb:
order_target_value(future_l,0,side='long')
#做多螺紋鋼主力
order_target_value(main_rb,cash_rb, side='long')
#對非主力合約的空倉標的全部清空
if len(hold_future_s)>0:
for future_s in hold_future_s:
if (future_s != main_i) and (future_s != main_j):
order_target_value(future_s,0,side='short')
#做空鐵礦石、焦煤主力
order_target_value(main_i,cash_i, side='short')
order_target_value(main_j,cash_j, side='short')
#移倉換月邏輯
#主力合約變更進行換倉
if g.main_rb != main_rb:
print 'rb主力合約由%s變化為%s'%(g.main_rb,main_rb)
if g.main_rb in hold_future_s:
order_target_value(g.main_rb,0,side='short')
order_target_value(main_rb,cash_rb,side='short')
elif g.main_rb in hold_future_l:
order_target_value(g.main_rb,0,side='long')
order_target_value(main_rb,cash_rb,side='long')
if g.main_j != main_j:
print 'j主力合約由%s變化為%s'%(g.main_j,main_j)
if g.main_j in hold_future_s:
order_target_value(g.main_j,0,side='short')
order_target_value(main_j,cash_j,side='short')
elif g.main_j in hold_future_l:
order_target_value(g.main_j,0,side='long')
order_target_value(main_j,cash_j,side='long')
if g.main_i != main_i:
print 'i主力合約由%s變化為%s'%(g.main_i,main_i)
if g.main_i in hold_future_s:
order_target_value(g.main_i,0,side='short')
order_target_value(main_i,cash_i,side='short')
elif g.main_i in hold_future_l:
order_target_value(g.main_i,0,side='long')
order_target_value(main_i,cash_i,side='long')
#更新主力合約記錄
g.main_rb = main_rb
g.main_j = main_j
g.main_i = main_i
#策略滾動回撤
def rolling_risk(context):
#記錄21天內最大回撤數值
value = context.portfolio.total_value
g.tot_values.append(value)
#更新賬戶總價值
g.tot_values = g.tot_values[1:]
max_down = 1 - value*1.0/max(g.tot_values)
#設置最大回撤閾值觸發(fā)止損信號
if max_down >= g.maxdown:
print('觸發(fā)滾動最大回撤,進行清倉')
g.risk_days = 0
g.tot_values = [value]*10 #賬戶價值序列重置
return 1
else:
return 0
#獲取交易信號
def get_signal(se):
#最新價格
price_now = se[-1]
se_temp = se[-g.ma:]
mid = np.mean(se_temp)
up = mid + g.up_std*np.std(se_temp)
down= mid - g.down_std*np.std(se_temp)
signal = 0
#進行狀態(tài)記錄
#當前價格所在軌道區(qū)間
if price_now>up:
state_new = 2
elif price_now < down:
state_new = -2
elif price_now > mid:
state_new = 1
elif price_now < mid:
state_new = -1
else:
state_new = 0
#進行信號判斷
#記錄上下穿越軌道的信號
#下穿上軌
if g.state==2 and state_new <2:
signal=2
#上穿下軌
elif g.state==-2 and state_new >-2:
signal=-2
elif g.state<0 and state_new > 0:
signal=1
elif g.state>0 and state_new <0:
signal= -1
else:
signal= 0
#更新軌道區(qū)間狀態(tài)
g.state = state_new
return signal
## 收盤后運行函數
def after_market_close(context):
cash_ratio = 1 - context.portfolio.available_cash*1.0/context.portfolio.total_value
print '當日策略資金占用比例為:%s'%cash_ratio
l_hold_future = context.portfolio.long_positions.keys()
s_hold_future = context.portfolio.short_positions.keys()
for future in l_hold_future:
print '%s有多頭持倉:%s'% (future,context.portfolio.long_positions[future].total_amount)
for future in s_hold_future:
print '%s有空頭持倉:%s'% (future,context.portfolio.short_positions[future].total_amount)