In letzter Zeit gibt es eine heiße Diskussion über dieprint money
Eine sehr alte Strategie ist wieder in die Augen der Quants gekommen: LeeksReaper.
Das Roboterhandelsprinzip derprint money
Also habe ich die ursprüngliche Strategie nochmals sorgfältig durchgelesen und mir die transplantierte Version des transplantierten OKCoin-Poees auf dem FMZ Quant angesehen.
Die Strategie des auf der FMZ Quant Plattform basierenden transplantierten Laucherners wird analysiert, um die Idee der Strategie zu erforschen.
In diesem Artikel werden wir mehr aus den Aspekten der Strategieidee und Absicht analysieren, um den langweiligen Inhalt im Zusammenhang mit der Programmierung zu minimieren.
Der Quellcode der [Transplantation von OKCoin LeeksReaper] Strategie:
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)
}
}
Im Allgemeinen, wenn Sie eine Strategie zu studieren haben, sollten Sie sich zunächst die Gesamtprogrammstruktur ansehen. Der Strategiecode ist nicht sehr lang, mit weniger als 200 Zeilen Code, es ist sehr prägnant, und die ursprüngliche Strategie ist stark restauriert, fast identisch.main()
Der gesamte Strategiecode, mit Ausnahmemain()
, ist eine Funktion namensLeeksReaper()
. DieLeeksReaper()
Die Funktion ist sehr leicht zu verstehen, sie kann als Konstruktor des Logikmoduls (ein Objekt) verstanden werden.LeeksReaper()
ist für den Aufbau einer Logik für den Handel mit Porenernten verantwortlich.
Schlüsselwörter:
· Die erste Strategieliniemain
Funktion:var reaper = LeeksReaper()
, der Code erklärt eine lokale Variablereaper
und ruft dann die LeeksReaper() Funktion auf, um ein Strategie-Logikobjekt zu konstruieren, das einem Wert zuweistreaper
.
Der nächste Schritt der Strategiemain
Funktion:
while (true) {
reaper.poll()
Sleep(TickInterval)
}
Geben Sie einwhile
endlose Schleife und halten die Ausführung der Verarbeitung Funktionpoll()
derreaper
Gegenstand, diepoll()
Die Funktion ist genau dort, wo die Hauptlogik der Handelsstrategie liegt und das gesamte Strategieprogramm beginnt, die Handelslogik immer wieder auszuführen.
Was die Linie angeht:Sleep(TickInterval)
, ist leicht zu verstehen, ist es, die Pausezeit nach jeder Ausführung der gesamten Handelslogik zu steuern, mit dem Ziel, die Rotationsfrequenz der Handelslogik zu steuern.
LeeksReaper()
KonstrukteurSchauen Sie sich an, wie dieLeeksReaper()
Die Funktion konstruiert ein Strategie-Logikobjekt.
DieLeeksReaper()
Die Funktion beginnt mit der Angabe eines leeren Objekts.var self = {}
, und während der Ausführung derLeeksReaper()
Funktion wird allmählich einige Methoden und Attribute zu diesem leeren Objekt hinzufügen, schließlich die Konstruktion dieses Objekts abzuschließen und es zurückzugeben (das heißt, der Schritt vonmain()
Funktion innerhalb dervar reaper = LeeksReaper()
, wird das zurückgegebene Objekt zugeordnetreaper
).
Hinzufügen von Attributen zumself
Gegenstand
Als nächstes habe ich viele Attribute hinzugefügt.self
. Ich werde jedes Attribut wie folgt beschreiben, was den Zweck und die Absicht dieser Attribute und Variablen schnell verstehen, das Verständnis von Strategien erleichtern und Verwirrung beim Ansehen des Codes vermeiden kann.
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
Nach dem Hinzufügen dieser Attribute zu selbst, beginnen Sie Methoden zu demself
Objekt, so dass dieses Objekt einige Arbeit leisten und einige Funktionen haben kann.
Die erste Funktion fügte hinzu:
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)
}
Die FunktionupdateTrades
Der Hauptzweck der Strategie ist es, die neuesten Markttransaktionsdaten zu erhalten, einige Berechnungen auf der Grundlage der Daten durchzuführen und sie für die spätere Nutzung in der Logik der Strategie zu erfassen.
Die Zeile für Zeile Kommentare habe ich direkt in den Code oben geschrieben.
Für_.reduce
, kann jemand, der keine Grundkenntnisse im Programmieren hat, verwirrt sein. Lassen Sie uns kurz darüber sprechen,_.reduce
ist eine Funktion der Underscore.js-Bibliothek. Die FMZJS-Strategie unterstützt diese Bibliothek, so dass sie für iterative Berechnungen sehr praktisch ist.https://underscorejs.net/#reduce)
Die Bedeutung ist auch sehr einfach, zum Beispiel:
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
}
Das heißt, addieren Sie jede Zahl im Array[1, 2, 3, 4]
Zurück zu unserer Strategie addieren wir die Handelsvolumenwerte der einzelnen Transaktionsdaten in dertrades
Erhalten Sie eine Gesamtsumme des letzten Transaktionsvolumensself.vol = 0.7 * self.vol + 0.3 * _.reduce (...)
, hier verwenden wir...
Es ist nicht schwierig, die Berechnung vonself.vol
ist auch ein gewichteter Durchschnitt, d. h. das neu generierte Handelsvolumen macht 30% des Gesamtvolumens aus und das letzte gewichtete Handelsvolumen 70% dieses Verhältnisses wurde vom Strategieaufsteller künstlich festgelegt und kann mit den Marktregeln zusammenhängen.
Was Ihre Frage angeht, was wäre, wenn die Schnittstelle, um die neuesten Transaktionsdaten zu erhalten, auf die duplizierten alten Daten zurückkehrte, dann waren die Daten falsch und es wird nicht sinnvoll sein? Keine Sorge. Dieses Problem wurde im Strategie-Design berücksichtigt, so dass der Code:
if ((trade.Id > self.lastTradeId) || (trade.Id == 0 && trade.Time > self.lastTradeId)) {
...
}
Die Akkumulation wird nur ausgelöst, wenn die ID größer ist als die ID des letzten Datensatzes oder wenn die Austauschoberfläche keine ID bereitstellt, d. h.trade.Id == 0
, verwenden Sie den Zeitstempel im Transaktionsdatensatz zu beurteilen.self.lastTradeId
Speichert den Zeitstempel des Transaktionsprotokolls anstelle der ID.
Die zweite Funktion fügte hinzu:
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))
}
Als nächstes schauen wir uns die Funktion an.updateOrderBook
. Aus dem Namen der Funktion können wir sehen, dass sie zum Aktualisieren des Auftragsbuchs verwendet wird. Sie aktualisiert jedoch nicht nur das Auftragsbuch. Die Funktion ruft die FMZ API-Funktion auf.GetDepth()
Um die Daten des aktuellen Auftragsbuchs des Marktes zu erhalten (einen verkaufen... n verkaufen, einen kaufen... n kaufen) und die Auftragsbuchdaten inself.orderBook
. Als nächstes, entscheiden, ob die Bestellung und Verkaufsbestellung der Bestellbuchdaten weniger als 3, wenn ja, wird die ungültige Funktion direkt zurückgegeben.
Anschließend werden zwei Daten berechnet:
· Berechnung des Konnossementpreises Bei der Berechnung der Bestellung beträgt das Gewicht des Kaufpreises, der dem Transaktionspreis am nächsten liegt, 61,8% (0,618) und das Gewicht des Verkaufspreises, der dem Transaktionspreis am nächsten liegt, 38,2% (0,382). Bei der Berechnung des Frachtbriefes wird der Verkaufspreis, der dem Transaktionspreis am nächsten liegt, mit dem gleichen Gewicht bewertet. Wie für warum ist 0,618, kann es sein, dass der Autor das goldene Querschnittsverhältnis bevorzugt. Wie für den letzten Preis (0,01), ist es leicht zur Mitte der Öffnung zu verschieben.
· Aktualisierung des gewichteten Durchschnittspreises der ersten drei Stufen des Auftragsbuchs auf der Zeitreihe
Für die ersten drei Stufen der Bestellpreise im Auftragsbuch wird der gewichtete Durchschnitt berechnet. Das Gewicht der ersten Stufe ist 0,7, das Gewicht der zweiten Stufe ist 0,2 und das Gewicht der dritten Stufe ist 0,1. Jemand mag sagen:
(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
Wie wir hier sehen können, ist der endgültig berechnete Preis eigentlich eine Antwort auf die Preisposition der Mitte der dritten Öffnung auf dem aktuellen Markt.
Verwenden Sie dann diesen berechneten Preis, um das Array zu aktualisierenself.prices
, die eine der ältesten Daten (durch dieshift()
Diese Daten werden in der Datenbank (z. B.push()
Funktion, Verschiebung und Push-Funktionen sind Methoden des JS-Sprache-Array-Objektes, Sie können die JS-Daten für Details überprüfen).self.prices
, das ein Datenstrom mit einer Zeitreihenfolge ist.
Also lassen Sie uns hier eine Pause machen, und wir sehen uns in der nächsten Ausgabe ~