Анализ стратегии LeeksReaper (1)

Автор:Лидия., Создано: 2021-09-04 15:42:26, Обновлено: 2023-09-15 21:08:48

img

Анализ стратегии LeeksReaper (1)

В последнее время идет горячая дискуссия по поводуprint moneyОчень старая стратегия снова попала в глаза Квантов: LeeksReaper. Принцип торговли роботамиprint moneyЯ перечитал первоначальную стратегию внимательно и посмотрел на пересаженную версию пересаженного OKCoin на FMZ Quant. Стратегия трансплантированного лисокоса на базе платформы FMZ Quant анализируется для изучения идеи стратегии. В этой статье мы подробно рассмотрим аспекты стратегии идеи и намерения, чтобы свести к минимуму скучный контент, связанный с программированием.

Исходный код стратегии [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)
    }
}

Обзор стратегии

Как правило, когда вы получаете стратегию для изучения, вы должны сначала взглянуть на общую структуру программы.main()Весь код стратегии, кромеmain(), это функция под названиемLeeksReaper().LeeksReaper()функция очень легко понять, она может быть понята как конструктор логического модуля (объекта).LeeksReaper()отвечает за создание логики торговли поромчиками.

Ключевые слова:

img

· Первая линия стратегииmainФункция:var reaper = LeeksReaper(), код объявляет локальную переменнуюreaperи затем вызывает функцию LeeksReaper() для построения логического объекта стратегии, который присваивает значениеreaper.

Следующий шаг в стратегииmainФункция:

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

Введитеwhileбесконечный цикл и продолжать выполнять функцию обработкиpoll()В соответствии сreaperобъекта,poll()Функция находится именно там, где лежит основная логика торговой стратегии и вся программа стратегии начинает выполнять логику торговли снова и снова. Что касается линииSleep(TickInterval), это легко понять, это контролировать время паузы после каждого выполнения общей логики торговли, с целью контроля частоты вращения логики торговли.

АнализLeeksReaper()конструктор

Посмотрите, какLeeksReaper()Функция строит логический объект стратегии.

ВLeeksReaper()функция начинается с объявления пустого объекта,var self = {}, и в ходе исполненияLeeksReaper()Функция будет постепенно добавлять некоторые методы и атрибуты к этому пустому объекту, наконец завершая конструкцию этого объекта и возвращая его (то есть шагmain()Функция внутриvar reaper = LeeksReaper(), возвращенный объект присваиваетсяreaper).

Добавить атрибуты вselfобъект Далее я добавил множество атрибутов кself. Я опишу каждый атрибут следующим образом, который может быстро понять цель и намерение этих атрибутов и переменных, облегчить понимание стратегий и избежать путаницы при просмотре кода.

    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

Добавление методов к объектам self

После добавления этих атрибутов к самому себе, начните добавлять методы кselfобъект, так что этот объект может делать некоторые работы и иметь некоторые функции.

Первая функция добавляет:

    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)

    }

ФункцияupdateTradesГлавное - получить последние данные о рыночных транзакциях, выполнить некоторые расчеты на основе данных и записать их для использования в последующей логике стратегии. Комментарии строки за строкой я написал в код выше непосредственно. Для_.reduce, кто-то, у кого нет базовых знаний программирования может быть смущен._.reduceявляется функцией библиотеки Underscore.js. Стратегия FMZJS поддерживает эту библиотеку, поэтому она очень удобна для итеративного вычисления.https://underscorejs.net/#reduce)

Смысл также очень прост, например:

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
}

То есть, сложить каждое число в массиве[1, 2, 3, 4]Возвращаясь к нашей стратегии, мы суммируем значения объема торговли данных каждой записи транзакций вtradesПолучить сумму последнего объема транзакцийself.vol = 0.7 * self.vol + 0.3 * _.reduce (...), здесь мы используем...Это несложно увидеть расчетself.volЭто означает, что вновь созданный объем торгов составляет 30% от общего объема, а последний взвешенный объем торгов составляет 70%. Что касается вашего вопроса, что если интерфейс для получения последних данных транзакций вернулся к дублированным старым данным, то данные, которые я получил, были неправильными, и это не будет иметь смысла? Не волнуйтесь. Эта проблема была рассмотрена в разработке стратегии, поэтому код имеет:

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

Он может быть оценен на основе идентификатора транзакции в записи транзакции. Накопление запускается только тогда, когда идентификатор больше идентификатора последней записи или если интерфейс обмена не предоставляет идентификатор, то естьtrade.Id == 0, используйте временную метку в записи транзакции для суждения.self.lastTradeIdхранит временную метку записи транзакции вместо идентификатора.

Вторая функция добавляет:

    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))
    }

Теперь давайте посмотрим на функциюupdateOrderBook. Из названия функции мы видим, что она используется для обновления книги заказов. Однако она не обновляет только книгу заказов. Функция начинает вызвать функцию FMZ APIGetDepth()Получить текущие данные книги заказов рынка (продать один... продать n, купить один... купить n) и записать данные книги заказов вself.orderBook. Далее, судить, если заказ покупки и заказ продажи данных книги заказов меньше 3, если да, недействительная функция будет возвращена непосредственно.

После этого вычисляются два элемента данных:

· Расчет стоимости коносамента При расчете заказа покупательская цена, наиболее близкая к цене сделки, составляет 61,8% (0,618), а цена продажи, наиболее близкая к цене сделки, - 38,2% (0,382). При расчете стоимости товарооборота тот же вес придается продажной цене, которая ближе всего к цене сделки. Что касается why - 0,618, то может быть, что автор предпочитает золотое сечение соотношения. Что касается последней цены (0,01), то она должна немного сместиться к центру открытия.

· Обновление средневзвешенной цены первых трех уровней книги заказов по временным рядам Для первых трех уровней цен на заказы покупки и продажи в книге заказов рассчитывается средневзвешенное значение. Вес первого уровня равен 0,7, вес второго уровня равен 0,2, а вес третьего уровня равен 0,1. Кто-то может сказать: "О, нет, в коде есть 0,7, 0,2, 0,1". Давайте расширим расчет:

(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

Как мы можем видеть здесь, окончательная рассчитанная цена на самом деле является ответом на ценовую позицию середины третьего открытия на текущем рынке. Затем используйте эту рассчитанную цену для обновления массиваself.prices, выталкивая один из старейших данных (черезshift()(и обновление одной из новейших данных в нее)push()функция, shift и push функции являются методами языка JS массив объекта, вы можете проверить данные JS для подробности). Таким образом формирование массиваself.prices, который представляет собой поток данных с порядком временных рядов.

Так что давайте отдохнем здесь, и мы увидимся в следующем номере ~


Связанные

Больше