对大宗商品黑色产业链期货的研究,进行钢厂利润套利涉及螺纹钢(代码rb
)、铁矿石(代码i
)、焦炭(代码j
)。在钢厂炼钢生产过程中影响产品成本的最主要因素就是原料。
通常对于基本面的研究我们认为,螺纹钢期货价格 = 1.6 * 铁矿石期货价格 + 0.5 * 焦炭期货价格 + 其它
。
以上是理想的情况,而期货价格是变化的,有溢价的。所以这个等式在实际的情况中是不相等的。从成品、原料组合的价格差变动来看,等式左右两边的价差可以理解为钢厂炼钢的利润,那么价差的波动就是钢厂利润的波动,因此追随钢厂利润波动的模式就是钢厂利润套利的模式。
公式变动为:
钢厂1吨螺纹钢的利润 = 1吨螺纹钢合约价格 – 1.6吨铁矿石合约价格 – 0.5吨焦炭合约价格 – 其它成本
钢厂炼钢利润波动的逻辑:如果炼钢利润高过一定水平,铁矿石和焦炭价格会跟涨,挤压炼钢利润;炼钢利润低过一定水平,钢材价格回升。我们可以看到钢厂利润波动的逻辑性较强。当钢厂利润达到高位时可以做空利润,即做空螺纹钢做多铁矿石焦炭。当钢厂利润处于底位时,可以做多利润,即做多螺纹钢做空铁矿石焦炭。
对于钢厂利润套利策略,该示例策略仅用于示范,实盘时请根据自己的策略/经验进行评估。
需要注意的是铁矿石合约和焦炭合约一手是100吨,螺纹钢合约一手是10吨。那么就需要计算出一个合理的对冲头寸。让一次对冲的比例为:螺纹钢吨数 : 铁矿石吨数 : 焦炭吨数 = 1 : 1.6 : 0.5
。
策略计算出最小的下单对冲组合手数分别是:
rb手数:100 ,换算吨数:1000吨
i手数:16,换算吨数:1600吨
j手数:5,换算吨数:500吨
达成比例1000 :1600 : 500 = 螺纹钢吨数 : 铁矿石吨数 : 焦炭吨数 = 1 : 1.6 : 0.5
可见,100手rb合约,16手i合约,5手j合约是最小的整数组合了。如果允许偏差一点也可以100:16:5
比例下减小5倍即:20:3:1
。即使这样,这个保证金量也是不小的,看来这个玩法不太适合小散。
使用布林指标捕获较大的偏差,做空钢厂利润。捕捉较小的偏差,做多钢厂利润。
/*backtest
start: 2021-03-01 09:00:00
end: 2021-07-28 15:00:00
period: 1d
basePeriod: 1h
exchanges: [{"eid":"Futures_CTP","currency":"FUTURES"}]
*/
// 参数
var symbol_rb = "rb2201"
var symbol_j = "j2201"
var symbol_i = "i2201"
var bollPeriod = 50
// 全局变量
var q = $.NewTaskQueue()
var p = $.NewPositionManager()
function calcCommonMultiple(a, b, c) {
var divisor = -1
var commonMultiple = a * b * c
for (var i = 1 ; i < (commonMultiple / Math.min(a, b, c)) ; i++) {
if (commonMultiple / a % i == 0 && commonMultiple / b % i == 0 && commonMultiple / c % i == 0) {
divisor = i
}
}
if (divisor == -1) {
throw "没有找到"
} else {
return commonMultiple / divisor
}
}
function main() {
var preTS = 0
var isFirst = true
var initAccount = null
while (true) {
Sleep(1000)
if (exchange.IO("status")) {
if (!$.IsTrading(symbol_rb) || !$.IsTrading(symbol_i) || !$.IsTrading(symbol_j)) {
continue
}
if (isFirst) {
initAccount = _C(exchange.GetAccount)
isFirst = false
}
// 订阅螺纹钢合约
var info_rb = exchange.SetContractType(symbol_rb)
if (!info_rb) {
continue
}
var records_rb = exchange.GetRecords()
// 订阅铁矿石合约
var info_i = exchange.SetContractType(symbol_i)
if (!info_i) {
continue
}
var records_i = exchange.GetRecords()
// 订阅焦炭合约
var info_j = exchange.SetContractType(symbol_j)
if (!info_j) {
continue
}
var records_j = exchange.GetRecords()
// 校验records数据
if (!records_rb || !records_i || !records_j || Math.min(records_rb.length, records_i.length, records_j.length) < bollPeriod) {
continue
}
// 更新持仓
var holds = {rb : 0, i : 0, j : 0}
var pos = _C(exchange.GetPosition)
for (var i = 0 ; i < pos.length ; i++) {
if (pos[i].ContractType == symbol_rb) {
if (pos[i].Type == PD_LONG || pos[i].Type == PD_LONG_YD) {
holds.rb += pos[i].Amount
} else {
holds.rb -= pos[i].Amount
}
} else if (pos[i].ContractType == symbol_i) {
if (pos[i].Type == PD_LONG || pos[i].Type == PD_LONG_YD) {
holds.i += pos[i].Amount
} else {
holds.i -= pos[i].Amount
}
} else if (pos[i].ContractType == symbol_j) {
if (pos[i].Type == PD_LONG || pos[i].Type == PD_LONG_YD) {
holds.j += pos[i].Amount
} else {
holds.j -= pos[i].Amount
}
}
}
// minCommonMultiple
var minCommonMultiple = calcCommonMultiple(info_rb.VolumeMultiple, info_i.VolumeMultiple, info_j.VolumeMultiple)
var amount_rb = minCommonMultiple / info_rb.VolumeMultiple * 10
var amount_i = minCommonMultiple / info_i.VolumeMultiple * 16
var amount_j = minCommonMultiple / info_j.VolumeMultiple * 5
// 一般情况是1.5-1.6(矿石)+0.4-0.5(焦炭)=1吨粗钢 , 1 : 1.6 : 0.5 = 10 : 16 : 5
var r = []
for (var i = Math.min(records_rb.length, records_i.length, records_j.length) ; i > 0 ; i--) {
var bar_rb = records_rb[records_rb.length - i]
var bar_i = records_i[records_i.length - i]
var bar_j = records_j[records_j.length - i]
r.push(bar_rb.Close - 1.6 * bar_i.Close - 0.5 * bar_j.Close)
}
if (r.length < bollPeriod) {
continue
}
var boll = TA.BOLL(r, bollPeriod, 2)
var up = boll[0]
var down = boll[2]
if (records_rb[records_rb.length - 1].Time == records_i[records_i.length - 1].Time && records_i[records_i.length - 1].Time == records_j[records_j.length - 1].Time && preTS != records_j[records_j.length - 1].Time) {
preTS = records_j[records_j.length - 1].Time
$.PlotLine("close", r[r.length - 2], preTS)
$.PlotLine("up", up[up.length - 2], preTS)
$.PlotLine("down", down[down.length - 2], preTS)
}
// 判断触发条件
if (r[r.length - 1] > up[up.length - 1] && holds.rb == 0 && holds.i == 0 && holds.j == 0) {
// 空rb多i,j
q.pushTask(exchange, symbol_rb, "sell", amount_rb, function(task, ret) {
Log(task.desc, ret)
if (ret) {
q.pushTask(exchange, symbol_i, "buy", amount_i, function(task, ret) {
Log(task.desc, ret)
if (ret) {
q.pushTask(exchange, symbol_j, "buy", amount_j, function(task, ret) {
Log(task.desc, ret)
$.PlotFlag(new Date().getTime(), '空rb多i,j', 'up')
})
}
})
}
})
} else if (r[r.length - 1] < down[down.length - 1] && holds.rb == 0 && holds.i == 0 && holds.j == 0) {
// 多rb空i,j
q.pushTask(exchange, symbol_rb, "buy", amount_rb, function(task, ret) {
Log(task.desc, ret)
if (ret) {
q.pushTask(exchange, symbol_i, "sell", amount_i, function(task, ret) {
Log(task.desc, ret)
if (ret) {
q.pushTask(exchange, symbol_j, "sell", amount_j, function(task, ret) {
Log(task.desc, ret)
$.PlotFlag(new Date().getTime(), '多rb空i,j', 'down')
})
}
})
}
})
} else if (r[r.length - 1] > up[up.length - 1] && holds.rb > 0 && holds.i < 0 && holds.j < 0) {
// 平rb多,平i,j空
q.pushTask(exchange, symbol_rb, "coverall", -1, function(task, ret) {
Log(task.desc, ret)
if (ret) {
q.pushTask(exchange, symbol_i, "coverall", -1, function(task, ret) {
Log(task.desc, ret)
if (ret) {
q.pushTask(exchange, symbol_j, "coverall", -1, function(task, ret) {
Log(task.desc, ret)
LogProfit(_C(exchange.GetAccount).Balance - initAccount.Balance)
$.PlotFlag(new Date().getTime(), '平多rb平空i,j', 'up')
})
}
})
}
})
} else if (r[r.length - 1] < down[down.length - 1] && holds.rb < 0 && holds.i > 0 && holds.j > 0) {
// 平rb空,平i,j多
q.pushTask(exchange, symbol_rb, "coverall", -1, function(task, ret) {
Log(task.desc, ret)
if (ret) {
q.pushTask(exchange, symbol_i, "coverall", -1, function(task, ret) {
Log(task.desc, ret)
if (ret) {
q.pushTask(exchange, symbol_j, "coverall", -1, function(task, ret) {
Log(task.desc, ret)
LogProfit(_C(exchange.GetAccount).Balance - initAccount.Balance)
$.PlotFlag(new Date().getTime(), '平空rb平多i,j', 'down')
})
}
})
}
})
}
q.poll()
} else {
LogStatus(_D())
}
}
}
策略需要勾选「画线类库」、「商品期货交易类库」
策略仅用于研究学习,实盘慎用。
zhaosunday 问下画线类库在哪里没找到
雨幕(youquant) 可以在策略广场里搜索:https://www.fmz.cn/square