输入/搜索内容
8
关注
1364
关注者
币圈量化交易萌新看过来--带你走近币圈量化(八)
交流分享
创建于 2021-06-18 18:24:23  更新于 2024-12-04 21:16:56
 6
 4515

img

币圈量化交易萌新看过来--带你走近币圈量化(八)

上一篇文章,我们一起设计了一个多品种的合约差价监控策略,本篇文章我们继续完善这个思路。来看一下这个思路是否可行,并且用OKEX V5的模拟盘跑一跑,验证一下策略设计。这些过程也是做数字货币程序化交易、量化交易过程中需要经历的,希望萌新们能积累下宝贵的经验。

先剧透一下,策略跑起来了,有些小激动!

img

img

img

策略整体设计按照最简思路去实现,虽然对于细节处理没有过于苛求,但是还是可以从代码中学习到一些小技巧。策略代码整体不到400行,阅读理解起来不会很枯燥。当然这个只是一个测试DEMO,需要跑一段时间测试看看。所以我要说的是:目前策略仅仅是成功的开仓,还有平仓等等各种情况要实际测试检验,程序设计中BUG不可避免,所以测试,DEBUG(排错)很重要!

回到策略设计来说,在上篇文章的代码基础上,给策略加上了:

  • 数据持久化设计(使用_G函数保存数据,在重启后可以恢复数据)
  • 给每个监控的合约差价对增加了网格数据结构(用来控制对冲开平仓)
  • 实现了一个简单的对冲函数用来对冲开平仓
  • 增加了一个总权益获取函数用来计算浮动盈亏
  • 增加了一些状态栏数据输出显示。

以上就是增加的功能,为了简化设计,策略只设计了做正对冲(空远期合约,多近期合约)。目前永续合约(近期)为负费率,正好做多永续合约,看能不能在增加点费率收益。

让策略跑一会儿~

测试了大概3天时间,差价波动其实还是可以的。

img

img

img

可以看到有部分资金费率的收益。

img

下面分享一下策略源码:

var arrNearContractType = strNearContractType.split(",") var arrFarContractType = strFarContractType.split(",") var nets = null var initTotalEquity = null var OPEN_PLUS = 1 var COVER_PLUS = 2 function createNet(begin, diff, initAvgPrice, diffUsagePercentage) { if (diffUsagePercentage) { diff = diff * initAvgPrice } var oneSideNums = 3 var up = [] var down = [] for (var i = 0 ; i < oneSideNums ; i++) { var upObj = { sell : false, price : begin + diff / 2 + i * diff } up.push(upObj) var j = (oneSideNums - 1) - i var downObj = { sell : false, price : begin - diff / 2 - j * diff } if (downObj.price <= 0) { // 价格不能小于等于0 continue } down.push(downObj) } return down.concat(up) } function createCfg(symbol) { var cfg = { extension: { layout: 'single', height: 300, col: 6 }, title: { text: symbol }, xAxis: { type: 'datetime' }, series: [{ name: 'plus', data: [] }] } return cfg } function formatSymbol(originalSymbol) { var arr = originalSymbol.split("-") return [arr[0] + "_" + arr[1], arr[0], arr[1]] } function main() { if (isSimulate) { exchange.IO("simulate", true) // 切换为模拟环境 Log("仅支持OKEX V5 API,切换为OKEX V5 模拟盘:") } else { exchange.IO("simulate", false) // 切换为实盘 Log("仅支持OKEX V5 API,切换为OKEX V5 实盘:") } if (exchange.GetName() != "Futures_OKCoin") { throw "支持OKEX期货" } // 初始化 if (isReset) { _G(null) LogReset(1) LogProfitReset() LogVacuum() Log("重置所有数据", "#FF0000") } // 初始化标记 var isFirst = true // 收益打印周期 var preProfitPrintTS = 0 // 总权益 var totalEquity = 0 var posTbls = [] // 持仓表格数组 // 声明arrCfg var arrCfg = [] _.each(arrNearContractType, function(ct) { arrCfg.push(createCfg(formatSymbol(ct)[0])) }) var objCharts = Chart(arrCfg) objCharts.reset() // 创建对象 var exName = exchange.GetName() + "_V5" var nearConfigureFunc = $.getConfigureFunc()[exName] var farConfigureFunc = $.getConfigureFunc()[exName] var nearEx = $.createBaseEx(exchange, nearConfigureFunc) var farEx = $.createBaseEx(exchange, farConfigureFunc) // 预先写入需要订阅的合约 _.each(arrNearContractType, function(ct) { nearEx.pushSubscribeSymbol(ct) }) _.each(arrFarContractType, function(ct) { farEx.pushSubscribeSymbol(ct) }) while (true) { var ts = new Date().getTime() // 获取行情数据 nearEx.goGetTickers() farEx.goGetTickers() var nearTickers = nearEx.getTickers() var farTickers = farEx.getTickers() if (!farTickers || !nearTickers) { Sleep(2000) continue } var tbl = { type : "table", title : "远期-近期差价", cols : ["交易对", "远期", "近期", "正对冲", "反对冲"], rows : [] } var subscribeFarTickers = [] var subscribeNearTickers = [] _.each(farTickers, function(farTicker) { _.each(arrFarContractType, function(symbol) { if (farTicker.originalSymbol == symbol) { subscribeFarTickers.push(farTicker) } }) }) _.each(nearTickers, function(nearTicker) { _.each(arrNearContractType, function(symbol) { if (nearTicker.originalSymbol == symbol) { subscribeNearTickers.push(nearTicker) } }) }) var pairs = [] _.each(subscribeFarTickers, function(farTicker) { _.each(subscribeNearTickers, function(nearTicker) { if (farTicker.symbol == nearTicker.symbol) { var pair = {symbol: nearTicker.symbol, nearTicker: nearTicker, farTicker: farTicker, plusDiff: farTicker.bid1 - nearTicker.ask1, minusDiff: farTicker.ask1 - nearTicker.bid1} pairs.push(pair) tbl.rows.push([pair.symbol, farTicker.originalSymbol, nearTicker.originalSymbol, pair.plusDiff, pair.minusDiff]) for (var i = 0 ; i < arrCfg.length ; i++) { if (arrCfg[i].title.text == pair.symbol) { objCharts.add([i, [ts, pair.plusDiff]]) } } } }) }) // 初始化 if (isFirst) { isFirst = false var recoveryNets = _G("nets") var recoveryInitTotalEquity = _G("initTotalEquity") if (!recoveryNets) { // 检查持仓 _.each(subscribeFarTickers, function(farTicker) { var pos = farEx.getFuPos(farTicker.originalSymbol, ts) if (pos.length != 0) { Log(farTicker.originalSymbol, pos) throw "初始化时有持仓" } }) _.each(subscribeNearTickers, function(nearTicker) { var pos = nearEx.getFuPos(nearTicker.originalSymbol, ts) if (pos.length != 0) { Log(nearTicker.originalSymbol, pos) throw "初始化时有持仓" } }) // 构造nets nets = [] _.each(pairs, function (pair) { farEx.goGetAcc(pair.farTicker.originalSymbol, ts) nearEx.goGetAcc(pair.nearTicker.originalSymbol, ts) var obj = { "symbol" : pair.symbol, "farSymbol" : pair.farTicker.originalSymbol, "nearSymbol" : pair.nearTicker.originalSymbol, "initPrice" : (pair.nearTicker.ask1 + pair.farTicker.bid1) / 2, "prePlus" : pair.farTicker.bid1 - pair.nearTicker.ask1, "net" : createNet((pair.farTicker.bid1 - pair.nearTicker.ask1), diff, (pair.nearTicker.ask1 + pair.farTicker.bid1) / 2, true), "initFarAcc" : farEx.getAcc(pair.farTicker.originalSymbol, ts), "initNearAcc" : nearEx.getAcc(pair.nearTicker.originalSymbol, ts), "farTicker" : pair.farTicker, "nearTicker" : pair.nearTicker, "farPos" : null, "nearPos" : null, } nets.push(obj) }) var currTotalEquity = getTotalEquity() if (currTotalEquity) { initTotalEquity = currTotalEquity } else { throw "初始化获取总权益失败!" } } else { // 恢复 nets = recoveryNets initTotalEquity = recoveryInitTotalEquity } } // 检索网格,检查是否触发交易 _.each(nets, function(obj) { var currPlus = null _.each(pairs, function(pair) { if (pair.symbol == obj.symbol) { currPlus = pair.plusDiff obj.farTicker = pair.farTicker obj.nearTicker = pair.nearTicker } }) if (!currPlus) { Log("没有查询到", obj.symbol, "的差价") return } // 检查网格,动态添加 while (currPlus >= obj.net[obj.net.length - 1].price) { obj.net.push({ sell : false, price : obj.net[obj.net.length - 1].price + diff * obj.initPrice, }) } while (currPlus <= obj.net[0].price) { var price = obj.net[0].price - diff * obj.initPrice if (price <= 0) { break } obj.net.unshift({ sell : false, price : price, }) } // 检索网格 for (var i = 0 ; i < obj.net.length - 1 ; i++) { var p = obj.net[i] var upP = obj.net[i + 1] if (obj.prePlus <= p.price && currPlus > p.price && !p.sell) { if (hedge(nearEx, farEx, obj.nearSymbol, obj.farSymbol, obj.nearTicker, obj.farTicker, hedgeAmount, OPEN_PLUS)) { // 正对冲开仓 p.sell = true } } else if (obj.prePlus >= p.price && currPlus < p.price && upP.sell) { if (hedge(nearEx, farEx, obj.nearSymbol, obj.farSymbol, obj.nearTicker, obj.farTicker, hedgeAmount, COVER_PLUS)) { // 正对冲平仓 upP.sell = false } } } obj.prePlus = currPlus // 记录本次差价,作为缓存,下次用于判断上穿下穿 // 增加其它表格输出 }) if (ts - preProfitPrintTS > 1000 * 60 * 5) { // 5分钟打印一次 var currTotalEquity = getTotalEquity() if (currTotalEquity) { totalEquity = currTotalEquity LogProfit(totalEquity - initTotalEquity, "&") // 打印动态权益收益 } // 检查持仓 posTbls = [] // 重置,更新 _.each(nets, function(obj) { var currFarPos = farEx.getFuPos(obj.farSymbol) var currNearPos = nearEx.getFuPos(obj.nearSymbol) if (currFarPos && currNearPos) { obj.farPos = currFarPos obj.nearPos = currNearPos } var posTbl = { "type" : "table", "title" : obj.symbol, "cols" : ["合约代码", "数量", "价格"], "rows" : [] } _.each(obj.farPos, function(pos) { posTbl.rows.push([pos.symbol, pos.amount, pos.price]) }) _.each(obj.nearPos, function(pos) { posTbl.rows.push([pos.symbol, pos.amount, pos.price]) }) posTbls.push(posTbl) }) preProfitPrintTS = ts } // 显示网格 var netTbls = [] _.each(nets, function(obj) { var netTbl = { "type" : "table", "title" : obj.symbol, "cols" : ["网格"], "rows" : [] } _.each(obj.net, function(p) { var color = "" if (p.sell) { color = "#00FF00" } netTbl.rows.push([JSON.stringify(p) + color]) }) netTbl.rows.reverse() netTbls.push(netTbl) }) LogStatus(_D(), "总权益:", totalEquity, "初始总权益:", initTotalEquity, " 浮动盈亏:", totalEquity - initTotalEquity, "\n`" + JSON.stringify(tbl) + "`" + "\n`" + JSON.stringify(netTbls) + "`" + "\n`" + JSON.stringify(posTbls) + "`") Sleep(interval) } } function getTotalEquity() { var totalEquity = null var ret = exchange.IO("api", "GET", "/api/v5/account/balance", "ccy=USDT") if (ret) { try { totalEquity = parseFloat(ret.data[0].details[0].eq) } catch(e) { Log("获取账户总权益失败!") return null } } return totalEquity } function hedge(nearEx, farEx, nearSymbol, farSymbol, nearTicker, farTicker, amount, tradeType) { var farDirection = null var nearDirection = null if (tradeType == OPEN_PLUS) { farDirection = farEx.OPEN_SHORT nearDirection = nearEx.OPEN_LONG } else { farDirection = farEx.COVER_SHORT nearDirection = nearEx.COVER_LONG } var nearSymbolInfo = nearEx.getSymbolInfo(nearSymbol) var farSymbolInfo = farEx.getSymbolInfo(farSymbol) nearAmount = nearEx.calcAmount(nearSymbol, nearDirection, nearTicker.ask1, amount * nearSymbolInfo.multiplier) farAmount = farEx.calcAmount(farSymbol, farDirection, farTicker.bid1, amount * farSymbolInfo.multiplier) if (!nearAmount || !farAmount) { Log(nearSymbol, farSymbol, "下单量计算错误:", nearAmount, farAmount) return } nearEx.goGetTrade(nearSymbol, nearDirection, nearTicker.ask1, nearAmount[0]) farEx.goGetTrade(farSymbol, farDirection, farTicker.bid1, farAmount[0]) var nearIdMsg = nearEx.getTrade() var farIdMsg = farEx.getTrade() return [nearIdMsg, farIdMsg] } function onexit() { Log("执行扫尾函数", "#FF0000") _G("nets", nets) _G("initTotalEquity", initTotalEquity) Log("保存数据:", _G("nets"), _G("initTotalEquity")) }

img

策略公开地址:https://www.fmz.com/strategy/288559

策略用到了一个我自己写的模板类库,由于写的太菜就不公开了,以上策略源码可以修改一下不使用这个模板。

有兴趣的可以挂个OKEX V5模拟盘测试。
哦!对了,这策略不能回测~

相关推荐
评论
全部评论 (6)

    时隔两年,梦总~这个策略如何设定不同交易对,不同的开仓数量

    3 年前
    // 声明arrCfg var arrCfg = [] _.each(arrNearContractType, function(ct) { arrCfg.push(createCfg(formatSymbol(ct)[0])) })

    把hedgeAmount 这个参数也做类似这样的处理,把这个参数设计成一个字符串类似:100,200,330 。 这样100就对应第一个对冲组合,然后具体去使用就行了。

    3 年前

    改到了币安来,但是下单那块不懂ok怎么操作的,下单还没改成功

    5 年前

    下单那块可以自己封装一下,例子中用到了一个模板类库,没公开。

    5 年前

    怎么可以在ok模拟盘上跑。。。我用那个插件好像不行啊。。。

    5 年前

    什么插件 ?

    5 年前
  • 1
iPhone 下载
社区
回测系统
© 2015 - ∞ INVENTOR PTE LTD (SG)