Type/to search
8
Follow
1361
Followers
Análise da estratégia da colheitadeira de alho-poró (1)
HFT
Created 2020-11-12 22:11:32  Updated 2024-12-06 22:20:54
 26
 11226

img

Análise da estratégia da colheitadeira de alho-poró

Recentemente, o inventor do grupo de discussão quantitativa WeChatprint moneyA discussão sobre robôs foi muito acalorada, e uma estratégia muito antiga voltou a entrar na visão dos quants:Colheitadeira de alho-poró
print moneyO princípio de negociação do robô se baseia na estratégia da colheitadeira de alho-poró. Eu me culpo por não entender a estratégia da colheitadeira de alho-poró claramente naquela época. Então, revisei cuidadosamente a estratégia original novamente e também revisei a versão transplantada no Inventor Quant.Transplante OKCoin Colheitadeira de Alho-poró
Vamos pegar a versão transplantada da estratégia da colheitadeira de alho-poró da Plataforma Quantitativa do Inventor para analisar a estratégia e explorar as ideias por trás dela. Para que os usuários da plataforma possam aprender essa ideia estratégica.
Neste artigo, analisaremos mais a partir da perspectiva do pensamento estratégico, intenções, etc., e tentaremos reduzir o conteúdo chato relacionado à programação.

[Transplante OKCoin Leek Harvester] Código-fonte da estratégia:

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

Visão geral da estratégia

Geralmente, quando você recebe uma estratégia para aprender, ao lê-la, você deve primeiro observar a estrutura geral do programa. Essa estratégia não tem muito código, apenas menos de 200 linhas de código, o que é muito conciso, e o grau de restauração da estratégia original é muito alto, sendo basicamente o mesmo. O código da política vai demain()A função começa a ser executada e todo o código da estratégia émain(), é umLeeksReaper()A função,LeeksReaper()A função também é fácil de entender. Esta função pode ser entendida como o construtor do módulo de lógica de estratégia do leek harvester (um objeto). SimplificandoLeeksReaper()Ele é responsável por construir uma lógica de transação do coletor de alho-poró.

Palavras-chave:
img
img

  • EstratégiamainA primeira linha da função:
    var reaper = LeeksReaper(), o código declara uma variável localreaperEm seguida, chame a função LeeksReaper() para construir um objeto de lógica de estratégia e atribuí-lo areaper

  • EstratégiamainA função é a seguinte:

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

    Digite umwhileLoop morto, execução infinitareaperFunção de processamento de objetospoll()poll()A função é a lógica principal da estratégia de negociação, e todo o programa de estratégia começa a executar a lógica de negociação continuamente.
    Quanto aSleep(TickInterval)Esta linha é fácil de entender. Ela é para controlar o tempo de pausa após cada execução da lógica geral da transação. O propósito é controlar a frequência de rotação da lógica da transação.

Criação de perfilLeeksReaper()Construtor

dê uma olhadaLeeksReaper()Como as funções constroem um objeto de lógica de estratégia.

LeeksReaper()No início da função, um objeto vazio é declarado.var self = {},existLeeksReaper()Durante a execução da função, alguns métodos e propriedades serão gradualmente adicionados a este objeto vazio e, finalmente, a construção deste objeto será concluída e, finalmente, este objeto será retornado (ou seja,main()Dentro da funçãovar reaper = LeeksReaper()Nesta etapa, o objeto retornado é atribuído areaper)。

DarselfAdicionar propriedades a um objeto

Próximo darselfMuitas propriedades foram adicionadas. Descreverei cada propriedade abaixo para que você possa entender rapidamente o propósito e a intenção dessas propriedades e variáveis, o que facilitará seu entendimento da estratégia e evitará confusão ao ver essa pilha de código.

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 # 记录当前收益数值

DarselfMétodo de adição de objeto

Depois de adicionar esses atributos a si mesmo, comece aselfAdicionar métodos a um objeto permite que ele faça algum trabalho e tenha algumas funções.

A primeira função adicionada:

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

updateTradesO objetivo desta função é obter os dados mais recentes de transações de mercado, realizar alguns cálculos com base nos dados e registrá-los para uso na lógica subsequente da estratégia.
Escrevi os comentários linha por linha diretamente no código acima.
para_.reduceAlunos que não têm uma base de programação podem ficar confusos. Aqui vai uma breve explicação._.reducesimUnderscore.jsAs funções desta biblioteca são suportadas pela estratégia FMZJS, por isso é muito conveniente usá-la para cálculos iterativos.Link de dados Underscore.js

O significado também é muito simples, por exemplo:

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 }

Ou seja, a matriz[1, 2, 3, 4]Some cada número em . Voltando à nossa estratégia,tradesOs valores de volume de transação de cada registro de transação na matriz são somados. Obtenha o registro de transações mais recente e o volume total de transações.self.vol = 0.7 * self.vol + 0.3 * _.reduce(...)Por favor, permita-me usar...Em vez desse monte de código. Não é difícil ver aqui queself.volO cálculo também é uma média ponderada. Ou seja, o volume total de transações mais recente representa 30% do peso, e o volume de transações obtido pelo cálculo ponderado anterior representa 70%. Essa proporção é definida artificialmente pelo autor da estratégia e pode estar relacionada à observação de padrões de mercado.
Quanto à sua pergunta, e se a interface para obter os dados de transação mais recentes retornar dados antigos duplicados para mim? Então os dados que eu obtiver estarão errados, então há algum sentido em usá-la? Não se preocupe, esse problema foi levado em consideração ao projetar a estratégia, por isso está incluído no código.

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

Este julgamento. Pode ser julgado com base no ID da transação no registro da transação. A acumulação é acionada somente quando o ID é maior que o ID do último registro, ou se a interface de troca não fornece o ID, ou seja,trade.Id == 0, usando o registro de data e hora no registro da transação para determinar, neste momentoself.lastTradeIdO que é armazenado é o registro de data e hora do registro da transação, não o ID.

A segunda função adicionou:

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

Próxima observaçãoupdateOrderBookComo o nome sugere, esta função é usada para atualizar o livro de ordens. No entanto, é mais do que apenas atualizar o livro de pedidos. A função começa a chamar a função API do FMZGetDepth()Obtenha os dados atuais do livro de ordens do mercado (venda um... venda n, compre um... compre n) e registre os dados do livro de ordens emself.orderBookmeio. Em seguida, se os dados do livro de ordens contiverem menos de 3 ordens de compra e venda, a função será considerada inválida e retornará diretamente.

Posteriormente, foram calculados dois dados:

  • Calcular o preço do conhecimento de embarque
    O cálculo do preço do conhecimento de embarque também é baseado na média ponderada. Ao calcular a ordem de compra, a ordem de compra recebe um peso maior de 61,8% (0,618), e a ordem de venda responde pelo peso restante de 38,2% ( (0,382).
    O mesmo se aplica ao calcular o preço de venda do conhecimento de embarque, sendo dado maior peso ao preço de venda. Quanto ao motivo de ser 0,618, pode ser que o autor prefira a proporção áurea. Quanto ao ligeiro aumento ou diminuição final do preço (0,01), é para deslocá-lo ligeiramente em direção ao centro do mercado.

  • Atualizar o preço médio ponderado dos três primeiros níveis do livro de ordens na série temporal
    Um cálculo de média ponderada é realizado nos três primeiros níveis de preços de ordens de compra e venda no livro de ordens, com o primeiro nível tendo um peso de 0,7, o segundo nível tendo um peso de 0,2 e o terceiro nível tendo um peso de 0,1. . Alguns alunos podem dizer: "Ah, isso não está certo, não há 0,7, 0,2, 0,1 no código"
    Vamos expandir o cálculo:

    (买一 + 卖一) * 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

    Aqui você pode ver que o preço final calculado na verdade reflete a posição de preço médio dos três níveis no mercado atual.
    Em seguida, use este preço calculado para atualizarself.pricesMatriz, expulse os dados mais antigos (por meio deshift()função), atualizar os dados mais recentes (por meio depush()Function, shift e push functions são métodos de objetos array da linguagem JS. Você pode verificar as informações do JS para obter detalhes). Assim formandoself.pricesUma matriz é um fluxo de dados ordenados em séries temporais.

Ahem, beba um pouco de água, vou parar por aqui por enquanto, até a próxima~

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)