往期文章中我们实现了一个简单的现货跟单机器人,今天我们实现一个合约版的简易跟单机器人。
设计思路
合约版的跟单机器人和现货版的有很大区别了,现货跟单主要可以通过监控账户资产变化实现。期货版的则需要监控账户持仓变动情况。所以期货版的情况就复杂一些,因为期货有多头持仓,空头持仓,有不同的合约。需要对这一系列的细节处理。核心思路就是监控持仓变动量。根据持仓变动量去触发跟单动作。最初设计时是计划把多头、空头一起处理,发现这样处理会变得很复杂。分析问题之后决定对于持仓的多头、空头分开处理。
策略实现
策略参数:
支持回测,可以直接使用默认设置回测观察。
策略源码
function test() {
var ts = new Date().getTime()
if (ts % (1000 * 60 * 60 * 6) > 1000 * 60 * 60 * 5.5) {
Sleep(1000 * 60 * 10)
var nowPosAmount = getPosAmount(_C(exchange.GetPosition), refCt)
var longPosAmount = nowPosAmount.long
var shortPosAmount = nowPosAmount.short
var x = Math.random()
if (x > 0.7) {
exchange.SetDirection("buy")
exchange.Buy(-1, _N(Math.max(1, x * 10), 0), "参考账户测试开单#FF0000")
} else if(x < 0.2) {
exchange.SetDirection("sell")
exchange.Sell(-1, _N(Math.max(1, x * 10), 0), "参考账户测试开单#FF0000")
} else if(x >= 0.2 && x <= 0.5 && longPosAmount > 4) {
exchange.SetDirection("closebuy")
exchange.Sell(-1, longPosAmount, "参考账户测试平仓#FF0000")
} else if(shortPosAmount > 4) {
exchange.SetDirection("closesell")
exchange.Buy(-1, _N(shortPosAmount / 2, 0), "参考账户测试平仓#FF0000")
}
}
}
function getPosAmount(pos, ct) {
var longPosAmount = 0
var shortPosAmount = 0
_.each(pos, function(ele) {
if (ele.ContractType == ct && ele.Type == PD_LONG) {
longPosAmount = ele.Amount
} else if (ele.ContractType == ct && ele.Type == PD_SHORT) {
shortPosAmount = ele.Amount
}
})
return {long: longPosAmount, short: shortPosAmount}
}
function trade(e, ct, type, delta) {
var nowPosAmount = getPosAmount(_C(e.GetPosition), ct)
var nowAmount = type == PD_LONG ? nowPosAmount.long : nowPosAmount.short
if (delta > 0) {
var tradeFunc = type == PD_LONG ? e.Buy : e.Sell
e.SetDirection(type == PD_LONG ? "buy" : "sell")
tradeFunc(-1, delta)
} else if (delta < 0) {
var tradeFunc = type == PD_LONG ? e.Sell : e.Buy
e.SetDirection(type == PD_LONG ? "closebuy" : "closesell")
if (nowAmount <= 0) {
Log("未检测到持仓")
return
}
tradeFunc(-1, Math.min(nowAmount, Math.abs(delta)))
} else {
throw "错误"
}
}
function main() {
LogReset(1)
if (exchanges.length < 2) {
throw "没有跟单的交易所"
}
var exName = exchange.GetName()
if (!exName.includes("Futures_")) {
throw "仅支持期货跟单"
}
Log("开始监控", exName, "交易所", "#FF0000")
for (var i = 1 ; i < exchanges.length ; i++) {
if (exchanges[i].GetName() != exName) {
throw "跟单的期货交易所和参考交易所不同!"
}
}
_.each(exchanges, function(e) {
if (!IsVirtual()) {
e.SetCurrency(refCurrency)
if (isSimulate) {
if (e.GetName() == "Futures_OKCoin") {
e.IO("simulate", true)
}
}
}
e.SetContractType(refCt)
})
var initRefPosAmount = getPosAmount(_C(exchange.GetPosition), refCt)
while(true) {
if (IsVirtual()) {
test()
}
Sleep(5000)
var nowRefPosAmount = getPosAmount(_C(exchange.GetPosition), refCt)
var tbl = {
type : "table",
title : "持仓",
cols : ["名称", "标签", "多仓", "空仓", "账户资产(Stocks)", "账户资产(Balance)"],
rows : []
}
_.each(exchanges, function(e) {
var pos = getPosAmount(_C(e.GetPosition), refCt)
var acc = _C(e.GetAccount)
tbl.rows.push([e.GetName(), e.GetLabel(), pos.long, pos.short, acc.Stocks, acc.Balance])
})
LogStatus(_D(), "\n`" + JSON.stringify(tbl) + "`")
var longPosDelta = nowRefPosAmount.long - initRefPosAmount.long
var shortPosDelta = nowRefPosAmount.short - initRefPosAmount.short
if (longPosDelta == 0 && shortPosDelta == 0) {
continue
} else {
for (var i = 1 ; i < exchanges.length ; i++) {
if (longPosDelta != 0) {
Log(exchanges[i].GetName(), exchanges[i].GetLabel(), "执行多头跟单,变动量:", longPosDelta)
trade(exchanges[i], refCt, PD_LONG, longPosDelta)
}
if (shortPosDelta != 0) {
Log(exchanges[i].GetName(), exchanges[i].GetLabel(), "执行空头跟单,变动量:", shortPosDelta)
trade(exchanges[i], refCt, PD_SHORT, shortPosDelta)
}
}
}
initRefPosAmount = nowRefPosAmount
}
}
测试
鉴于OKEX更新V5接口之后,可以使用OKEX的模拟盘,我就使用两个OKEX的模拟盘API KEY非常方便的进行测试。第一个添加的交易所对象为参考交易所,跟单交易所都是跟着这个交易所账户去操作的。在OKEX模拟盘页面,参考交易所账户上手动下3张ETH的季度币本位合约。
可以看到实盘就检测到了参考交易所账户持仓的变动,进而跟随操作。
我们再来平掉2张刚才开的合约仓位试下,平仓之后的持仓如图:
实盘跟随操作,平掉2张合约。
本策略设计本着简单易懂的方式,没有做优化,完善的部分还需要处理跟单时的资产检测等细节,为了设计简便,跟单用了市价单。策略仅仅提供学习思路,实盘根据需求自行优化。策略地址:https://www.fmz.com/strategy/270012
欢迎留言。
|