Récemment, les utilisateurs du groupe FMZ Quant WeChat ont discuté du bot deprint money
La discussion a été très chaude et une stratégie très ancienne est revenue dans la vision des quantités:Résultat RécolteuseJe suis désolée.
Le principe du trading de robotsprint money
Je me reproche de ne pas avoir bien compris la stratégie de la récolte des profits à l'époque.Porting OKCoin profit harvesterJe suis désolée.
Prenant comme exemple la version portée de la stratégie de récolte de profit de FMZ, nous allons analyser cette stratégie et extraire les idées de la stratégie, afin que nos utilisateurs de plateforme puissent apprendre cette idée de stratégie.
Dans cet article, nous analysons davantage du niveau de la pensée stratégique, de l'intention, etc., afin de minimiser le contenu ennuyeux lié à la programmation.
Le code source de la stratégie:
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)
}
}
Généralement, lorsque vous obtenez une stratégie à apprendre, lorsque vous lisez, regardez d'abord la structure globale du programme. Le code de stratégie n'est pas long, moins de 200 lignes; on peut dire qu'il est très simplifié, et a une restauration élevée pour la stratégie de la version originale, qui est essentiellement la même que celle-ci.main()
L'ensemble du code de la stratégie, à l'exception de lamain()
, a seulement une fonction nomméeLeeksReaper()
LeLeeksReaper()
Cette fonction peut être comprise comme le constructeur du module logique de stratégie de la récolte de profit (un objet).LeeksReaper()
est responsable de la construction de la logique de négociation d'un récolteur de profit.
La première ligne de lamain
fonction dans la stratégie:var reaper = LeeksReaper()
; le code déclare une variable localereaper
, puis appelle la fonction LeeksReaper() pour construire un objet logique de stratégie et l'affecte àreaper
.
La partie suivante de lamain
fonction:
while (true) {
reaper.poll()
Sleep(TickInterval)
}
Entrez unewhile
La fonction de traitement de l'appareil est exécutée en continu par une boucle infinie.reaper
objetpoll()
, et lepoll()
La fonction est la logique principale de la stratégie, donc l'ensemble du programme de stratégie commence à exécuter continuellement la logique de trading.
En ce qui concerne la ligne deSleep(TickInterval)
, il est facile à comprendre, qui est de contrôler le temps de pause après chaque exécution de la logique globale de négociation, et dont le but est de contrôler la fréquence de rotation de la logique de négociation.
LeeksReaper()
Voyons comment la fonctionLeeksReaper()
construit un objet dans la logique de stratégie.
LeLeeksReaper()
fonction commence et déclare un objet nul,var self = {}
Au cours de l'exécution de laLeeksReaper()
En fin de compte, la construction de l'objet est terminée, et l'objet est retourné (c'est-à-dire, levar reaper = LeeksReaper()
dans lemain()
fonction, et l'objet retourné est attribué àreaper
).
self
Ensuite, j'ai ajouté beaucoup d'attributs àself
Vous pouvez rapidement comprendre le but et l'intention de ces attributs et variables, ce qui facilitera la compréhension de la stratégie, et éviter d'être confus lorsque vous voyez la pile de code.
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
Après avoir ajouté ces attributs à self
objet, de sorte que cet objet peut faire certaines opérations et avoir certaines fonctions.
Première fonction ajoutée:
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 fonction deupdateTrades
Il s'agit d'obtenir les données d'exécution les plus récentes sur le marché, de faire des calculs et d'enregistrer en fonction des données, et de fournir les résultats pour une utilisation dans la logique de stratégie ultérieure.
J'ai directement écrit les commentaires ligne par ligne dans le code ci-dessus.
Les étudiants qui n'ont peut-être pas de base de programmation seront confus_.reduce
, voici donc une brève introduction._.reduce
est une fonction deUnderscore.jsIl est donc très pratique de l'utiliser pour effectuer des calculs itératifs.lien d'information de Underscore.js
Très facile à comprendre, par exemple:
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
}
C' est l' addition de chaque nombre dans les tableaux, à savoir:[1, 2, 3, 4]
Pour revenir à notre stratégie, il s'agit d'additionner la valeur de volume de chaque enregistrement d'exécution dans letrades
Le nombre total de volumes d'exécution est le nombre total de volumes d'exécution.self.vol = 0.7 * self.vol + 0.3 * _.reduce(...)
Permettez-moi de remplacer ce morceau de code par...
Il n'est pas difficile de voir ici que le calcul deself.vol
est également une moyenne pondérée, c'est-à-dire que le volume total d'exécution nouvellement généré représente 30% et le volume d'exécution précédemment calculé représente 70%.
En ce qui concerne votre question
if ((trade.Id > self.lastTradeId) || (trade.Id == 0 && trade.Time > self.lastTradeId)) {
...
}
Le jugement peut être basé sur l'identifiant d'exécution dans l'enregistrement d'exécution des transactions. Seulement lorsque l'identifiant est plus grand que l'identifiant de la dernière fois, l'accumulation est déclenchée. Ou, si l'interface de la plate-forme ne fournit pas d'identifiant, c'est-à-dire,trade.Id == 0
, utilisez l'horodatage dans l'enregistrement d'exécution de la transaction pour juger.self.lastTradeId
est l'horodatage de l'enregistrement d'exécution et non l'identifiant.
Deuxième fonction ajoutée:
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))
}
Ensuite, regardons leupdateOrderBook
fonction. D'après la signification du nom de la fonction, on peut voir que la fonction est de mettre à jour le carnet de commandes. Cependant, elle ne met pas simplement à jour le carnet de commandes. La fonction appelle d'abord la fonction FMZ API.GetDepth()
pour obtenir les données actuelles du carnet de commandes du marché (vendre 1...vendre n, acheter 1...acheter n), et enregistrer les données du carnet de commandes dans lein self.orderBook
Ensuite, il juge si le niveau des données du carnet de commandes pour les ordres d'achat et les ordres de vente est inférieur à 3 niveaux; dans ce cas, il est jugé comme une fonction invalide et renvoie directement.
Ensuite, deux calculs de données ont été effectués:
Calculer le prix de la commande de livraison Le calcul du prix de livraison est également basé sur le calcul de la moyenne pondérée. Lors du calcul du prix d'enchère d'un ordre d'achat, donner une plus grande pondération au prix d'achat 1, à savoir 61,8% (0,618), et le prix de vente 1 représente le poids restant de 38,2% (0,382). Il en va de même lors du calcul du prix d'enchère de l'ordre de livraison, en donnant plus de poids au prix de vente 1. En ce qui concerne la raison pour laquelle il est de 0,618, il se peut que le développeur préfère le ratio d'or. En ce qui concerne le peu de prix (0,01) ajouté ou soustrait à la fin, il s'agit de compenser un peu plus loin au centre du marché.
Mise à jour du prix moyen pondéré des trois premiers niveaux du carnet de commandes sur les séries chronologiques Pour les trois premiers niveaux d'ordres d'achat et de vente dans le carnet de commandes, un calcul moyen pondéré est effectué. Le poids du premier niveau est de 0,7, le poids du deuxième niveau est de 0,2 et le poids du troisième niveau est de 0,1.
Voyons le calcul élargi:
(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
Il ressort de ce qui précède que le prix calculé final reflète en fait la position moyenne des prix des tiers niveaux sur le marché actuel.
Ensuite, utilisez ce prix calculé pour mettre à jour leself.prices
L'ensemble des données de base (par l'intermédiaire de lashift()
Les résultats de l'enquête sont publiés dans les journaux de l'Union européenne.push()
fonction; self.prices
est un flux de données dans l'ordre des séries temporelles.
On va finir l'analyse ici, et on se voit la prochaine fois!