Vor kurzem diskutierten Nutzer in der FMZ Quant WeChat-Gruppe über den Bot vonprint money
Die Diskussion war sehr heiß, und eine sehr alte Strategie ist wieder in die Vision der Quanten eingetreten:Gewinn Erntegerät- Ich weiß.
Das Prinzip des Bot-Handelsprint money
Ich habe mir die Schuld gegeben, dass ich die Strategie des Gewinnsammlers damals nicht zu gut verstanden habe.Übertragung von OKCoin Profit Harvester- Ich weiß.
Wir analysieren diese Strategie und holen die Ideen der Strategie heraus, damit unsere Plattformbenutzer diese Strategie-Idee lernen können.
In diesem Artikel analysieren wir mehr aus der Ebene des strategischen Denkens, der Absicht usw., um den langweiligen Inhalt im Zusammenhang mit der Programmierung zu minimieren.
[Port OKCoin Profit Harvester] Strategie Quellcode:
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)
}
}
Im Allgemeinen, wenn Sie eine Strategie zu lernen bekommen, beim Lesen, zuerst schauen Sie sich die Gesamtprogrammstruktur. Der Strategie-Code ist nicht lang, weniger als 200 Zeilen; es kann gesagt werden, sehr vereinfacht, und hat eine hohe Wiederherstellung für die ursprüngliche Version Strategie, die im Grunde das gleiche wie dieses ist.main()
Der gesamte Strategie-Code, mit Ausnahme dermain()
, hat nur eine Funktion namensLeeksReaper()
. DieLeeksReaper()
Diese Funktion kann als Konstruktor des Profit Harvester-Strategie-Logikmoduls (ein Objekt) verstanden werden.LeeksReaper()
ist für die Konstruktion der Handelslogik eines Gewinnsammlers verantwortlich.
Die erste Zeile desmain
Funktion in der Strategie:var reaper = LeeksReaper()
; der Code erklärt eine lokale Variablereaper
, und ruft dann die LeeksReaper() Funktion auf, um ein Strategie-Logikobjekt zu konstruieren und zuweist es anreaper
.
Der folgende Teil dermain
Funktion:
while (true) {
reaper.poll()
Sleep(TickInterval)
}
Geben Sie einwhile
Dabei wird die Verarbeitungsfunktion derreaper
Gegenstandpoll()
, und diepoll()
Funktion ist die Hauptlogik der Strategie, also beginnt das gesamte Strategieprogramm, die Handelslogik kontinuierlich auszuführen.
Was die Linie derSleep(TickInterval)
, ist leicht verständlich, der die Pausezeit nach jeder Ausführung der gesamten Handelslogik steuert und dessen Zweck es ist, die Rotationsfrequenz der Handelslogik zu steuern.
LeeksReaper()
Lassen Sie uns sehen, wie die FunktionLeeksReaper()
Konstruiert ein Objekt in der Strategielogik.
DieLeeksReaper()
Funktion beginnt und deklariert ein Nullobjekt,var self = {}
. Während der Vollstreckung desLeeksReaper()
Wenn das Objekt mit der Funktion null verknüpft ist, wird dieses Nullobjekt allmählich geändert, indem einige Methoden und Attribute hinzugefügt werden.var reaper = LeeksReaper()
in dermain()
Funktion, und das zurückgegebene Objekt wird zugewiesenreaper
).
self
Als nächstes habe ich viele Attribute hinzugefügtself
Sie können schnell den Zweck und die Absicht dieser Attribute und Variablen verstehen, was es einfacher macht, die Strategie zu verstehen und verwirrt zu bleiben, wenn Sie den Haufen Code sehen.
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
Nachdem Sie diese Attribute zu self
Objekt, so dass dieses Objekt einige Operationen ausführen und einige Funktionen haben kann.
Erste Funktion:
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)
}
Die Funktion derupdateTrades
Die wichtigste Aufgabe des Unternehmens ist es, die neuesten Ausführungsdaten auf dem Markt zu erhalten, einige Berechnungen durchzuführen und entsprechend aufzuzeichnen und die Ergebnisse für die spätere Strategie zu verwenden.
Ich habe die Kommentare Zeile für Zeile direkt in den obigen Code geschrieben.
Studenten, die keine Grundlagen für die Programmierung haben, werden verwirrt sein._.reduce
Hier ist eine kurze Einführung._.reduce
ist eine Funktion vonUnderscore.jsDaher ist es sehr praktisch, es zu verwenden, um iterative Berechnungen durchzuführen.Informationslink von Underscore.js
Sehr leicht zu verstehen, 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 is 10
}
Das ist die Summe jeder Zahl in den Arrays, nämlich[1, 2, 3, 4]
Zurück zu unserer Strategie: Wir addieren den Volumenwert jeder Ausführungsdaten in dertrades
Das Ergebnis ist die Summe des letzten Ausführungsvolumens.self.vol = 0.7 * self.vol + 0.3 * _.reduce(...)
, erlauben Sie mir, dieses Stück Code durch...
Es ist hier nicht schwer zu erkennen, daß die Berechnung vonself.vol
ist auch ein gewichteter Durchschnitt, d. h. das neu generierte Gesamtvolumen der Ausführung beträgt 30% und das vorher berechnete Gesamtvolumen der Ausführung 70%. Dieses Verhältnis wird vom Strategieentwickler künstlich festgelegt, was möglicherweise mit der Beobachtung der Marktregeln zusammenhängt.
Was Ihre Frage angeht, was sollte ich tun, wenn die Schnittstelle zur Erfassung der neuesten Ausführungsdaten mir die doppelten alten Daten zurückgibt? Dann sind die erhaltenen Daten falsch, also gibt es einen Sinn zu verwenden? " Keine Sorge, die Strategie wurde mit diesem Gedanken konzipiert, so dass der Code den folgenden Inhalt hat:
if ((trade.Id > self.lastTradeId) || (trade.Id == 0 && trade.Time > self.lastTradeId)) {
...
}
Das Urteil kann auf der Grundlage der Ausführung ID in der Handelsausführung Rekord. Nur wenn die ID größer als die ID des letzten Mal ist, Akkumulation ausgelöst wird. Oder, wenn die Plattform-Schnittstelle keine ID, das heißt,trade.Id == 0
, verwenden Sie den Zeitstempel in der Handelsausführung Aufzeichnung zu beurteilen.self.lastTradeId
ist der Zeitstempel des Ausführungsdatensatzes, nicht die ID.
Zweite Funktion:
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 dieupdateOrderBook
Funktion. Aus der Bedeutung des Funktionsnamens lässt sich erkennen, dass die Funktion das Bestellbuch aktualisieren soll. Allerdings aktualisiert sie nicht nur das Bestellbuch. Die Funktion ruft zunächst die FMZ API-Funktion aufGetDepth()
Um die Daten des aktuellen Auftragsbuchs des Marktes zu erhalten (verkaufen 1...verkaufen n, kaufen 1...kaufen n) und die Auftragsbuchdaten inin self.orderBook
Anschließend wird beurteilt, ob das Niveau der Auftragsbuchdaten für Kauf- und Verkaufsbestellungen weniger als 3 Ebenen beträgt. Ist dies der Fall, wird es als ungültige Funktion beurteilt und wird direkt zurückgegeben.
Danach wurden zwei Datenberechnungen durchgeführt:
Berechnung des Lieferpreises Die Berechnung des Lieferpreises basiert ebenfalls auf der gewichteten Durchschnittsberechnung. Bei der Berechnung des Gebotspreises einer Kaufbestellung wird dem Kaufpreis 1 ein größeres Gewicht gegeben, nämlich 61.8% (0.618), und der Verkaufspreis 1 macht das verbleibende Gewicht von 38.2% (0.382) aus. Das gleiche gilt bei der Berechnung des Gebotspreises der Lieferbestellung, indem dem Verkaufspreis 1 mehr Gewicht gegeben wird. Warum es 0.618 ist, kann sein, dass der Entwickler das goldene Verhältnis bevorzugt. Was das kleine Teil des Preises (0.01) angeht, das am Ende hinzugefügt oder subtrahiert wird, ist es, um etwas weiter zum Marktzentrum zu kompensieren.
Aktualisierung des gewichteten Durchschnittspreises der ersten drei Ebenen des Auftragsbuchs auf der Zeitreihe
Für die ersten drei Stufen der Bestellungen im Auftragsbuch wird ein gewichteter Durchschnitt berechnet. Das Gewicht der ersten Stufe beträgt 0,7, das Gewicht der zweiten Stufe 0,2 und das Gewicht der dritten Stufe 0,1. Einige Schüler sagen vielleicht:
Schauen wir uns die erweiterte Berechnung an:
(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
Es wird hier festgestellt, dass der endgültig berechnete Preis tatsächlich die mittlere Preisposition der dritten Stufen auf dem aktuellen Markt widerspiegelt.
Verwenden Sie dann diesen berechneten Preis, um dieself.prices
Die Daten werden in einem Array aufgeteilt.shift()
Die Daten werden in derpush()
Funktion; self.prices
ist ein Datenfluss in Zeitreihenfolge.
Lasst uns die Analyse hier beenden, und wir sehen uns beim nächsten Mal!