Related articles:https://www.fmz.com/bbs-topic/7932
Currently, the main focus is on the test version, strategic exchange and learning, and the real disk is self-sufficient.
function createManager(fuEx, spEx, symbolPairs, cmdHedgeAmount, fuMarginLevel, fuMarginReservedRatio) { var self = {} self.fuEx = fuEx self.spEx = spEx self.symbolPairs = symbolPairs self.pairs = [] self.fuExTickers = null self.spExTickers = null self.tickerUpdateTS = 0 self.fuMarginLevel = fuMarginLevel self.fuMarginReservedRatio = fuMarginReservedRatio self.cmdHedgeAmount = cmdHedgeAmount self.preUpdateAccTS = 0 self.accAndPosUpdateCount = 0 self.profit = [] self.allPairs = [] self.PLUS = 0 self.MINUS = 1 self.COVER_PLUS = 2 self.COVER_MINUS = 3 self.arrTradeTypeDesc = ["正套", "反套", "平正套", "平反套"] self.updateTickers = function() { self.fuEx.goGetTickers() self.spEx.goGetTickers() var fuExTickers = self.fuEx.getTickers() var spExTickers = self.spEx.getTickers() if (!fuExTickers || !spExTickers) { return null } self.fuExTickers = fuExTickers self.spExTickers = spExTickers self.tickerUpdateTS = new Date().getTime() return true } self.hedge = function(index, fuSymbol, spSymbol, tradeType, amount) { var fe = self.fuEx var se = self.spEx var pair = self.pairs[index] var timeStamp = new Date().getTime() var fuDirection = null var spDirection = null var fuPrice = null var spPrice = null if (tradeType == self.PLUS) { fuDirection = fe.OPEN_SHORT spDirection = se.OPEN_LONG fuPrice = pair.fuTicker.bid1 spPrice = pair.spTicker.ask1 } else if (tradeType == self.MINUS) { fuDirection = fe.OPEN_LONG spDirection = se.OPEN_SHORT fuPrice = pair.fuTicker.ask1 spPrice = pair.spTicker.bid1 } else if (tradeType == self.COVER_PLUS) { fuDirection = fe.COVER_SHORT spDirection = se.COVER_LONG fuPrice = pair.fuTicker.ask1 spPrice = pair.spTicker.bid1 } else if (tradeType == self.COVER_MINUS) { fuDirection = fe.COVER_LONG spDirection = se.COVER_SHORT fuPrice = pair.fuTicker.bid1 spPrice = pair.spTicker.ask1 } else { throw "unknow tradeType!" } fe.goGetAcc(fuSymbol, timeStamp) se.goGetAcc(spSymbol, timeStamp) var nowFuAcc = fe.getAcc(fuSymbol, timeStamp) var nowSpAcc = se.getAcc(spSymbol, timeStamp) if (!nowFuAcc || !nowSpAcc) { Log(fuSymbol, spSymbol, ",获取账户数据失败") return } pair.nowFuAcc = nowFuAcc pair.nowSpAcc = nowSpAcc var nowFuPos = fe.getFuPos(fuSymbol, timeStamp) var nowSpPos = se.getSpPos(spSymbol, spPrice, pair.initSpAcc, pair.nowSpAcc) if (!nowFuPos || !nowSpPos) { Log(fuSymbol, spSymbol, ",获取持仓数据失败") return } pair.nowFuPos = nowFuPos pair.nowSpPos = nowSpPos var fuAmount = amount var spAmount = amount if (tradeType == self.PLUS || tradeType == self.MINUS) { if (nowFuAcc.Balance < (pair.initFuAcc.Balance + pair.initFuAcc.FrozenBalance) * self.fuMarginReservedRatio + (fuAmount * fuPrice / self.fuMarginLevel)) { Log(pair.fuSymbol, "保证金不足!", "本次计划使用", (fuAmount * fuPrice / self.fuMarginLevel), "当前可用:", nowFuAcc.Balance, "计划预留:", (pair.initFuAcc.Balance + pair.initFuAcc.FrozenBalance) * self.fuMarginReservedRatio) return } if ((tradeType == self.PLUS && nowSpAcc.Balance < spAmount * spPrice)) { Log(pair.spSymbol, "资金不足!", "本次买入计划使用", spAmount * spPrice, "当前可用:", nowSpAcc.Balance) return } else if (tradeType == self.MINUS && nowSpAcc.Stocks < spAmount) { Log(pair.spSymbol, "资金不足!", "本次卖出计划使用", spAmount, "当前可用:", nowSpAcc.Stocks) return } } else { var fuLongPos = self.getLongPos(nowFuPos) var fuShortPos = self.getShortPos(nowFuPos) var spLongPos = self.getLongPos(nowSpPos) var spShortPos = self.getShortPos(nowSpPos) if ((tradeType == self.COVER_PLUS && !fuShortPos) || (tradeType == self.COVER_MINUS && !fuLongPos)) { Log(fuSymbol, spSymbol, ",期货没有对应持仓!") return } else if (tradeType == self.COVER_PLUS && Math.abs(fuShortPos.amount) < fuAmount) { fuAmount = Math.abs(fuShortPos.amount) } else if (tradeType == self.COVER_MINUS && Math.abs(fuLongPos.amount) < fuAmount) { fuAmount = Math.abs(fuLongPos.amount) } if ((tradeType == self.COVER_PLUS && !spLongPos) || (tradeType == self.COVER_MINUS && !spShortPos)) { Log(fuSymbol, spSymbol, ",现货没有对应持仓!") return } else if (tradeType == self.COVER_PLUS && Math.min(Math.abs(spLongPos.amount), nowSpAcc.Stocks) < spAmount) { spAmount = Math.min(Math.abs(spLongPos.amount), nowSpAcc.Stocks) } else if (tradeType == self.COVER_MINUS && Math.min(Math.abs(spShortPos.amount), nowSpAcc.Balance / spPrice) < spAmount) { spAmount = Math.min(Math.abs(spShortPos.amount), nowSpAcc.Balance / spPrice) } } fuAmount = fe.calcAmount(fuSymbol, fuDirection, fuPrice, fuAmount) spAmount = se.calcAmount(spSymbol, spDirection, spPrice, spAmount) if (!fuAmount || !spAmount) { Log(fuSymbol, spSymbol, "下单量计算错误:", fuAmount, spAmount) return } else { fuAmount = fe.calcAmount(fuSymbol, fuDirection, fuPrice, fuAmount[1]) spAmount = se.calcAmount(spSymbol, spDirection, spPrice, Math.min(fuAmount[1], spAmount[1])) if (!fuAmount || !spAmount) { Log(fuSymbol, spSymbol, "下单量计算错误:", fuAmount, spAmount) return } } Log("合约代码:", fuSymbol + "/" + spSymbol, "方向:", self.arrTradeTypeDesc[tradeType], "差价:", fuPrice - spPrice, "期货数量:", fuAmount, "现货数量:", spAmount, "@") fe.goGetTrade(fuSymbol, fuDirection, fuPrice, fuAmount[0]) se.goGetTrade(spSymbol, spDirection, spPrice, spAmount[0]) var feIdMsg = fe.getTrade() var seIdMsg = se.getTrade() return [feIdMsg, seIdMsg] } self.process = function() { var nowTS = new Date().getTime() if(!self.updateTickers()) { return } _.each(self.pairs, function(pair, index) { var fuTicker = null var spTicker = null _.each(self.fuExTickers, function(ticker) { if (ticker.originalSymbol == pair.fuSymbol) { fuTicker = ticker } }) _.each(self.spExTickers, function(ticker) { if (ticker.originalSymbol == pair.spSymbol) { spTicker = ticker } }) if (fuTicker && spTicker) { pair.canTrade = true } else { pair.canTrade = false } fuTicker = fuTicker ? fuTicker : {} spTicker = spTicker ? spTicker : {} pair.fuTicker = fuTicker pair.spTicker = spTicker pair.plusDiff = fuTicker.bid1 - spTicker.ask1 pair.minusDiff = fuTicker.ask1 - spTicker.bid1 if (pair.plusDiff && pair.minusDiff) { pair.plusDiff = _N(pair.plusDiff, Math.max(self.fuEx.judgePrecision(fuTicker.bid1), self.spEx.judgePrecision(spTicker.ask1))) pair.minusDiff = _N(pair.minusDiff, Math.max(self.fuEx.judgePrecision(fuTicker.ask1), self.spEx.judgePrecision(spTicker.bid1))) } if (nowTS - self.preUpdateAccTS > 1000 * 60 * 5) { self.fuEx.goGetAcc(pair.fuSymbol, nowTS) self.spEx.goGetAcc(pair.spSymbol, nowTS) var fuAcc = self.fuEx.getAcc(pair.fuSymbol, nowTS) var spAcc = self.spEx.getAcc(pair.spSymbol, nowTS) if (fuAcc) { pair.nowFuAcc = fuAcc } if (spAcc) { pair.nowSpAcc = spAcc } var nowFuPos = self.fuEx.getFuPos(pair.fuSymbol, nowTS) var nowSpPos = self.spEx.getSpPos(pair.spSymbol, (pair.spTicker.ask1 + pair.spTicker.bid1) / 2, pair.initSpAcc, pair.nowSpAcc) if (nowFuPos && nowSpPos) { pair.nowFuPos = nowFuPos pair.nowSpPos = nowSpPos self.keepBalance(pair) } else { Log(pair.fuSymbol, pair.spSymbol, "组合仓位更新失败,nowFuPos:", nowFuPos, " nowSpPos:", nowSpPos) } self.accAndPosUpdateCount++ } }) if (nowTS - self.preUpdateAccTS > 1000 * 60 * 5) { self.preUpdateAccTS = nowTS self.profit = self.calcProfit() LogProfit(self.profit[0], "期货:", self.profit[1], "现货:", self.profit[2], "&") // 打印总收益曲线,使用&字符不打印收益日志 } var cmd = GetCommand() if(cmd) { Log("交互命令:", cmd) var arr = cmd.split(":") if(arr[0] == "plus") { var pair = self.pairs[parseFloat(arr[1])] self.hedge(parseFloat(arr[1]), pair.fuSymbol, pair.spSymbol, self.PLUS, self.cmdHedgeAmount) } else if (arr[0] == "cover_plus") { var pair = self.pairs[parseFloat(arr[1])] self.hedge(parseFloat(arr[1]), pair.fuSymbol, pair.spSymbol, self.COVER_PLUS, self.cmdHedgeAmount) } } LogStatus("当前时间:", _D(), " 数据更新时间:", _D(self.tickerUpdateTS), "持仓账户更新计数:", self.accAndPosUpdateCount, "\n", "盈亏:", self.profit[0], " 期货盈亏:", self.profit[1], " 现货盈亏:", self.profit[2], "\n`" + JSON.stringify(self.returnTbl()) + "`", "\n`" + JSON.stringify(self.returnPosTbl()) + "`") } self.keepBalance = function (pair) { var nowFuPos = pair.nowFuPos var nowSpPos = pair.nowSpPos var fuLongPos = self.getLongPos(nowFuPos) var fuShortPos = self.getShortPos(nowFuPos) var spLongPos = self.getLongPos(nowSpPos) var spShortPos = self.getShortPos(nowSpPos) if (fuLongPos || spShortPos) { Log("不支持反套") } if (fuShortPos || spLongPos) { var fuHoldAmount = fuShortPos ? fuShortPos.amount : 0 var spHoldAmount = spLongPos ? spLongPos.amount : 0 var sum = fuHoldAmount + spHoldAmount if (sum > 0) { var spAmount = self.spEx.calcAmount(pair.spSymbol, self.spEx.COVER_LONG, pair.spTicker.bid1, Math.abs(sum), true) if (spAmount) { Log(pair.fuSymbol, pair.spSymbol, "现货头寸多出", Math.abs(sum), "fuShortPos:", fuShortPos, "spLongPos:", spLongPos) self.spEx.goGetTrade(pair.spSymbol, self.spEx.COVER_LONG, pair.spTicker.bid1, spAmount[0]) var seIdMsg = self.spEx.getTrade() } } else if (sum < 0) { var fuAmount = self.fuEx.calcAmount(pair.fuSymbol, self.fuEx.COVER_SHORT, pair.fuTicker.ask1, Math.abs(sum), true) if (fuAmount) { Log(pair.fuSymbol, pair.spSymbol, "期货头寸多出", Math.abs(sum), "fuShortPos:", fuShortPos, "spLongPos:", spLongPos) self.fuEx.goGetTrade(pair.fuSymbol, self.fuEx.COVER_SHORT, pair.fuTicker.ask1, fuAmount[0]) var feIdMsg = self.fuEx.getTrade() } } } } self.getLongPos = function (positions) { return self.getPosByDirection(positions, PD_LONG) } self.getShortPos = function (positions) { return self.getPosByDirection(positions, PD_SHORT) } self.getPosByDirection = function (positions, direction) { var ret = null if (positions.length > 2) { Log("持仓错误,检测到三个持仓:", JSON.stringify(positions)) return ret } _.each(positions, function(pos) { if ((direction == PD_LONG && pos.amount > 0) || (direction == PD_SHORT && pos.amount < 0)) { ret = pos } }) return ret } self.calcProfit = function() { var arrInitFuAcc = [] var arrNowFuAcc = [] _.each(self.pairs, function(pair) { arrInitFuAcc.push(pair.initFuAcc) arrNowFuAcc.push(pair.nowFuAcc) }) var fuProfit = self.fuEx.calcProfit(arrInitFuAcc, arrNowFuAcc) var spProfit = 0 var deltaBalance = 0 _.each(self.pairs, function(pair) { var nowSpAcc = pair.nowSpAcc var initSpAcc = pair.initSpAcc var stocksDiff = nowSpAcc.Stocks + nowSpAcc.FrozenStocks - (initSpAcc.Stocks + initSpAcc.FrozenStocks) var price = stocksDiff > 0 ? pair.spTicker.bid1 : pair.spTicker.ask1 spProfit += stocksDiff * price deltaBalance = nowSpAcc.Balance + nowSpAcc.FrozenBalance - (initSpAcc.Balance + initSpAcc.FrozenBalance) }) spProfit += deltaBalance return [fuProfit + spProfit, fuProfit, spProfit] } self.returnPosTbl = function() { var posTbl = { type : "table", title : "positions", cols : ["索引", "期货", "期货杠杆", "数量", "现货", "数量"], rows : [] } _.each(self.pairs, function(pair, index) { var nowFuPos = pair.nowFuPos var nowSpPos = pair.nowSpPos for (var i = 0 ; i < nowFuPos.length ; i++) { if (nowSpPos.length > 0) { posTbl.rows.push([index, nowFuPos[i].symbol, nowFuPos[i].marginLevel, nowFuPos[i].amount, nowSpPos[0].symbol, nowSpPos[0].amount]) } else { posTbl.rows.push([index, nowFuPos[i].symbol, nowFuPos[i].marginLevel, nowFuPos[i].amount, "--", "--"]) } } }) return posTbl } self.returnTbl = function() { var fuExName = "[" + self.fuEx.getExName() + "]" var spExName = "[" + self.spEx.getExName() + "]" var combiTickersTbl = { type : "table", title : "combiTickersTbl", cols : ["期货", "代码" + fuExName, "卖一", "买一", "现货", "代码" + spExName, "卖一", "买一", "正对冲差价", "反对冲差价", "正对冲", "正对冲平仓"], rows : [] } _.each(self.pairs, function(pair, index) { var spSymbolInfo = self.spEx.getSymbolInfo(pair.spTicker.originalSymbol) combiTickersTbl.rows.push([ pair.fuTicker.symbol, pair.fuTicker.originalSymbol, pair.fuTicker.ask1, pair.fuTicker.bid1, pair.spTicker.symbol, pair.spTicker.originalSymbol, pair.spTicker.ask1, pair.spTicker.bid1, pair.plusDiff, pair.minusDiff, {'type':'button', 'cmd': 'plus:' + String(index), 'name': '正套'}, {'type':'button', 'cmd': 'cover_plus:' + String(index), 'name': '平正套'} ]) }) var accsTbl = { type : "table", title : "accs", cols : ["代码" + fuExName, "初币", "初冻币", "初钱", "初冻钱", "币", "冻币", "钱", "冻钱", "代码" + spExName, "初币", "初冻币", "初钱", "初冻钱", "币", "冻币", "钱", "冻钱"], rows : [] } _.each(self.pairs, function(pair) { var arr = [pair.fuTicker.originalSymbol, pair.initFuAcc.Stocks, pair.initFuAcc.FrozenStocks, pair.initFuAcc.Balance, pair.initFuAcc.FrozenBalance, pair.nowFuAcc.Stocks, pair.nowFuAcc.FrozenStocks, pair.nowFuAcc.Balance, pair.nowFuAcc.FrozenBalance, pair.spTicker.originalSymbol, pair.initSpAcc.Stocks, pair.initSpAcc.FrozenStocks, pair.initSpAcc.Balance, pair.initSpAcc.FrozenBalance, pair.nowSpAcc.Stocks, pair.nowSpAcc.FrozenStocks, pair.nowSpAcc.Balance, pair.nowSpAcc.FrozenBalance] for (var i = 0 ; i < arr.length ; i++) { if (typeof(arr[i]) == "number") { arr[i] = _N(arr[i], 6) } } accsTbl.rows.push(arr) }) var symbolInfoTbl = { type : "table", title : "symbolInfos", cols : ["合约代码" + fuExName, "量精度", "价格精度", "乘数", "最小下单量", "现货代码" + spExName, "量精度", "价格精度", "乘数", "最小下单量"], rows : [] } _.each(self.pairs, function(pair) { var fuSymbolInfo = self.fuEx.getSymbolInfo(pair.fuTicker.originalSymbol) var spSymbolInfo = self.spEx.getSymbolInfo(pair.spTicker.originalSymbol) symbolInfoTbl.rows.push([fuSymbolInfo.symbol, fuSymbolInfo.amountPrecision, fuSymbolInfo.pricePrecision, fuSymbolInfo.multiplier, fuSymbolInfo.min, spSymbolInfo.symbol, spSymbolInfo.amountPrecision, spSymbolInfo.pricePrecision, spSymbolInfo.multiplier, spSymbolInfo.min]) }) var allPairs = [] _.each(self.fuExTickers, function(fuTicker) { _.each(self.spExTickers, function(spTicker) { if (fuTicker.symbol == spTicker.symbol) { allPairs.push({symbol: fuTicker.symbol, fuSymbol: fuTicker.originalSymbol, spSymbol: spTicker.originalSymbol, plus: fuTicker.bid1 - spTicker.ask1}) } }) }) _.each(allPairs, function(pair) { var findPair = null _.each(self.allPairs, function(selfPair) { if (pair.fuSymbol == selfPair.fuSymbol && pair.spSymbol == selfPair.spSymbol) { findPair = selfPair } }) if (findPair) { findPair.minPlus = pair.plus < findPair.minPlus ? pair.plus : findPair.minPlus findPair.maxPlus = pair.plus > findPair.maxPlus ? pair.plus : findPair.maxPlus pair.minPlus = findPair.minPlus pair.maxPlus = findPair.maxPlus } else { self.allPairs.push({symbol: pair.symbol, fuSymbol: pair.fuSymbol, spSymbol: pair.spSymbol, plus: pair.plus, minPlus: pair.plus, maxPlus: pair.plus}) pair.minPlus = pair.plus pair.maxPlus = pair.plus } }) return [combiTickersTbl, accsTbl, symbolInfoTbl] } self.onexit = function() { _G("pairs", self.pairs) _G("allPairs", self.allPairs) Log("执行扫尾处理,数据保存", "#FF0000") } self.init = function() { var fuExName = self.fuEx.getExName() var spExName = self.spEx.getExName() var gFuExName = _G("fuExName") var gSpExName = _G("spExName") if ((gFuExName && gFuExName != fuExName) || (gSpExName && gSpExName != spExName)) { throw "交易所对象发生变化,需要重置数据" } if (!gFuExName) { _G("fuExName", fuExName) } if (!gSpExName) { _G("spExName", spExName) } self.allPairs = _G("allPairs") if (!self.allPairs) { self.allPairs = [] } var arrPair = _G("pairs") if (!arrPair) { arrPair = [] } var arrStrPair = self.symbolPairs.split(",") var timeStamp = new Date().getTime() _.each(arrStrPair, function(strPair) { var arrSymbol = strPair.split("|") var recoveryPair = null _.each(arrPair, function(pair) { if (pair.fuSymbol == arrSymbol[0] && pair.spSymbol == arrSymbol[1]) { recoveryPair = pair } }) if (!recoveryPair) { var pair = { fuSymbol : arrSymbol[0], spSymbol : arrSymbol[1], fuTicker : {}, spTicker : {}, plusDiff : null, minusDiff : null, canTrade : false, initFuAcc : null, initSpAcc : null, nowFuAcc : null, nowSpAcc : null, nowFuPos : null, nowSpPos : null, fuMarginLevel : null } self.pairs.push(pair) Log("初始化:", pair) } else { self.pairs.push(recoveryPair) Log("恢复:", recoveryPair) } self.fuEx.pushSubscribeSymbol(arrSymbol[0]) self.spEx.pushSubscribeSymbol(arrSymbol[1]) if (!self.pairs[self.pairs.length - 1].initFuAcc) { self.fuEx.goGetAcc(arrSymbol[0], timeStamp) var nowFuAcc = self.fuEx.getAcc(arrSymbol[0], timeStamp) self.pairs[self.pairs.length - 1].initFuAcc = nowFuAcc self.pairs[self.pairs.length - 1].nowFuAcc = nowFuAcc } if (!self.pairs[self.pairs.length - 1].initSpAcc) { self.spEx.goGetAcc(arrSymbol[1], timeStamp) var nowSpAcc = self.spEx.getAcc(arrSymbol[1], timeStamp) self.pairs[self.pairs.length - 1].initSpAcc = nowSpAcc self.pairs[self.pairs.length - 1].nowSpAcc = nowSpAcc } Sleep(300) }) Log("self.pairs:", self.pairs) _.each(self.pairs, function(pair) { var fuSymbolInfo = self.fuEx.getSymbolInfo(pair.fuSymbol) if (!fuSymbolInfo) { throw pair.fuSymbol + ",品种信息获取失败!" } else { Log(pair.fuSymbol, fuSymbolInfo) } var spSymbolInfo = self.spEx.getSymbolInfo(pair.spSymbol) if (!spSymbolInfo) { throw pair.spSymbol + ",品种信息获取失败!" } else { Log(pair.spSymbol, spSymbolInfo) } }) _.each(self.pairs, function(pair) { pair.fuMarginLevel = self.fuMarginLevel var ret = self.fuEx.setMarginLevel(pair.fuSymbol, self.fuMarginLevel) Log(pair.fuSymbol, "杠杆设置:", ret) if (!ret) { throw "初始设置杠杆失败!" } }) } self.init() return self } var manager = null function main() { if(isReset) { _G(null) LogReset(1) LogProfitReset() LogVacuum() Log("重置所有数据", "#FF0000") } if (isOKEX_V5_Simulate) { for (var i = 0 ; i < exchanges.length ; i++) { if (exchanges[i].GetName() == "Futures_OKCoin" || exchanges[i].GetName() == "OKEX") { var ret = exchanges[i].IO("simulate", true) Log(exchanges[i].GetName(), "切换模拟盘") } } } var fuConfigureFunc = null var spConfigureFunc = null if (exchanges.length != 2) { throw "需要添加两个交易所对象!" } else { var fuName = exchanges[0].GetName() if (fuName == "Futures_OKCoin" && isOkexV5) { fuName += "_V5" Log("使用OKEX V5接口") } var spName = exchanges[1].GetName() fuConfigureFunc = $.getConfigureFunc()[fuName] spConfigureFunc = $.getConfigureFunc()[spName] if (!fuConfigureFunc || !spConfigureFunc) { throw (fuConfigureFunc ? "" : fuName) + " " + (spConfigureFunc ? "" : spName) + " not support!" } } var fuEx = $.createBaseEx(exchanges[0], fuConfigureFunc) var spEx = $.createBaseEx(exchanges[1], spConfigureFunc) manager = createManager(fuEx, spEx, symbolPairs, cmdHedgeAmount, fuMarginLevel, fuMarginReservedRatio) while(true) { manager.process() Sleep(interval) } } function onerror() { if (manager) { manager.onexit() } } function onexit() { if (manager) { manager.onexit() } }