Les ressources ont été chargées... Je charge...

Un modèle de stratégie vous permet d'utiliser WebSocket sans heurts

Auteur:Le foin, Créé: 2024-10-30 09:49:20, mis à jour: 2024-11-05 17:45:31

img

Il s'agit d'un modèle de marché WebSocket officiellement développé par FMZ, qui peut être copié et sauvegardé comme modèle en cliquant sur ce modèle dans la nouvelle stratégie:https://www.fmz.com/strategy/470349

Pourquoi WebSocket?

La stratégie actuelle de FMZ est principalement l'emballage des API REST traditionnelles, qui permettent d'établir une connexion réseau à chaque étape d'accès à l'API pour obtenir des données de marché par consultation. Cette méthode est simple et facile à utiliser et suffit parfaitement pour la plupart des besoins.

Cependant, le protocole REST présente des problèmes de retard inhérents, qui sont amplifiés lorsque plusieurs paires de transactions ou plusieurs stratégies d'échange sont nécessaires. Bien que la fonction Go de la plate-forme puisse être exécutée en parallèle, le problème de retard persiste, car il est difficile de répondre aux besoins de transactions stratégiques à une fréquence relativement élevée, et les transactions sont trop nombreuses, la fréquence de consultation est trop rapide et la fréquence d'accès à la plate-forme est limitée.

Les échanges actuels sont également très lourds sur le serveur, offrant des protocoles WebSocket parfaits et recommandés aux utilisateurs d'API. Par rapport au protocole REST, le WebSocket offre une connexion bidirectionnelle durable qui permet aux échanges de pousser des données vers le client en temps réel, en évitant des demandes et des réponses fréquentes, ce qui réduit considérablement les retards. En général, si le délai d'accès à l'API REST est d'environ 20 ms, le WebSocket a probablement un retard de 2 ms.

Modèle de secteur WebSocket

La plate-forme de trading quantitative FMZ a été très tôt compatible avec le protocole WebSocket et est relativement facile à appeler, mais pour les utilisateurs débutants, il est trop compliqué de traiter plusieurs abonnements, de souscrire à plusieurs marchés d'échanges et d'être intégré efficacement et facilement dans l'ensemble du processus stratégique. Le modèle public WebSocket pour l'accélération des données de marché en temps réel, qui est très facile à utiliser, est entièrement compatible avec les appels d'API actuellement enveloppés. Pour la plupart des stratégies REST originales, il suffit de modifier directement l'utilisation et d'accélérer votre stratégie.

Les principales caractéristiques:

  • Le soutien de plusieurs marchésCette politique permet de connecter WebSocket à plusieurs plateformes comme Bitcoin, OKX, ByBit, Bitget, etc. Les utilisateurs peuvent imiter la méthode d'emballage de ce modèle pour prendre en charge plus d'exchanges.
  • Les abonnements peuvent être personnalisésLes canaux permettent de s'abonner à des marchés spécifiques (tels que la profondeur, les transactions, etc.) et de traiter efficacement les données reçues pour une stratégie de trading instantanée.
  • Le traitement des erreurs de haut niveauLes mécanismes d'erreur de suivi et de reconnexion WebSocket sont intégrés pour assurer la fiabilité et la continuité du flux de données.

Une introduction simple à la mise en œuvre

Notez que cette stratégie utilise TypeScript, ce qui peut sembler un peu étrange si vous n'êtes qu'un habitué de JavaScript. TypeScript introduit un système de types et des fonctionnalités linguistiques plus riches basées sur JavaScript, pour des applications qui nécessitent un traitement de la logique complexe comme les transactions quantitatives.

En outre, la stratégie utilise le mécanisme d'asynchronisation de la plate-forme FMZ, où les fils peuvent envoyer des messages au fil principal via la fonction __threadPostMessage. Cette méthode est asynchrone et s'applique aux fils principaux pour les notifications de mise à jour des données générées dans les fils. Les données peuvent être partagées entre le fil principal et les fils principaux via les fonctions __threadGetData et __threadSetData. Cette méthode permet aux fils d'accéder et de modifier l'état de partage.

Le principe principal de cette stratégie est de connecter les échanges de devises numériques traditionnelles via WebSocket pour recevoir des données de marché en temps réel (comme des informations de profondeur et des informations de transaction) afin de fournir un support de données pour des décisions de transaction quantitatives.

1. Configuration des connexions WebSocket

setupWebsocketUne fonction est utilisée pour initialement connecter un WebSocket et recevoir des données de marché. Elle reçoit un paramètremain_exchangesLes échanges en ligne sont des échanges en ligne, ce qui signifie que vous devez être connecté.

  • MyDialLes fonctions: crée une connexion WebSocket, enregistre le temps de connexion, et exécute le temps de fermeture lorsque la connexion est fermée.
  • updateSymbolsLes fonctions• Vérifiez régulièrement s'il y a de nouvelles demandes d'abonnement et mettez à jour la liste des transactions en cours si nécessaire.

2. Traitement des données

supportsLes objets définissent les échanges pris en charge et leurs fonctions de traitement (par exemple:BinanceLes fonctions de traitement de chaque échange sont chargées d'analyser les messages reçus et d'extraire les données pertinentes.

  • processMsgLes fonctionsLes informations fournies par les échanges sont traitées pour identifier les différents types de données (comme les mises à jour en profondeur, les transactions, etc.) et les formater en objets d'événements unifiés.

3. Données d'abonnement

À chaque connexion, le système trace les canaux de données de marché liés aux abonnements en fonction de la transaction en cours.

  • getFunctionLes fonctionsLes fonctions de traitement correspondantes sont obtenues selon le nom de l'échange.
  • this.wssPublicLes fonctions: initialize la connexion WebSocket et démarre la réception de données.

4. Gestion des fils

Pour chaque échange, un fil est lancé, les données sont reçues en temps réel et traitées par des fonctions de rappel.

  • threadMarketLes fonctions: recevoir des données, analyser et stocker les dernières informations de profondeur et de transaction dans les sous-thèmes;

5. Réécrire les méthodes d'acquisition des données

Pour chaque échange, réécrivez la méthode d'accès à la profondeur et à l'information sur les transactions, en donnant la priorité aux données mises à jour en temps réel.

Comment utiliser les modèles

  1. InitialementUtilisation:$.setupWebsocket()Initializez la connexion WebSocket de l'échange cible.
  2. Les abonnésLe système s'abonne automatiquement aux chaînes correspondant à la variété que vous négociez (comme la profondeur, les transactions, etc.).
  3. Acquisition de donnéesEn appelant:GetDepth()etGetTrades()Fonction qui utilise automatiquement les données WebSocket en temps réel pour effectuer des retours de profondeur de marché et de transaction.
  4. Une mauvaise gestionLa stratégie comprend un mécanisme de suivi pour enregistrer les erreurs de connexion et de données et essayer automatiquement de reconnecter lorsque la connexion est interrompue.

Si vous ajoutez la fonction EventLoop à votre stratégie, elle se transforme en un mécanisme de déclenchement qui est automatiquement récupéré immédiatement lorsque les données sont mises à jour.

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

Je vous invite à lire mon précédent guide sur les stratégies de trading en devises.https://www.fmz.com/digest-topic/10506Il est très facile de modifier la prise en charge de WebSocket ici:

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

Comment ajouter des échanges personnels

Si vous suivez le modèle de la stratégie, vous pouvez copier le format ci-dessous et consulter la documentation de l'API de l'échange:

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

Plus de