avatar of 发明者量化-小小梦 发明者量化-小小梦
집중하다 사신
4
집중하다
1271
수행원

암호화폐 업계의 양적 거래 초보자 여러분, 이 글을 꼭 읽어보세요 - 암호화폐 업계의 양적 거래에 한 걸음 더 다가가기 (VI)

만든 날짜: 2021-06-04 10:08:48, 업데이트 날짜: 2024-12-04 21:14:15
comments   6
hits   2596

암호화폐 업계의 양적 거래 초보자 여러분, 이 글을 꼭 읽어보세요 - 암호화폐 업계의 양적 거래에 한 걸음 더 다가가기 (VI)

암호화폐 업계의 양적 거래 초보자 여러분, 이 글을 꼭 읽어보세요 - 암호화폐 업계의 양적 거래에 한 걸음 더 다가가기 (VI)

이전 글에서 우리는 간단한 그리드 전략을 만들기 위해 함께 작업했습니다. 이 글에서는 이 전략을 다양한 스팟 그리드 전략으로 업그레이드하고 확장하여 실제 전투에서 이 전략을 테스트합니다. 목적은 ‘성배’를 찾는 것이 아니라, 전략을 설계할 때 다양한 문제와 해결책을 탐구하는 것입니다. 이 글에서는 이 전략을 설계하는 데 있어서 제가 겪은 몇 가지 경험을 설명하겠습니다. 이 글의 내용은 약간 복잡하며 프로그래밍에 대한 특정 기초가 필요합니다.

전략적 요구 사항에 기반한 디자인 사고

이 기사는 이전 기사와 마찬가지로 Inventor Quantization(FMZ.COM) 기반 디자인에 대해 논의합니다.

  • 다양한 종류 솔직히 말해서, 저는 이 그리드 전략이BTC_USDT, 또한 할 수 있습니다LTC_USDT/EOS_USDT/DOGE_USDT/ETC_USDT/ETH_USDT. 어쨌든, 현물 거래 쌍의 경우, 거래하고자 하는 모든 상품을 동시에 그리드 거래할 수 있습니다.

음~~다양한 종류의 변동이 심한 시장을 포착하는 건 기분 좋죠. 요구사항은 간단해 보이지만 설계 과정에서 문제가 발생합니다.

    1. 먼저 다양한 품종의 시장정보를 얻습니다. 이것이 해결해야 할 첫 번째 문제입니다. 거래소의 API 문서를 확인한 결과, 대부분 거래소는 통합된 시장 정보 인터페이스를 제공하는 것으로 나타났습니다. 좋습니다. 집계된 시장 데이터 인터페이스를 사용하여 데이터를 얻으세요.
    1. 두 번째로 발생하는 문제는 계정 자산입니다. 다양한 종류의 전략을 구현하고자 하기 때문에 각 거래에 대한 자산의 별도 관리를 고려해야 합니다. 그리고 모든 자산에 대한 데이터와 기록을 한꺼번에 얻습니다. 계정 자산 데이터가 필요한 이유는 무엇입니까? 각 거래 쌍에 대해 별도의 기록을 보관해야 합니까? 주문을 할 때 사용 가능한 자산을 판단해야 하므로, 판단을 내리기 전에 먼저 자산을 확보해야 합니까? 그리고 수입을 계산해야 합니다. 또한 초기 계정 자산 데이터를 먼저 기록한 다음 현재 계정 자산 데이터를 얻어 초기 계정 자산 데이터와 비교하여 손익을 계산해야 합니까? 다행히도 거래소의 자산 계정 인터페이스는 일반적으로 모든 통화의 자산 데이터를 반환합니다. 우리는 그것을 한 번만 얻고 데이터를 처리하면 됩니다.
    1. 전략 매개변수 설계. 여러 품종의 매개변수 설계는 단일 품종의 매개변수 설계와 상당히 다릅니다. 왜냐하면 각 품종의 거래 로직은 동일하지만 거래 중의 매개변수는 다를 수 있기 때문입니다. 예를 들어, 그리드 전략에서 BTC_USDT 거래 쌍을 할 때마다 0.01 BTC를 거래하고 싶을 수 있습니다. 그러나 DOGE_USDT를 할 때 여전히 이 매개변수(0.01 코인 거래)를 사용한다면 분명히 부적절합니다. 물론, 또한 USDT 금액에 따라 처리합니다. 하지만 여전히 문제가 있을 것입니다. 만약 당신이 BTC_USDT로 1000U를 거래하고 싶고 DOGE_USDT로 10U를 거래하고 싶다면 어떨까요? 수요는 결코 충족될 수 없습니다. 일부 학생들은 이 질문에 대해 생각한 다음 “여러 거래 쌍의 매개변수를 개별적으로 제어하기 위해 여러 매개변수 그룹을 설정할 수 있습니다.“라고 말할 수 있습니다. 여전히 유연하게 요구 사항을 충족할 수 없습니다. 매개변수 그룹을 몇 개 설정해야 합니까? 매개변수 3개가 설정되어 있습니다. 4가지 품종을 거래하고 싶다면 어떻게 해야 합니까? 전략을 수정하고 매개변수를 추가하는 것이 가능할까요? 따라서 다양한 전략의 매개변수를 설계할 때 차별화된 매개변수에 대한 수요를 충분히 고려해야 합니다. 한 가지 해결책은 매개변수를 일반 문자열이나 JSON 문자열로 설계하는 것입니다. 예를 들어:
    ETHUSDT:100:0.002|LTCUSDT:20:0.1
    

    “|”는 각 품종의 데이터를 구분하는데, 이는ETHUSDT:100:0.002ETH_USDT 거래 쌍을 관리합니다.LTCUSDT:20:0.1LTC_USDT 거래 쌍을 관리합니다. 가운데의 “|“는 구분 기호 역할을 합니다. ETHUSDT:100:0.002여기서 ETHUSDT는 거래하려는 거래 쌍을 나타내고, 100은 그리드 간격이며, 0.002는 각 그리드에서 거래되는 ETH 코인의 수이고, “:” 기호는 이러한 데이터를 구분하는 데 사용됩니다(물론 이러한 매개변수 규칙은 다음과 같습니다. 전략 디자이너가 설정합니다). 귀하의 필요에 맞게 디자인할 수 있습니다). 이러한 문자열에는 거래하려는 각 제품의 매개변수 정보가 포함되어 있습니다. 전략에서 이러한 문자열을 구문 분석하고 전략의 변수에 특정 값을 할당하여 각 제품의 거래 로직을 제어합니다. 그러면 어떻게 분석할까요? 위의 예를 다시 사용해 보겠습니다.

    function main() {
        var net = []  // 记录的网格参数,具体运行到网格交易逻辑时,使用这里面的数据
        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]              // 交易对名称
            var diff = parseFloat(arr[1])    // 网格间距
            var amount = parseFloat(arr[2])  // 网格下单量
            net.push({symbol : symbol, diff : diff, amount : amount})
        })
        Log("网格参数数据:", net)
    }
    

    암호화폐 업계의 양적 거래 초보자 여러분, 이 글을 꼭 읽어보세요 - 암호화폐 업계의 양적 거래에 한 걸음 더 다가가기 (VI)

    매개변수가 이런 방식으로 파싱되는 것을 볼 수 있습니다. 물론 JSON 문자열을 직접 사용할 수도 있는데, 더 간단합니다.

    function main() {        
        var params = '[{"symbol":"ETHUSDT","diff":100,"amount":0.002},{"symbol":"LTCUSDT","diff":20,"amount":0.1}]'
        var net = JSON.parse(params)  // 记录的网格参数,具体运行到网格交易逻辑时,使用这里面的数据        
        _.each(net, function(pair) {
            Log("交易对:", pair.symbol, pair)
        })
    }
    

    암호화폐 업계의 양적 거래 초보자 여러분, 이 글을 꼭 읽어보세요 - 암호화폐 업계의 양적 거래에 한 걸음 더 다가가기 (VI)

    1. 데이터 지속성 실제에 적용할 수 있는 전략과 가르치는 전략 사이에도 큰 차이가 있습니다. 이전 기사의 가르치는 전략은 전략 논리와 설계에 대한 예비 테스트일 뿐입니다. 실제 실행에서는 고려해야 할 문제가 더 많습니다. 실제 거래 중에 실제 거래를 시작하고 중단할 수 있습니다. 이때, 실시간 작업 중의 모든 데이터는 손실됩니다. 그러면 실제 디스크를 중지한 다음 다시 시작하여 이전 상태에서 계속 실행하려면 어떻게 해야 할까요? 여기서는 시스템을 재시작할 때 데이터를 읽고 계속 사용할 수 있도록 실시간 작업 중에 주요 데이터를 유지하는 것이 필요합니다. Inventor Quantitative Trading Platform에서 사용 가능합니다._G()함수 또는 데이터베이스 작업 함수를 사용하세요DBExec()자세한 내용은 FMZ API 문서를 참조하세요.

    예를 들어, 우리는 다음을 사용하여 스윕 함수를 설계합니다._G()함수, 그리드 데이터를 저장합니다.

    var net = null 
    function main() {  // 策略主函数
        // 首先读取储存的net
        net = _G("net")
    
    
        // ...
    }
    
    
    function onExit() {
        _G("net", net)
        Log("执行扫尾处理,保存数据", "#FF0000")
    }
    
    
    function onexit() {    // 平台系统定义的退出扫尾函数,在点击实盘停止时触发执行
        onExit()
    }
    
    
    function onerror() {   // 平台系统定义的异常退出函数,在程序发生异常时触发执行
        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()
    
    // 需要实现的接口
    self.interfaceGetTickers = null   // 创建异步获取聚合行情数据线程的函数
    self.interfaceGetAcc = null       // 创建异步获取账户数据线程的函数
    self.interfaceGetPos = null       // 获取持仓
    self.interfaceTrade = null        // 创建并发下单
    self.waitTickers = null           // 等待并发行情数据 
    self.waitAcc = null               // 等待账户并发数据
    self.waitTrade = null             // 等待下单并发数据
    self.calcAmount = null            // 根据交易对精度等数据计算下单量
    self.init = null                  // 初始化工作,获取精度等数据
    
    // 执行配置函数,给对象配置
    funcConfigure(self)

    // 检测configList约定的接口是否都实现
    _.each(configList, function(funcName) {
        if (!self[funcName]) {
            throw "接口" + funcName + "未实现"
        }
    })
    
    return self
}

$.createBaseEx = createBaseEx
$.getConfigureFunc = function(exName) {
    dicRegister = {
        "Futures_OKCoin" : funcConfigure_Futures_OKCoin,    // OK期货的实现
        "Huobi" : funcConfigure_Huobi,
        "Futures_Binance" : funcConfigure_Futures_Binance,
        "Binance" : funcConfigure_Binance,
        "WexApp" : funcConfigure_WexApp,                    // 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) {
        // 获取交易对信息
        var symbolInfo = self.getSymbolInfo(symbol)
        if (!symbol) {
            throw symbol + ",交易对信息查询不到"
        }
        var tradeAmount = null 
        var equalAmount = null  // 记录币数
        if (type == self.OPEN_LONG || type == self.COVER_SHORT) {
            tradeAmount = _N(amount * price, parseFloat(symbolInfo.pricePrecision))
            // 检查最小交易量
            if (tradeAmount < symbolInfo.min) {
                Log(self.name, " tradeAmount:", tradeAmount, "小于", symbolInfo.min)
                return false 
            }
            equalAmount = tradeAmount / price
        } else {
            tradeAmount = _N(amount, parseFloat(symbolInfo.amountPrecision))
            // 检查最小交易量
            if (tradeAmount < symbolInfo.min / price) {
                Log(self.name, " tradeAmount:", tradeAmount, "小于", symbolInfo.min / price)
                return false 
            }
            equalAmount = tradeAmount
        }
        return [tradeAmount, equalAmount]
    }

    self.init = function init() {   // 自动处理精度等条件的函数
        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()
    
    // 测试获取行情
    ex.goGetTickers()
    var tickers = ex.getTickers()
    Log("tickers:", tickers)
    
    // 测试获取账户信息
    ex.goGetAcc(symbol, ts)
    
    _.each(arrTestSymbol, function(symbol) {        
        _.each(tickers, function(ticker) {
            if (symbol == ticker.originalSymbol) {
                // 打印行情数据
                Log(symbol, ticker)
            }
        })

        // 打印资产数据
        var acc = ex.getAcc(symbol, ts)
        Log("acc:", acc.symbol, acc)
    })
}

전략 실제 시장

위의 템플릿을 기반으로 전략을 설계하고 작성하는 것은 매우 간단합니다. 전체 전략은 약 300줄 이상으로, 디지털 통화 스팟 멀티버리티 그리드 전략을 구현합니다.

암호화폐 업계의 양적 거래 초보자 여러분, 이 글을 꼭 읽어보세요 - 암호화폐 업계의 양적 거래에 한 걸음 더 다가가기 (VI)

암호화폐 업계의 양적 거래 초보자 여러분, 이 글을 꼭 읽어보세요 - 암호화폐 업계의 양적 거래에 한 걸음 더 다가가기 (VI)

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

다음은 몇 가지 등록 코드입니다. 관심이 있다면 wexApp에서 시도해 볼 수 있습니다.

购买地址: https://www.fmz.com/m/s/284507
注册码: 
adc7a2e0a2cfde542e3ace405d216731
f5db29d05f57266165ce92dc18fd0a30
1735dca92794943ddaf277828ee04c27
0281ea107935015491cda2b372a0997d
1d0d8ef1ea0ea1415eeee40404ed09cc

US는 200명 남짓이었고, 운영을 시작하자마자 큰 일방적 시장에 부딪혔고 천천히 회복되었습니다. 스팟 그리드의 가장 큰 장점은 “푹신푹신하게 잠을 잘 수 있다!“는 것입니다. 안정성은 괜찮아요. 5월 27일 이후로는 건드리지 않았어요. 당장은 선물 그리드를 시도할 생각도 없어요.