商品期货跨品种套利策略

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)
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,607评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,047评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,496评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,405评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,400评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,479评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,883评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,535评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,743评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,544评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,612评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,309评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,881评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,891评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,136评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,783评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,316评论 2 342

推荐阅读更多精彩内容

  • 商品期货套利策略 套利策略一般包括期现套利、跨期套利、跨市场套利、跨品种套利等。 对于商品期货而言,期现套利必须交...
    鸿鹄Max阅读 2,745评论 0 1
  • 话说期货的暴涨暴跌给交易者们结结实实地上了一堂风险教育课,不少人一夜暴亏血本无归。于是,很多畏惧风险、追求稳健的交...
    风暴之王阅读 922评论 1 2
  • 跨品种价差套利简介 套利原理 ​ 通俗地讲,就是两个合约相关性很好,突然市场出了一个bug,破坏了两个合约之间的平...
    鸿鹄Max阅读 1,215评论 0 0
  • 教你炒期货:基于概率思维与逻辑思维的交易系统 Jerry Ma◆ 序言 期货市场注定是一个充满话题的市场,有人在这...
    binzeng阅读 2,019评论 0 2
  • 字符串 1.什么是字符串 使用单引号或者双引号括起来的字符集就是字符串。 引号中单独的符号、数字、字母等叫字符。 ...
    mango_2e17阅读 7,493评论 1 7