Recentemente, tem havido uma discussão acalorada sobre aprint money
Uma estratégia muito antiga voltou aos olhos dos Quants: o LeeksReaper.
O princípio de negociação do robô deprint money
Então, eu li novamente a estratégia original cuidadosamente novamente e olhei para a versão transplantada do transplantado OKCoin leeksreaper no FMZ Quant.
A estratégia de rebanho de alho transplantado baseada na plataforma FMZ Quant é analisada para explorar a ideia da estratégia.
Neste artigo, vamos analisar mais a partir dos aspectos de estratégia ideia e intenção para minimizar o conteúdo chato relacionado à programação.
Código de origem da estratégia [Transplante 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)
}
}
Geralmente, quando se tem uma estratégia para estudar, deve-se dar uma olhada na estrutura geral do programa primeiro. O código de estratégia não é muito longo, com menos de 200 linhas de código, é muito conciso, e a estratégia original é altamente restaurada, quase a mesma.main()
O código de estratégia completo, excetomain()
, é uma função chamadaLeeksReaper()
. OLeeksReaper()
função é muito fácil de entender, pode ser entendido como o construtor do módulo lógico estratégia poreksreaper (um objeto).LeeksReaper()
É responsável pela construção de uma lógica de comércio de poros.
Palavras chave:
· A primeira linha da estratégiamain
Função:var reaper = LeeksReaper()
, o código declara uma variável localreaper
e então chama a função LeeksReaper() para construir um objeto lógico estratégia que atribui um valor parareaper
.
O próximo passo da estratégiamain
Função:
while (true) {
reaper.poll()
Sleep(TickInterval)
}
Insira umwhile
ciclo infinito e continuar a executar a função de processamentopoll()
doreaper
O objectivo, opoll()
A função é exatamente onde a lógica principal da estratégia de negociação está e todo o programa de estratégia começa a executar a lógica de negociação repetidamente.
Quanto à linhaSleep(TickInterval)
, é fácil de entender, é para controlar o tempo de pausa após cada execução da lógica de negociação geral, com o objetivo de controlar a frequência de rotação da lógica de negociação.
LeeksReaper()
ConstrutorVeja como oLeeksReaper()
A função constrói um objeto lógico de estratégia.
OLeeksReaper()
A função inicia-se declarando um objeto vazio,var self = {}
, e durante a execução doLeeksReaper()
função irá gradualmente adicionar alguns métodos e atributos para este objeto vazio, finalmente concluindo a construção deste objeto e devolvendo-o (ou seja, a etapa demain()
função no interior dovar reaper = LeeksReaper()
, o objeto devolvido é atribuído areaper
).
Adicionar atributos aoself
Objeto
Em seguida, eu adicionei muitos atributos paraself
Eu vou descrever cada atributo da seguinte forma, que pode entender o propósito e a intenção desses atributos e variáveis rapidamente, facilitar a compreensão de estratégias, e evitar ser confundido ao ver o código.
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
Depois de adicionar estes atributos para auto, começar a adicionar métodos para oself
objeto para que este objeto possa fazer algum trabalho e ter algumas funções.
A primeira função acrescentou:
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)
}
A funçãoupdateTrades
O objectivo principal é obter os dados mais recentes das transacções de mercado e fazer alguns cálculos com base nos dados e registrá-los para utilização na lógica subsequente da estratégia.
Os comentários linha por linha que escrevi no código acima diretamente.
Para_.reduce
, alguém que não tem aprendizado básico de programação pode ficar confuso._.reduce
é uma função da biblioteca Underscore.js. A estratégia FMZJS suporta esta biblioteca, por isso é muito conveniente para cálculo iterativo.https://underscorejs.net/#reduce)
O significado também é muito simples, por exemplo:
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, somar cada número na matriz[1, 2, 3, 4]
De volta à nossa estratégia, somamos os valores do volume de negociação de cada dado do registo de transacções notrades
Obtenha um total do volume de transações mais recenteself.vol = 0.7 * self.vol + 0.3 * _.reduce (...)
, aqui usamos...
Não é difícil ver o cálculo deself.vol
O volume de negociação recentemente gerado representa 30% do total e o último volume de negociação ponderado representa 70%. Este rácio foi fixado artificialmente pelo autor da estratégia e pode estar relacionado com as regras do mercado.
Quanto à sua pergunta, e se a interface para obter os dados de transação mais recentes retornar aos dados antigos duplicados, então os dados que obtive estavam errados e não serão significativos? Não se preocupe. Este problema foi considerado no projeto da estratégia, então o código tem:
if ((trade.Id > self.lastTradeId) || (trade.Id == 0 && trade.Time > self.lastTradeId)) {
...
}
A acumulação é desencadeada apenas quando o ID é maior que o ID do último registro, ou se a interface de troca não fornecer um ID, ou seja,trade.Id == 0
, use o carimbo de hora no registro da transação para julgar.self.lastTradeId
Armazena o carimbo de data e hora do registo da transacção em vez do ID.
A segunda função acrescentou:
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))
}
Em seguida, vamos olhar para a funçãoupdateOrderBook
. A partir do nome da função, podemos ver que é usado para atualizar o livro de ordens. No entanto, ele não atualiza apenas o livro de ordens. A função começa a chamar a função FMZ API.GetDepth()
Obter os dados do livro de ordens do mercado atual (vender um... vender n, comprar um... comprar n) e registar os dados do livro de ordens emself.orderBook
. Em seguida, julgue se a ordem de compra e a ordem de venda dos dados do livro de encomendas são menores que 3, se assim for, a função inválida será devolvida diretamente.
Depois disso, são calculados dois dados:
· Calcular o preço do conhecimento de embarque O preço do conhecimento de embarque é igualmente calculado utilizando o método da média ponderada.No cálculo da ordem de compra, o peso atribuído ao preço de compra mais próximo do preço de transacção é de 61,8% (0,618) e o peso atribuído ao preço de venda mais próximo do preço de transacção é de 38,2% (0,382) Quando se calcula o preço da nota de embarque, o mesmo peso é dado ao preço de venda mais próximo do preço da transação. Quanto ao porquê é 0,618, pode ser que o autor prefira a proporção da seção dourada. Quanto ao último preço (0,01), é para compensar para o centro da abertura ligeiramente.
· Atualizar o preço médio ponderado dos três primeiros níveis da carteira de pedidos nas séries temporais
Para os três primeiros níveis de preços das ordens de compra e venda na carteira de pedidos, a média ponderada é calculada. O peso do primeiro nível é 0,7, o peso do segundo nível é 0,2 e o peso do terceiro nível é 0,1. Alguém pode dizer:
(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
Como podemos ver aqui, o preço final calculado é na verdade uma resposta à posição de preço do meio da terceira abertura no mercado atual.
Então use este preço calculado para atualizar a matrizself.prices
, eliminando um dos dados mais antigos (através doshift()
A função de informação e de informação (e de actualização de um dos dados mais recentes) através dapush()
função, shift e push funções são métodos da linguagem JS array objeto, você pode verificar os dados JS para detalhes). Assim formando a matrizself.prices
, que é um fluxo de dados com uma ordem de séries temporais.
Então vamos descansar aqui, e ver-nos-emos na próxima edição ~