Type/to search
8
Follow
1361
Followers
Analyse der Lauchernte-Strategie (1)
HFT
Created 2020-11-12 22:11:32  Updated 2024-12-06 22:20:54
 26
 11229

img

Analyse der Lauchernte-Strategie

Vor kurzem der Erfinder der quantitativen WeChat-Gruppendiskussionprint moneyDie Diskussion über Roboter war sehr hitzig und eine sehr alte Strategie tauchte wieder im Blickfeld der Quants auf:Laucherntemaschine
print moneyDas Handelsprinzip des Roboters basiert auf der Lauchernte-Strategie. Ich mache mir Vorwürfe, weil ich die Lauchernte-Strategie damals nicht klar verstanden habe. Daher habe ich die ursprüngliche Strategie noch einmal sorgfältig geprüft und auch die übertragene Version auf Inventor Quant überprüft.OKCoin Lauch-Erntemaschine verpflanzen
Nehmen wir die transplantierte Version der Lauchernte-Strategie der Inventor Quantitative Platform, um die Strategie zu analysieren und die Ideen dahinter zu erkunden. Damit können Plattformbenutzer diese strategische Idee erlernen.
In diesem Artikel werden wir die Analyse stärker aus der Perspektive des strategischen Denkens, der Absichten usw. durchführen und versuchen, die langweiligen Inhalte im Zusammenhang mit der Programmierung zu reduzieren.

[Transplantation von OKCoin Leek Harvester] Quellcode der Strategie:

pine
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("开始平衡", 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("开始平衡", 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) } }

Strategieübersicht

Wenn Sie eine Strategie lernen möchten, sollten Sie sich beim Lesen grundsätzlich zuerst die Gesamtstruktur des Programms ansehen. Diese Strategie hat nicht viel Code, nur weniger als 200 Codezeilen, was sehr prägnant ist, und der Grad der Wiederherstellung gegenüber der ursprünglichen Strategie ist sehr hoch und im Grunde gleich. Der Richtliniencode läuft vonmain()Die Funktion wird ausgeführt und der gesamte Strategiecode wirdmain()ist einLeeksReaper()Die Funktion,LeeksReaper()Die Funktion ist auch leicht zu verstehen. Diese Funktion kann als Konstruktor des Logikmoduls der Laucherntestrategie (ein Objekt) verstanden werden. Einfach ausgedrücktLeeksReaper()Es ist für den Aufbau einer Lauch-Harvester-Transaktionslogik verantwortlich.

Schlüsselwörter:
img
img

  • StrategiemainDie erste Zeile der Funktion:
    var reaper = LeeksReaper()deklariert der Code eine lokale VariablereaperRufen Sie dann die Funktion LeeksReaper() auf, um ein Strategie-Logikobjekt zu konstruieren und weisen Sie es zureaper

  • StrategiemainDie Funktion ist folgende:

    javascript
    while (true) { reaper.poll() Sleep(TickInterval) }

    Geben Sie einenwhileDead Loop, endlose AusführungreaperObjektverarbeitungsfunktionpoll()poll()Die Funktion ist die Hauptlogik der Handelsstrategie, und das gesamte Strategieprogramm beginnt, die Handelslogik kontinuierlich auszuführen.
    WasSleep(TickInterval)Diese Zeile ist leicht zu verstehen. Sie dient zur Steuerung der Pausenzeit nach jeder Ausführung der gesamten Transaktionslogik, um die Rotationsfrequenz der Transaktionslogik zu steuern.

ProfilerstellungLeeksReaper()Konstruktor

schau mal reinLeeksReaper()Wie Funktionen ein Strategie-Logikobjekt konstruieren.

LeeksReaper()Zu Beginn der Funktion wird ein leeres Objekt deklariert.var self = {},existierenLeeksReaper()Während der Ausführung der Funktion werden diesem leeren Objekt nach und nach einige Methoden und Eigenschaften hinzugefügt, und schließlich wird die Konstruktion dieses Objekts abgeschlossen und schließlich wird dieses Objekt zurückgegeben (d. h.main()Innerhalb der Funktionvar reaper = LeeksReaper()In diesem Schritt wird das zurückgegebene Objekt zugewiesen anreaper)。

GebenselfHinzufügen von Eigenschaften zu einem Objekt

Als nächstes gebenselfEs wurden viele Eigenschaften hinzugefügt. Ich werde jede Eigenschaft unten beschreiben, damit Sie den Zweck und die Absicht dieser Eigenschaften und Variablen schnell verstehen. Dies erleichtert Ihnen das Verständnis der Strategie und vermeidet Verwirrung, wenn Sie diesen Haufen Code sehen.

python
self.numTick = 0 # 用来记录poll函数调用时未触发交易的次数,当触发下单并且下单逻辑执行完时,self.numTick重置为0 self.lastTradeId = 0 # 交易市场已经成交的订单交易记录ID,这个变量记录市场当前最新的成交记录ID self.vol = 0 # 通过加权平均计算之后的市场每次考察时成交量参考(每次循环获取一次市场行情数据,可以理解为考察了行情一次) self.askPrice = 0 # 卖单提单价格,可以理解为策略通过计算后将要挂卖单的价格 self.bidPrice = 0 # 买单提单价格 self.orderBook = {Asks:[], Bids:[]} # 记录当前获取的订单薄数据,即深度数据(卖一...卖n,买一...买n) self.prices = [] # 一个数组,记录订单薄中前三档加权平均计算之后的时间序列上的价格,简单说就是每次储存计算得到的订单薄前三档加权平均价格,放在一个数组中,用于后续策略交易信号参考,所以该变量名是prices,复数形式,表示一组价格 self.tradeOrderId = 0 # 记录当前提单下单后的订单ID self.p = 0.5 # 仓位比重,币的价值正好占总资产价值的一半时,该值为0.5,即平衡状态 self.account = null # 记录账户资产数据,由GetAccount()函数返回数据 self.preCalc = 0 # 记录最近一次计算收益时的时间戳,单位毫秒,用于控制收益计算部分代码触发执行的频率 self.preNet = 0 # 记录当前收益数值

GebenselfObjekt hinzufügen (Methode)

Nachdem Sie diese Attribute zu sich selbst hinzugefügt haben, beginnen Sie mitselfDurch das Hinzufügen von Methoden zu einem Objekt kann das Objekt bestimmte Aufgaben ausführen und über bestimmte Funktionen verfügen.

Die erste hinzugefügte Funktion:

cpp
self.updateTrades = function() { var trades = _C(exchange.GetTrades) # 调用FMZ封装的接口GetTrades,获取当前最新的市场成交数据 if (self.prices.length == 0) { # 当self.prices.length == 0时,需要给self.prices数组填充数值,只有策略启动运行时才会触发 while (trades.length == 0) { # 如果近期市场上没有更新的成交记录,这个while循环会一直执行,直到有最新成交数据,更新trades变量 trades = trades.concat(_C(exchange.GetTrades)) # concat 是JS数组类型的一个方法,用来拼接两个数组,这里就是把“trades”数组和“_C(exchange.GetTrades)”返回的数组数据拼接成一个数组 } for (var i = 0; i < 15; i++) { # 给self.prices填充数据,填充15个最新成交价格 self.prices[i] = trades[trades.length - 1].Price } } self.vol = 0.7 * self.vol + 0.3 * _.reduce(trades, function(mem, trade) { # _.reduce 函数迭代计算,累计最新成交记录的成交量 // 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) }

updateTradesDer Zweck dieser Funktion besteht darin, die neuesten Markttransaktionsdaten abzurufen, einige Berechnungen auf der Grundlage der Daten durchzuführen und diese zur Verwendung in der nachfolgenden Logik der Strategie aufzuzeichnen.
Die zeilenweisen Kommentare habe ich direkt in den obigen Code geschrieben.
für_.reduceStudierende ohne Programmierkenntnisse sind möglicherweise verwirrt. Hier ist eine kurze Erklärung._.reduceJaUnderscore.jsDie Funktionen dieser Bibliothek werden durch die FMZJS-Strategie unterstützt, daher ist die Verwendung iterativer Berechnungen sehr praktisch.Underscore.js-Datenlink

Auch die Bedeutung ist ganz einfach, zum Beispiel:

python
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, das Array[1, 2, 3, 4]Addieren Sie jede Zahl in . Zurück zu unserer Strategie,tradesDie Transaktionsvolumenwerte jedes Transaktionsdatensatzes im Array werden addiert. Erhalten Sie den aktuellsten Transaktionsdatensatz und das gesamte Transaktionsvolumen.self.vol = 0.7 * self.vol + 0.3 * _.reduce(...)Bitte erlauben Sie mir,...Anstelle dieses Haufens Code. Es ist nicht schwer zu erkennen, dassself.volBei der Berechnung handelt es sich ebenfalls um einen gewichteten Durchschnitt. Das heißt, das letzte Gesamttransaktionsvolumen macht 30 % der Gewichtung aus, und das durch die vorherige Gewichtungsberechnung ermittelte Transaktionsvolumen macht 70 % aus. Dieses Verhältnis wird vom Strategieautor künstlich festgelegt und kann mit der Beobachtung von Marktmustern zusammenhängen.
Was Ihre Frage betrifft: Was passiert, wenn mir die Schnittstelle zum Abrufen der neuesten Transaktionsdaten alte Daten doppelt zurückgibt? Dann sind die Daten, die ich erhalte, falsch. Macht es dann überhaupt Sinn, sie zu verwenden? Keine Sorge, dieses Problem wurde bei der Entwicklung der Strategie berücksichtigt und daher im Code berücksichtigt.

python
if ((trade.Id > self.lastTradeId) || (trade.Id == 0 && trade.Time > self.lastTradeId)) { ... }

Dieses Urteil. Dies kann anhand der Transaktions-ID im Transaktionsdatensatz beurteilt werden. Die Akkumulation wird nur ausgelöst, wenn die ID größer als die ID des letzten Datensatzes ist oder wenn die Austauschschnittstelle die ID nicht bereitstellt, d. h.trade.Id == 0, anhand des Zeitstempels im Transaktionsdatensatz lässt sich zu diesem Zeitpunkt feststellen,self.lastTradeIdGespeichert wird der Zeitstempel des Transaktionsdatensatzes, nicht die ID.

Die zweite hinzugefügte Funktion:

python
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)) }

Nächste UhrupdateOrderBookWie der Name schon sagt, dient diese Funktion der Aktualisierung des Auftragsbuchs. Allerdings geht es dabei um mehr als nur die Aktualisierung des Auftragsbuchs. Die Funktion startet den Aufruf der API-Funktion von FMZGetDepth()Holen Sie sich die aktuellen Markt-Orderbuchdaten (verkaufe eins...verkaufe n, kaufe eins...kaufe n) und erfassen Sie die Orderbuchdaten inself.orderBookMitte. Wenn die Auftragsbuchdaten weniger als 3 Kauf- und Verkaufsaufträge enthalten, wird die Funktion als ungültig erachtet und direkt zurückgegeben.

Anschließend wurden zwei Daten berechnet:

  • Berechnen Sie den Frachtbriefpreis
    Der Frachtbriefpreis wird ebenfalls anhand des gewichteten Durchschnitts berechnet. Bei der Berechnung der Kauforder erhält die Kauforder ein größeres Gewicht von 61,8 % (0,618), und die Verkaufsorder hat das verbleibende Gewicht von 38,2 % (0,382).
    Dasselbe gilt für die Berechnung des Verkaufspreises im Konnossement, wobei dem Verkaufspreis größeres Gewicht beigemessen wird. Der Grund für den Wert 0,618 liegt möglicherweise darin, dass der Autor den Goldenen Schnitt bevorzugt. Die abschließende leichte Preiserhöhung bzw. -senkung (0,01) dient dazu, den Kurs leicht in Richtung Marktmitte zu verschieben.

  • Aktualisieren Sie den gewichteten Durchschnittspreis der ersten drei Ebenen des Auftragsbuchs in der Zeitreihe
    Für die ersten drei Stufen der Kauf- und Verkaufsauftragspreise im Auftragsbuch wird eine gewichtete Durchschnittsberechnung durchgeführt, wobei die erste Stufe eine Gewichtung von 0,7, die zweite Stufe eine Gewichtung von 0,2 und die dritte Stufe eine Gewichtung von 0,1 hat. . Einige Schüler sagen vielleicht: „Oh, das stimmt nicht, im Code gibt es keine 0,7, 0,2, 0,1“
    Erweitern wir die Berechnung:

    (买一 + 卖一) * 0.35 + (买二 + 卖二) * 0.1 + (买三 + 卖三) * 0.05 -> (买一 + 卖一) / 2 * 2 * 0.35 + (买二 + 卖二) / 2 * 2 * 0.1 + (买三 + 卖三) / 2 * 2 * 0.05 -> (买一 + 卖一) / 2 * 0.7 + (买二 + 卖二) / 2 * 0.2 + (买三 + 卖三) / 2 * 0.1 -> 第一档平均的价格 * 0.7 + 第二档平均的价格 * 0.2 + 第三档平均的价格 * 0.1

    Hier sehen Sie, dass der endgültig berechnete Preis tatsächlich die mittlere Preisposition der drei Ebenen im aktuellen Markt widerspiegelt.
    Verwenden Sie dann diesen berechneten Preis zum Aktualisierenself.pricesArray, wirf die ältesten Daten raus (durchshift()Funktion), aktualisieren Sie die neuesten Daten (durchpush()Funktions-, Shift- und Push-Funktionen sind Methoden von Array-Objekten in der JS-Sprache. Weitere Einzelheiten finden Sie in den JS-Informationen. So entstehtself.pricesEin Array ist ein zeitlich geordneter Datenstrom.

Ähm, trink etwas Wasser, ich höre hier jetzt auf, bis zum nächsten Mal~

Related Recommendations
Comment
All comments (21)

    梦总你好,想问一下。self.prices 先填充的15个历史成交价格,然后又填充订单薄前三档加权平均价格. 对吧

    5 years ago

    是的。

    5 years ago

    我想给梦总点个赞

    5 years ago

    可惜了,很多交易所的做市商已经将盘口买一卖一价压到了只剩下一个tick,让策略里这种尝试插入到买一卖一中间的操作没有了意义

    6 years ago

    主要是学习思路,洞察这些可以高频的交易机会。

    6 years ago

    谢谢了,我写了python的版本在山寨币上跑了一下,真是手续费收割机啊。。。不到5分钟20刀没了:)

    6 years ago

    不客气,可以看下草神写的策略原理分析的文章,高频策略是需要一些支持的。

    6 years ago

    太棒了,没有梦总的解释还真的不能完全看懂,感谢大佬的耐心解释!

    6 years ago

    感谢支持~如果喜欢,帮忙分享下,嘿嘿~

    6 years ago

    黄金分割0.618 0.382是用的斐波
    梦总牛批

    6 years ago

    上学那会儿,学到这个黄金分割比例记得特别清楚,说是这样比例长宽的矩形最美~~但是不知道为什么。。。。

    6 years ago

    确实牛批啊这么详细

    6 years ago

    感谢支持!

    6 years ago

    大大的赞,这么详细的解答.感谢分享

    6 years ago

    感谢支持!

    6 years ago

    梦总牛p

    6 years ago

    感谢支持!

    6 years ago

    梦总,牛批!!
    虽然注释了,但是看起来还是非常复杂.....

    6 years ago

    其实不复杂,这次注释的比较啰嗦,逐行都尽量用最容易理解的方式描述了。通篇看下来应该理解不难。

    6 years ago

    梦总,牛批!!

    6 years ago

    感谢支持。

    6 years ago
  • 1
iPhone Download
Forums
PINE Language
© 2015 - ∞ INVENTOR PTE LTD (SG)