Type/to search
0
Follow
20
Followers
Cryptocurrency Manual Futures & Spot Hedge Strategy
Help
Created 2022-04-12 15:12:36  Updated 2022-04-12 15:44:18
 0
 854

Cryptocurrency Manual Futures & Spot Hedge Strategy

In view of the fact that the hedging frequency of the current hedge strategy is not particularly high, it can actually be operated manually. However, if you do it manually, switch pages of various platforms, observe prices, and calculate the price spreads, which is very inconvenient. Sometimes, you may want to see more symbols, and it is not necessary to set up several displays to show the market quotes. Is it possible to achieve this requirement of manual operation with a semi-automatic strategy? And it is better to have more symbols? oh! Yes. It is best to open and close positions with one click. Oh, right. There is also a position display...

If there is a need, do it now!

Design a Cryptocurrency Manual Futures & Spot Hedge Strategy

The code written here is a little bit tedious, not reaching 600 lines.

javascript
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, ", fail to obtain the 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, ",fail to obtain the 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, "Inadequate margin!", "This time, plan to use", (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, "Inadequate assets!", "This time, buy and plan to use", spAmount * spPrice, "Currently available:", nowSpAcc.Balance) return } else if (tradeType == self.MINUS && nowSpAcc.Stocks < spAmount) { Log(pair.spSymbol, "Inadequate assets!", "This time, sell and plan 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, ", no corresponding position of 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, ", no corresponding position of 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 amount 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 amount calculation error:", fuAmount, spAmount) return } } Log("Contract code:", fuSymbol + "/" + spSymbol, "Direction:", self.arrTradeTypeDesc[tradeType], "Spread:", fuPrice - spPrice, "Futures amount:", fuAmount, "Spot amount:", 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, "Fail to update combined position, 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], "Spot:", self.profit[2], "&") // print the total equity curve, and use charater "&" to not print the equity log } var cmd = GetCommand() if(cmd) { Log("Interactive command:", 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 date:", _D(), " Data update date:", _D(self.tickerUpdateTS), "Update count of postion account:", 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("Do not support reverse arbitrage") } 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, "spot long position", 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, "futures long position", 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, and three positions are 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", "Futures", "Futures Leverage", "Amount", "Spot", "Amount"], 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 : ["Futures", "Code" + fuExName, "Sell 1", "Buy 1", "Spot", "Code" + spExName, "Sell 1", "Buy 1", "Positive Hedge Spread", "Reverse Hedge Spread", "Positive Hedge", "Positive Hedge to close Positions"], 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 Symbol", "Initial Frozen Symbol", "Initial Assets", "Initial Frozen Assets", "Symbol", "Frozen Symbol", "Assets", "Frozen Assets", "Code" + spExName, "Initial Symbol", "Initial Frozen Symbol", "Initial Assets", "Initial Frozen Assets", "Symbol", "Frozen Symbol", "Assets", "Forzen Assets"], 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, "Amount Precision", "Price Precision", "Multiplier", "Minimum Order Amount", "Spot Code" + spExName, "Amount Precision", "Price Precision", "Multiplier", "Minimum Order Amount"], 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("Execute clean-up processing, and save the 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 exchenge object is changed, so reset the data" } 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("Initialize:", pair) } else { self.pairs.push(recoveryPair) Log("Recover:", 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 + ", fail to obtain the symbol information!" } else { Log(pair.fuSymbol, fuSymbolInfo) } var spSymbolInfo = self.spEx.getSymbolInfo(pair.spSymbol) if (!spSymbolInfo) { throw pair.spSymbol + ", fail to obtain the symbol information!" } 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 Setting:", ret) if (!ret) { throw "Leverage initial setting 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 to simulated bot") } } } 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-symbol strategy is more suitable for IO design, a template library named MultiSymbolCtrlLib is used in the code (encapsulated and calling the exchange interface through IO). Therefore, the strategy cannot be backtested, but it can be tested with the simulated bot (although it has been run in a real bot for 2 months, in the stage of test and familiarization, you'd better run it in the simulated bot).

Parameter

Before test, let's talk about the parameter design.

img

The strategy parameters are not too many, and the important ones are:

LTC-USDT-211231|LTC_USDT,BTC-USDT-211231|BTC_USDT

Here is to set the strategy to monitor those combinations. For example, the above setting is to monitor the Litecoin contract (LTC-USDT-211231) of the futures platform and the Litecoin (LTC_USDT) of the spot platform. A combined futures contract and spot trading pair are split by | symbol to form a combination. Different combinations are split by , symbol. 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 platform, not defined on the FMZ platform.
For example, the contract LTC-USDT-211231 is currently a next-quarter contract, called next_quarter on FMZ, and OKEX interface system is called LTC-USDT-211231 . For the LTC/USDT trading pair, the WexApp simulated bot is written as LTC_USDT. So how to fill in here depends on the name defined in the specific platform.

  • Hedging amount of interactive controls
    Click the control button in the status bar, namely the hedging amount. The unit is currency amount, which will be automatically converted into the contract amount by the strategy to place orders.

    img

The other functions are to set the simulated bot, reset the data, use OKEX V5 interface (because it is also compatible with V3) and other functions, which are not particularly important.

Test

The first added exchange object selects the platform adding futures, and the second one selects the spot exchange object.

Futures platforms use OKEX V5 interface simulated bot, and spot platforms use wexApp simulated bot.

I clicked the positive arbitrage button of BTC combination and opened the position.

img

img

img

Then, click the positive arbitrage to close positions.

img

img

loss! ! ! It seems that when the profit spread is small, closing the position cannot cover the fee. It is necessary to calculate the fee and the approximate slippoint, and then reasonably plan the spread for closing the position, and then close the position.

Strategy Source Code: https://www.fmz.com/strategy/314352

Anyone who is interested can use and modify it.

Related Recommendations
Comment
All comments (0)
No data
No data
  • 1
iPhone Download
Forums
PINE Language
© 2015 - ∞ INVENTOR PTE LTD (SG)