Type/to search
0
Follow
20
Followers
Novice, Check it Out —— Take You to Cryptocurrency Quantitative Trading (8)
Help
Created 2022-04-22 14:32:14  
 0
 890

Novice, Check it Out —— Take You to Cryptocurrency Quantitative Trading (8)

In the last article, we designed a multi-symbol contract spread monitoring strategy together. In this article, we will continue to improve this idea. Let's see if the idea is feasible, and run it with OKEX V5 simulated bot to verify the strategy design. These processes are also required to be experienced in the process of cryptocurrency programmed trading and quantitative trading. I hope that you can accumulate valuable experience from that.

Spoiler: the strategy has been running, which is a little bit exciting.

img

img

img

The overall design of the strategy is implemented by the simplest idea. Although there is no strict requirement for the detail processing, you can still learn some tricks from the code. The entire strategy is less than 400 lines, so it is not too boring to read it. Of course, this is only a DEMO for test, and we need to run it for a while to see the result. What I want to say is: the present strategy is only successful in opening positions, and there are various situations, such as closing positions, to be actually tested and detected. Bugs in the program design are unavoidable, so testing and debugging are very important!

Back to the strategy design, based on the code in last article, I have added:

  • Data persistence design (by using the function _G to store data, and restore the data after restart);
  • Adding the grid data structure to each monitored contract spread pair (to control the hedge to close positions);
  • Implementing a simple hedging function to open or close positions by hedge;
  • Adding a function of obtaining the total equity to calculate the floating profit and loss;
  • Adding some displays of exporting status bar data.

Those above are the functions added. To be simple, the strategy only designed positive hedge (make short for long-term contract; make long for short-term contract). At present, the perpetual contract (short-term) has a negative funding rate; just make in the perpetual contract, to see if the return of the funding rate can be increased.

Let the strategy run for a while.

It has been tested for about 3 days, and the spread fluctuations are actually fine.

img

img

img

Part of the return from the funding rate can be seen in the following picture.

img

The strategy source code is shared as follows:

javascript
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) { // the price cannot be less than or equal to 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) // switch to the simulated environment Log("Only support OKEX V5 API, and switch to OKEX V5 simulated bot:") } else { exchange.IO("simulate", false) // switch to the bot Log("Only support OKEX V5 API, and switch to OKEX V5 bot:") } if (exchange.GetName() != "Futures_OKCoin") { throw "support OKEX Futures" } // initialize if (isReset) { _G(null) LogReset(1) LogProfitReset() LogVacuum() Log("reset all data", "#FF0000") } // initialize the mark var isFirst = true // the profit prints the period var preProfitPrintTS = 0 // the total equity var totalEquity = 0 var posTbls = [] // the array of position table // declare arrCfg var arrCfg = [] _.each(arrNearContractType, function(ct) { arrCfg.push(createCfg(formatSymbol(ct)[0])) }) var objCharts = Chart(arrCfg) objCharts.reset() // create objects var exName = exchange.GetName() + "_V5" var nearConfigureFunc = $.getConfigureFunc()[exName] var farConfigureFunc = $.getConfigureFunc()[exName] var nearEx = $.createBaseEx(exchange, nearConfigureFunc) var farEx = $.createBaseEx(exchange, farConfigureFunc) // write the contracts to be subscribed in advance _.each(arrNearContractType, function(ct) { nearEx.pushSubscribeSymbol(ct) }) _.each(arrFarContractType, function(ct) { farEx.pushSubscribeSymbol(ct) }) while (true) { var ts = new Date().getTime() // obtain the market quotes nearEx.goGetTickers() farEx.goGetTickers() var nearTickers = nearEx.getTickers() var farTickers = farEx.getTickers() if (!farTickers || !nearTickers) { Sleep(2000) continue } var tbl = { type : "table", title : "long-short term spread", cols : ["trading pair", "long term", "shaort term", "positive hedge", "negative hedge"], 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]]) } } } }) }) // initialize if (isFirst) { isFirst = false var recoveryNets = _G("nets") var recoveryInitTotalEquity = _G("initTotalEquity") if (!recoveryNets) { // detect positions _.each(subscribeFarTickers, function(farTicker) { var pos = farEx.getFuPos(farTicker.originalSymbol, ts) if (pos.length != 0) { Log(farTicker.originalSymbol, pos) throw "There are positions during the initialization" } }) _.each(subscribeNearTickers, function(nearTicker) { var pos = nearEx.getFuPos(nearTicker.originalSymbol, ts) if (pos.length != 0) { Log(nearTicker.originalSymbol, pos) throw "There are positions during the initialization" } }) // construct 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 "Fail to obtain the total equity by initialization!" } } else { // recover nets = recoveryNets initTotalEquity = recoveryInitTotalEquity } } // query the grid, to detect whether a trade is triggered _.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("not detected", obj.symbol, "spread") return } // examine the grid; dynamically add 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, }) } // detect grid 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)) { // positive hedge, open positions 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)) { // positive hedge, close positions upP.sell = false } } } obj.prePlus = currPlus // record the spread of the time, as cache, which will be used to judge upcross or downcross for the next time // add other tables to export }) if (ts - preProfitPrintTS > 1000 * 60 * 5) { // print every 5 minutes var currTotalEquity = getTotalEquity() if (currTotalEquity) { totalEquity = currTotalEquity LogProfit(totalEquity - initTotalEquity, "&") // print the dynamic profit of equity } // detect positions posTbls = [] // reset and update _.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" : ["contract code", "amount", "price"], "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 } // display the grid var netTbls = [] _.each(nets, function(obj) { var netTbl = { "type" : "table", "title" : obj.symbol, "cols" : ["grid"], "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(), "total equity:", totalEquity, "initial equity:", initTotalEquity, "floating profit and loss: ", 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("Fail to obtain the total equity of the account!") 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, "Wrong calculation of the order amount:", 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("execute the onexit function", "#FF0000") _G("nets", nets) _G("initTotalEquity", initTotalEquity) Log("Save data:", _G("nets"), _G("initTotalEquity")) }

img

Strategy Address: https://www.fmz.com/strategy/288559

The strategy used one of the templates designed by myself; the template is not good enough to be shown here, so you can use another template by modifying the strategy source code a little bit.

If you are interested, you can run the strategy in the OKEX V5 simulated bot.
Oh, right, the strategy cannot be backtested!

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