리소스 로딩... 로딩...

초보자, 확인해 보세요 암호화폐 양적 거래로 안내 (8)

저자:니나바다스, 창작: 2022-04-22 14:32:14, 업데이트:

초보자, 확인해 보세요 암호화폐 양적 거래로 안내 (8)

지난 기사에서, 우리는 다중 기호 계약 스프레드 모니터링 전략을 함께 설계했습니다. 이 기사에서는 이 아이디어를 계속 개선할 것입니다. 이 아이디어가 실행 가능하는지 확인하고 전략 디자인을 확인하기 위해 OKEX V5 시뮬레이션 봇으로 실행해보겠습니다. 이 프로세스는 또한 암호화폐 프로그래밍 거래 및 양적 거래 과정에서 경험해야합니다. 그로부터 귀중한 경험을 축적 할 수 있기를 바랍니다.

스포일러: 전략이 실행되고 있습니다. 조금 흥미롭습니다.

img

img

img

전략의 전체적인 디자인은 가장 간단한 아이디어에 의해 구현됩니다. 세부 처리에 대한 엄격한 요구 사항이 없음에도 불구하고 코드에서 몇 가지 트릭을 배울 수 있습니다. 전체 전략은 400 줄 미만이기 때문에 읽는 것이 너무 지루하지 않습니다. 물론, 이것은 테스트를위한 데모입니다. 그리고 우리는 결과를 보기 위해 잠시 실행해야합니다. 내가 말하고 싶은 것은: 현재의 전략은 포지션을 열 때만 성공하며, 실제로 테스트하고 탐지해야하는 폐쇄 포지션과 같은 다양한 상황이 있습니다. 프로그램 설계의 버그는 피할 수 없으므로 테스트와 디버깅이 매우 중요합니다!

지난 기사의 코드를 바탕으로 한 전략 디자인으로 돌아가서, 저는 이렇게 추가했습니다.

  • 데이터 지속성 설계 (데이터를 저장하고 재시작 후 데이터를 복원하기 위해 함수 _G를 사용하여)
  • 각 모니터링 된 계약 스프레드 쌍에 그리드 데이터 구조를 추가합니다. (상장을 닫기 위한 헤지 컨트롤)
  • 간단한 헤지 기능을 구현하여 헤지 방식으로 포지션을 열거나 닫습니다.
  • 변동 수익과 손실을 계산하기 위해 전체 자산을 얻는 함수를 추가합니다.
  • 상태 표시줄 데이터를 수출하는 몇 가지 표시를 추가합니다.

위의 기능이 추가됩니다. 간단하기 위해 전략은 긍정적 인 헤지 (장기 계약에 대해 짧게; 단기 계약에 대해 길게) 를 설계했습니다. 현재 영구 계약 (단기) 은 부정적인 투자율을 가지고 있습니다. 투자율의 수익률을 높일 수 있는지 확인하기 위해 영구 계약에 넣으십시오.

전략이 잠시 진행되도록 하자.

약 3일 동안 테스트를 해왔는데 스프레드 변동은 실제로 괜찮습니다.

img

img

img

자금 조달율의 수익률은 다음 그림에서 볼 수 있습니다.

img

전략 소스 코드는 다음과 같이 공유됩니다.

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

전략 주소:https://www.fmz.com/strategy/288559

이 전략은 제가 직접 디자인한 템플릿 중 하나를 사용했습니다. 템플릿은 여기 표시하기에 충분하지 않으므로 다른 템플릿을 사용하여 전략 소스 코드를 약간 수정할 수 있습니다.

만약 당신이 관심이 있다면, 당신은 OKEX V5 시뮬레이션 봇에서 전략을 실행할 수 있습니다. 맞아요, 전략은 뒷검사할 수 없습니다!


더 많은