Análise da Estratégia de Recolha de Lucro (2)

Autora:Ninabadass, Criado: 2022-04-26 15:57:02, Atualizado: 2022-04-26 15:57:53

Análise da Estratégia de Recolha de Lucro (2)

Vamos continuar oconteúdo da última vezPara explicar.

Terceira função adicionada:

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

Quando o construtorLeeksReaper()é a construção de um objeto, obalanceAccount()A função adicionada ao objeto é usada para atualizar as informações do ativo da conta, que são armazenadas noself.account, ou seja, para construir O atributoaccountCalcule e imprima o valor de retorno regularmente. Em seguida, de acordo com as informações mais recentes do ativo da conta, a relação de saldo dos símbolos de moeda ao instante (saldo da posição ao instante) é calculada e, quando o limiar de compensação é acionado, pequenas ordens são fechadas para fazer os símbolos (posições) voltarem a um estado de equilíbrio. Espere um certo período de tempo para executar a negociação e, em seguida, cancele todas as ordens pendentes e execute a função na próxima rodada, o saldo será detectado novamente e o processamento correspondente será feito.

Vamos olhar para o código desta função declaração por declaração: Em primeiro lugar, a primeira afirmaçãovar account = exchange.GetAccount()declara uma variável localaccount, chama oexchange.GetAccount()função na interface FMZ API, obter os dados mais recentes da conta corrente e atribuí-lo para a variávelaccountEntão, julgue a variável.account; se o valor da variável for:null(o que acontecerá quando não conseguir obter a variável, como timeout, rede, exceção de interface de plataforma, etc.), ele retornará diretamente (correspondente aif (!account ){...}aqui).

A declaraçãoself.account = accounté atribuir a variável localaccountpara o atributoaccountdo objeto construído para registar as últimas informações de conta no objeto construído.

A declaraçãovar now = new Date().getTime()declara uma variável localnow, e chama ogetTime()função do objeto hora e data da linguagem JavaScript para retornar o carimbo de tempo atual e atribuir o carimbo de tempo para a variávelnow.

O código:if (self.orderBook.Bids.length > 0 && now - self.preCalc > (CalcNetInterval * 1000)) {...}Julga a diferença entre o carimbo horário atual e o último carimbo horário registado; se o valor exceder o parâmetroCalcNetInterval * 1000, significa que excedeu o limite deCalcNetInterval * 1000milissegundos (CalcNetIntervalO preço de compra 1 no mercado deve ser usado ao calcular o lucro, a condição também é limitada à condição de que o preço de compra 1 no mercado seja utilizado para calcular o lucro.self.orderBook.Bids.length > 0(dados de profundidade, que devem ser válidos na lista de ordens de compra como informação de nível).

Quando a condição da instrução if é acionada, executarself.preCalc = nowpara atualizar a variável de carimbo horárioself.preCalcdo último lucro impresso até à data e hora correntesnowAqui, as estatísticas de lucro utilizam o método de cálculo do valor líquido, o código é:var net = _N(account.Balance + account.FrozenBalance + self.orderBook.Bids[0].Price * (account.Stocks + account.FrozenStocks)), ou seja, converter a moeda em ativo (moeda da cotação) de acordo com o preço de compra 1 atual e, em seguida, somá-lo junto com o montante do ativo na conta e atribuí-lo à variável local declaradanetDetermine se o valor líquido total actual é consistente com o último valor líquido total registado:

            if (net != self.preNet) {
                self.preNet = net
                LogProfit(net)
            }

Se for inconsistente, ou seja:net != self.preNeté verdade, atualize o atributoself.preNetque registra o valor líquido com onetEm seguida, imprima os dados de valor líquido totalnetpara o gráfico da curva de lucro do bot da Plataforma de Negociação Quant FMZ (você pode consultar oLogProfitfunção na documentação da API FMZ).

Se a impressão regular de devolução não é desencadeada, em seguida, continuar o seguinte processo: registroaccount.Stocks(os símbolos de moeda disponíveis na conta) eaccount.Balance(ativos disponíveis em curso na conta)self.btceself.cny. Calcular o rácio de offset e atribuí-lo, que é registrado emself.p.

self.p = self.btc * self.prices[self.prices.length-1] / (self.btc * self.prices[self.prices.length-1] + self.cny)

O algoritmo também é muito simples, que é calcular quantos por cento do valor da moeda corrente no valor líquido total da conta.

Então, como você julga quando o saldo da moeda (posição) é acionado? Aqui, o desenvolvedor usa 50% para cima e para baixo 2 pontos percentuais como um amortecedor; se exceder o amortecedor, executar o saldo, ou seja, quandoself.p < 0.48Se você acha que a quantidade de moeda é pequena, cada vez que o preço aumenta 0,01, coloque três pequenas ordens.self.p > 0.52, se você acha que a quantidade de moeda é grande, esperar pequenas ordens de vender 1 preço no mercado.Sleep(BalanceTimeout), e cancelar todos os pedidos.

        var orders = exchange.GetOrders()                  # obtain all the current pending orders, and save them in the variable orders"
        if (orders) {                                      # if the variable "orders", which obtains all the current pending orders, is not null
            for (var i = 0; i < orders.length; i++) {      # use the loop to traverse "orders", and cancel the orders one by one 
                if (orders[i].Id != self.tradeOrderId) {
                    exchange.CancelOrder(orders[i].Id)     # call "exchange.CancelOrder", and cancel orders by "orders[i].Id"
                }
            }
        }

Quarta função adicionada:

Aqui vem a parte central da estratégia, o destaque.self.poll = function() {...}A função é a lógica principal de toda a estratégia.main( )função, começar a executar; antes de entrar nowhileLoop infinito, usamosvar reaper = LeeksReaper()para construir o objeto de colheita de lucro, e depoisReaper.poll()é chamado de ciclicamente nomain() function.

Oself.pollfunção começa a executar, e faz alguns preparativos antes de cada loop;self.numTick++Aumentar a contagem;self.updateTrades()Atualiza os registos de negociação recentes no mercado e calcula os dados utilizados;self.updateOrderBook()Atualiza os dados do mercado (carteira de ordens) e calcula os dados relevantes;self.balanceAccount()verifica o saldo da moeda (posição).

        var burstPrice = self.prices[self.prices.length-1] * BurstThresholdPct   # calculate the burst price 
        var bull = false             # declare the variable marked by the bull market; the initial value is false
        var bear = false             # declare the variable marked by the bear market; the initial value is false
        var tradeAmount = 0          # declare the variable of trading amount; the initial value is 0

Em seguida, precisamos julgar se o mercado de curto prazo atual é um touro ou um urso.

        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
        }

Lembras-te doself.updateOrderBook()função no artigo anterior, em que usamos o algoritmo média ponderada para construir uma série de tempopricesEste pedaço de código usa três novas funções, a saber:_.min, _.max, slice, que também são muito fáceis de entender.

  • _.min: A função é encontrar o mínimo na matriz de parâmetros.

  • _.max: A função é encontrar o máximo na matriz de parâmetros.

  • slice: Esta função é uma função membro do objeto de matriz JavaScript. É para interceptar e retornar uma parte da matriz de acordo com o índice. Por exemplo:

    function main() {
        // index     .. -8 -7 -6 -5 -4 -3 -2 -1
        var arr = [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
        Log(arr.slice(-5, -1))    // it will intercept several elements from 4 to 1, and return a new array: [4,3,2,1]
    }
    

Neste caso, as condições para julgar se se trata de um mercado de alta ou de baixa são:

  • self.numTick > 2deve ser verdadeiro, ou seja, se a explosão de preço ocorrer em uma nova rodada de detecção, ela deve ser desencadeada após pelo menos três rodadas de detecção, e evitar desencadear no início.
  • Últimos dados da série de preçosself.prices, ou seja, a diferença entre os dados mais recentes e o preço máximo ou mínimo no mercadoself.pricesmatriz no intervalo anterior deve romper através doburstPrice .

Se todas as condições forem verdadeiras, marquebulloubearcomotrue, e atribuir um valor à variáveltradeAmountE planeia uma troca de cavalos.

Então, para o parâmetroBurstThresholdVol, com base noself.volA taxa de crescimento da populaçãoself.updateTrades()A decisão de reduzir a intensidade de negociação (reduzir o volume de negociação planeado) é tomada em função da função de mercado.

        if (self.vol < BurstThresholdVol) {
            tradeAmount *= self.vol / BurstThresholdVol   // reduce the planned trading volume, and reduce it to the previous volume multiplied by "self.vol / BurstThresholdVol" 
        }
        
        if (self.numTick < 5) {
            tradeAmount *= 0.8      // reduced to 80% of the plan 
        }
        
        if (self.numTick < 10) {    // reduced to 80% of the plan
            tradeAmount *= 0.8
        }

Em seguida, julgue se o sinal de negociação e o volume de negociação cumprem os requisitos:

        if ((!bull && !bear) || tradeAmount < MinStock) {   # if it is not a bull market nor a bear market, or the planned trading volume "tradeAmount" is less than the minimum trading volume "MinStock" set by the parameter, the "poll" function returns directly without any trading operation
            return
        }

Após a sentença acima, executarvar tradePrice = bull ? self.bidPrice : self.askPriceDependendo de se tratar de um mercado de baixa ou de alta, fixar o preço de negociação e atribuir o valor ao preço da ordem de entrega correspondente.

Por fim, introduzir umwhileLoop; a única condição de parada e ruptura do loop étradeAmount >= MinStock, ou seja, o volume de negociação previsto é inferior ao volume mínimo de negociação. No loop, de acordo com o estado atual do mercado de touros ou o estado do mercado de ursos, executar a ordem. e gravar o ID da ordem na variávelorderIdExecutar.Sleep(200)O ciclo então julga se a ordem foi ou não executada.orderIdé verdade (se a ordem falhar, o ID da ordem não será devolvido e a condição if não será acionada). Se a condição for verdadeira, obtenha o ID da ordem e atribua-o aself.tradeOrderId.

Declarar uma variávelorderpara armazenar os dados da ordem com o valor inicial denull. Em seguida, use um loop para obter os dados da ordem com o ID e determine se a ordem está no estado de ordem pendente; se estiver no estado de ordem pendente, cancele a ordem com o ID; se não estiver no estado de ordem pendente, sairá do loop de detecção.

                var order = null           // declare a variable to save the order data 
                while (true) {             // a while loop 
                    order = exchange.GetOrder(orderId)    // call "GetOrder" to query the order data with the ID of  orderId
                    if (order) {                          // if the order data is queried,and the query fails, the order is null, and "if" will not be triggered  
                        if (order.Status == ORDER_STATE_PENDING) {   // judge whether the current order status is pending order
                            exchange.CancelOrder(orderId)            // if the current order status is pending order, cancel the order 
                            Sleep(200)
                        } else {                                     // if not, execute "break" to break out of the while loop 
                            break
                        }
                    }
                }

Em seguida, execute o seguinte processo:

                self.tradeOrderId = 0              // reset "self.tradeOrderId"
                tradeAmount -= order.DealAmount    // update "tradeAmount", and subtract the executed amount of the orders in the delivery order 
                tradeAmount *= 0.9                 // reduce the intensity of ordering  
                if (order.Status == ORDER_STATE_CANCELED) {     // if the order is canceled 
                    self.updateOrderBook()                      // update the data, including the order book data
                    while (bull && self.bidPrice - tradePrice > 0.1) {   // in a bull market, if the updated bid price exceeds the current trading price by 0.1, reduce the trading intensity, and slightly adjust the trading price 
                        tradeAmount *= 0.99
                        tradePrice += 0.1
                    }
                    while (bear && self.askPrice - tradePrice < -0.1) {  // in a bear market, if the updated ask price exceeds the current trading price by 0.1, reduce the trading intensity, and slightly adjust the trading price 
                        tradePrice -= 0.1
                    }
                }

Quando o fluxo do programa sai dowhile (tradeAmount >= MinStock) {...}Loop, significa que a execução do processo de negociação de rajada de preço está concluída. Execuçãoself.numTick = 0, isto é, reiniciarself.numTickpara 0.

A última execução do construtorLeeksReaper()Retorna o valorselfObjeto, isto é, quandovar reaper = LeeksReaper(), o objeto é devolvido parareaper.

Até agora, analisámos como oLeeksReaper()constructor constrói este objeto de colheita de lucro, os vários métodos do objeto e o processo de execução das principais funções lógicas.


Mais.