초보자용 암호화폐 양적 거래 - 암호화폐 양적 거래에 더 가까워지도록 (6)

저자:리디아, 창작: 2022-08-05 17:13:26, 업데이트: 2023-09-21 21:02:17

img

지난 기사에서 우리는 간단한 그리드 전략을 함께 만들었습니다. 이 기사에서는 이 전략을 멀티스펙트 스팟 그리드 전략으로 업그레이드하고 확장했으며, 이 전략을 실무에서 테스트하도록 했습니다. 목적은 "성스러운 잔"을 찾는 것이 아니라 전략을 설계할 때 다양한 문제와 해결책을 논의하는 것입니다. 이 기사는 이 전략을 설계하는 내 경험의 일부를 설명할 것입니다. 이 기사의 내용은 약간 복잡하며 프로그래밍에 대한 특정 기초가 필요합니다.

전략적 필요에 기초한 디자인 사고

이 기사에서는 이전 기사와 마찬가지로 FMZ Quant (FMZ.COM).

  • 다종 솔직히 말해서, 저는 이 네트워크 전략이BTC_USDT, 또한LTC_USDT/EOS_USDT/DOGE_USDT/ETC_USDT/ETH_USDT어쨌든, 현금 거래 쌍과 실행하려는 품종은 모두 동시에 그리드에서 거래됩니다.

    여러 종의 변동적인 시장을 포착하는 것이 좋습니다. 이 요구 사항은 매우 간단해 보이지만, 문제는 설계할 때 발생합니다.

      1. 먼저, 여러 종류의 시장 코팅을 얻습니다. 이것은 해결해야 할 첫 번째 문제입니다. 거래소의 API 문서를 참조 한 후 대부분의 거래소가 집계 된 시장 인터페이스를 제공하는 것을 발견했습니다. OK, 데이터를 얻기 위해 집계된 시장 인터페이스를 사용하세요.
      1. 두 번째 문제는 계정 자산입니다. 그것은 다종 전략이기 때문에 각 거래 쌍 자산의 관리를 개별적으로 고려해야합니다. 그리고 우리는 한 번에 모든 자산의 데이터를 얻고 기록해야합니다. 우리는 왜 계정 자산 데이터를 얻어야합니까? 우리는 왜 각 쌍의 기록을 분리해야합니까? 왜냐하면 주문을 할 때 사용 가능한 자산을 판단해야 하기 때문입니다. 판단하기 전에 그것을 얻는 것이 필요한가? 그리고 이윤을 계산하기 위해서는 초기 계정 자산 데이터를 먼저 기록하고, 그 다음 현금 계정 자산 데이터를 얻고 이윤과 손실을 계산하기 위해 초기 데이터와 비교해야 합니다 다행히도, 거래소의 자산 계정 인터페이스는 보통 모든 통화 자산 데이터를 반환합니다.
      1. 전략 매개 변수 설계. 멀티 종의 매개 변수 설계는 단일 종의 매개 변수 설계와 상당히 다릅니다. 멀티 종의 각 종의 거래 논리가 동일하지만 거래 도중 매개 변수가 다를 수 있습니다. 예를 들어, 그리드 전략에서 BTC_USDT 거래 쌍을 할 때 매번 0.01 BTC를 거래하고 싶을 수 있지만, DOGE_USDT를 할 때 이 매개 변수 (0.01 코인 거래) 를 사용하는 것은 분명히 부적절합니다. 물론, USDT 금액도 처리할 수 있습니다. 그러나 여전히 문제가 발생할 것입니다. BTC_USDT에 1000U, DOGE_USDT에 10U를 거래하고 싶다면 어떻게 될까요? 수요는 결코 만족될 수 없습니다. 이 문제에 대해 생각할 수 있는 사람이 있을지도 모릅니다. 그리고 이렇게 물어볼 수도 있습니다. "나는 여러 개의 매개 변수를 설정하여 다른 거래 쌍의 매개 변수를 개별적으로 제어할 수 있습니다". 이것은 여전히 요구 사항을 충족하기에 충분히 유연하지 않습니다, 몇 개의 매개 변수를 설정하는 것이 좋습니다? 세 개의 매개 변수를 설정하고, 4 가지 종류를 만들고 싶다면 어떻게 될까요? 전략을 수정하고 매개 변수를 증가시켜야합니까?.. 따라서, 다종 전략의 매개 변수를 설계할 때, 이러한 차별화된 매개 변수의 필요를 완전히 고려할 필요가 있다. 하나의 해결책은 매개 변수를 일반 문자열 또는 JSON 문자열로 설계하는 것이다. 예를 들어:
      ETHUSDT:100:0.002|LTCUSDT:20:0.1
      

      그 중에서도, 은 각 종의 데이터를 나누고,ETHUSDT:100:0.002거래 쌍 ETH_USDT를 제어하고,LTCUSDT:20:0.1LTC_USDT 거래 쌍을 제어합니다. 중간에 있는 셈은 분할에 사용됩니다.ETHUSDT:100:0.002, 여기서 ETHUSDT는 당신이 하고 싶은 거래 쌍을 나타냅니다. 100은 그리드 간격입니다. 0.002는 각 그리드에서 거래되는 ETH 동전의 수입니다. 이 문자열은 당신이 하고 싶은 각 종의 매개 변수 정보를 포함합니다. 전략에서 이러한 문자열을 분석하고 각 종의 거래 논리를 제어하기 위해 전략의 변수에 값을 할당합니다. 어떻게 분석합니까? 여전히 위의 예를 사용합니다.

      function main() {
          var net = []  // The recorded grid parameters, use the data when running to the grid trading logic
          var params = "ETHUSDT:100:0.002|LTCUSDT:20:0.1"
          var arrPair = params.split("|")
          _.each(arrPair, function(pair) {
              var arr = pair.split(":")
              var symbol = arr[0]              // Trading pair name
              var diff = parseFloat(arr[1])    // Grid spacing
              var amount = parseFloat(arr[2])  // Grid order volume
              net.push({symbol : symbol, diff : diff, amount : amount})
          })
          Log("Grid parameter data:", net)
      }
      

      img

      이렇게 보면 매개 변수가 분석됩니다. 물론 JSON 문자열을 직접 사용할 수도 있습니다.

      function main() {        
          var params = '[{"symbol":"ETHUSDT","diff":100,"amount":0.002},{"symbol":"LTCUSDT","diff":20,"amount":0.1}]'
          var net = JSON.parse(params)  // The recorded grid parameters, use the data when running to the grid trading logic        
          _.each(net, function(pair) {
              Log("Trading pairs:", pair.symbol, pair)
          })
      }
      

      img

      1. 데이터 지속성 실제에 적용될 수 있는 전략과 튜토리얼 전략 사이에도 큰 차이가 있다. 이전 문서의 튜토리얼 전략은 전략 논리와 설계의 예비 테스트에 불과하며, 실제 세계와 관련하여 고려해야 할 문제가 더 많다. 실제 봇에서는 실제 거래를 시작하고 중지할 수 있다. 이 시점에서 실제 봇 동작 중 모든 데이터가 손실될 것이다. 그렇다면 실제 봇을 다시 시작하여 중지된 후에도 이전 상태에서 계속 실행하도록 만드는 방법은 무엇입니까? 여기서, 실제 봇이 실행될 때 핵심 데이터를 지속적으로 저장하는 것이 필요하기 때문에 데이터를 읽고 다시 시작할 때 계속 실행할 수 있습니다. 당신은_G()FMZ 양적 거래 플랫폼의 기능 또는 데이터베이스 운영 기능을 사용DBExec(), 그리고 FMZ API 문서를 확인해 보세요.

      예를 들어, 우리는 꼬리 sweep 함수를 설계하고_G()그리드 데이터를 저장하는 기능

      var net = null 
      function main() {  // Strategy main functions
          // Read the stored net first
          net = _G("net")
          
          // ...
      }
      
      function onExit() {
          _G("net", net)
          Log("Perform tail-sweeping processing and save data", "#FF0000")
      }
      
      function onexit() {    // The exit sweep function defined by the platform system, triggered the execution when the real bot is clicked to stop
          onExit()
      }
      
      function onerror() {   // The abnormal exit function defined by the platform system, triggered the execution when the program is abnormal
          onExit()
      }
      
      1. 주문량 정확성, 주문 가격 정확성, 최소 주문량, 최소 주문 금액 등과 같은 제한

      백테스팅 시스템은 주문 금액과 주문 정확성에 대해 그러한 엄격한 제한을 두지 않지만 각 거래소는 실제 봇에 주문을 할 때 가격과 주문 금액에 대한 엄격한 기준을 가질 수 있으며 이러한 제한은 다른 거래소에서 동일하지 않습니다. 따라서 문제없이 백테스팅 시스템에서 테스트하는 초보자도 있습니다. 실제 봇이 시작되면 거래가 활성화되면 다양한 문제가 발생하고 오류 메시지의 내용이 읽히지 않아 다양한 미친 현상이 나타납니다.

      여러 종의 경우,이 요구 사항은 더 복잡합니다. 단일 종 전략의 경우, 정확성과 같은 정보를 지정하기 위해 매개 변수를 설계할 수 있습니다. 그러나 여러 종의 전략을 설계할 때,이 정보를 매개 변수에 작성하면 매개 변수가 매우 부풀어 오르는 것이 분명합니다.

      이 시점에서, 교환 문서에 거래 쌍과 관련된 인터페이스 정보가 있는지 확인하기 위해 거래소의 API 문서를 확인해야합니다. 만약 그렇다면, 정확성과 같은 정보를 얻기 위해 전략에 자동 액세스 인터페이스를 설계하고 거래에 관련된 거래 쌍 정보로 구성할 수 있습니다. (단순히, 정확성 또는 무언가가 자동으로 거래소에서 얻어지고 전략 매개 변수와 관련된 변수에 적응됩니다.)

      1. 다른 거래소에 대한 적응 왜 이 질문을 끝으로 했나요? 우리가 위에서 언급한 이 문제에 대한 해결책이 마지막 문제를 가져올 것이기 때문에 우리의 전략은 집계된 시장 인터페이스를 사용한다는 계획이기 때문에 거래 거래 쌍의 정확성과 다른 데이터 적응, 각 거래 쌍을 개별적으로 처리하기 위해 계정 정보에 대한 접근, 이 해결책은 거래소에서 거래소에 크게 다를 수 있습니다. 인터페이스 호출과 메커니즘에 차이가 있다. 스팟 거래소에서는 그리드 전략을 선물 버전으로 확장하면 차이가 작다. 다양한 거래소의 메커니즘의 차이는 더욱 크다. 한 가지 해결책은 FMZ 템플릿 클래스 라이브러리를 설계하는 것입니다. 이러한 차이점을 구현하기 위해 클래스 라이브러리에 디자인을 작성하십시오. 전략 자체와 거래소 사이의 결합을 줄이십시오. 단점은 템플릿 클래스 라이브러리를 작성하고 이 템플릿의 각 교환 차이에 대해 구체적으로 구현해야 한다는 것입니다.

템플릿 클래스 라이브러리를 설계

위의 분석을 바탕으로, 템플릿 클래스 라이브러리는 전략과 교환 메커니즘과 인터페이스 사이의 결합을 줄이기 위해 설계되었습니다.

이 템플릿 클래스 라이브러리를 이렇게 디자인할 수 있습니다 (코드의 일부가 생략되었습니다):

function createBaseEx(e, funcConfigure) {
    var self = {}
    self.e = e 
    
    self.funcConfigure = funcConfigure
    self.name = e.GetName()
    self.type = self.name.includes("Futures_") ? "Futures" : "Spot"
    self.label = e.GetLabel()
    
    // Interfaces to be implemented
    self.interfaceGetTickers = null   // Create a function to asynchronously obtain a thread of aggregated market data
    self.interfaceGetAcc = null       // Create a function that asynchronously obtains account data thread
    self.interfaceGetPos = null       // Get a position
    self.interfaceTrade = null        // Create concurrent orders
    self.waitTickers = null           // Waiting for concurrent market data 
    self.waitAcc = null               // Waiting for account concurrent data
    self.waitTrade = null             // Waiting for order concurrent data
    self.calcAmount = null            // Calculate the order volume based on data such as trading pair accuracy
    self.init = null                  // Initialization work, obtaining data such as accuracy
    
    // Execute the configuration function to configure the object
    funcConfigure(self)

    // Check whether the interfaces agreed by configList are implemented
    _.each(configList, function(funcName) {
        if (!self[funcName]) {
            throw "interface" + funcName + "unimplemented"
        }
    })
    
    return self
}

$.createBaseEx = createBaseEx
$.getConfigureFunc = function(exName) {
    dicRegister = {
        "Futures_OKCoin" : funcConfigure_Futures_OKCoin,    // Implementation of OK futures
        "Huobi" : funcConfigure_Huobi,
        "Futures_Binance" : funcConfigure_Futures_Binance,
        "Binance" : funcConfigure_Binance,
        "WexApp" : funcConfigure_WexApp,                    // Implementation of wexApp
    }
    return dicRegister
}

템플릿에서는 특정 거래소를 위해 작성되어 있습니다. 예를 들어 FMZ의 시뮬레이션 봇 WexApp를 참조하십시오.

function funcConfigure_WexApp(self) {
    var formatSymbol = function(originalSymbol) {
        // BTC_USDT
        var arr = originalSymbol.split("_")
        var baseCurrency = arr[0]
        var quoteCurrency = arr[1]
        return [originalSymbol, baseCurrency, quoteCurrency]
    }

    self.interfaceGetTickers = function interfaceGetTickers() {
        self.routineGetTicker = HttpQuery_Go("https://api.wex.app/api/v1/public/tickers")
    }

    self.waitTickers = function waitTickers() {
        var ret = []
        var arr = JSON.parse(self.routineGetTicker.wait()).data
        _.each(arr, function(ele) {
            ret.push({
                bid1: parseFloat(ele.buy), 
                bid1Vol: parseFloat(-1),
                ask1: parseFloat(ele.sell), 
                ask1Vol: parseFloat(-1),
                symbol: formatSymbol(ele.market)[0],
                type: "Spot", 
                originalSymbol: ele.market
            })
        })
        return ret 
    }

    self.interfaceGetAcc = function interfaceGetAcc(symbol, updateTS) {
        if (self.updateAccsTS != updateTS) {
            self.routineGetAcc = self.e.Go("GetAccount")
        }
    }

    self.waitAcc = function waitAcc(symbol, updateTS) {
        var arr = formatSymbol(symbol)
        var ret = null 
        if (self.updateAccsTS != updateTS) {
            ret = self.routineGetAcc.wait().Info
            self.bufferGetAccRet = ret 
        } else {
            ret = self.bufferGetAccRet
        }
        if (!ret) {
            return null 
        }        
        var acc = {symbol: symbol, Stocks: 0, FrozenStocks: 0, Balance: 0, FrozenBalance: 0, originalInfo: ret}
        _.each(ret.exchange, function(ele) {
            if (ele.currency == arr[1]) {
                // baseCurrency
                acc.Stocks = parseFloat(ele.free)
                acc.FrozenStocks = parseFloat(ele.frozen)
            } else if (ele.currency == arr[2]) {
                // quoteCurrency
                acc.Balance = parseFloat(ele.free)
                acc.FrozenBalance = parseFloat(ele.frozen)
            }
        })
        return acc
    }

    self.interfaceGetPos = function interfaceGetPos(symbol, price, initSpAcc, nowSpAcc) {
        var symbolInfo = self.getSymbolInfo(symbol)
        var sumInitStocks = initSpAcc.Stocks + initSpAcc.FrozenStocks
        var sumNowStocks = nowSpAcc.Stocks + nowSpAcc.FrozenStocks
        var diffStocks = _N(sumNowStocks - sumInitStocks, symbolInfo.amountPrecision)
        if (Math.abs(diffStocks) < symbolInfo.min / price) {
            return []
        }
        return [{symbol: symbol, amount: diffStocks, price: null, originalInfo: {}}]
    }

    self.interfaceTrade = function interfaceTrade(symbol, type, price, amount) {
        var tradeType = ""
        if (type == self.OPEN_LONG || type == self.COVER_SHORT) {
            tradeType = "bid"
        } else {
            tradeType = "ask"
        }
        var params = {
            "market": symbol,
            "side": tradeType,
            "amount": String(amount),
            "price" : String(-1),
            "type" : "market"
        }
        self.routineTrade = self.e.Go("IO", "api", "POST", "/api/v1/private/order", self.encodeParams(params))
    }

    self.waitTrade = function waitTrade() {
        return self.routineTrade.wait()
    }

    self.calcAmount = function calcAmount(symbol, type, price, amount) {
        // Obtain trading pair information
        var symbolInfo = self.getSymbolInfo(symbol)
        if (!symbol) {
            throw symbol + ", the trading pair information cannot be checked"
        }
        var tradeAmount = null 
        var equalAmount = null  // Number of coins recorded
        if (type == self.OPEN_LONG || type == self.COVER_SHORT) {
            tradeAmount = _N(amount * price, parseFloat(symbolInfo.pricePrecision))
            // Check the minimum trading volume
            if (tradeAmount < symbolInfo.min) {
                Log(self.name, " tradeAmount:", tradeAmount, "less than", symbolInfo.min)
                return false 
            }
            equalAmount = tradeAmount / price
        } else {
            tradeAmount = _N(amount, parseFloat(symbolInfo.amountPrecision))
            // Check the minimum trading volume
            if (tradeAmount < symbolInfo.min / price) {
                Log(self.name, " tradeAmount:", tradeAmount, "less than", symbolInfo.min / price)
                return false 
            }
            equalAmount = tradeAmount
        }
        return [tradeAmount, equalAmount]
    }

    self.init = function init() {   // Functions that deal with conditions such as accuracy automatically
        var ret = JSON.parse(HttpQuery("https://api.wex.app/api/v1/public/markets"))
        _.each(ret.data, function(symbolInfo) {
            self.symbolsInfo.push({
                symbol: symbolInfo.pair,
                amountPrecision: parseFloat(symbolInfo.basePrecision),
                pricePrecision: parseFloat(symbolInfo.quotePrecision),
                multiplier: 1,
                min: parseFloat(symbolInfo.minQty),
                originalInfo: symbolInfo
            })
        })        
    }
}

이 템플릿을 전략에 사용하는 것은 간단합니다.

function main() {
    var fuExName = exchange.GetName()
    var fuConfigureFunc = $.getConfigureFunc()[fuExName]
    var ex = $.createBaseEx(exchange, fuConfigureFunc)

    var arrTestSymbol = ["LTC_USDT", "ETH_USDT", "EOS_USDT"]
    var ts = new Date().getTime()
    
    // Test to get tickers
    ex.goGetTickers()
    var tickers = ex.getTickers()
    Log("tickers:", tickers)
    
    // Test to obtain account information
    ex.goGetAcc(symbol, ts)
    
    _.each(arrTestSymbol, function(symbol) {        
        _.each(tickers, function(ticker) {
            if (symbol == ticker.originalSymbol) {
                // print ticker data
                Log(symbol, ticker)
            }
        })

        // print asset data
        var acc = ex.getAcc(symbol, ts)
        Log("acc:", acc.symbol, acc)
    })
}

전략 실제 봇

위 템플릿을 기반으로 전략을 설계하고 작성하는 것은 매우 간단합니다. 전체 전략은 약 300 + 라인이며 디지털 통화 스팟 다종 격자 전략을 구현합니다.

img

img

현재는 돈을 잃고 있습니다.T_T, 소스 코드는 아직 공개되지 않습니다.

여기 몇 개의 등록 코드가 있습니다. 관심 있으시면 wexApp를 이용해서 시도해 볼 수 있습니다.

Buy address: https://www.fmz.com/m/s/284507
Registration code: 
adc7a2e0a2cfde542e3ace405d216731
f5db29d05f57266165ce92dc18fd0a30
1735dca92794943ddaf277828ee04c27
0281ea107935015491cda2b372a0997d
1d0d8ef1ea0ea1415eeee40404ed09cc

200U를 조금 넘었을 때, 제가 달리기 시작했을 때, 저는 큰 일방적인 시장에 부딪혔습니다. 하지만 저는 천천히 회복했습니다. 안정성은 나쁘지 않습니다. 5월 27일 이후로 변경되지 않았습니다.


관련

더 많은