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 atributoaccount
Calcule 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ávelaccount
Entã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 localaccount
para o atributoaccount
do 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 * 1000
milissegundos (CalcNetInterval
O 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 self.preCalc = now
para atualizar a variável de carimbo horárioself.preCalc
do último lucro impresso até à data e hora correntesnow
Aqui, 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 declaradanet
Determine 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.preNet
que registra o valor líquido com onet
Em seguida, imprima os dados de valor líquido totalnet
para o gráfico da curva de lucro do bot da Plataforma de Negociação Quant FMZ (você pode consultar oLogProfit
funçã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.btc
eself.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.48
Se 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 nowhile
Loop infinito, usamosvar reaper = LeeksReaper()
para construir o objeto de colheita de lucro, e depoisReaper.poll()
é chamado de ciclicamente nomain()
function.
Oself.poll
funçã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 tempoprices
Este 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 > 2
deve 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.self.prices
, ou seja, a diferença entre os dados mais recentes e o preço máximo ou mínimo no mercadoself.prices
matriz no intervalo anterior deve romper através doburstPrice
.Se todas as condições forem verdadeiras, marquebull
oubear
comotrue
, e atribuir um valor à variáveltradeAmount
E planeia uma troca de cavalos.
Então, para o parâmetroBurstThresholdVol
, com base noself.vol
A 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.askPrice
Dependendo 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 umwhile
Loop; 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ávelorderId
Executar.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 self.tradeOrderId
.
Declarar uma variávelorder
para 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.numTick
para 0.
A última execução do construtorLeeksReaper()
Retorna o valorself
Objeto, 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.