3
konzentrieren Sie sich auf
1238
Anhänger

Eine Richtlinienvorlage ermöglicht Ihnen die nahtlose Nutzung des WebSocket-Marktes

Erstellt in: 2024-10-30 09:49:20, aktualisiert am: 2024-11-05 17:45:31
comments   0
hits   604

Eine Richtlinienvorlage ermöglicht Ihnen die nahtlose Nutzung des WebSocket-Marktes

Dies ist eine offiziell von FMZ entwickelte WebSocket-Marktvorlage. Kopieren und speichern Sie sie als Vorlage. Wählen Sie diese Vorlage dann in der neuen Strategie aus, um sie zu verwenden: https://www.fmz.com/strategy/470349

Warum brauchen wir WebSocket?

Derzeit basiert die FMZ-Strategie hauptsächlich auf der traditionellen REST-API-Kapselung. Jeder API-Zugriff erfordert das Herstellen einer Netzwerkverbindung und das Abrufen von Marktdaten durch Polling. Diese Methode ist einfach und leicht anzuwenden und reicht für die meisten Anforderungen aus.

Das REST-Protokoll weist jedoch inhärente Latenzprobleme auf, die sich noch verstärken, wenn mehrere Handelspaare und mehrere Austauschstrategien erforderlich sind. Obwohl die Go-Funktionen der Plattform gleichzeitig ausgeführt werden können, besteht immer noch das Verzögerungsproblem, was es schwierig macht, die Anforderungen des relativ hochfrequenten Strategiehandels zu erfüllen. Darüber hinaus, wenn es zu viele Handelspaare gibt und die Abfragefrequenz zu hoch ist schnell, wird es an die Zugriffshäufigkeitsbeschränkung der Handelsplattform stoßen. .

Derzeit sind auch die Server der Börsen stark ausgelastet. Sie alle bieten ein vollständiges WebSocket-Protokoll an und empfehlen es API-Benutzern. Im Vergleich zum REST-Protokoll bietet WebSocket eine dauerhafte bidirektionale Verbindungsmethode, die es dem Austausch ermöglicht, Daten in Echtzeit an den Client zu übertragen, wodurch häufige Anforderungen und Antworten vermieden und die Latenz erheblich reduziert wird. Im Allgemeinen gilt: Wenn die Latenz beim Zugriff auf die REST-API etwa 20 ms beträgt, liegt die Latenz beim Pushen von Daten über WebSocket bei etwa 2 ms. Darüber hinaus ist das WebSocket-Protokoll nicht durch die Zugriffshäufigkeit der Plattform beschränkt und es ist grundsätzlich möglich, Dutzende von Handelspaaren gleichzeitig zu abonnieren.

Einführung in die WebSocket-Angebotsvorlage

Die quantitative Handelsplattform von FMZ unterstützt das WebSocket-Protokoll schon seit langem und ist relativ bequem aufzurufen. Für unerfahrene Benutzer ist es jedoch immer noch zu kompliziert, mehrere Abonnements zu verwalten, mehrere Börsenkurse zu abonnieren und diese effizient und bequem in die Plattform einzubetten. gesamten Strategieprozess. . Diese öffentliche WebSocket-Vorlage zur Beschleunigung von Marktdaten in Echtzeit löst dieses Problem. Sie ist sehr einfach zu verwenden und vollständig kompatibel mit den aktuellen gekapselten API-Aufrufen. Die meisten der ursprünglichen REST-Strategien können Sie einfach ändern und direkt zur Beschleunigung verwenden. Ihre Strategie.

Hauptmerkmale:

  • Unterstützung mehrerer Börsen: Diese Strategie unterstützt WebSocket-Verbindungen mehrerer Börsen wie Binance, OKX, Bybit, Bitget usw. Benutzer können die Verpackungsmethode dieser Vorlage imitieren, um selbst mehr Börsen zu unterstützen.
  • Anpassbares Abonnement: Ermöglicht das Abonnement bestimmter Marktkanäle (wie Tiefe, Handel usw.) und die effiziente Verarbeitung empfangener Daten zur sofortigen Verwendung durch Handelsstrategien.
  • Erweiterte Fehlerbehandlung: Integrierte Fehlerverfolgung und WebSocket-Wiederverbindungsmechanismus, um die Zuverlässigkeit und Kontinuität des Datenflusses sicherzustellen.

Eine kurze Einführung in das Implementierungsprinzip

Beachten Sie, dass diese Strategie TypeScript verwendet, das Ihnen möglicherweise etwas ungewohnt vorkommt, wenn Sie nur mit JavaScript vertraut sind. TypeScript führt ein Typsystem und umfangreichere Sprachfunktionen auf Basis von JavaScript ein. Bei Anwendungen wie dem quantitativen Handel, die komplexe Logik verarbeiten müssen, kann die Verwendung von TypeScript potenzielle Fehler reduzieren und die Lesbarkeit und Wartbarkeit des Codes verbessern. Daher ist es empfehlenswert, es einfach zu lernen.

Darüber hinaus verwendet die Strategie den asynchronen Mechanismus der FMZ-Plattform, und der Mechanismus-Subthread kannDie Funktion threadPostMessage sendet eine Nachricht an den Hauptthread. Diese Methode ist asynchron und eignet sich dazu, den Hauptthread über im untergeordneten Thread generierte Datenaktualisierungen zu benachrichtigen. Der Haupt-Thread und der untergeordnete Thread können verbunden werden durchthreadGetData und__Die Funktion threadSetData gibt Daten frei. Dieser Ansatz ermöglicht es Threads, auf den gemeinsamen Status zuzugreifen und ihn zu ändern. Wenn Sie etwas über Multithreading lernen möchten, ist diese Strategie in Verbindung mit der Plattformdokumentation auch ein gutes Lernbeispiel.

Das Hauptprinzip dieser Strategie besteht darin, sich über WebSocket mit den gängigen digitalen Währungsbörsen zu verbinden und Marktdaten (wie Tiefeninformationen und Transaktionsinformationen) in Echtzeit zu empfangen, um Datenunterstützung für quantitative Handelsentscheidungen bereitzustellen. Der konkrete Implementierungsprozess läuft wie folgt ab:

1. WebSocket-Verbindungseinstellungen

setupWebsocket Diese Funktion wird verwendet, um eine WebSocket-Verbindung zu initialisieren und Marktdaten zu empfangen. Es empfängt einen Parametermain_exchanges, die die Vermittlungsstelle angibt, die verbunden werden muss.

  • MyDial Funktion: Erstellen Sie eine WebSocket-Verbindung, zeichnen Sie die Verbindungszeit auf und geben Sie die Schließzeit aus, wenn die Verbindung geschlossen wird.
  • updateSymbols Funktion: Prüfen Sie regelmäßig, ob neue Abonnementanfragen vorliegen, und aktualisieren Sie die aktuelle Handelspaarliste nach Bedarf.

2. Datenverarbeitung

supports Das Objekt definiert die unterstützten Börsen und ihre Verarbeitungsfunktionen (wieBinance). Die Verarbeitungsfunktion jeder Börse ist für das Parsen der empfangenen Nachrichten und das Extrahieren relevanter Daten verantwortlich.

  • processMsg Funktion: Verarbeiten Sie Nachrichten von Börsen, identifizieren Sie verschiedene Datentypen (z. B. Tiefenaktualisierungen, Transaktionen usw.) und formatieren Sie sie in einheitliche Ereignisobjekte.

3. Abonnementdaten

Bei jeder Verbindung abonniert das System die relevanten Marktdatenkanäle basierend auf dem aktuellen Handelspaar.

  • getFunction Funktion: Holen Sie sich die entsprechende Verarbeitungsfunktion entsprechend dem Börsennamen.
  • this.wssPublic Funktion: Initialisieren Sie die WebSocket-Verbindung und beginnen Sie mit dem Empfangen von Daten.

4. Thread-Verwaltung

Starten Sie für jeden Austausch einen Thread, empfangen Sie Daten in Echtzeit und verarbeiten Sie die Daten über Rückruffunktionen.

  • threadMarket Funktion: Daten im untergeordneten Thread empfangen, die neuesten Tiefen- und Transaktionsinformationen analysieren und speichern.

5. Schreiben Sie die Datenerfassungsmethode neu

Schreiben Sie die Methoden zum Abrufen von Tiefen- und Handelsinformationen für jede Börse neu und geben Sie dabei der Rückgabe von in Echtzeit aktualisierten Daten den Vorzug.

So verwenden Sie die Vorlage

  1. Initialisierung:verwenden $.setupWebsocket() Initialisieren Sie die WebSocket-Verbindung zum Zielaustausch.
  2. Abonnement: Das System abonniert automatisch relevante Kanäle (wie Tiefe, Handel usw.) für die von Ihnen gehandelten Produkte.
  3. Datenerfassung: Durch einen AnrufGetDepth() Und GetTrades() Funktion, verwendet automatisch WebSocket-Echtzeitdaten, um Markttiefe und Transaktionsaufzeichnungen zurückzugeben.
  4. Fehlerbehandlung: Die Richtlinie umfasst einen Tracking-Mechanismus, der Verbindungs- und Datenfehler protokolliert und automatisch versucht, die Verbindung wiederherzustellen, wenn die Verbindung verloren geht.

Wenn der Strategie die Funktion EventLoop () hinzugefügt wird, wird sie in einen Auslösemechanismus geändert. Wenn die WSS-Daten aktualisiert werden, werden sie automatisch sofort abgerufen, und wenn keine neuesten Daten vorhanden sind, wird gewartet. Es entspricht einer intelligenten Sleep-Funktion. Natürlich kannst du Sleep auch direkt nutzen.

function main() {
    $.setupWebsocket()
    while (true) {
        exchanges.map(e=>{
            Log(e.GetName(), e.GetDepth())
            Log(e.GetName(), e.GetTrades())
        })
        EventLoop(100) // trigger by websocket
    }
}

Weitere Informationen finden Sie in meinem vorherigen Leitfaden zu Handelsstrategien für mehrere Währungen https://www.fmz.com/digest-topic/10506. Dort können Sie ihn ganz einfach zur Unterstützung von WebSocket ä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);
    }
}

So fügen Sie selbst eine neue Börse hinzu

Folgen Sie einfach der Strategievorlage, ahmen Sie das folgende Format nach und lesen Sie die Dokumentation der Exchange-API:

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