В процессе загрузки ресурсов... загрузка...

Анализ стратегии "пожиратель прибыли" (1)

Автор:Нинабадасс., Создано: 2022-04-26 11:31:51, Обновлено:

Анализ стратегии "пожиратель прибыли" (1)

Недавно пользователи в группе FMZ Quant WeChat обсуждали ботаprint moneyДискуссия была очень горячей, и очень старая стратегия снова вошла в видение квантов:Прибыль Уборщик- Да, конечно. Принцип торговли ботамиprint moneyЯ обвиняю себя в том, что не очень хорошо понимал стратегию по уборке прибыли в то время.Портирование OKCoin Profit Harvester- Да, конечно. В качестве примера мы рассмотрим порт-версию стратегии FMZ по сбору прибыли. Мы проанализируем эту стратегию и извлечем идеи стратегии, чтобы пользователи нашей платформы могли изучить эту стратегию. В этой статье мы анализируем больше с уровня стратегического мышления, намерения и т. Д., Чтобы минимизировать скучный контент, связанный с программированием.

[Port OKCoin Profit Harvester] Стратегия Исходный код:

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

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

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

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

  • Следующая частьmainФункция:

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

    Введитеwhileбесконечная петля, непрерывно выполнять функцию обработкиreaperобъектpoll(), иpoll()функция является основной логикой стратегии, так что вся стратегия программы начинает непрерывно выполнять логику торговли.

    Что касается линииSleep(TickInterval), это легко понять, который должен контролировать время паузы после каждого выполнения общей логики торговли, и цель которого - контролировать частоту вращения логики торговли.

Проанализируйте ТворцаLeeksReaper()

Давайте посмотрим, как функцияLeeksReaper()создает объект в логике стратегии.

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

Добавить атрибут к объектуself

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

    self.numTick = 0         # it is used to record the number of times that the trading is not triggered when the poll function is called. When placing an order is triggered and the order logic is executed, reset self.numTick to 0
    self.lastTradeId = 0     # the trading record ID of the order that has been executed in the trading market; this variable records the current latest execution record ID in the market
    self.vol = 0             # after the weighted average calculation, the trading volume reference of the market at each inspection (the market data is obtained once per time of the loop, which can be understood as the inspection of the market once)
    self.askPrice = 0        # ask price of delivery order, which can be understood as the price of the pending sell order calculated by the strategy 
    self.bidPrice = 0        # bid price of delivery order
    self.orderBook = {Asks:[], Bids:[]}    # record the currently obtained order book data, that is, depth data (sell 1...sell n, buy 1...buy n)
    self.prices = []                       # an array that records the price in the time series after the weighted average calculation of the first three levels in the order book. Simply put, it is the weighted average price of the first three levels of the order book obtained by storing each time, and put them in an array as reference of the subsequent strategic trading signals. Therefore, the variable name is "prices", plural, which means a set of prices
    self.tradeOrderId = 0    # record the order ID after currently lading and ordering 
    self.p = 0.5             # position proportion; when the currency value is exactly half of the total asset value, the value of it is 0.5, which means the balanced state 
    self.account = null      # record the account asset data, which will be returned by the function GetAccount()
    self.preCalc = 0         # record the timestamp when calculating the return of the latest time, in milliseconds, which is used to control the frequency of triggering execution of the return calculation code
    self.preNet = 0          # record the current return value  

Добавить метод к объектуself

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

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

    self.updateTrades = function() {
        var trades = _C(exchange.GetTrades)  # call the encapsulated interface "GetTrades" of FMZ, to obtain the currently latest execution data in the market
        if (self.prices.length == 0) {       # when self.prices.length == 0, you need to fill values in the array of self.prices, which is only triggered when the strategy is started 
            while (trades.length == 0) {     # if there is no latest execution record in the market, the while loop will run infinitely, until there is new execution data; the, update the variable "trades"
                trades = trades.concat(_C(exchange.GetTrades))   # "concat" is a method of JS array type, which is used to match two arrays; here we use it to match the array "trades" and the array returned by "_C(exchange.GetTrades)" into one array 
            }
            for (var i = 0; i < 15; i++) {   # fill in values for "self.prices"; fill 15 latest execution prices 
                self.prices[i] = trades[trades.length - 1].Price
            }
        }
        self.vol = 0.7 * self.vol + 0.3 * _.reduce(trades, function(mem, trade) {  # _.reduce function iteratively calculates the accumulated execution volume of the latest execution record 
            // 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Поэтому очень удобно использовать его для выполнения итеративных вычислений.информационная ссылка на Underscore.js

Очень легко понять, например:

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

   Log("sum:", sum)    # sum is 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()Получить текущие данные книги заказов рынка (продать 1...продать n, купить 1...покупить n) и записать данные книги заказов вin self.orderBookДалее он оценивает, является ли уровень данных книги заказов для заказов на покупку и продажу меньше 3 уровней; если это так, он считается недействительной функцией и возвращает непосредственно.

После этого были выполнены два расчета данных:

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

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

    Давайте посмотрим на расширенный расчет:

    (Buy 1 + Sell 1) * 0.35 + (Buy 2 + Sell 2) * 0.1 + (Buy 3 + Sell 3) * 0.05
    ->
    (Buy 1 + Sell 1) / 2 * 2 * 0.35 + (Buy 2 + Sell 2) / 2 * 2 * 0.1 + (Buy 3 + Sell 3) / 2 * 2 * 0.05
    ->
    (Buy 1 + Sell 1) / 2 * 0.7 + (Buy 2 + Sell 2) / 2 * 0.2 + (Buy 3 + Sell 3) / 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является потоком данных в порядке временных рядов.

    Давай закончим анализ здесь, и увидимся в следующий раз!


Больше