Ich schrieb 2020 einen Artikel, in dem ich Hochfrequenz-Handelsstrategien vorstellte (https://www.fmz.com/digest-topic/6228) Obwohl es einige Aufmerksamkeit erhielt, war es nicht sehr tiefgründig. Seitdem sind zweieinhalb Jahre vergangen, und der Markt hat sich geändert. Nachdem ich diesen Artikel veröffentlicht habe, konnte meine Hochfrequenzstrategie lange Zeit stabile Gewinne erzielen, aber die Gewinne gingen allmählich zurück und hielten sogar an einem Punkt an. In den letzten Monaten habe ich Zeit damit verbracht, sie zu überarbeiten, und sie kann immer noch einige kleine Gewinne erzielen. Dieser Artikel wird eine detailliertere Einführung in meine Hochfrequenzhandelsstrategie und einen vereinfachten Code geben, der als Ausgangspunkt für Diskussionen und Feedback dient.
Rückstellungsrechnungen der Kommission
Binance bietet derzeit einen Maker-Rabatt von 0,05% pro 100.000 gehandelten Einheiten an. Wenn das tägliche Handelsvolumen 100 Millionen U beträgt, beträgt der Rabatt 5.000 U. Natürlich hängt die Abnahmegebühr immer noch von der VIP-Rate ab, so dass, wenn die Strategie keine Aufträge annehmen muss, das VIP-Niveau wenig Auswirkungen auf die Hochfrequenzstrategie hat. Verschiedene Niveaus von Provisionsrabaten sind auf verschiedenen Börsen verfügbar, was ein hohes Handelsvolumen erfordert. In den frühen Tagen konnte immer noch ohne Rabatte Gewinn erzielt werden, aber als der Wettbewerb sich verschärfte, machten Rabatte einen größeren Prozentsatz des Gewinns aus, und Hochfrequenzhändler verfolgten die Spitzenraten.
Geschwindigkeit
Der Hochfrequenzhandel wird wegen seiner schnellen Geschwindigkeit so genannt. Der Beitritt zu einem Colocation-Server einer Handelsbörse und das Erhalten der niedrigsten Latenzzeit und der stabilsten Verbindung ist eine der Wettbewerbsbedingungen geworden. Die interne Verarbeitungszeit der Strategie sollte auch so gering wie möglich sein. In diesem Artikel werde ich das von mir verwendete WebSocket-Framework vorstellen, das gleichzeitige Ausführung verwendet.
Geeigneter Markt
Hochfrequenzhandel gilt als das Juwel in der Krone des quantitativen Handels, und ich glaube, dass viele algorithmische Trader es versucht haben, aber die meisten Menschen hätten aufhören sollen, weil sie kein Geld verdienen konnten und keinen Weg zur Verbesserung finden konnten. Der Hauptgrund ist wahrscheinlich, dass sie den falschen Handelsmarkt gewählt haben. In der Anfangsphase der Strategie sollten relativ einfache Märkte für den Handel gezielt profitieren und Feedback für die Verbesserung erhalten, was dem Fortschritt der Strategie förderlich ist. Wenn Sie auf dem hart umkämpften Markt beginnen und mit vielen Gegnern konkurrieren, werden Sie Geld verlieren, egal wie hart Sie es versuchen, und Sie werden schnell aufgeben. Ich empfehle, mit neu eingeführten Perpetual Contract-Handelspaaren zu beginnen, wo es weniger Wettbewerber gibt, insbesondere diejenigen mit relativ großen Handelsvolumina, was es einfacher macht, Geld zu verdienen. BTC und ETH haben die höchsten und aktivsten Handelsvolumina, sind aber auch am schwierigsten im Potenzial zu überleben.
Der Konkurrenz gegenüber
Der Markt für jeden Handel verändert sich ständig, und keine Handelsstrategie kann eine einmalige Lösung sein. Dies ist noch offensichtlicher im Hochfrequenzhandel, wo der Markteintritt bedeutet, direkt mit den klügsten und fleißigsten Händlern zu konkurrieren. In einem Nullsummenspielmarkt verdienen Sie je mehr, desto weniger verdienen andere. Je später Sie eintreten, desto schwieriger wird es, und diejenigen, die bereits auf dem Markt sind, müssen sich ständig verbessern und jederzeit eliminiert werden können. Vor drei oder vier Jahren war wahrscheinlich die beste Gelegenheit, aber mit dem jüngsten allgemeinen Rückgang der Aktivität auf dem digitalen Währungsmarkt ist es für Anfänger sehr schwierig geworden, mit dem Hochfrequenzhandel zu beginnen.
Es gibt mehrere Hochfrequenz-Handelsstrategien, wie zum Beispiel Hochfrequenz-Arbitrage, bei dem es darum geht, durch diesen oder andere Börsen Arbitragechancen zu finden, die Gelegenheit zu nutzen, Aufträge vor anderen zu verbrauchen und mit Geschwindigkeitsvorteil Gewinne zu erzielen; Hochfrequenz-Trendhandel, bei dem es darum geht, von kurzfristigen Trends zu profitieren; und Marketmaking, bei dem es darum geht, Aufträge auf beiden Seiten der Kauf- und Verkaufstransaktionen zu platzieren, Positionen gut zu kontrollieren und durch Provisionsrabate Gewinne zu erzielen. Meine Strategie kombiniert Trend und Marketmaking, zunächst Trends zu identifizieren und dann Aufträge zu platzieren, sofort nach der Ausführung zu verkaufen und keine Lagerpositionen zu halten. Im Folgenden finden Sie eine Einführung in den Strategiecode.
Der folgende Code basiert auf der Grundarchitektur des Binance-Perpetual-Kontrakts und abonniert hauptsächlich die Websocket-Tiefe-Orderfluss-Trades und Positionsinformationen. Da Marktdaten und Kontoinformationen separat abonniert werden, muss das Lesen (-1) kontinuierlich verwendet werden, um festzustellen, ob die neuesten Informationen erhalten wurden. Hier wird EventLoop (1000) verwendet, um direkte tote Schleifen zu vermeiden und die Systembelastung zu reduzieren. EventLoop (1000) blockiert, bis es eine wss oder gleichzeitige Aufgabenrückgabe mit einem Timeout von 1000 ms gibt.
var datastream = null
var tickerstream = null
var update_listenKey_time = 0
function ConncetWss(){
if (Date.now() - update_listenKey_time < 50*60*1000) {
return
}
if(datastream || tickerstream){
datastream.close()
tickerstream.close()
}
// need APIKEY
let req = HttpQuery(Base+'/fapi/v1/listenKey', {method: 'POST',data: ''}, null, 'X-MBX-APIKEY:' + APIKEY)
let listenKey = JSON.parse(req).listenKey
datastream = Dial("wss://fstream.binance.com/ws/" + listenKey + '|reconnect=true', 60)
// Symbols is the pair of symbol
let trade_symbols_string = Symbols.toLowerCase().split(',')
let wss_url = "wss://fstream.binance.com/stream?streams="+trade_symbols_string.join(Quote.toLowerCase()+"@aggTrade/")+Quote.toLowerCase()+"@aggTrade/"+trade_symbols_string.join(Quote.toLowerCase()+"@depth20@100ms/")+Quote.toLowerCase()+"@depth20@100ms"
tickerstream = Dial(wss_url+"|reconnect=true", 60)
update_listenKey_time = Date.now()
}
function ReadWss(){
let data = datastream.read(-1)
let ticker = tickerstream.read(-1)
while(data){
data = JSON.parse(data)
if (data.e == 'ACCOUNT_UPDATE') {
updateWsPosition(data)
}
if (data.e == 'ORDER_TRADE_UPDATE'){
updateWsOrder(data)
}
data = datastream.read(-1)
}
while(ticker){
ticker = JSON.parse(ticker).data
if(ticker.e == 'aggTrade'){
updateWsTrades(ticker)
}
if(ticker.e == 'depthUpdate'){
updateWsDepth(ticker)
}
ticker = tickerstream.read(-1)
}
makerOrder()
}
function main() {
while(true){
ConncetWss()
ReadWss()
worker()
updateStatus()
EventLoop(1000)
}
}
Wie bereits erwähnt, erfordert meine Hochfrequenzstrategie zunächst die Identifizierung von Trends vor der Ausführung von Kauf- und Verkaufstransaktionen. Das Beurteilen von kurzfristigen Trends basiert hauptsächlich auf Transaktionsdaten, d.h. dem AggTrade-Abonnement, das die Richtung, den Preis, die Menge und die Transaktionszeit umfasst. Kauf- und Verkaufstransaktionen beziehen sich hauptsächlich auf Tiefe und Transaktionsvolumen. Nachfolgend sind detaillierte Indikatoren aufgeführt, die berücksichtigt werden müssen, von denen die meisten in zwei Gruppen für Kauf und Verkauf unterteilt sind und innerhalb eines bestimmten Zeitfensters dynamisch gezählt werden. Mein Strategie-Zeitfenster liegt innerhalb von 10 Sekunden.
let bull = last_sell_price > avg_sell_price && last_buy_price > avg_buy_price &&
avg_buy_amount / avg_buy_time > avg_sell_amount / avg_sell_time;
let bear = last_sell_price < avg_sell_price && last_buy_price < avg_buy_price &&
avg_buy_amount / avg_buy_time < avg_sell_amount / avg_sell_time;
Wenn der letzte Angebotspreis größer ist als der durchschnittliche Angebotspreis und der letzte Angebotspreis größer ist als der durchschnittliche Angebotspreis und der Wert des Kaufauftrages größer ist als der Wert des Verkaufsauftrages in einem festen Intervall, dann gilt es als kurzfristiger bullischer Markt.
function updatePrice(depth, bid_amount, ask_amount) {
let buy_price = 0
let sell_price = 0
let acc_bid_amount = 0
let acc_ask_amount = 0
for (let i = 0; i < Math.min(depth.asks.length, depth.bids.length); i++) {
acc_bid_amount += parseFloat(depth.bids[i][1])
acc_ask_amount += parseFloat(depth.asks[i][1])
if (acc_bid_amount > bid_amount && buy_price == 0) {
buy_price = parseFloat(depth.bids[i][0]) + tick_size
}
if (acc_ask_amount > ask_amount && sell_price == 0) {
sell_price = parseFloat(depth.asks[i][0]) - tick_size
}
if (buy_price > 0 && sell_price > 0) {
break
}
}
return [buy_price, sell_price]
}
Hier wird die alte Methode der Iteration Tiefe auf die erforderliche Menge noch verwendet. Angenommen, dass eine Kauf-Order, die für 10 Münzen innerhalb von 1 Sekunde ausgeführt werden kann und ohne Berücksichtigung der Situation der neuen Aufträge, wird der Verkaufspreis auf die Position gesetzt, wo die Kauf-Order
Die spezifische Zeitfenstergröße muss man selbst festlegen.
let buy_amount = Ratio * avg_sell_amount / avg_sell_time
let sell_amount = Ratio * avg_buy_amount / avg_buy_time
Die Ratio stellt einen festen Anteil der letzten Verkaufsbestellungsmenge dar, wobei die Kaufbestellungsmenge als fester Anteil der letzten Verkaufsbestellungsmenge dargestellt wird.
if(bull && (sell_price-buy_price) > N * avg_diff) {
trade('buy', buy_price, buy_amount)
}else if(position.amount < 0){
trade('buy', buy_price, -position.amount)
}
if(bear && (sell_price-buy_price) > N * avg_diff) {
trade('sell', sell_price, sell_amount)
}else if(position.amount > 0){
trade('sell', sell_price, position.amount)
}
Der Durchschnittliche Differenzspreis ist der Durchschnittsdifferenzspreis, und nur wenn die Kauf- und Verkaufsdifferenz bei der Auftragserteilung größer als ein bestimmtes Vielfaches dieses Wertes ist und der Markt bullisch ist, wird eine Kauforder platziert. Wenn eine Shortposition gehalten wird, wird die Position auch geschlossen, um zu vermeiden, dass die Position lange gehalten wird. Nur Maker-Orders können platziert werden, um sicherzustellen, dass die Aufträge ausgefüllt werden, und benutzerdefinierte Order-IDs können verwendet werden, um zu vermeiden, auf Auftragsergebnisse zu warten.
var tasks = []
var jobs = []
function worker(){
let new_jobs = []
for(let i=0; i<tasks.length; i++){
let task = tasks[i]
jobs.push(exchange.Go.apply(this, task.param))
}
_.each(jobs, function(t){
let ret = t.wait(-1)
if(ret === undefined){
new_jobs.push(t)//未返回的任务下次继续等待
}
})
jobs = new_jobs
tasks = []
}
/*
tasks.push({'type':'order','param': ["IO", "api", "POST","/fapi/v1/order",
"symbol="+symbol+Quote+"&side="+side+"&type=LIMIT&timeInForce=GTX&quantity="+
amount+"&price="+price+"&newClientOrderId=" + UUID() +"×tamp="+Date.now()]})
*/