In view of the fact that the hedging frequency of the futures and spots hedging strategy is not high, it is actually possible to operate manually. However, if you do it manually, it is very inconvenient to switch pages of various exchanges, observe prices, and calculate the difference, and sometimes you may want to see more varieties, and it is not necessary to set up several monitors to display the market. Is it possible to achieve this goal of manual operation with a semi-automatic strategy? It is better to have multi-species, oh! Yes, it is best to open and close positions with one-click. Oh! Yes, there is also a position display…
When there is a need, do it now!
Design a hedging strategy of cryptocurrency manual futures and spot
The writing is rather long-winded, with less than 600 lines of code.
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 = ["positive arbitrage", "reverse arbitrage", "close positive arbitrage", "close reverse arbitrage"] 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, ", failed to get account data") 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, ", failed to get position data") 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, "insufficient deposit!", "this plan uses", (fuAmount * fuPrice / self.fuMarginLevel), "currently available:", nowFuAcc.Balance, "Plan to reserve:", (pair.initFuAcc.Balance + pair.initFuAcc.FrozenBalance) * self.fuMarginReservedRatio) return } if ((tradeType == self.PLUS && nowSpAcc.Balance < spAmount * spPrice)) { Log(pair.spSymbol, "insufficient funds!", "this purchase plans to use", spAmount * spPrice, "currently available:", nowSpAcc.Balance) return } else if (tradeType == self.MINUS && nowSpAcc.Stocks < spAmount) { Log(pair.spSymbol, "insufficient funds!", "this selling plans to use", spAmount, "currently available:", 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, ", there is no corresponding position in futures!") 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, ", there is no corresponding position in the spot!") 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, "order quantity calculation error:", 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, "order quantity calculation error:", fuAmount, spAmount) return } } Log("contract code:", fuSymbol + "/" + spSymbol, "direction:", self.arrTradeTypeDesc[tradeType], "difference:", fuPrice - spPrice, "quantity of futures:", fuAmount, "quantity of spots:", 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, "portfolio position update failed, nowFuPos:", nowFuPos, " nowSpPos:", nowSpPos) } self.accAndPosUpdateCount++ } }) if (nowTS - self.preUpdateAccTS > 1000 * 60 * 5) { self.preUpdateAccTS = nowTS self.profit = self.calcProfit() LogProfit(self.profit[0], "futures:", self.profit[1], "spots:", self.profit[2], "&") // Print the total profit curve, use the & character not to print the profit log } var cmd = GetCommand() if(cmd) { Log("interactive commands:", 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("current time:", _D(), "data update time:", _D(self.tickerUpdateTS), "position account update count:", self.accAndPosUpdateCount, "\n", "Profit and loss:", self.profit[0], "futures profit and loss:", self.profit[1], "spot profit and loss:", 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("reverse arbitrage is not supported") } 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, "excess spot positions", 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, "long futures positions", 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("position error, three positions detected:", 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 : ["index", "future", "future leverage", "qunatity", "spot", "qunatity"], 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 : ["future", "code" + fuExName, "entrusted selling", "entrusted purchase", "spot", "code" + spExName, "entrusted selling", "entrusted purchase", "positive hedging spreads", "reverse hedging spreads", "positive hedge", "positive hedge closeout"], 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': 'positive arbitrage'}, {'type':'button', 'cmd': 'cover_plus:' + String(index), 'name': 'close positive arbitrage'} ]) }) var accsTbl = { type : "table", title : "accs", cols : ["code" + fuExName, "initial coin", "initial frozen coin", "initial money", "initial frozen money", "coin", "frozen coin", "money", "frozen money", "code" + spExName, "initial coin", "initial frozen coin", "initial money", "initial frozen money", "coin", "frozen coin", "money", "frozen money"], 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 : ["contract code" + fuExName, "quantity accuracy", "price accuracy", "multiplier", "minimum order quantity", "spot code" + spExName, "quantity accuracy", "price accuracy", "multiplier", "minimum order quantity"], 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("perform tailing processing and save data", "#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 "the exchange object has changed and the data needs to be reset" } 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 + ", species information acquisition failure!" } else { Log(pair.fuSymbol, fuSymbolInfo) } var spSymbolInfo = self.spEx.getSymbolInfo(pair.spSymbol) if (!spSymbolInfo) { throw pair.spSymbol + ", species information acquisition failure!" } 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, "leverage settings:", ret) if (!ret) { throw "initial setting of leverage failed!" } }) } self.init() return self } var manager = null function main() { if(isReset) { _G(null) LogReset(1) LogProfitReset() LogVacuum() Log("reset all data", "#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(), "switch analog disk") } } } var fuConfigureFunc = null var spConfigureFunc = null if (exchanges.length != 2) { throw "two exchange objects need to be added!" } else { var fuName = exchanges[0].GetName() if (fuName == "Futures_OKCoin" && isOkexV5) { fuName += "_V5" Log("Use OKEX V5 interface") } 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() } }
Since the multi-species strategy is more suitable for IO design, a template class library named MultiSymbolCtrlLib
is used in the code (encapsulation, calling the exchange interface through IO). Therefore, the strategy cannot be backtested, but it can be tested with the simulated bot (although the real bot has been run for 2 months, the test and familiarization stage is still run with the simulated bot).
Parameters
Before starting the test, let's talk about the parameter design first.
There are not many strategy parameters, the more important ones are:
- Hedging control table
LTC-USDT-211231|LTC_USDT,BTC-USDT-211231|BTC_USDT
Here is the setup strategy to monitor those combinations. For example, the above setup is to monitor the Litecoin contract (LTC-USDT-211231) of the future exchange and the Litecoin (LTC_USDT) of the spot exchange. A combination of future contract and spot trading pair are separated by |
symbols to form a combination. Different combinations are separated by ,
symbols. Note that the symbols here are all in the state of the English input method!
Then you may ask me how to find the contract code. These contract codes and spot trading pairs are all defined by the exchange, not defined by the FMZ platform.
For example, LTC-USDT-211231
is a second-quarter contract currently, called next_quarter
on FMZ, and OKEX's interface system is called LTC-USDT-211231
. For the Litecoin/USDT trading pair, the WexApp simulation bot is written as LTC_USDT
. So how to fill in here depends on the name defined in the exchange.
- Hedging amount for interactive control hedging
Click the status bar control button to hedge the amount. The unit is the number of coins, and the strategy will be automatically converted into the number of contracts to place an order.
Other functions are to set the analog disk, reset the data, use the OKEX V5 interface (because it is also compatible with V3) and so on, which are not particularly important.
Testing
The first exchange object adds the futures exchange, and the second adds the spot exchange object.
Futures exchanges use OKX's V5 interface simulation bots, and spot exchanges use WexApp simulation bots.
Click the positive set button of the BTC combination and open the position.
Click to close the positive arbitrage then.
Lose!!! It seems that closing the position cannot cover the handling fee when the profit spread is small, it is necessary to calculate the handling fee, the approximate slippage, and plan the spread reasonably, and then close the position.
Strategy source code: https://www.fmz.com/strategy/314352
Those who are interested can use and modify it.