Type/to search
8
Follow
1361
Followers
Анализ стратегии уборки лука-порея (1)
HFT
Created 2020-11-12 22:11:32  Updated 2024-12-06 22:20:54
 26
 11224

img

Анализ стратегии уборки лука-порея

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

[[Трансплантация OKCoin Leek Harvester] Исходный код стратегии:

pine
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("开始平衡", 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("开始平衡", 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()Он отвечает за построение логики транзакций комбайна для уборки лука-порея.

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

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

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

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

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

ПрофилированиеLeeksReaper()Конструктор

посмотриLeeksReaper()Как функции создают объект логики стратегии.

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

ДаватьselfДобавление свойств к объекту

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

python
self.numTick = 0 # 用来记录poll函数调用时未触发交易的次数,当触发下单并且下单逻辑执行完时,self.numTick重置为0 self.lastTradeId = 0 # 交易市场已经成交的订单交易记录ID,这个变量记录市场当前最新的成交记录ID self.vol = 0 # 通过加权平均计算之后的市场每次考察时成交量参考(每次循环获取一次市场行情数据,可以理解为考察了行情一次) self.askPrice = 0 # 卖单提单价格,可以理解为策略通过计算后将要挂卖单的价格 self.bidPrice = 0 # 买单提单价格 self.orderBook = {Asks:[], Bids:[]} # 记录当前获取的订单薄数据,即深度数据(卖一...卖n,买一...买n) self.prices = [] # 一个数组,记录订单薄中前三档加权平均计算之后的时间序列上的价格,简单说就是每次储存计算得到的订单薄前三档加权平均价格,放在一个数组中,用于后续策略交易信号参考,所以该变量名是prices,复数形式,表示一组价格 self.tradeOrderId = 0 # 记录当前提单下单后的订单ID self.p = 0.5 # 仓位比重,币的价值正好占总资产价值的一半时,该值为0.5,即平衡状态 self.account = null # 记录账户资产数据,由GetAccount()函数返回数据 self.preCalc = 0 # 记录最近一次计算收益时的时间戳,单位毫秒,用于控制收益计算部分代码触发执行的频率 self.preNet = 0 # 记录当前收益数值

ДаватьselfМетод добавления объекта

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

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

cpp
self.updateTrades = function() { var trades = _C(exchange.GetTrades) # 调用FMZ封装的接口GetTrades,获取当前最新的市场成交数据 if (self.prices.length == 0) { # 当self.prices.length == 0时,需要给self.prices数组填充数值,只有策略启动运行时才会触发 while (trades.length == 0) { # 如果近期市场上没有更新的成交记录,这个while循环会一直执行,直到有最新成交数据,更新trades变量 trades = trades.concat(_C(exchange.GetTrades)) # concat 是JS数组类型的一个方法,用来拼接两个数组,这里就是把“trades”数组和“_C(exchange.GetTrades)”返回的数组数据拼接成一个数组 } for (var i = 0; i < 15; i++) { # 给self.prices填充数据,填充15个最新成交价格 self.prices[i] = trades[trades.length - 1].Price } } self.vol = 0.7 * self.vol + 0.3 * _.reduce(trades, function(mem, trade) { # _.reduce 函数迭代计算,累计最新成交记录的成交量 // 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, поэтому очень удобно использовать итерационные вычисления.Ссылка на данные Underscore.js

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

python
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%. Это соотношение устанавливается искусственно автором стратегии и может быть связано с наблюдением за рыночными закономерностями.
Что касается вашего вопроса, что если интерфейс для получения последних данных о транзакциях вернет мне дубликаты старых данных? Тогда полученные мной данные будут неверными, так есть ли смысл его использовать? Не волнуйтесь, эта проблема была учтена при разработке стратегии, поэтому она включена в код.

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

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

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

python
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Как следует из названия, эта функция используется для обновления книги заказов. Однако это больше, чем просто обновление книги заказов. Функция начинает вызывать функцию API FMZGetDepth()Получите текущие данные книги рыночных ордеров (продать одну...продать n, купить одну...купить n) и запишите данные книги ордеров вself.orderBookсередина. Далее, если данные книги ордеров содержат менее 3 ордеров на покупку и продажу, функция считается недействительной и возвращается напрямую.

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

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

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

    (买一 + 卖一) * 0.35 + (买二 + 卖二) * 0.1 + (买三 + 卖三) * 0.05 -> (买一 + 卖一) / 2 * 2 * 0.35 + (买二 + 卖二) / 2 * 2 * 0.1 + (买三 + 卖三) / 2 * 2 * 0.05 -> (买一 + 卖一) / 2 * 0.7 + (买二 + 卖二) / 2 * 0.2 + (买三 + 卖三) / 2 * 0.1 -> 第一档平均的价格 * 0.7 + 第二档平均的价格 * 0.2 + 第三档平均的价格 * 0.1

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

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

Related Recommendations
Comment
All comments (21)

    梦总你好,想问一下。self.prices 先填充的15个历史成交价格,然后又填充订单薄前三档加权平均价格. 对吧

    5 years ago

    是的。

    5 years ago

    我想给梦总点个赞

    5 years ago

    可惜了,很多交易所的做市商已经将盘口买一卖一价压到了只剩下一个tick,让策略里这种尝试插入到买一卖一中间的操作没有了意义

    6 years ago

    主要是学习思路,洞察这些可以高频的交易机会。

    6 years ago

    谢谢了,我写了python的版本在山寨币上跑了一下,真是手续费收割机啊。。。不到5分钟20刀没了:)

    6 years ago

    不客气,可以看下草神写的策略原理分析的文章,高频策略是需要一些支持的。

    6 years ago

    太棒了,没有梦总的解释还真的不能完全看懂,感谢大佬的耐心解释!

    6 years ago

    感谢支持~如果喜欢,帮忙分享下,嘿嘿~

    6 years ago

    黄金分割0.618 0.382是用的斐波
    梦总牛批

    6 years ago

    上学那会儿,学到这个黄金分割比例记得特别清楚,说是这样比例长宽的矩形最美~~但是不知道为什么。。。。

    6 years ago

    确实牛批啊这么详细

    6 years ago

    感谢支持!

    6 years ago

    大大的赞,这么详细的解答.感谢分享

    6 years ago

    感谢支持!

    6 years ago

    梦总牛p

    6 years ago

    感谢支持!

    6 years ago

    梦总,牛批!!
    虽然注释了,但是看起来还是非常复杂.....

    6 years ago

    其实不复杂,这次注释的比较啰嗦,逐行都尽量用最容易理解的方式描述了。通篇看下来应该理解不难。

    6 years ago

    梦总,牛批!!

    6 years ago

    感谢支持。

    6 years ago
  • 1
iPhone Download
Forums
PINE Language
© 2015 - ∞ INVENTOR PTE LTD (SG)