В последнее время идет горячая дискуссия по поводу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()
отвечает за создание логики торговли поромчиками.
Ключевые слова:
· Первая линия стратегии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.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
, который представляет собой поток данных с порядком временных рядов.
Так что давайте отдохнем здесь, и мы увидимся в следующем номере ~