Novato, comprueba Te llevará al comercio cuantitativo de criptomonedas (8)

El autor:No lo sé., Creado: 2022-04-22 14:32:14, Actualizado:

Novato, comprueba Te llevará al comercio cuantitativo de criptomonedas (8)

En el último artículo, diseñamos una estrategia de monitoreo de propagación de contratos de símbolos múltiples juntos. En este artículo, continuaremos mejorando esta idea. Veamos si la idea es factible, y ejecutémosla con el bot simulado OKEX V5 para verificar el diseño de la estrategia. Estos procesos también se requieren para ser experimentados en el proceso de comercio programado de criptomonedas y comercio cuantitativo. Espero que puedas acumular una valiosa experiencia de eso.

Spoiler: la estrategia ha estado funcionando, lo cual es un poco emocionante.

img

img

img

El diseño general de la estrategia se implementa por la idea más simple. Aunque no hay un requisito estricto para el procesamiento de detalles, todavía se pueden aprender algunos trucos del código. La estrategia entera es inferior a 400 líneas, por lo que no es demasiado aburrido leerla. Por supuesto, esto es sólo una demostración para la prueba, y necesitamos ejecutarla por un tiempo para ver el resultado. Lo que quiero decir es: la estrategia actual sólo tiene éxito en las posiciones de apertura, y hay varias situaciones, como posiciones de cierre, que realmente se prueban y detectan. Los errores en el diseño del programa son inevitables, por lo que las pruebas y depuración son muy importantes!

Volviendo al diseño de la estrategia, basado en el código del artículo anterior, he añadido:

  • Diseño de persistencia de datos (utilizando la función _G para almacenar datos y restaurar los datos después de reiniciar);
  • Añadir la estructura de datos de la red a cada par de diferencias de contratación monitoreado (para controlar la cobertura para cerrar posiciones);
  • Implementar una función de cobertura simple para abrir o cerrar posiciones mediante cobertura;
  • Añadir una función de obtención del patrimonio neto total para calcular las ganancias y pérdidas variables;
  • Añadiendo algunas visualizaciones de datos de barra de estado de exportación.

Para ser simples, la estrategia solo diseñó cobertura positiva (hacer corto para el contrato a largo plazo; hacer largo para el contrato a corto plazo). Actualmente, el contrato perpetuo (a corto plazo) tiene una tasa de financiación negativa; simplemente haga en el contrato perpetuo, para ver si se puede aumentar el rendimiento de la tasa de financiación.

Deja que la estrategia funcione por un tiempo.

Se ha probado durante unos 3 días, y las fluctuaciones de propagación son realmente buenas.

img

img

img

Una parte del rendimiento del tipo de financiación se puede ver en la siguiente imagen.

img

El código fuente de la estrategia se comparte de la siguiente manera:

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

Dirección de la estrategia:https://www.fmz.com/strategy/288559

La estrategia utilizó una de las plantillas diseñadas por mí mismo; la plantilla no es lo suficientemente buena para mostrarse aquí, por lo que puede utilizar otra plantilla modificando un poco el código fuente de la estrategia.

Si estás interesado, puedes ejecutar la estrategia en el bot simulado OKEX V5. Oh, correcto, la estrategia no puede ser comprobada!


Más.