Phân tích chiến lược của LeeksReaper (1)

Tác giả:Lydia., Tạo: 2022-11-04 15:42:26, Cập nhật: 2023-09-15 21:08:48

img

Phân tích chiến lược của LeeksReaper (1)

Gần đây, có một cuộc thảo luận sôi nổi vềprint moneyMột chiến lược rất cũ đã trở lại mắt của Quants: LeeksReaper. Nguyên tắc giao dịch robot củaprint moneyVì vậy, tôi đọc lại chiến lược ban đầu một lần nữa một cách cẩn thận và nhìn vào phiên bản cấy ghép của OKCoin cấy ghép trên FMZ Quant. Chiến lược của người cấy ghép dựa trên nền tảng FMZ Quant được phân tích để khám phá ý tưởng của chiến lược để người dùng nền tảng có thể tìm hiểu ý tưởng của chiến lược này. Trong bài viết này, chúng tôi sẽ phân tích thêm từ các khía cạnh của ý tưởng chiến lược và ý định để giảm thiểu nội dung nhàm chán liên quan đến lập trình.

Mã nguồn của chiến lược [Transplanting OKCoin LeeksReaper]:

function LeeksReaper() {
    var self = {}
    self.numTick = 0
    self.lastTradeId = 0
    self.vol = 0
    self.askPrice = 0
    self.bidPrice = 0
    self.orderBook = {Asks:[], Bids:[]}
    self.prices = []
    self.tradeOrderId = 0
    self.p = 0.5
    self.account = null
    self.preCalc = 0
    self.preNet = 0

    self.updateTrades = function() {
        var trades = _C(exchange.GetTrades)
        if (self.prices.length == 0) {
            while (trades.length == 0) {
                trades = trades.concat(_C(exchange.GetTrades))
            }
            for (var i = 0; i < 15; i++) {
                self.prices[i] = trades[trades.length - 1].Price
            }
        }
        self.vol = 0.7 * self.vol + 0.3 * _.reduce(trades, function(mem, trade) {
            // Huobi not support trade.Id
            if ((trade.Id > self.lastTradeId) || (trade.Id == 0 && trade.Time > self.lastTradeId)) {
                self.lastTradeId = Math.max(trade.Id == 0 ? trade.Time : trade.Id, self.lastTradeId)
                mem += trade.Amount
            }
            return mem
        }, 0)

    }
    self.updateOrderBook = function() {
        var orderBook = _C(exchange.GetDepth)
        self.orderBook = orderBook
        if (orderBook.Bids.length < 3 || orderBook.Asks.length < 3) {
            return
        }
        self.bidPrice = orderBook.Bids[0].Price * 0.618 + orderBook.Asks[0].Price * 0.382 + 0.01
        self.askPrice = orderBook.Bids[0].Price * 0.382 + orderBook.Asks[0].Price * 0.618 - 0.01
        self.prices.shift()
        self.prices.push(_N((orderBook.Bids[0].Price + orderBook.Asks[0].Price) * 0.35 +
            (orderBook.Bids[1].Price + orderBook.Asks[1].Price) * 0.1 +
            (orderBook.Bids[2].Price + orderBook.Asks[2].Price) * 0.05))
    }
    self.balanceAccount = function() {
        var account = exchange.GetAccount()
        if (!account) {
            return
        }
        self.account = account
        var now = new Date().getTime()
        if (self.orderBook.Bids.length > 0 && now - self.preCalc > (CalcNetInterval * 1000)) {
            self.preCalc = now
            var net = _N(account.Balance + account.FrozenBalance + self.orderBook.Bids[0].Price * (account.Stocks + account.FrozenStocks))
            if (net != self.preNet) {
                self.preNet = net
                LogProfit(net)
            }
        }
        self.btc = account.Stocks
        self.cny = account.Balance
        self.p = self.btc * self.prices[self.prices.length-1] / (self.btc * self.prices[self.prices.length-1] + self.cny)
        var balanced = false
        
        if (self.p < 0.48) {
            Log("Start balance", self.p)
            self.cny -= 300
            if (self.orderBook.Bids.length >0) {
                exchange.Buy(self.orderBook.Bids[0].Price + 0.00, 0.01)
                exchange.Buy(self.orderBook.Bids[0].Price + 0.01, 0.01)
                exchange.Buy(self.orderBook.Bids[0].Price + 0.02, 0.01)
            }
        } else if (self.p > 0.52) {
            Log("Start balance", self.p)
            self.btc -= 0.03
            if (self.orderBook.Asks.length >0) {
                exchange.Sell(self.orderBook.Asks[0].Price - 0.00, 0.01)
                exchange.Sell(self.orderBook.Asks[0].Price - 0.01, 0.01)
                exchange.Sell(self.orderBook.Asks[0].Price - 0.02, 0.01)
            }
        }
        Sleep(BalanceTimeout)
        var orders = exchange.GetOrders()
        if (orders) {
            for (var i = 0; i < orders.length; i++) {
                if (orders[i].Id != self.tradeOrderId) {
                    exchange.CancelOrder(orders[i].Id)
                }
            }
        }
    }

    self.poll = function() {
        self.numTick++
        self.updateTrades()
        self.updateOrderBook()
        self.balanceAccount()
        
        var burstPrice = self.prices[self.prices.length-1] * BurstThresholdPct
        var bull = false
        var bear = false
        var tradeAmount = 0
        if (self.account) {
            LogStatus(self.account, 'Tick:', self.numTick, ', lastPrice:', self.prices[self.prices.length-1], ', burstPrice: ', burstPrice)
        }
        
        if (self.numTick > 2 && (
            self.prices[self.prices.length-1] - _.max(self.prices.slice(-6, -1)) > burstPrice ||
            self.prices[self.prices.length-1] - _.max(self.prices.slice(-6, -2)) > burstPrice && self.prices[self.prices.length-1] > self.prices[self.prices.length-2]
            )) {
            bull = true
            tradeAmount = self.cny / self.bidPrice * 0.99
        } else if (self.numTick > 2 && (
            self.prices[self.prices.length-1] - _.min(self.prices.slice(-6, -1)) < -burstPrice ||
            self.prices[self.prices.length-1] - _.min(self.prices.slice(-6, -2)) < -burstPrice && self.prices[self.prices.length-1] < self.prices[self.prices.length-2]
            )) {
            bear = true
            tradeAmount = self.btc
        }
        if (self.vol < BurstThresholdVol) {
            tradeAmount *= self.vol / BurstThresholdVol
        }
        
        if (self.numTick < 5) {
            tradeAmount *= 0.8
        }
        
        if (self.numTick < 10) {
            tradeAmount *= 0.8
        }
        
        if ((!bull && !bear) || tradeAmount < MinStock) {
            return
        }
        var tradePrice = bull ? self.bidPrice : self.askPrice
        while (tradeAmount >= MinStock) {
            var orderId = bull ? exchange.Buy(self.bidPrice, tradeAmount) : exchange.Sell(self.askPrice, tradeAmount)
            Sleep(200)
            if (orderId) {
                self.tradeOrderId = orderId
                var order = null
                while (true) {
                    order = exchange.GetOrder(orderId)
                    if (order) {
                        if (order.Status == ORDER_STATE_PENDING) {
                            exchange.CancelOrder(orderId)
                            Sleep(200)
                        } else {
                            break
                        }
                    }
                }
                self.tradeOrderId = 0
                tradeAmount -= order.DealAmount
                tradeAmount *= 0.9
                if (order.Status == ORDER_STATE_CANCELED) {
                    self.updateOrderBook()
                    while (bull && self.bidPrice - tradePrice > 0.1) {
                        tradeAmount *= 0.99
                        tradePrice += 0.1
                    }
                    while (bear && self.askPrice - tradePrice < -0.1) {
                        tradeAmount *= 0.99
                        tradePrice -= 0.1
                    }
                }
            }
        }
        self.numTick = 0
    }
    return self
}

function main() {
    var reaper = LeeksReaper()
    while (true) {
        reaper.poll()
        Sleep(TickInterval)
    }
}

Tổng quan chiến lược

Thông thường, khi bạn có một chiến lược để nghiên cứu, bạn nên xem xét cấu trúc chương trình tổng thể trước. mã chiến lược không quá dài, với ít hơn 200 dòng mã, nó rất ngắn gọn, và chiến lược ban đầu được khôi phục cao, gần như giống nhau. mã chiến lược chạy từmain()Toàn bộ mã chiến lược, ngoại trừmain(), là một hàm có tênLeeksReaper().LeeksReaper()chức năng rất dễ hiểu, nó có thể được hiểu như là các nhà xây dựng của các phương thức logic chiến lược (một đối tượng).LeeksReaper()chịu trách nhiệm xây dựng một logic giao dịch cho người thu hoạch.

Từ khóa:

img

· Dòng đầu tiên của chiến lượcmainchức năng:var reaper = LeeksReaper(), mã tuyên bố một biến địa phươngreapervà sau đó gọi hàm LeeksReaper() để xây dựng một đối tượng logic chiến lược gán giá trị choreaper.

Bước tiếp theo của chiến lượcmainchức năng:

while (true) {
    reaper.poll()
    Sleep(TickInterval)
}

Nhập mộtwhilevòng lặp vô tận và tiếp tục thực hiện chức năng xử lýpoll()củareaperđối tượng,poll()chức năng chính xác là nơi logic chính của chiến lược giao dịch nằm và toàn bộ chương trình chiến lược bắt đầu thực hiện logic giao dịch một lần nữa và một lần nữa. Còn về đường dâySleep(TickInterval), nó dễ hiểu, nó là để kiểm soát thời gian tạm dừng sau mỗi lần thực hiện tổng hợp logic giao dịch, với mục đích kiểm soát tần suất xoay của logic giao dịch.

Phân tíchLeeksReaper()nhà chế tạo

Nhìn xemLeeksReaper()hàm xây dựng một đối tượng logic chiến lược.

CácLeeksReaper()hàm bắt đầu bằng cách tuyên bố một đối tượng trống,var self = {}, và trong quá trình thực hiện cácLeeksReaper()hàm sẽ dần dần thêm một số phương thức và thuộc tính cho đối tượng trống này, cuối cùng hoàn thành việc xây dựng đối tượng này và trả về nó (tức là bước củamain()chức năng bên trongvar reaper = LeeksReaper(), đối tượng được trả về được gán choreaper).

Thêm thuộc tính vàoselfđối tượng Tiếp theo, tôi thêm rất nhiều thuộc tính vàoself. Tôi sẽ mô tả mỗi thuộc tính như sau, có thể hiểu mục đích và ý định của các thuộc tính và biến nhanh chóng, tạo điều kiện dễ dàng để hiểu các chiến lược, và tránh bị nhầm lẫn khi nhìn thấy mã.

    self.numTick = 0         # It is used to record the number of transactions not triggered when the poll function is called. When the order is triggered and the order logic is executed, self.numTick is reset to 0
    self.lastTradeId = 0     # The transaction record ID of the order that has been transacted in the transaction market. This variable records the current transaction record ID of the market
    self.vol = 0             # Reference to the trading volume of each market inspection after weighted average calculation (market data is obtained once per loop, which can be interpreted as a time of market inspection)
    self.askPrice = 0        # The bill of lading price of the sales order can be understood as the price of the listing order after the strategy is calculated
    self.bidPrice = 0        # Purchase order bill of lading price
    self.orderBook = {Asks:[], Bids:[]}    # Record the currently obtained order book data, that is, depth data (sell one... sell n, buy one... buy n)
    self.prices = []                       # An array that records the prices on the time series after the calculation of the first three weighted averages in the order book, which means that each time the first three weighted averages of the order book are stored, they are placed in an array and used as a reference for subsequent strategy trading signals, so the variable name is prices, in plural form, indicating a set of prices
    self.tradeOrderId = 0    # Record the order ID after the current bill of lading is placed
    self.p = 0.5             # Position proportion: when the value of currency accounts for exactly half of the total asset value, the value is 0.5, that is, the equilibrium state
    self.account = null      # Record the account asset data, which is returned by the GetAccount() function
    self.preCalc = 0         # Record the timestamp of the last time when the revenue was calculated, in milliseconds, to control the frequency of triggering the execution of the revenue calculation code
    self.preNet = 0          # Record current return values

Thêm phương thức vào các đối tượng self

Sau khi thêm các thuộc tính này cho bản thân, bắt đầu thêm các phương thức vàoselfđối tượng để đối tượng này có thể làm một số công việc và có một số chức năng.

Chức năng đầu tiên thêm:

    self.updateTrades = function() {
        var trades = _C(exchange.GetTrades)  # Call the FMZ encapsulated interface GetTrades to obtain the latest market transaction data
        if (self.prices.length == 0) {       # When self.prices.length == 0, the self.prices array needs to be filled with numeric values, which will be triggered only when the strategy starts running
            while (trades.length == 0) {     # If there is no recent transaction record in the market, the while loop will keep executing until the latest transaction data is available and update the trades variable
                trades = trades.concat(_C(exchange.GetTrades))   # concat is a method of JS array type, which is used to concatenate two arrays, here is to concatenate the "trades" array and the array data returned by "_C(exchange.GetTrades)" into one array
            }
            for (var i = 0; i < 15; i++) {   # Fill in data to self.prices, and fill in 15 pieces of latest transaction prices
                self.prices[i] = trades[trades.length - 1].Price
            }
        }
        self.vol = 0.7 * self.vol + 0.3 * _.reduce(trades, function(mem, trade) {  # _. Reduce function is used for iterative calculation to accumulate the amount of the latest transaction records
            // Huobi not support trade.Id
            if ((trade.Id > self.lastTradeId) || (trade.Id == 0 && trade.Time > self.lastTradeId)) {
                self.lastTradeId = Math.max(trade.Id == 0 ? trade.Time : trade.Id, self.lastTradeId)
                mem += trade.Amount
            }
            return mem
        }, 0)

    }

Chức năngupdateTradeslà để có được dữ liệu giao dịch thị trường mới nhất và thực hiện một số tính toán dựa trên dữ liệu và ghi lại nó để sử dụng trong logic tiếp theo của chiến lược. Các bình luận từng dòng tôi đã viết trong mã trên trực tiếp. Đối với_.reduce, một người không có kiến thức lập trình cơ bản có thể bị nhầm lẫn._.reducelà một hàm của thư viện Underscore.js. Chiến lược FMZJS hỗ trợ thư viện này, vì vậy nó rất thuận tiện cho tính toán lặp lại.https://underscorejs.net/#reduce)

Ý nghĩa cũng rất đơn giản, ví dụ:

function main () {
   var arr = [1, 2, 3, 4]
   var sum = _.reduce(arr, function(ret, ele){
       ret += ele
       
       return ret
   }, 0)

   Log("sum:", sum)    # sum = 10
}

Đó là, cộng lên mỗi số trong mảng[1, 2, 3, 4]Trở lại chiến lược của chúng tôi, chúng tôi cộng các giá trị khối lượng giao dịch của mỗi dữ liệu ghi chép giao dịch trongtradesLấy tổng số lượng giao dịch mới nhấtself.vol = 0.7 * self.vol + 0.3 * _.reduce (...), ở đây chúng ta sử dụng...Nó không khó để thấy tính toán củaself.volcũng là một mức trung bình được cân nhắc. nghĩa là khối lượng giao dịch mới tạo ra chiếm 30% tổng số, và khối lượng giao dịch được cân nhắc cuối cùng chiếm 70%. Tỷ lệ này được thiết lập bởi tác giả chiến lược một cách nhân tạo và nó có thể liên quan đến các quy tắc thị trường. Đối với câu hỏi của bạn, nếu giao diện để lấy dữ liệu giao dịch mới nhất trở lại dữ liệu cũ trùng lặp, sau đó dữ liệu tôi nhận được là sai, và nó sẽ không có ý nghĩa?

if ((trade.Id > self.lastTradeId) || (trade.Id == 0 && trade.Time > self.lastTradeId)) {
    ...
}

phán quyết. Nó có thể được đánh giá dựa trên ID giao dịch trong bản ghi giao dịch. Sự tích lũy chỉ được kích hoạt khi ID lớn hơn ID của bản ghi cuối cùng, hoặc nếu giao diện trao đổi không cung cấp ID, nghĩa làtrade.Id == 0, sử dụng dấu thời gian trong hồ sơ giao dịch để đánh giá.self.lastTradeIdlưu trữ dấu thời gian của hồ sơ giao dịch thay vì ID.

Chức năng thứ hai thêm:

    self.updateOrderBook = function() {
        var orderBook = _C(exchange.GetDepth)
        self.orderBook = orderBook
        if (orderBook.Bids.length < 3 || orderBook.Asks.length < 3) {
            return
        }
        self.bidPrice = orderBook.Bids[0].Price * 0.618 + orderBook.Asks[0].Price * 0.382 + 0.01
        self.askPrice = orderBook.Bids[0].Price * 0.382 + orderBook.Asks[0].Price * 0.618 - 0.01
        self.prices.shift()
        self.prices.push(_N((orderBook.Bids[0].Price + orderBook.Asks[0].Price) * 0.35 +
            (orderBook.Bids[1].Price + orderBook.Asks[1].Price) * 0.1 +
            (orderBook.Bids[2].Price + orderBook.Asks[2].Price) * 0.05))
    }

Tiếp theo, chúng ta hãy xem xét hàmupdateOrderBook. Từ tên của hàm, chúng ta có thể thấy rằng nó được sử dụng để cập nhật sổ lệnh. Tuy nhiên, nó không cập nhật chỉ sổ lệnh. Chức năng bắt đầu gọi hàm FMZ APIGetDepth()để có được dữ liệu sổ lệnh thị trường hiện tại (bán một... bán n, mua một... mua n), và ghi lại dữ liệu sổ lệnh trongself.orderBook. Tiếp theo, phán xét nếu đơn đặt hàng mua và đơn đặt hàng bán của dữ liệu sổ đặt hàng là ít hơn 3, nếu có, chức năng không hợp lệ sẽ được trả về trực tiếp.

Sau đó, hai dữ liệu được tính toán:

· Tính toán giá vận chuyển Giá vận chuyển cũng được tính bằng cách sử dụng phương pháp trung bình trọng số. Khi tính toán đơn đặt hàng, trọng số cho giá mua gần nhất với giá giao dịch là 61,8% (0,618) và trọng số cho giá bán gần nhất với giá giao dịch là 38,2% (0,382) Khi tính toán giá vận chuyển, trọng lượng tương tự được đưa ra cho giá bán gần nhất với giá giao dịch. Đối với tại sao là 0,618, có thể tác giả thích tỷ lệ phần vàng. Đối với giá cuối cùng (0,01), nó là để bù đắp đến trung tâm của sự mở cửa một chút.

· Cập nhật giá trung bình trọng số của ba cấp đầu tiên của sổ đơn đặt hàng trên chuỗi thời gian Đối với ba mức giá đặt hàng mua và bán đầu tiên trong sổ đơn đặt hàng, trung bình trọng số được tính toán. Trọng số của mức đầu tiên là 0,7, trọng số của mức thứ hai là 0,2, và trọng số của mức thứ ba là 0,1. Hãy mở rộng tính toán:

(Buy one+Sell one) * 0.35+(Buy two+Sell two) * 0.1+(Buy three+Sell three) * 0.05
->
(Buy one+sell one)/2 * 2 * 0.35+(Buy two+sell two)/2 * 2 * 0.1+(Buy three+sell three)/2 * 2 * 0.05
->
(Buy one+sell one)/2 * 0.7+(Buy two+sell two)/2 * 0.2+(Buy three+sell three)/2 * 0.1
->
Average price of the first level * 0.7+average price of the second level * 0.2+average price of the third level * 0.1

Như chúng ta có thể thấy ở đây, giá cuối cùng được tính toán thực sự là một phản ứng với vị trí giá giữa mở thứ ba trong thị trường hiện tại. Sau đó sử dụng giá tính toán này để cập nhật mảngself.prices, đá ra một trong những dữ liệu lâu đời nhất (thông quashift()và cập nhật một trong những dữ liệu mới nhất vào nó (thông quapush()function, shift và push là các phương thức của đối tượng mảng ngôn ngữ JS, bạn có thể kiểm tra dữ liệu JS để biết chi tiết).self.prices, đó là một luồng dữ liệu với một thứ tự chuỗi thời gian.

Vì vậy, chúng ta hãy nghỉ ngơi ở đây, và chúng ta sẽ thấy bạn vấn đề tiếp theo ~


Có liên quan

Thêm nữa