In the previous article, we designed a multi-species contract spread monitoring strategy together. In this article, we will continue to improve this idea. Let's see if this idea is feasible, and run it with the OKEX V5 simulation bot to verify the strategy design. These processes are also required to be experienced in the process of cryptocurrency programmatic tradings and quantitative tradings. I hope that beginners can accumulate valuable experience.
Spoiler alert, the strategy is running, and I am a little excited!
/upload/asset/28d33189dc5ca7a584747.png
/upload/asset/28d5e7ed9a3250c356d0c.png
/upload/asset/28ebcee2d13464aac08fa.png
The overall design of the strategy is implemented in the simplest way. Although the details are not too demanding, you can still learn some tips from the code. The overall strategy code is less than 400 lines, so it will not be boring to read and understand. Of course, this is just a test DEMO, it takes a while to test it. So what I want to say is: the current strategy is only successful in opening positions, and various situations such as closing a position need to be tested and verified. BUGs in program design are inevitable, so testing and DEBUG are very important!
Back to the strategy design, based on the code in the previous article, the strategy is added:
- Data persistence design (use _G function to save data and restore data after restart)
- Added grid data structure for each monitored CFD pair (used to control hedging opening and closing positions)
- Implemented a simple hedging function to hedge open and close positions
- Added a total equity acquisition function to calculate floating profit and loss
- Added status bar data output display.
The above are the added functions. In order to simplify the design, the strategy is only designed for positive hedging (short long-term contracts, long near-term contracts). At present, the perpetual contract (near-term) has a negative fee rate, which just can long for the perpetual contract to see if it can increase the rate profits.
Let the strategy run for a while ~
After testing for about 3 days, the spread fluctuation is still feasible.
/upload/asset/28e6dbd6d7484928303dc.png
/upload/asset/28deaf54acdc7091df821.png
/upload/asset/28e103d7789fbed433aff.png
Here we can see the profits of some funding rates.
/upload/asset/28e4acbd1c306f412e7ab.png
Share the source code of the strategy below:
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) { // 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 simulation environment Log("Only OKEX V5 API is supported, switch to OKEX V5 simulation bot:") } else { exchange.IO("simulate", false) // Switch to real bot Log("Only OKEX V5 API is supported, switch to OKEX V5 simulation bot:") } if (exchange.GetName() != "Futures_OKCoin") { throw "Support OKEX futures" } // Initialization if (isReset) { _G(null) LogReset(1) LogProfitReset() LogVacuum() Log("Reset all data", "#FF0000") } // Initialization marker var isFirst = true // Profit print period var preProfitPrintTS = 0 // Total equity var totalEquity = 0 var posTbls = [] // Position table array // Declare arrCfg var arrCfg = [] _.each(arrNearContractType, function(ct) { arrCfg.push(createCfg(formatSymbol(ct)[0])) }) var objCharts = Chart(arrCfg) objCharts.reset() // Create object var exName = exchange.GetName() + "_V5" var nearConfigureFunc = $.getConfigureFunc()[exName] var farConfigureFunc = $.getConfigureFunc()[exName] var nearEx = $.createBaseEx(exchange, nearConfigureFunc) var farEx = $.createBaseEx(exchange, farConfigureFunc) // Pre-write the contract that require subscriptions _.each(arrNearContractType, function(ct) { nearEx.pushSubscribeSymbol(ct) }) _.each(arrFarContractType, function(ct) { farEx.pushSubscribeSymbol(ct) }) while (true) { var ts = new Date().getTime() // Obtain market data nearEx.goGetTickers() farEx.goGetTickers() var nearTickers = nearEx.getTickers() var farTickers = farEx.getTickers() if (!farTickers || !nearTickers) { Sleep(2000) continue } var tbl = { type : "table", title : "Long term-near term spread", cols : ["Trading pair", "long term", "near term", "positive hedging", "negative hedging"], 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]]) } } } }) }) // Initialization if (isFirst) { isFirst = false var recoveryNets = _G("nets") var recoveryInitTotalEquity = _G("initTotalEquity") if (!recoveryNets) { // Check positions _.each(subscribeFarTickers, function(farTicker) { var pos = farEx.getFuPos(farTicker.originalSymbol, ts) if (pos.length != 0) { Log(farTicker.originalSymbol, pos) throw "Initialized with a position" } }) _.each(subscribeNearTickers, function(nearTicker) { var pos = nearEx.getFuPos(nearTicker.originalSymbol, ts) if (pos.length != 0) { Log(nearTicker.originalSymbol, pos) throw "Initialized with a position" } }) // 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 "Initialization to obtain total equity failed!" } } else { // Recovery nets = recoveryNets initTotalEquity = recoveryInitTotalEquity } } // Retrieve the grid and check if the trading 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 found", obj.symbol, " 's spread") return } // Check grid, add dynamically 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, }) } // Search 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 hedging opening position 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 hedging closing position upP.sell = false } } } obj.prePlus = currPlus // Record the current spread as a cache, and use it to judge whether it's above the SMA or below the SMA next time // Add other chart outputs }) if (ts - preProfitPrintTS > 1000 * 60 * 5) { // Print every 5 minutes var currTotalEquity = getTotalEquity() if (currTotalEquity) { totalEquity = currTotalEquity LogProfit(totalEquity - initTotalEquity, "&") // Print dynamic equity profits } // Check positions posTbls = [] // Reset, 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 } // Show 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 total 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("Failed 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, "Order amount calculation error:", 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 tail function", "#FF0000") _G("nets", nets) _G("initTotalEquity", initTotalEquity) Log("save the data:", _G("nets"), _G("initTotalEquity")) }
/upload/asset/28d985d389607a4e93667.png
Strategy public address: https://www.fmz.com/strategy/288559
The strategy uses a template class library written by myself, which is not public because it is not too good. The above strategy source code can be modified without using this template.
are interested, you can use an OKEX V5 simulation bot to test.
Oh! By the way, this strategy cannot be backtested~