Người mới bắt đầu, hãy kiểm tra nó Đưa bạn đến giao dịch định lượng tiền điện tử (6)

Tác giả:Ninabadass, Tạo: 2022-04-21 18:13:03, Cập nhật: 2022-04-22 12:00:05

Người mới bắt đầu, hãy kiểm tra nó Đưa bạn đến giao dịch định lượng tiền điện tử (6)

Trong bài viết trước đây, chúng tôi đã cùng nhau tạo ra một chiến lược lưới đơn giản. Trong bài viết này, chúng tôi đã nâng cấp và mở rộng chiến lược này thành một chiến lược lưới điểm đa biểu tượng, và để cho chiến lược này được thử nghiệm trong thực tế. Mục đích không phải là để tìm ra một Holy Grail, mà là để thảo luận về các vấn đề và giải pháp khác nhau trong quá trình thiết kế chiến lược. Bài viết này sẽ giải thích một số kinh nghiệm của tôi trong việc thiết kế chiến lược. Nội dung của bài viết này hơi phức tạp và đòi hỏi một nền tảng nhất định trong lập trình.

Suy nghĩ về thiết kế dựa trên yêu cầu chiến lược

Trong bài viết này, giống như bài trước, chúng tôi thảo luận về thiết kế dựa trên nền tảng FMZ Quant Trading (FMZ.COM).

  • Biểu tượng đa Thành thật mà nói, tôi muốn chiến lược lưới điện không chỉBTC_USDT, nhưng cũngLTC_USDT/EOS_USDT/DOGE_USDT/ETC_USDT/ETH_USDTDù sao, đối với các cặp giao dịch giao ngay, vận hành giao dịch lưới của tất cả các biểu tượng mà bạn muốn giao dịch cùng một lúc.

    Ừ, cảm giác tốt khi nắm bắt được sự rung động của thị trường với nhiều biểu tượng.
    Mặc dù yêu cầu nghe có vẻ đơn giản, nhưng nó trở nên khó khăn khi bạn bắt đầu thiết kế.

      1. Đầu tiên, lấy báo giá thị trường của nhiều biểu tượng. Đó là vấn đề đầu tiên được giải quyết. Sau khi đọc tài liệu API nền tảng, tôi thấy rằng nền tảng thường cung cấp các giao diện tổng hợp. Được rồi, chúng tôi sử dụng giao diện dữ liệu thị trường tổng hợp để lấy dữ liệu.
      1. Vấn đề thứ hai là tài sản tài khoản. Vì chúng ta muốn thực hiện các chiến lược đa biểu tượng, chúng ta nên xem xét quản lý riêng tài sản của mỗi cặp giao dịch, và lấy tất cả dữ liệu tài sản và ghi lại một lần. Tại sao chúng ta nên lấy dữ liệu tài sản tài khoản? Và tại sao cũng ghi lại mỗi cặp giao dịch riêng biệt?

    Bởi vì bạn cần đánh giá các tài sản có sẵn khi đặt hàng, không cần thiết để có được dữ liệu trước khi đánh giá? Hơn nữa, lợi nhuận cần phải được tính toán. Chúng ta có nên ghi lại dữ liệu tài sản tài khoản ban đầu trước? và sau đó lấy dữ liệu tài sản của tài khoản vãng lai và tính toán lợi nhuận và lỗ bằng cách so sánh với ban đầu? May mắn thay, giao diện tài khoản tài sản của một nền tảng thường trả về tất cả dữ liệu tài sản tiền tệ, vì vậy chúng ta chỉ cần lấy nó một lần, và sau đó xử lý dữ liệu.

      1. Thiết kế tham số chiến lược. Thiết kế tham số của chiến lược đa biểu tượng khá khác so với thiết kế tham số của một biểu tượng duy nhất, bởi vì ngay cả logic giao dịch của mỗi biểu tượng trong chiến lược đa biểu tượng cũng giống nhau, có thể các tham số của mỗi biểu tượng trong quá trình giao dịch khác nhau. Ví dụ, trong chiến lược lưới, bạn có thể muốn giao dịch 0.01 BTC mỗi lần khi giao dịch cặp BTC_USDT, nhưng rõ ràng không phù hợp khi sử dụng tham số này (giao dịch 0.01 tiền tệ) khi giao dịch DOGE_USDT. Tất nhiên, bạn cũng có thể xử lý theo số tiền USDT. Nhưng vẫn sẽ có vấn đề. Nếu bạn muốn giao dịch 1000U bởi BTC_USDT và 10U bởi DOGE_USDT thì sao? Nhu cầu không bao giờ có thể được thỏa mãn. Có lẽ, một số sinh viên có thể nghĩ về câu hỏi, và đề xuất rằng, Tôi có thể thiết lập nhiều nhóm tham số hơn, và riêng biệt kiểm soát các tham số của các cặp giao dịch khác nhau để được vận hành. Điều đó vẫn không thể linh hoạt đáp ứng nhu cầu, cho bao nhiêu nhóm các tham số nên được thiết lập? chúng tôi thiết lập 3 nhóm; nếu chúng tôi muốn vận hành 4 biểu tượng? chúng tôi cần phải sửa đổi chiến lược và tăng các tham số? Do đó, hãy suy nghĩ đầy đủ về nhu cầu phân biệt khi thiết kế các tham số chiến lược đa ký hiệu. Một giải pháp là thiết kế các tham số thành các chuỗi thông thường hoặc chuỗi JSON.
        Ví dụ:
      ETHUSDT:100:0.002|LTCUSDT:20:0.1
      

      được sử dụng để chia dữ liệu của mỗi biểu tượng, cho thấy rằngETHUSDT:100:0.002kiểm soát cặp giao dịch ETH_USDT, vàLTCUSDT:20:0.1điều khiển cặp giao dịch LTC_USDT. TrongETHUSDT:100:0.002, ETHUSDT đại diện cho cặp giao dịch bạn muốn vận hành; 100 là khoảng cách lưới; 0.002 là số tiền ETH giao dịch của mỗi lưới; : được sử dụng để chia dữ liệu được đề cập ở trên (chắc chắn, các quy tắc tham số được thiết kế bởi nhà thiết kế chiến lược; bạn có thể thiết kế bất cứ điều gì bạn muốn dựa trên nhu cầu của mình).
      Các chuỗi này đã chứa thông tin tham số của mỗi biểu tượng bạn cần vận hành. Bạn có thể phân tích các chuỗi và gán giá trị cho các biến trong chiến lược, để kiểm soát logic giao dịch của mỗi biểu tượng. Làm thế nào để phân tích? Hãy sử dụng ví dụ được đề cập ở trên.

      function main() {
          var net = []  // the recorded grid parameters; when specifically running the grid trading logic, use the data from here 
          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 amount 
              net.push({symbol : symbol, diff : diff, amount : amount})
          })
          Log("Grid parameter data:", net)
      }
      

      img

      Chắc chắn, bạn có thể trực tiếp sử dụng các chuỗi JSON, dễ dàng hơn.

      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; when specifically running the grid trading logic, use the data from here         
          _.each(net, function(pair) {
              Log("Trading pair:", pair.symbol, pair)
          })
      }
      

      img

      1. Tính bền vững của dữ liệu Có một sự khác biệt lớn giữa một chiến lược thực tế và một chiến lược giảng dạy. Chiến lược giảng dạy trong bài viết trước đây chỉ là để kiểm tra ban đầu về logic và thiết kế chiến lược. Có nhiều vấn đề để lo lắng khi thực sự chạy một chiến lược trong một bot. Khi chạy một bot, bot có thể được khởi động và dừng lại. Vào thời điểm đó, tất cả dữ liệu trong quá trình chạy bot sẽ bị mất. Vì vậy, làm thế nào để tiếp tục trạng thái trước đó khi khởi động lại bot, sau khi bot được dừng lại? Ở đây, cần phải lưu dữ liệu chính liên tục khi bot đang chạy, để dữ liệu có thể được đọc và bot tiếp tục chạy khi bot được khởi động lại. Bạn có thể sử dụng_G()chức năng trên FMZ Quant, hoặc sử dụng chức năng hoạt độngDBExec()trong cơ sở dữ liệu, và bạn có thể truy vấn các tài liệu FMZ API cho các chi tiết.

      Ví dụ, chúng ta muốn thiết kế một chức năng dọn dẹp bằng cách sử dụng chức năng_G(), để lưu dữ liệu lưới.

      var net = null 
      function main() {  // strategy main function 
          // first read the stored net 
          net = _G("net")
          
          // ...
      }
      
      function onExit() {
          _G("net", net)
          Log("Execute the clean-up processing, and save the data", "#FF0000")
      }
      
      function onexit() {    // the onexit function defined by the platform system, which will be triggered when clicking the bot to stop 
          onExit()
      }
      
      function onerror() {   // the onerror function defined by the platform system, which will be triggered when the program exception occurs 
          onExit()
      }
      
      1. Giới hạn về độ chính xác số tiền đặt hàng, độ chính xác giá đặt hàng, khối lượng đặt hàng tối thiểu, số tiền đặt hàng tối thiểu, v.v.

      Hệ thống backtest không có giới hạn nghiêm ngặt về khối lượng lệnh và độ chính xác lệnh; nhưng trong bot, mỗi nền tảng có các tiêu chuẩn nghiêm ngặt về giá lệnh và khối lượng lệnh, và các cặp giao dịch khác nhau có giới hạn khác nhau. Do đó, người mới bắt đầu thường kiểm tra OKEX trong hệ thống backtest. Một khi chiến lược được chạy trên bot, có nhiều vấn đề khác nhau khi giao dịch được kích hoạt, và sau đó nội dung của thông báo lỗi không được đọc, và nhiều hiện tượng điên rồ xuất hiện.

      Đối với các trường hợp nhiều ký hiệu, yêu cầu phức tạp hơn. Đối với một chiến lược một ký hiệu, bạn có thể thiết kế một tham số để chỉ định thông tin như độ chính xác. Tuy nhiên, khi bạn thiết kế một chiến lược nhiều ký hiệu, rõ ràng là viết thông tin vào một tham số sẽ làm cho tham số rất tẻ nhạt.

      Tại thời điểm này, bạn cần kiểm tra tài liệu API của nền tảng để xem liệu có giao diện cho thông tin liên quan đến cặp giao dịch trong tài liệu không. Nếu có giao diện này, bạn có thể thiết kế giao diện truy cập tự động trong chiến lược để có được thông tin như độ chính xác, và cấu hình nó vào thông tin cặp giao dịch trong giao dịch (tóm lại, độ chính xác được tự động lấy từ nền tảng và sau đó điều chỉnh theo biến liên quan đến tham số chiến lược).

      1. Điều chỉnh nền tảng khác nhau Tại sao vấn đề được đề cập đến ở cuối? Việc xử lý tất cả các vấn đề mà chúng tôi đã đề cập ở trên sẽ dẫn đến vấn đề cuối cùng. Bởi vì chiến lược của chúng tôi có kế hoạch sử dụng giao diện thị trường tổng hợp, các giải pháp như truy cập chính xác cặp giao dịch nền tảng và thích nghi dữ liệu khác, cũng như truy cập thông tin tài khoản để xử lý từng cặp giao dịch riêng biệt, v.v., sẽ mang lại sự khác biệt lớn do các nền tảng khác nhau. Có sự khác biệt trong cuộc gọi giao diện và sự khác biệt trong cơ chế. Đối với các nền tảng spot, sự khác biệt tương đối nhỏ nếu chiến lược lưới được mở rộng sang phiên bản tương lai. Sự khác biệt trong cơ chế của mỗi nền tảng thậm chí còn lớn hơn. Một giải pháp là thiết kế thư viện mẫu FMZ; viết thiết kế thực hiện sự khác biệt trong thư viện để giảm sự kết nối giữa chính chiến lược và nền tảng. Nhược điểm của điều đó là bạn cần phải viết một thư viện mẫu, và trong mẫu này, cụ thể thực hiện sự khác biệt dựa trên mỗi nền tảng.

Thiết kế thư viện mẫu

Dựa trên phân tích trên, chúng tôi thiết kế một thư viện mẫu để giảm sự kết nối giữa chiến lược, cơ chế nền tảng và giao diện.
Chúng ta có thể thiết kế thư viện mẫu như thế này (một phần của mã bị bỏ qua):

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()
    
    // the interfaces that need to be implemented 
    self.interfaceGetTickers = null   // create a function that asynchronously obtains the aggregated market quote threads
    self.interfaceGetAcc = null       // create a function that asynchronously obtains the account data threads 
    self.interfaceGetPos = null       // obtain positions 
    self.interfaceTrade = null        // create concurrent orders 
    self.waitTickers = null           // wait for the concurrent market quote data  
    self.waitAcc = null               // wait for the account concurrent data 
    self.waitTrade = null             // wait for order concurrent data
    self.calcAmount = null            // calculate the order amount according to the trading pair precision and other data 
    self.init = null                  // initialization; obtain the precision and other data 
    
    // execute the configuration function, to configure objects 
    funcConfigure(self)

    // detect whether all the interfaces arranged by configList can be implemented 
    _.each(configList, function(funcName) {
        if (!self[funcName]) {
            throw "interface" + funcName + "not implemented"
        }
    })
    
    return self
}

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

Trong mẫu, thực hiện viết mã nhằm vào một hình thức chơi cụ thể; lấy bot mô phỏng FMZ WexApp làm ví dụ:

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 the trading pair information 
        var symbolInfo = self.getSymbolInfo(symbol)
        if (!symbol) {
            throw symbol + ",trading pair information not found"
        }
        var tradeAmount = null 
        var equalAmount = null  // record the symbol amount  
        if (type == self.OPEN_LONG || type == self.COVER_SHORT) {
            tradeAmount = _N(amount * price, parseFloat(symbolInfo.pricePrecision))
            // detect the minimum trading amount 
            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))
            // detect the minimum trading amount 
            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() {   // the function that automatically processes conditions like precision, etc.  
        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
            })
        })        
    }
}

Nó sẽ rất dễ dàng để sử dụng mẫu trong chiến lược:

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 obtain the market quotes 
    ex.goGetTickers()
    var tickers = ex.getTickers()
    Log("tickers:", tickers)
    
    // test to obtain the account information 
    ex.goGetAcc(symbol, ts)
    
    _.each(arrTestSymbol, function(symbol) {        
        _.each(tickers, function(ticker) {
            if (symbol == ticker.originalSymbol) {
                // print the market quote data 
                Log(symbol, ticker)
            }
        })

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

Bot chiến lược

Nó rất đơn giản để thiết kế và viết chiến lược dựa trên mẫu trên. Toàn bộ chiến lược có khoảng hơn 300 dòng mã.

img

img

Ngay bây giờ, nó có những tổn thấtT_T, vì vậy mã nguồn sẽ không được cung cấp.

Có một số mã đăng ký; nếu bạn quan tâm, bạn có thể thử chúng trong wexApp:

Purchase Address: https://www.fmz.com/m/s/284507
Registration Code:
adc7a2e0a2cfde542e3ace405d216731
f5db29d05f57266165ce92dc18fd0a30
1735dca92794943ddaf277828ee04c27
0281ea107935015491cda2b372a0997d
1d0d8ef1ea0ea1415eeee40404ed09cc

Chỉ có hơn 200 USD, và khi bot mới được khởi động, nó đã gặp một thị trường đơn phương tuyệt vời. Nó cần thời gian để bù đắp tổn thất. Lợi thế lớn nhất của chiến lược lưới điểm là: cảm thấy an toàn để ngủ! Sự ổn định của chiến lược là tốt, và tôi đã không sửa đổi nó kể từ ngày 27 tháng Năm. Tôi không dám thử chiến lược lưới tương lai tạm thời.


Thêm nữa