avatar of 发明者量化-小小梦 发明者量化-小小梦
Suivre Messages privés
4
Suivre
1271
Abonnés

Discussion sur la réception du signal externe de la plateforme FMZ : API étendue vs service HTTP intégré à la stratégie

Créé le: 2024-12-12 18:33:26, Mis à jour le: 2025-05-16 15:53:52
comments   0
hits   583

Discussion sur la réception du signal externe de la plateforme FMZ : API étendue vs service HTTP intégré à la stratégie

Préface

Il y avait plusieurs articles dans la bibliothèque de la plateforme sur la connexion aux webhooks de Trading View, qui permettaient aux stratégies de générer des transactions en fonction de signaux provenant de systèmes externes. À cette époque, la plateforme ne disposait pas d’une fonction de service http intégrée prenant en charge le langage JavaScript. L’interface API étendue de la plateforme est utilisée :CommandRobotEn termes simples, la requête http/https du signal externe est envoyée à la plateforme FMZ, et la plateforme transmet le signal sous forme de message d’interaction stratégique au programme de stratégie.

Au fur et à mesure que la plateforme se développe et évolue, de nombreuses nouvelles fonctionnalités ont été mises à niveau et mises à jour. Il existe également de nouvelles solutions pour recevoir des signaux externes. Chaque solution a ses propres avantages. Dans cet article, nous allons aborder 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 de signaux externes :

Système externe (webhook Trading View) –> Service API étendu FMZ –> Stratégie marché réel

  1. Système externe (webhook Trading View) : par exemple, le script PINE exécuté sur Trading View peut définir une alarme, qui enverra une requête http à l’adresse URL du webhook définie comme signal lorsqu’elle sera déclenchée.
  2. Service API étendu FMZ : après avoir accédé avec succès à l’interface, la plateforme transmet les informations et les envoie au marché réel stratégique sous forme de message interactif.
  3. Implémentation de la stratégie : dans l’implémentation de la stratégie, vous pouvez concevoir la fonction GetCommand pour écouter les messages interactifs et exécuter les opérations spécifiées après avoir détecté les messages.

Par rapport à l’utilisation du service HTTP intégré pour créer directement un service de réception de signaux, il existe une étape supplémentaire au milieu (transfert de plateforme).

Stratégie intégrée au service Http

Une fois que la plateforme prend en charge la fonction de service HTTP intégrée du langage JavaScript, vous pouvez directement créer un service simultané pour écouter les signaux externes. Les avantages sont les suivants : le service HTTP créé est un thread séparé et n’affecte pas la logique de la fonction principale. Il peut surveiller les messages comme la fonction GetCommand et surveiller directement les signaux externes. Par rapport à l’utilisation de la solution API étendue, il élimine le lien de transfert.

Le processus de réception de signaux externes :

Système externe (webhook Trading View) –> Stratégie de trading en direct

  1. Système externe (webhook Trading View) : par exemple, le script PINE exécuté sur Trading View peut définir une alarme. Lorsqu’il est déclenché, il envoie une requête http à l’adresse URL du webhook défini en guise de signal.
  2. Mise en œuvre de la stratégie : la stratégie exécute simultanément un service HTTP pour recevoir directement des signaux externes.

Cette solution permet de gagner une étape, mais afin d’améliorer la sécurité, il est préférable de configurer le service https, ce qui demande quelques efforts. C’est un peu plus gênant que d’utiliser l’API étendue.

Code de test

Testez deux solutions. La stratégie suivante enverra 10 requêtes HTTP/HTTPS simultanément à chaque cycle pour simuler des signaux externes. Ensuite, la stratégie surveille les « messages d’interaction » et les « messages poussés par le thread du service HTTP ». Le programme de stratégie fait ensuite correspondre le message du signal externe avec le signal reçu 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("测试开始...", "#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("等待结束...", "#FF0000")
                
        var tbls = t.deal()
        LogStatus(_D(), "\n`" + JSON.stringify(tbls) + "`")
        Sleep(20000)
    }
}

Si vous effectuez un test, vous devez renseigner 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 thread du service HTTP : Dépendre devar msg = threading.mainThread().peekMessage(-1)moniteur.

  • Messages d’interaction transmis par l’interface API étendue : Dépendre devar cmd = GetCommand()moniteur.

  1. Les processus d’envoi et de réception des signaux sont non bloquants. La plateforme optimise le mécanisme de recyclage des ressources multithread sous-jacent.Threadouexchange.GoLes fonctions simultanées n’ont plus besoin d’attendre explicitement que les tâches simultanées se terminent (telles que les fonctions de jointure, les fonctions d’attente, etc.), et le système sous-jacent gérera automatiquement le recyclage des ressources (nécessite la dernière version de l’hôte pour prendre en charge cette fonction).
    // 摘录代码片段,发送信号
    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)                       // 并发调用,非阻塞
        }
    }

    // 摘录代码片段,接收信号
    var cmd = GetCommand()                              // 监听来自扩展API的消息,非阻塞
    var msg = threading.mainThread().peekMessage(-1)    // 监听来自自建Http服务的消息,使用了参数-1,非阻塞

Ensuite, examinons ce processus de test, où les informations sont annotées directement dans le code :

function main() {
    __Serve("http://0.0.0.0:8088", serverFunc)      // 在当前策略实例中,创建一个并发的http服务

    var t = createMsgTester(accessKey, secretKey, httpUrl)   // 创建一个用于测试管理的对象
    while (true) {                                           // 策略主循环开始
        Log("测试开始...", "#FF0000")
        t.run()                                              // 每次循环开始,调用测试管理对象的run函数,使用两种方式(1、通过扩展API发送信号,2、直接向当前策略创建的Http服务发送信号),每种方式并发发送10个请求

        var beginTS = new Date().getTime()
        while (new Date().getTime() - beginTS < 60 * 1000) {   // 循环检测来自扩展API的交互消息,循环检测来自自建Http服务的消息
            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()        // 检测到自建的Http服务收到的消息,更新时间为收到时间
                    t.getEcho(obj)                          // ...
                } catch (e) {
                    Log(e)
                }
            }
        }
        Log("等待结束...", "#FF0000")
                
        var tbls = t.deal()                                  // 根据记录的消息,配对,检查是否有未配对的消息,如果有说明有信号丢失
        LogStatus(_D(), "\n`" + JSON.stringify(tbls) + "`")
        Sleep(20000)
    }
}

Résultats des tests

Discussion sur la réception du signal externe de la plateforme FMZ : API étendue vs service HTTP intégré à la stratégie

Discussion sur la réception du signal externe de la plateforme FMZ : API étendue vs service HTTP intégré à la stratégie

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

La stratégie dispose d’un service HTTP intégré pour recevoir les signaux. Cette méthode de test n’est pas très rigoureuse et la requête doit provenir de l’extérieur. Par souci de simplicité, ce facteur peut être ignoré. Pour les deux méthodes d’acquisition du signal, le service HTTP intégré de la stratégie réduit finalement un lien et devrait avoir une vitesse de réponse plus rapide. Pour la stabilité du signal, il est plus important que le signal ne soit pas perdu ou manqué. Les résultats des tests montrent que l’API étendue de la plateforme FMZ est également stable. Aucune perte de signal n’a été observée pendant le test, mais il n’est pas exclu que divers facteurs tels que le réseau puissent provoquer des problèmes de signal. Utilisation du service Http intégré recevoir directement des signaux externes est également un meilleur moyen.

Cet article n’est qu’un point de départ. Le service HTTP intégré dans le code n’est pas vérifié et reçoit simplement les données des messages. Dans le prochain article, nous implémenterons entièrement un modèle de service HTTP intégré utilisable pour recevoir des signaux Trading View externes. Bienvenue pour discuter. Merci de votre lecture.