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

Discussion sur la réception de signaux externes de la plateforme FMZ: API étendue VS stratégie intégrée au service HTTP

Auteur:FMZ~Lydia, Créé à: 2024-12-13 13:12:31, Mis à jour à: 2024-12-16 11:32:35

img

Préface

Il y a plusieurs articles dans la plateforme Digest sur la connexion aux webhooks de Trading View, ce qui permet aux stratégies de conduire les transactions avec des signaux provenant de systèmes externes.CommandRobot, en termes simples, la demande http/https du signal externe est envoyée à la plateforme FMZ, et la plateforme transmet le signal en tant que message d'interaction stratégique au programme stratégique.

Au fur et à mesure que la plateforme se développe et s'itère, de nombreuses nouvelles fonctionnalités ont été mises à jour et améliorées. Il existe également de nouvelles solutions pour recevoir des signaux externes. Chaque solution a ses propres avantages. Dans cet article, nous discuterons de ce sujet ensemble.

Utilisez la plateforme FMZ pour étendre l'interface API

Les avantages de l'utilisation de cette méthode pour se connecter à des systèmes externes sont qu'elle est relativement simple, hautement sécurisée et repose sur la stabilité de l'interface API étendue de la plateforme.

Le processus de réception des signaux externes:

Système externe (Trading View webhook) > FMZ service étendu d'API > Stratégie de négociation en direct

  1. Système externe (Trading View webhook): Par exemple, le script PINE exécuté sur Trading View peut déclencher une alarme et, lorsqu'il est déclenché, il envoie une requête http à l'adresse url du webhook définie comme signal.
  2. Service d'API étendu FMZ: Après avoir accédé à l'interface avec succès, la plateforme transmet les informations et les envoie à la stratégie de trading en direct sous forme de message interactif.
  3. Stratégie de trading en direct: dans la stratégie de trading en direct, vous pouvez concevoir la fonction GetCommand pour écouter le message interactif et exécuter l'opération spécifiée après avoir détecté le message.

Par rapport à l'utilisation du service Http intégré pour créer un service pour recevoir des signaux directement, il y a une étape supplémentaire au milieu (transfert de plate-forme).

Stratégie Service HTTP intégré

Une fois que la plateforme prend en charge la fonction de service Http intégrée du langage JavaScript, vous pouvez créer un service concurrent pour écouter directement les signaux externes. Les avantages sont les suivants: le service Http créé est un fil distinct et n'affecte pas la logique de la fonction principale. Il peut écouter des messages comme la fonction GetCommand et écouter directement les signaux externes. Par rapport à l'utilisation de la solution API étendue, il élimine le besoin de transit.

Le processus de réception des signaux externes:

Système externe (Trading View webhook) > Stratégie négociation en direct

  1. Système externe (Trading View webhook): Par exemple, le script PINE exécuté sur Trading View peut déclencher une alarme, qui enverra une demande http à l'adresse url du webhook défini en tant que signal lorsqu'elle est déclenchée.
  2. Stratégie de trading en direct: La stratégie exécute simultanément un service HTTP pour recevoir directement des signaux externes.

Cette solution permet d'économiser une étape, mais pour améliorer la sécurité, il est préférable de configurer le service https, ce qui nécessite un certain effort.

Code de test

Testez deux solutions. La stratégie suivante enverra 10 requêtes Http/Https en parallèle dans chaque cycle pour simuler des signaux externes. Ensuite, la stratégie surveille messages d'interaction et messages poussés par des threads de service Http. Ensuite, le programme de stratégie correspond aux messages de signal externes et aux signaux reçus un par un, détecte s'il y a une perte de signal et calcule la consommation de temps.

var httpUrl = "http://123.123.123.123:8088/CommandRobot"
var accessKey = ""
var secretKey = ""

function serverFunc(ctx) {
    var path = ctx.path()
    if (path == "/CommandRobot") {
        var body = ctx.body()
        threading.mainThread().postMessage(body)
        ctx.write("OK")
        // 200
    } else {
        ctx.setStatus(404)
    }
}

function createMsgTester(accessKey, secretKey, httpUrl) {
    var tester = {}
    
    tester.currentRobotId = _G()
    tester.arrSendMsgByAPI = []
    tester.arrSendMsgByHttp = []
    tester.arrEchoMsgByAPI = []
    tester.arrEchoMsgByHttp = []
    tester.idByAPI = 0
    tester.idByHttp = 0

    var sendMsgByAPI = function(msgByAPI, robotId, accessKey, secretKey) {
        var headers = {
            "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.153 Safari/537.36",
            "Content-Type": "application/json"
        }
        HttpQuery(`https://www.fmz.com/api/v1?access_key=${accessKey}&secret_key=${secretKey}&method=CommandRobot&args=[${robotId},+""]`, {"method": "POST", "body": JSON.stringify(msgByAPI), "headers": headers})
    }

    var sendMsgByHttp = function(msgByHttp, httpUrl) {
        HttpQuery(httpUrl, {"method": "POST", "body": JSON.stringify(msgByHttp)})
    }

    tester.run = function() {
        var robotId = tester.currentRobotId

        for (var i = 0; i < 10; i++) {
            var msgByAPI = {"ts": new Date().getTime(), "id": tester.idByAPI, "way": "ByAPI"}            
            tester.arrSendMsgByAPI.push(msgByAPI)
            tester.idByAPI++
            threading.Thread(sendMsgByAPI, msgByAPI, robotId, accessKey, secretKey)

            var msgByHttp = {"ts": new Date().getTime(), "id": tester.idByHttp, "way": "ByHttp"}
            tester.arrSendMsgByHttp.push(msgByHttp)
            tester.idByHttp++
            threading.Thread(sendMsgByHttp, msgByHttp, httpUrl)
        }
    }

    tester.getEcho =function(msg) {
        if (msg["way"] == "ByAPI") {
            tester.arrEchoMsgByAPI.push(msg)
        } else {
            tester.arrEchoMsgByHttp.push(msg)
        }
    }

    tester.deal = function() {
        var tbls = []
        for (var pair of [[tester.arrEchoMsgByHttp, tester.arrSendMsgByHttp, "ByHttp"], [tester.arrEchoMsgByAPI, tester.arrSendMsgByAPI, "ByAPI"]]) {
            var receivedMessages = pair[0]
            var sentMessages = pair[1]
            var testType = pair[2]

            var receivedMap = new Map()
            receivedMessages.forEach(message => {
                receivedMap.set(message["id"], message)
            })
            
            var matchedPairs = []
            var timeDifferences = []
            for (var sentMessage of sentMessages) {
                var receivedMessage = receivedMap.get(sentMessage["id"])
                if (receivedMessage) {
                    matchedPairs.push([JSON.stringify(sentMessage), JSON.stringify(receivedMessage), receivedMessage["ts"] - sentMessage["ts"]])
                    timeDifferences.push(receivedMessage["ts"] - sentMessage["ts"])
                } else {
                    Log("no matched sentMessage:", sentMessage, "#FF0000")
                }
            }
            
            var averageTimeDifference = timeDifferences.reduce((sum, diff) => sum + diff, 0) / timeDifferences.length
            
            var tbl = {
                "type": "table",
                "title": testType + " / averageTimeDifference:" + averageTimeDifference,
                "cols": ["send", "received", "ts diff"],
                "rows": []
            }

            for (var pair of matchedPairs) {
                tbl["rows"].push(pair)
            }

            tbls.push(tbl)
            Log(testType, ", averageTimeDifference:", averageTimeDifference, "ms")
        }

        tester.arrSendMsgByAPI = []
        tester.arrSendMsgByHttp = []
        tester.arrEchoMsgByAPI = []
        tester.arrEchoMsgByHttp = []

        return tbls
    }

    return tester
}

function main() {
    __Serve("http://0.0.0.0:8088", serverFunc)

    var t = createMsgTester(accessKey, secretKey, httpUrl)
    while (true) {
        Log("Test start...", "#FF0000")
        t.run()

        var beginTS = new Date().getTime()
        while (new Date().getTime() - beginTS < 60 * 1000) {
            var cmd = GetCommand()
            if (cmd) {
                try {
                    var obj = JSON.parse(cmd)
                    obj["ts"] = new Date().getTime()
                    t.getEcho(obj)
                } catch (e) {
                    Log(e)
                }
            }
            
            var msg = threading.mainThread().peekMessage(-1)
            if (msg) {
                try {
                    var obj = JSON.parse(msg)
                    obj["ts"] = new Date().getTime()
                    t.getEcho(obj)                
                } catch (e) {
                    Log(e)
                }
            }
        }
        Log("Waiting is over...", "#FF0000")
                
        var tbls = t.deal()
        LogStatus(_D(), "\n`" + JSON.stringify(tbls) + "`")
        Sleep(20000)
    }
}

Si vous testez, vous devez remplir l'adresse IP spécifique du serveur et la clé API étendue de la plateforme FMZ.

var httpUrl = "http://123.123.123.123:8088/CommandRobot"
var accessKey = "xxx"
var secretKey = "xxx"
  1. La fonction serverFunc crée un service Http simultané pour surveiller les signaux externes. Pour les messages externes reçus par l'interface API étendue, la fonction GetCommand est utilisée pour surveiller.
  • Messages envoyés par le fil de service Http: surveillés parvar msg = threading.mainThread().peekMessage(-1).

  • Messages d'interaction transmis par l'interface API étendue: surveillés parvar cmd = GetCommand().

  1. Les processus d'envoi et de réception du signal sont non-bloquant. La plateforme optimise le mécanisme de récupération de ressources sous-jacent multi-threaded.Threadouexchange.Go, il n'est pas nécessaire d'attendre explicitement l'achèvement des tâches simultanées (telles que les fonctions de jointure, les fonctions d'attente, etc.). Le système sous-jacent gérera automatiquement la récupération des ressources (exige la dernière version du docker pour le prendre en charge).
    // Extract code snippet, send signal
    tester.run = function() {
        var robotId = tester.currentRobotId

        for (var i = 0; i < 10; i++) {
            var msgByAPI = {"ts": new Date().getTime(), "id": tester.idByAPI, "way": "ByAPI"}            
            tester.arrSendMsgByAPI.push(msgByAPI)
            tester.idByAPI++
            threading.Thread(sendMsgByAPI, msgByAPI, robotId, accessKey, secretKey)   // Concurrent calls, non-blocking

            var msgByHttp = {"ts": new Date().getTime(), "id": tester.idByHttp, "way": "ByHttp"}
            tester.arrSendMsgByHttp.push(msgByHttp)
            tester.idByHttp++
            threading.Thread(sendMsgByHttp, msgByHttp, httpUrl)                       // Concurrent calls, non-blocking
        }
    }

    // Extract code snippet, receiving signal
    var cmd = GetCommand()                              // Listen for messages from the extension API, non-blocking
    var msg = threading.mainThread().peekMessage(-1)    // Listen for messages from self-built Http service, using parameter -1, non-blocking

Ensuite, regardons le processus de test, où l'information est annotée directement dans le code:

function main() {
    __Serve("http://0.0.0.0:8088", serverFunc)      // Create a concurrent http service in the current strategy instance

    var t = createMsgTester(accessKey, secretKey, httpUrl)   // Create an object for test management
    while (true) {                                           // The strategy main loop starts
        Log("Test start...", "#FF0000")
        t.run()                                              // At the beginning of each loop, the run function of the test management object is called in two ways (1. Sending signals through the extended API, 2. Sending signals directly to the Http service created by the current strategy), each way sends 10 requests concurrently

        var beginTS = new Date().getTime()
        while (new Date().getTime() - beginTS < 60 * 1000) {   // Loop detection of interactive messages from extended APIs and messages from self-built Http services
            var cmd = GetCommand()
            if (cmd) {
                try {
                    var obj = JSON.parse(cmd)
                    obj["ts"] = new Date().getTime()        // Detect interactive messages, record messages, and update time to the time received
                    t.getEcho(obj)                          // Record to the corresponding array
                } catch (e) {
                    Log(e)
                }
            }
            
            var msg = threading.mainThread().peekMessage(-1)
            if (msg) {
                try {
                    var obj = JSON.parse(msg)
                    obj["ts"] = new Date().getTime()        // Detects the message received by the self-built Http service, and the update time is the receiving time
                    t.getEcho(obj)                          // ...
                } catch (e) {
                    Log(e)
                }
            }
        }
        Log("Waiting is over...", "#FF0000")
                
        var tbls = t.deal()                                  // Pair according to the recorded messages and check if there is any unpaired message. If there is, it means the signal is lost.
        LogStatus(_D(), "\n`" + JSON.stringify(tbls) + "`")
        Sleep(20000)
    }
}

Résultats des tests

img

img

Après une période de test, on peut observer que la méthode Http prend en moyenne un peu moins de temps que la méthode API.

La stratégie a un service HTTP intégré pour recevoir des signaux. Cette méthode de test n'est pas très rigoureuse, et la demande doit venir de l'extérieur. Pour des raisons de compréhension simple, ce facteur peut être ignoré. Pour les deux méthodes d'acquisition de signal, le service HTTP intégré de la stratégie réduit une liaison après tout et devrait répondre plus rapidement. Pour la stabilité du signal, il est plus important que le signal ne puisse pas être perdu ou manqué. Les résultats des tests montrent que l'API étendue de la plate-forme FMZ est également stable et aucune perte de signal n'a été observée dans le test, mais il ne peut pas être exclu que des facteurs tels que le réseau puissent causer des problèmes de signal.

Cet article n'est qu'un point de départ, bienvenue à discuter, merci de lire.


Plus de