Récemment, il y a eu un débat houleux sur laprint money
Une stratégie très ancienne est revenue aux yeux des Quants: LeeksReaper.
Le principe du robot de négociation deprint money
Je me reproche de ne pas avoir lu trop dans la stratégie de la récolte de poireaux à ce moment-là et de ne pas l'avoir bien comprise.
La stratégie de la récolte de poireaux transplantés basée sur la plateforme FMZ Quant est analysée pour explorer l'idée de la stratégie afin que les utilisateurs de la plateforme puissent apprendre l'idée de cette stratégie.
Dans cet article, nous analyserons davantage les aspects de l'idée de stratégie et de l'intention pour minimiser le contenu ennuyeux lié à la programmation.
Le code source de la stratégie [Transplantation 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)
}
}
Généralement, lorsque vous avez une stratégie à étudier, vous devez d'abord jeter un coup d'œil à la structure globale du programme. Le code de stratégie n'est pas très long, avec moins de 200 lignes de code, il est très concis, et la stratégie originale est très restaurée, presque la même.main()
L'ensemble du code de stratégie, à l'exception de:main()
, est une fonction nomméeLeeksReaper()
LeLeeksReaper()
La fonction est très facile à comprendre, elle peut être comprise comme le constructeur du module logique de la stratégie leeksreaper (un objet).LeeksReaper()
Il est responsable de la construction d'une logique de négociation des poires.
Les mots clés:
· La première ligne de la stratégiemain
fonction:var reaper = LeeksReaper()
, le code déclare une variable localereaper
et appelle ensuite la fonction LeeksReaper() pour construire un objet logique de stratégie qui attribue une valeur àreaper
.
La prochaine étape de la stratégiemain
fonction:
while (true) {
reaper.poll()
Sleep(TickInterval)
}
Entrez unewhile
boucle sans fin et continuer à exécuter la fonction de traitementpoll()
de l'annéereaper
l'objet, lepoll()
la fonction est exactement où se trouve la logique principale de la stratégie de trading et l'ensemble du programme de stratégie commence à exécuter la logique de trading encore et encore.
Pour ce qui est de la ligneSleep(TickInterval)
, il est facile à comprendre, il est de contrôler le temps de pause après chaque exécution de la logique globale de négociation, dans le but de contrôler la fréquence de rotation de la logique de négociation.
LeeksReaper()
constructeurRegardez comment leLeeksReaper()
La fonction construit un objet logique de stratégie.
LeLeeksReaper()
la fonction démarre par la déclaration d'un objet vide,var self = {}
, et lors de l'exécution de laLeeksReaper()
fonction ajoutera progressivement des méthodes et des attributs à cet objet vide, terminant finalement la construction de cet objet et le renvoyant (c'est-à-dire l'étape demain()
fonction à l'intérieur de lavar reaper = LeeksReaper()
, l'objet retourné est attribué àreaper
).
Ajouter des attributs auself
objet
Ensuite, j'ai ajouté beaucoup d'attributs àself
. Je vais décrire chaque attribut comme suit, qui peut comprendre le but et l'intention de ces attributs et variables rapidement, faciliter la compréhension des stratégies, et éviter d'être confus en voyant le code.
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
Après avoir ajouté ces attributs à soi, commencer à ajouter des méthodes à laself
objet de sorte que cet objet peut faire un certain travail et avoir certaines fonctions.
La première fonction ajoutait:
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)
}
La fonctionupdateTrades
Il s'agit d'obtenir les données les plus récentes sur les transactions de marché et de faire des calculs basés sur les données et de les enregistrer pour les utiliser dans la logique ultérieure de la stratégie.
Les commentaires ligne par ligne que j'ai écrits dans le code ci-dessus directement.
Pour_.reduce
, quelqu'un qui n'a pas d'apprentissage de base de la programmation peut être confus._.reduce
est une fonction de la bibliothèque Underscore.js. La stratégie FMZJS prend en charge cette bibliothèque, il est donc très pratique pour le calcul itératif.https://underscorejs.net/#reduce)
La signification est également très simple, 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 = 10
}
C'est-à-dire, additionner chaque nombre dans le tableau[1, 2, 3, 4]
Pour revenir à notre stratégie, nous additionnons les valeurs du volume de négociation de chaque transaction enregistrée dans letrades
Obtenez un total du dernier volume de transactionself.vol = 0.7 * self.vol + 0.3 * _.reduce (...)
, ici nous utilisons...
Il n'est pas difficile de voir le calcul deself.vol
est également une moyenne pondérée, c'est-à-dire que le volume de négociation nouvellement généré représente 30% du total et que le dernier volume de négociation pondéré représente 70%.
Pour ce qui est de votre question, si l'interface pour obtenir les dernières données de transaction retourne aux anciennes données en double, alors les données que j'ai obtenues sont erronées, et cela n'aura pas de sens?
if ((trade.Id > self.lastTradeId) || (trade.Id == 0 && trade.Time > self.lastTradeId)) {
...
}
L'accumulation n'est déclenchée que lorsque l'identifiant est supérieur à l'identifiant du dernier enregistrement, ou si l'interface d'échange ne fournit pas d'identifiant, c'est-à-diretrade.Id == 0
, utilisez l'horodatage dans l'enregistrement de transaction pour juger.self.lastTradeId
stocke l'horodatage de l'enregistrement de transaction au lieu de l'ID.
La deuxième fonction ajoutait:
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 la fonctionupdateOrderBook
. D'après le nom de la fonction, nous pouvons voir qu'elle est utilisée pour mettre à jour le carnet de commandes. Cependant, elle ne met pas à jour le carnet de commandes seulement. La fonction commence à appeler la fonction FMZ API.GetDepth()
pour obtenir les données actuelles du carnet de commandes du marché (vendre un... vendre n, acheter un... acheter n), et enregistrer les données du carnet de commandes dansself.orderBook
. Ensuite, jugez si l'ordre d'achat et l'ordre de vente des données du carnet de commandes sont inférieurs à 3, si c'est le cas, la fonction invalide sera retournée directement.
Ensuite, deux données sont calculées:
· Calcul du prix du connaissement Le prix du connaissement est également calculé en utilisant la méthode de la moyenne pondérée. Lors du calcul de l'ordre d'achat, la pondération donnée au prix d'achat le plus proche du prix de transaction est de 61,8% (0,618) et la pondération donnée au prix de vente le plus proche du prix de transaction est de 38,2% (0,382) Lors du calcul du prix de la facture, le même poids est donné au prix de vente le plus proche du prix de la transaction.
· 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 de prix des ordres d'achat et de vente dans le carnet de commandes, la moyenne pondérée est calculée. 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. Quelqu'un peut dire:
(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
Comme on peut le voir ici, le prix calculé final est en réalité une réponse à la position de prix du milieu de la troisième ouverture sur le marché actuel.
Ensuite, utilisez ce prix calculé pour mettre à jour le tableau.self.prices
, en éliminant l'une des données les plus anciennes (à travers leshift()
La fonction de l'établissement est de mettre à jour l'une des données les plus récentes (par l'intermédiaire de l'établissement).push()
fonction, shift et push sont des méthodes de l'objet de matrice de langage JS, vous pouvez vérifier les données JS pour plus de détails).self.prices
, qui est un flux de données avec un ordre de séries chronologiques.
Alors, on se repose ici, et on se voit dans le prochain numéro ~