Recientemente, los usuarios en el grupo FMZ Quant WeChat discutieron sobre el bot deprint money
La discusión fue muy acalorada, y una estrategia muy antigua ha vuelto a entrar en la visión de los cuantos:ganancia cosechadora- ¿ Qué?
El principio de negociación de bots deprint money
me culpo de no entender muy bien la estrategia de cosecha de ganancias en ese momento así que leí la estrategia original de nuevo seriamente y también leí la versión portada:Portar el cosechador de ganancias OKCoin- ¿ Qué?
Tomando la versión portada de la estrategia de cosechadora de ganancias de FMZ como ejemplo, vamos a analizar esta estrategia y extraer las ideas de la estrategia, para que nuestros usuarios de la plataforma puedan aprender esta idea de estrategia.
En este artículo, analizamos más desde el nivel de pensamiento estratégico, intención, etc., para minimizar el contenido aburrido relacionado con la programación.
[Port OKCoin Recolector de ganancias] Estrategia Código de fuente:
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 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)
}
}
}
}
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)
}
}
Generalmente, cuando se obtiene una estrategia para aprender, al leer, primero se mira a la estructura general del programa. El código de estrategia no es largo, menos de 200 líneas; se puede decir que es muy simplificado, y tiene una alta restauración para la versión original de la estrategia, que es básicamente el mismo que este.main()
Todo el código de la estrategia, exceptomain()
, sólo tiene una función llamadaLeeksReaper()
El.LeeksReaper()
Esta función también es muy fácil de entender. Esta función se puede entender como el constructor del módulo lógico de estrategia de cosechadora de ganancias (un objeto).LeeksReaper()
es responsable de construir la lógica de negociación de un cosechador de ganancias.
La primera línea de lamain
función en la estrategia:var reaper = LeeksReaper()
; el código declara una variable localreaper
, y luego llama a la función LeeksReaper() para construir un objeto lógico de estrategia y lo asigna areaper
.
La siguiente parte de lamain
Función:
while (true) {
reaper.poll()
Sleep(TickInterval)
}
Introduzca unawhile
el ciclo infinito, ejecutar continuamente la función de procesamiento de lareaper
objetospoll()
, y elpoll()
La función es la lógica principal de la estrategia, por lo que todo el programa de estrategia comienza a ejecutar continuamente la lógica de negociación.
En cuanto a la línea deSleep(TickInterval)
, es fácil de entender, que es controlar el tiempo de pausa después de cada ejecución de la lógica de negociación general, y cuyo propósito es controlar la frecuencia de rotación de la lógica de negociación.
LeeksReaper()
Veamos cómo la funciónLeeksReaper()
Construye un objeto en la lógica de la estrategia.
ElLeeksReaper()
la función comienza y declara un objeto nulo,var self = {}
Durante la ejecución de laLeeksReaper()
Finalmente, la construcción del objeto se completa, y el objeto se devuelve (es decir, elvar reaper = LeeksReaper()
En elmain()
función, y el objeto devuelto se asigna areaper
).
self
A continuación, añadí muchos atributos aself
. Voy a describir cada atributo a continuación. Usted puede entender rápidamente el propósito y la intención de estos atributos y variables, lo que hará que sea más fácil de entender la estrategia, y evitar la confusión cuando se ve la pila de código.
self.numTick = 0 # it is used to record the number of times that the trading is not triggered when the poll function is called. When placing an order is triggered and the order logic is executed, reset self.numTick to 0
self.lastTradeId = 0 # the trading record ID of the order that has been executed in the trading market; this variable records the current latest execution record ID in the market
self.vol = 0 # after the weighted average calculation, the trading volume reference of the market at each inspection (the market data is obtained once per time of the loop, which can be understood as the inspection of the market once)
self.askPrice = 0 # ask price of delivery order, which can be understood as the price of the pending sell order calculated by the strategy
self.bidPrice = 0 # bid price of delivery order
self.orderBook = {Asks:[], Bids:[]} # record the currently obtained order book data, that is, depth data (sell 1...sell n, buy 1...buy n)
self.prices = [] # an array that records the price in the time series after the weighted average calculation of the first three levels in the order book. Simply put, it is the weighted average price of the first three levels of the order book obtained by storing each time, and put them in an array as reference of the subsequent strategic trading signals. Therefore, the variable name is "prices", plural, which means a set of prices
self.tradeOrderId = 0 # record the order ID after currently lading and ordering
self.p = 0.5 # position proportion; when the currency value is exactly half of the total asset value, the value of it is 0.5, which means the balanced state
self.account = null # record the account asset data, which will be returned by the function GetAccount()
self.preCalc = 0 # record the timestamp when calculating the return of the latest time, in milliseconds, which is used to control the frequency of triggering execution of the return calculation code
self.preNet = 0 # record the current return value
self
Después de añadir estos atributos a self
objeto, de modo que este objeto puede hacer algunas operaciones y tener algunas funciones.
Primera función añadida:
self.updateTrades = function() {
var trades = _C(exchange.GetTrades) # call the encapsulated interface "GetTrades" of FMZ, to obtain the currently latest execution data in the market
if (self.prices.length == 0) { # when self.prices.length == 0, you need to fill values in the array of self.prices, which is only triggered when the strategy is started
while (trades.length == 0) { # if there is no latest execution record in the market, the while loop will run infinitely, until there is new execution data; the, update the variable "trades"
trades = trades.concat(_C(exchange.GetTrades)) # "concat" is a method of JS array type, which is used to match two arrays; here we use it to match the array "trades" and the array returned by "_C(exchange.GetTrades)" into one array
}
for (var i = 0; i < 15; i++) { # fill in values for "self.prices"; fill 15 latest execution prices
self.prices[i] = trades[trades.length - 1].Price
}
}
self.vol = 0.7 * self.vol + 0.3 * _.reduce(trades, function(mem, trade) { # _.reduce function iteratively calculates the accumulated execution volume of the latest execution record
// 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)
}
La función de losupdateTrades
El objetivo es obtener los últimos datos de ejecución en el mercado, y hacer algunos cálculos y registrar de acuerdo con los datos, y proporcionar los resultados para su uso en la lógica de la estrategia posterior.
He escrito directamente los comentarios línea por línea en el código de arriba.
Los estudiantes que no tienen conocimientos básicos de programación estarán confundidos acerca de_.reduce
, así que aquí hay una breve introducción._.reduce
es una función deUnderscore.jsPor lo tanto, es muy conveniente usarlo para hacer cálculos iterativos.Enlace de información de Underscore.js
Muy fácil de entender, por ejemplo:
function main () {
var arr = [1, 2, 3, 4]
var sum = _.reduce(arr, function(ret, ele){
ret += ele
return ret
}, 0)
Log("sum:", sum) # sum is 10
}
Esto es la suma de cada número en las matrices, a saber,[1, 2, 3, 4]
Volviendo a nuestra estrategia, es sumar el valor de volumen de cada registro de ejecución de datos en eltrades
En este caso, el volumen de ejecución es el valor de la matriz, lo que resulta en un total del último volumen de ejecución.self.vol = 0.7 * self.vol + 0.3 * _.reduce(...)
, permítanme reemplazar ese fragmento de código con...
No es difícil ver aquí que el cálculo deself.vol
es también una media ponderada, es decir, el volumen total de ejecución recién generado representa el 30% y el volumen de ejecución anterior calculado representa el 70%.
En cuanto a su pregunta
if ((trade.Id > self.lastTradeId) || (trade.Id == 0 && trade.Time > self.lastTradeId)) {
...
}
El juicio puede basarse en el ID de ejecución en el registro de ejecución de operaciones. Sólo cuando el ID es mayor que el ID de la última vez, se activa la acumulación.trade.Id == 0
, utilizar la marca de tiempo en el registro de ejecución de la negociación para juzgar.self.lastTradeId
es la marca de tiempo del registro de ejecución, no la identificación.
Segunda función añadida:
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))
}
A continuación, vamos a ver elupdateOrderBook
La función, en un primer momento, llama a la función FMZ API.GetDepth()
para obtener los datos actuales del libro de pedidos del mercado (vender 1...vender n, comprar 1...comprar n) y registrar los datos del libro de pedidos enin self.orderBook
A continuación, juzga si el nivel de los datos del libro de pedidos para órdenes de compra y de venta es inferior a 3 niveles; en caso afirmativo, se considera que es una función no válida y devuelve directamente.
Después se realizaron dos cálculos de datos:
Calcular el precio de la orden de entrega El cálculo del precio de entrega también se basa en el cálculo de la media ponderada. Al calcular el precio de oferta de una orden de compra, dar un mayor peso al precio de compra 1, a saber, 61.8% (0.618), y el precio de venta 1 representa el peso restante del 38.2% (0.382). Lo mismo es cierto al calcular el precio de pedido de la orden de entrega, dando más peso al precio de venta 1.
Actualizar el precio medio ponderado de los tres primeros niveles de la cartera de pedidos en las series temporales Para los tres primeros niveles de órdenes de compra y de venta en el libro de pedidos, se realiza un cálculo promedio ponderado. El peso del primer nivel es de 0.7, el peso del segundo nivel es de 0.2, y el peso del tercer nivel es de 0.1.
Veamos el cálculo ampliado:
(Buy 1 + Sell 1) * 0.35 + (Buy 2 + Sell 2) * 0.1 + (Buy 3 + Sell 3) * 0.05
->
(Buy 1 + Sell 1) / 2 * 2 * 0.35 + (Buy 2 + Sell 2) / 2 * 2 * 0.1 + (Buy 3 + Sell 3) / 2 * 2 * 0.05
->
(Buy 1 + Sell 1) / 2 * 0.7 + (Buy 2 + Sell 2) / 2 * 0.2 + (Buy 3 + Sell 3) / 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
Se puede observar que el precio final calculado refleja en realidad la posición media de los precios de los terceros niveles en el mercado actual.
Luego utilice este precio calculado para actualizar elself.prices
array, expulsar los datos más antiguos (por elshift()
En la actualidad, el número de personas que se encuentran en el mercado de trabajo espush()
función; self.prices
es un flujo de datos en orden de serie temporal.
Vamos a terminar el análisis aquí, y nos vemos la próxima vez!