钢厂利润套利策略(螺纹钢、铁矿石、焦炭套利,难度:初级)

钢厂利润套利逻辑

目前国内商品期货套利模式主要包括产业链套利、跨期套利、内外盘套利和期现套利。我们针对黑色产业链期货进行研究,利用产业链关系进行钢厂利润套利,涉及螺纹钢、铁矿石、焦炭品种,炼钢工艺中影响总成本的主要因素是原料成本,即铁矿石、焦炭成本。根据研报内容,我们获知钢材的成本可以通过如下方式进行计算:

螺纹钢期货价格 = 1.6 * 铁矿石期货价格 + 0.5 * 焦炭期货价格 + 其他成本

上述等式是无套利的情形,而市场上的期货价格是波动的,上述等式在实际的市场中是不等的。如果从价差的变动来看,上述等式左右两边的价差可以理解为钢厂炼钢的利润,那么价差的波动就是钢厂利润的波动,因此追随钢厂利润波动的模式就是钢厂利润套利的模式,在实际操作中,我们用指数合约代替实际价格:

钢厂利润 = 1 * 螺纹钢指数合约价格 – 1.6 * 铁矿石指数合约价格 – 0.5 * 焦炭指数合约价格 – 其他成本

def cal_spread(klines_rb, klines_i, klines_j):
    index_spread = klines_rb.close - 1.6 * klines_i.close - 0.5 * klines_j.close
    ma_spread = ma(index_spread, 10)
    spread_std = np.std(index_spread)
    klines_rb["index_spread"] = index_spread
    klines_rb["index_spread.board"] = "index_spread"

    return index_spread, ma_spread, spread_std

关于钢厂炼钢利润波动的逻辑,参考研报内容:如果炼钢利润过高,铁矿石和焦炭价格会跟涨,挤压炼钢利润;炼钢利润过低,钢材价格回升。我们可以看到钢厂利润波动的逻辑性较强,基于此,当钢厂利润达到高位时,可以做空利润,即做空螺纹钢做多铁矿石焦炭,当钢厂利润处于底位时,可以做多利润,即做多螺纹钢做空铁矿石焦炭。

一般的套利做法是设置固定的价差值进行套利,在价格偏离价差平均水平时进行多空操作,下面是通过期货指数绘制的钢厂利润曲线:

从上面的图中我们发现价格并没有一个稳定的回归价格,即价差的分布并不对称,这样的序列显然不适合用传统的回归套利方法,在本报告中我们采用类似布林通道的策略思想,比如当价差超越长期或者短期均值一个标准差之时,可以认为此时的价差水平偏高,因此我们做空价格相对高的期货,做多价格相对低的期货,而当二者的价差回归到一个长期或短期均值的时候同时对二者进行平仓。这样策略获得价差回归的收益。

研报中模型具体设置为:

  • 开仓条件:价差在10日均值加1倍标准差和1.2倍标准差之间,有回归趋势开仓。
  • 平仓条件:回归到10日均值进行平仓。

策略原理

在该示例中,在回测过程中我们设置不同的开仓标准以及止损等条件,发现以下设置更为合适:

  • 交易条件:价差偏离15日均值加减0.5倍标准差时开仓
  • 具体操作:
    • 当价差超过上轨(15日均值+0.5倍标准差)并回落时,做空螺纹钢、做多铁矿石焦炭
    • 当价差低于下轨(15日均值-0.5倍标准差)并回升时,做多螺纹钢、做空铁矿石焦炭
if index_spread.iloc[-1] > 0.5 * spread_std + ma_spread.iloc[-1]:
    target_pos_rb.set_target_volume(-100)
    target_pos_i.set_target_volume(100)
    target_pos_j.set_target_volume(100)

elif index_spread.iloc[-1] < ma_spread.iloc[-1] - 0.5 * spread_std:
    target_pos_rb.set_target_volume(100)
    target_pos_i.set_target_volume(-100)
    target_pos_j.set_target_volume(-100)

策略回测

回测设定:

  • 初始账户资金:1000万
  • 回测日期:2019.06.04-2019.08.11
  • 多、空头开仓手数:100手
  • 合约:SHFE.rb2001,DCE.i2001,DCE.j2001

策略回测结果图

钢厂套利策略回测结果:

合约代码收益率风险度最大回撤年化夏普率
SHFE.rb2001,DCE.i2001,DCE.j200131.49%18.60%10.01%2.649

天勤量化源码

from tqsdk import TqApi, TargetPosTask
from tqsdk.tafunc import ma
import numpy as np

api = TqApi()

SYMBOL_rb = "SHFE.rb2001"
SYMBOL_i = "DCE.i2001"
SYMBOL_j = "DCE.j2001"

klines_rb = api.get_kline_serial(SYMBOL_rb, 86400)
klines_i = api.get_kline_serial(SYMBOL_i, 86400)
klines_j = api.get_kline_serial(SYMBOL_j, 86400)

target_pos_rb = TargetPosTask(api, SYMBOL_rb)
target_pos_i = TargetPosTask(api, SYMBOL_i)
target_pos_j = TargetPosTask(api, SYMBOL_j)

# 计算钢厂利润线,并将利润线画到副图
def cal_spread(klines_rb, klines_i, klines_j):
    index_spread = klines_rb.close - 1.6 * klines_i.close - 0.5 * klines_j.close
    # 使用15日均值,与注释保持一致
    ma_spread = ma(index_spread, 15)
    # 计算标准差
    spread_std = np.std(index_spread)
    klines_rb["index_spread"] = index_spread
    klines_rb["index_spread.board"] = "index_spread"

    return index_spread, ma_spread, spread_std


# 初始计算利润线
index_spread, ma_spread, spread_std = cal_spread(klines_rb, klines_i, klines_j)

print("ma_spread是%.2f,index_spread是%.2f,spread_std是%.2f" % (ma_spread.iloc[-1], index_spread.iloc[-1], spread_std))

# 记录当前持仓状态,避免重复发出信号
current_position = 0  # 0表示空仓,1表示多螺纹空焦炭焦煤,-1表示空螺纹多焦炭焦煤

while True:
    api.wait_update()
    
    # 每次有新日线生成时重新计算利润线
    if api.is_changing(klines_j.iloc[-1], "datetime"):
        index_spread, ma_spread, spread_std = cal_spread(klines_rb, klines_i, klines_j)
        
        # 计算上下轨
        upper_band = ma_spread.iloc[-1] + 0.5 * spread_std
        lower_band = ma_spread.iloc[-1] - 0.5 * spread_std
        
        print("ma_spread是%.2f,index_spread是%.2f,spread_std是%.2f" % (
            ma_spread.iloc[-1], index_spread.iloc[-1], spread_std))
        print("上轨是%.2f,下轨是%.2f" % (upper_band, lower_band))
        
        # 确保有足够的历史数据
        if len(index_spread) >= 2:
            # 1. 检测下穿上轨:前一个K线在上轨之上,当前K线在上轨之下或等于上轨
            if index_spread.iloc[-2] > upper_band and index_spread.iloc[-1] <= upper_band:
                if current_position != -1:  # 避免重复开仓
                    # 价差序列下穿上轨,利润冲高回落进行回复,策略空螺纹钢、多焦煤焦炭
                    target_pos_rb.set_target_volume(-100)
                    target_pos_i.set_target_volume(100)
                    target_pos_j.set_target_volume(100)
                    current_position = -1
                    print("下穿上轨:空螺纹钢、多焦煤焦炭")
            
            # 2. 检测上穿下轨:前一个K线在下轨之下,当前K线在下轨之上或等于下轨
            elif index_spread.iloc[-2] < lower_band and index_spread.iloc[-1] >= lower_band:
                if current_position != 1:  # 避免重复开仓
                    # 价差序列上穿下轨,利润过低回复上升,策略多螺纹钢、空焦煤焦炭
                    target_pos_rb.set_target_volume(100)
                    target_pos_i.set_target_volume(-100)
                    target_pos_j.set_target_volume(-100)
                    current_position = 1
                    print("上穿下轨:多螺纹钢、空焦煤焦炭")
    
    # 实时监控价差变化情况
    if api.is_changing(klines_rb.iloc[-1], "close") or api.is_changing(klines_i.iloc[-1], "close") or api.is_changing(klines_j.iloc[-1], "close"):
        # 实时更新价差
        current_spread = klines_rb.close.iloc[-1] - 1.6 * klines_i.close.iloc[-1] - 0.5 * klines_j.close.iloc[-1]
        # 可以添加实时监控输出
        # print("当前价差: %.2f" % current_spread)