Dies ist eine WebSocket-Marktvorlage, die von FMZ offiziell entwickelt wurde, und die als Vorlage kopiert und in der neuen Strategie ausgewählt werden kann:https://www.fmz.com/strategy/470349
Die derzeitige FMZ-Strategie basiert auf der traditionellen REST-API-Umschließung, bei der bei jedem API-Zugriff eine Netzwerkverbindung hergestellt wird, um Marktdaten per Abfrage zu erhalten.
Die REST-Protokoll hat jedoch ein inhärentes Verzögerungsproblem, das vergrößert wird, wenn mehrere Handelspare oder mehrere Börsenstrategien benötigt werden. Obwohl die Go-Funktion der Plattform parallel ausgeführt werden kann, besteht das Verzögerungsproblem, dass es schwierig ist, die Bedürfnisse für relativ hohe Frequenzen für strategische Trades zu befriedigen, und dass zu viele Trades zu schnell werden und die Zugriffsfrequenz der Plattform eingeschränkt wird.
Die Serverbelastung der aktuellen Börsen ist ebenfalls sehr hoch. Sie bieten ein vollwertiges WebSocket-Protokoll und empfehlen API-Benutzern, es zu verwenden. Im Vergleich zum REST-Protokoll bietet WebSocket eine dauerhafte, zwei-Wege-Verbindung, die es den Börsen ermöglicht, Daten in Echtzeit an die Kunden zu schicken, häufige Anfragen und Antworten zu vermeiden und die Verzögerung erheblich zu reduzieren.
Die FMZ-Quantitative-Trading-Plattform unterstützt seit frühem den WebSocket-Protokoll und ist relativ einfach zu bedienen, aber für Neulinge ist es zu kompliziert, mehrere Abonnements zu verarbeiten, mehrere Börsen zu abonnieren und effizient und einfach in den gesamten Strategieprozess eingebunden zu sein. Die öffentlich zugängliche WebSocket Real-Time Market Data Accelerator Template löst diesen Schmerzpunkt, ist sehr einfach zu bedienen, ist vollständig kompatibel mit den aktuell eingebundenen API-Aufrufen, und kann für die meisten ursprünglichen REST-Policies direkt verwendet werden, um Ihre Politik zu beschleunigen.
Die wichtigsten Merkmale:
Beachten Sie, dass diese Strategie mit TypeScript verwendet wird, was ein wenig unbekannt erscheinen könnte, wenn Sie nur mit JavaScript vertraut sind. TypeScript führt Typensysteme und reichere Sprachfunktionen auf der Basis von JavaScript ein, die für Anwendungen wie Quantitative Transaktionen, bei denen komplexe Logiken verarbeitet werden müssen, verwendet werden.
Zusätzlich nutzt die Strategie den Asynchronisierungsmechanismus der FMZ-Plattform, mit dem der Subthread einen Nachricht über die __threadPostMessage-Funktion an den Hauptthread senden kann. Diese Methode ist asynchronisiert und eignet sich für die Benachrichtigung des Hauptthreads über die Datenaktualisierung, die in den Subthreads erzeugt werden. Daten können zwischen dem Hauptthread und den Subthreads über __threadGetData und __threadSetData geteilt werden.
Der Hauptprinzip dieser Strategie besteht darin, über WebSocket die Mainstream-Digital-Währungs-Exchanges zu verbinden und in Echtzeit Marktdaten (z. B. Tiefen- und Transaktionsdaten) zu erhalten, um Daten zur Unterstützung von quantitativen Transaktionsentscheidungen zu liefern.
1. WebSocket-Verbindungseinrichtung
setupWebsocket
Die Funktion wird verwendet, um die WebSocket-Verbindung zu initialisieren und Marktdaten zu empfangen. Sie empfängt einen Parametermain_exchanges
Die Anschlüsse werden von den Benutzern des Netzwerkes angezeigt.
MyDial
Funktionen: Erstellt WebSocket-Verbindungen, zeichnet die Verbindungszeit auf und liefert die Abschaltzeit beim Abschalten der Verbindung.updateSymbols
FunktionenSie überprüfen regelmäßig, ob neue Abonnementanfragen eingegangen sind, und aktualisieren die Liste der aktuellen Transaktionen, wenn nötig.2. Datenverarbeitung
supports
Objekte definieren die unterstützten Börsen und ihre Verarbeitungsfunktionen (z. B.Binance
Die Prozessfunktionen der einzelnen Börsen analysieren die eingegangenen Nachrichten und extrahieren die entsprechenden Daten.
processMsg
Funktionen: Verarbeiten von Messages aus den Börsen, identifizieren verschiedene Arten von Daten (z.B. tiefe Updates, Transaktionen usw.) und formatieren sie in einheitliche Ereignisobjekte;3. Abonnementdaten
Bei jeder Verbindung wird ein Marktdatenkanal erstellt, der auf die aktuelle Transaktion basiert.
getFunction
Funktionen: Erhalten Sie die entsprechenden Verarbeitungsfunktionen nach dem Namen der Börse.this.wssPublic
Funktionen: WebSocket-Verbindung initialieren und Datenempfang starten.4. Threadmanagement
Es wird ein Thread für jede Börse gestartet, die Daten in Echtzeit empfängt und durch Rückruffunktionen verarbeitet wird.
threadMarket
Funktionen: Erhält Daten, analysiert und speichert die neuesten Tiefen- und Transaktionsinformationen in den Subthreads.5. Umschreiben der Datenerfassungsmethode
Die Methode, um die Tiefe und die Transaktionsinformationen für jede Börse neu zu schreiben, wird in Echtzeit priorisiert.
$.setupWebsocket()
Die WebSocket-Verbindung der Zielbörse wird initialisiert.GetDepth()
undGetTrades()
Funktion, die automatisch mit WebSocket-Realtime-Daten Markttiefe und Transaktionsprotokolle zurückgibt.Wenn Sie die EventLoop-Funktion in die Strategie einfügen, wird sie in einen Auslöser umgewandelt, der automatisch sofort abgerufen wird, wenn die WSS-Daten aktualisiert werden, und ohne aktuelle Daten wartet. Äquivalent zur intelligenten Sleep-Funktion.
function main() {
$.setupWebsocket()
while (true) {
exchanges.map(e=>{
Log(e.GetName(), e.GetDepth())
Log(e.GetName(), e.GetTrades())
})
EventLoop(100) // trigger by websocket
}
}
Hier ist mein letzter Artikel über die Strategien für den Handel mit verschiedenen Währungen.https://www.fmz.com/digest-topic/10506Hier ist eine sehr einfache Möglichkeit, um die Unterstützung für WebSockets zu ändern:
function MakeOrder() {
for (let i in Info.trade_symbols) {
let symbol = Info.trade_symbols[i];
let buy_price = exchange.GetDepth(symbol + '_USDT').Asks[0].Price;
let buy_amount = 50 / buy_price;
if (Info.position[symbol].value < 2000){
Trade(symbol, "buy", buy_price, buy_amount, symbol);
}
}
}
function OnTick() {
try {
UpdatePosition();
MakeOrder();
UpdateStatus();
} catch (error) {
Log("循环出错: " + error);
}
}
function main() {
$.setupWebsocket()
InitInfo();
while (true) {
let loop_start_time = Date.now();
if (Date.now() - Info.time.last_loop_time > Info.interval * 1000) {
OnTick();
Info.time.last_loop_time = Date.now();
Info.time.loop_delay = Date.now() - loop_start_time;
}
Sleep(5);
}
}
Wenn Sie die Schablone der Strategie befolgen, können Sie die folgenden Formate kopieren und sich an die API-Dokumentation der Börse wenden:
supports["Binance"] = function (ctx:ICtx) {
let processMsg = function (obj) {
let event = {
ts: obj.E,
instId: obj.s,
depth: null,
trades: [],
}
if (obj.e == "depthUpdate") {
let depth = {
asks: [],
bids: []
}
obj.b.forEach(function (item) {
depth.bids.push({
price: Number(item[0]),
qty: Number(item[1])
})
})
obj.a.forEach(function (item) {
depth.asks.push({
price: Number(item[0]),
qty: Number(item[1])
})
})
event.depth = depth
} else if (obj.e == 'bookTicker') {
event.depth = {
asks: [{ price: Number(obj.a), qty: Number(obj.A) }],
bids: [{ price: Number(obj.b), qty: Number(obj.B) }]
}
} else if (obj.e == 'aggTrade') {
event.ts = obj.E
event.trades = [{
price: Number(obj.p),
qty: Number(obj.q),
ts: obj.T,
side: obj.m ? "sell" : "buy"
}]
} else if (typeof (obj.asks) !== 'undefined') {
event.ts = obj.E || new Date().getTime()
let depth = {
asks: [],
bids: []
}
obj.bids.forEach(function (item) {
depth.bids.push({
price: Number(item[0]),
qty: Number(item[1])
})
})
obj.asks.forEach(function (item) {
depth.asks.push({
price: Number(item[0]),
qty: Number(item[1])
})
})
event.depth = depth
} else {
return
}
return event
}
let channels = ["depth20@100ms", /*"bookTicker", */"aggTrade"]
let ws = null
let endPoint = "wss://stream.binance.com/stream"
if (ctx.name == "Futures_Binance") {
endPoint = "wss://fstream.binance.com/stream"
}
while (true) {
if (!ws) {
let subscribes = []
ctx.symbols.forEach(function (symbol) {
channels.forEach(function (channel) {
subscribes.push(symbol.toLowerCase() + "@" + channel)
})
})
ws = MyDial(endPoint + (subscribes.length > 0 ? ("?streams=" + subscribes.join("/")) : ""))
}
if (!ws) {
Sleep(1000)
continue
}
updateSymbols(ctx, function(symbol:string, method:string) {
ws.write(JSON.stringify({
"method": method.toUpperCase(),
"params": channels.map(c=>symbol.toLowerCase()+'@'+c),
"id": 2
}))
})
let msg = ws.read(1000)
if (!msg) {
if (msg == "") {
trace("websocket is closed")
ws.close()
ws = null
}
continue
}
if (msg == 'ping') {
ws.write('pong')
} else if (msg == 'pong') {
} else {
let obj = JSON.parse(msg)
if (obj.error) {
trace(obj.error.msg, "#ff0000")
continue
}
if (!obj.stream) {
continue
}
if (obj.stream.indexOf("depth") != -1) {
if (typeof(obj.data.s) !== 'string') {
// patch
obj.data.s = obj.stream.split('@')[0].toUpperCase()
}
}
let event = processMsg(obj.data)
if (event) {
ctx.callback(event)
}
}
}
}