1、内容概括
目前国内商品期货套利模式主要包括产业链套利、跨期套利、内外盘套利和期现套利。下面的内容将讲述商品期货产业链套利模型,参考于东方证券《衍生品系列研究之五-商品期货套利策略实证》,根据其产业链价值构造逻辑,撰写策略,并进行实测。这篇研报的理论基础是产业链价值稳定,且利润回复性强,可以进行反向操作期货合约,获取产业链利润均值回复的交易性机会。
2、钢厂利润产业链关系
目前国内商品期货套利模式主要包括产业链套利、跨期套利、内外盘套利和期现套利。这里我们针对黑色产业链期货品种进行研究,利用产业链关系进行钢厂利润套利,涉及螺纹钢、铁矿、焦炭品种。炼钢工艺中影响总成本的主要因素是原料成本,即铁矿石、焦炭成本。根据研报内容,我们获知制造钢材时铁矿石与焦炭的消耗可以通过如下方式进行计算:
螺纹钢期货价格 = 1.6×铁矿石期货价格 + 0.5×焦炭期货价格 + 其他成本
上述等式是无套利的情形,而市场上的期货价格是波动的,上述等式在实际的市场中是不等的。如果从价差的变动来看,上述等式左右两边的价差可以理解为钢厂炼钢的利润,那么价差的波动就是钢厂利润的波动,因此追随钢厂利润波动的模式就是钢厂利润套利的模式,在实际操作中,我们用指数合约代替实际价格
钢厂利润=1×螺纹钢指数合约价格-1.6×铁矿石指数合约价格-0.5×焦炭指数合约价格-其他成本
关于钢厂炼钢利润波动的逻辑,参考研报内容:如果炼钢利润过高,铁矿和焦炭价格会跟涨,挤压炼钢利润;炼钢利润过低,钢材价格回升。我们可以看到钢厂利润波动的逻辑性较强,基于此,当钢厂利润达到高位时,可以做空利润,即做空螺纹钢做多铁矿石焦炭,当钢厂利润处于底位时,可以做多利润,即做多螺纹钢做空铁矿石焦炭。
3、策略模型构建
一般的套利做法是设置固定的价差值进行套利,在价格偏离价差平均水平时进行多空操作,下面是通过期货指数绘制的钢厂利润曲线
从上面的图中我们发现价格并没有一个稳定的回复价格,即价差的分布并不对称,这样的序列显然不适合用传统的回复套利方法,在本报告中我们采用类似布林通道的策略思路,比如当价差超越长期或者短期均值一定标准差之时,可以认为此时的价差水平偏高,因此我们做空价格相对高的期货,做多价格相对低的期货,而当二者的价差回归到一个长期或短期均值的时候同时对二者进行平仓。这样策略获得价差回复的收益。
我们对钢厂利润波动设计策略进行套利,考虑到不同的期货品种上市时间不一样,加上初始统计需要一定的初始数据长度,三个品种中,铁矿石期货是最晚,于 2013 年 10 月 18 日上市交易,考虑到计算均值需要一定的数据,我们统一将策略设置为2014 年 1 月 1 日开始回测。
价差序列下穿上轨,利润冲高回落进行回复,策略空螺纹钢、多焦煤焦炭;价差序列上穿下轨,利润过低回复上升,策略多螺纹钢、空焦煤焦炭。
研报中模型具体设置为
开仓条件:价差在10日均值加1倍标准差和1.2倍标准差之间,有回归趋势开仓。
平仓条件:回归到10日均值进行平仓。
止损:设置的止损为5%,止损后10天内不开仓。
在该示例中,在回测过程中我们设置不同的开仓标准以及止损等条件,发现以下设置更为合适
开仓条件:价差在15日均值加1.8倍标准差之间,有回归趋势开仓。
止损:设置的止损位3%,止损后10天内不开仓
手续费:万分之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')])
# 期货类每笔交易时的手续费是:买入时万分之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):
#风控部分
#触发风控规则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 '触发风控平空仓'
for future_s in hold_future_s:
order_target_value(future_s,0,side='short')
if len(hold_future_l)>0:
print '触发风控平多仓'
for future_l in hold_future_l:
order_target_value(future_l,0,side='long')
else:
#运行风控函数
risk = rolling_risk(context) #滚动计算策略回撤
if risk:
pass
#未触发风控逻辑执行交易逻辑
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 '触发交易信号:空螺纹钢、多铁矿石、焦煤'
#对非主力合约的空仓标的全部清空
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 '触发交易信号:多螺纹钢、空铁矿石、焦煤'
#对非主力合约的多仓的标的全部清空
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)
#设置最大回撤阈值触发止损信号
if max_down >= g.maxdown:
print('触发滚动最大回撤,进行清仓')
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
#进行状态记录
#当前价格所在轨道区间
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
#更新轨道区间状态
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)