avatar of 发明者量化-小小梦 发明者量化-小小梦
konzentrieren Sie sich auf Private Nachricht
4
konzentrieren Sie sich auf
1271
Anhänger

Diskussion zum externen Signalempfang der FMZ-Plattform: erweiterte API vs. Strategie integrierter HTTP-Dienst

Erstellt in: 2024-12-12 18:33:26, aktualisiert am: 2025-05-16 15:53:52
comments   0
hits   583

Diskussion zum externen Signalempfang der FMZ-Plattform: erweiterte API vs. Strategie integrierter HTTP-Dienst

Vorwort

In der Plattformbibliothek gab es mehrere Artikel über die Verbindung mit Trading View-Webhooks, die es Strategien ermöglichten, Trades basierend auf Signalen von externen Systemen durchzuführen. Zu diesem Zeitpunkt verfügte die Plattform noch nicht über eine integrierte HTTP-Servicefunktion, die die JavaScript-Sprache unterstützte. Dabei kommt die erweiterte API-Schnittstelle der Plattform zum Einsatz:CommandRobotEinfach ausgedrückt wird die http/https-Anforderung des externen Signals an die FMZ-Plattform gesendet und die Plattform leitet das Signal als Strategie-Interaktionsnachricht an das Strategieprogramm weiter.

Im Zuge der Weiterentwicklung und Aktualisierung der Plattform wurden viele neue Funktionen erweitert und aktualisiert. Auch für den Empfang externer Signale gibt es neue Lösungen. Jede Lösung hat ihre eigenen Vorteile. In diesem Artikel werden wir dieses Thema gemeinsam besprechen.

API-Schnittstelle durch FMZ-Plattform erweitern

Die Vorteile dieser Methode zur Verbindung mit externen Systemen bestehen darin, dass sie relativ einfach und sehr sicher ist und auf der Stabilität der erweiterten API-Schnittstelle der Plattform basiert.

Der Vorgang des Empfangens externer Signale:

Externes System (Trading View Webhook) –> FMZ erweiterter API-Dienst –> Strategie Realmarkt

  1. Externes System (Trading View-Webhook): Beispielsweise kann das auf Trading View ausgeführte PINE-Skript einen Alarm setzen, der bei Auslösung eine HTTP-Anfrage als Signal an die festgelegte Webhook-URL-Adresse sendet.
  2. FMZ erweiterter API-Service: Nach erfolgreichem Zugriff auf die Schnittstelle leitet die Plattform die Informationen weiter und sendet diese als interaktive Nachricht an den Strategie-Realmarkt.
  3. Strategieimplementierung: Bei der Strategieimplementierung können Sie die GetCommand-Funktion so gestalten, dass sie auf interaktive Nachrichten hört und nach dem Erkennen der Nachrichten die angegebenen Vorgänge ausführt.

Im Vergleich zur Verwendung des integrierten HTTP-Dienstes zum direkten Erstellen eines Dienstes zum Empfangen von Signalen gibt es dazwischen einen zusätzlichen Schritt (Plattformübertragung).

Strategie integrierter HTTP-Dienst

Nachdem die Plattform die integrierte HTTP-Dienstfunktion der JavaScript-Sprache unterstützt, können Sie direkt einen gleichzeitigen Dienst erstellen, um auf externe Signale zu hören. Die Vorteile sind: Der erstellte HTTP-Dienst ist ein separater Thread und hat keinen Einfluss auf die Hauptfunktionslogik. Er kann Nachrichten wie die GetCommand-Funktion überwachen und externe Signale direkt überwachen. Im Vergleich zur Verwendung der erweiterten API-Lösung entfällt die Übertragungsverbindung.

Der Vorgang des Empfangens externer Signale:

Externes System (Trading View Webhook) –> Strategie Live-Trading

  1. Externes System (Trading View-Webhook): Beispielsweise kann das auf Trading View ausgeführte PINE-Skript einen Alarm auslösen. Wenn dieser ausgelöst wird, sendet es als Signal eine HTTP-Anfrage an die festgelegte Webhook-URL-Adresse.
  2. Strategieimplementierung: Die Strategie führt gleichzeitig einen HTTP-Dienst aus, um externe Signale direkt zu empfangen.

Diese Lösung spart einen Schritt, aber um die Sicherheit zu verbessern, ist es am besten, einen https-Dienst zu konfigurieren, was einigen Aufwand erfordert. Dies ist etwas umständlicher als die Verwendung der erweiterten API.

Testcode

Testen Sie zwei Lösungen. Die folgende Strategie sendet in jedem Zyklus 10 HTTP/HTTPS-Anfragen gleichzeitig, um externe Signale zu simulieren. Anschließend überwacht die Strategie „Interaktionsnachrichten“ und „vom HTTP-Service-Thread gepushte Nachrichten“. Anschließend gleicht das Strategieprogramm die externe Signalnachricht nacheinander mit dem empfangenen Signal ab, erkennt, ob ein Signalverlust vorliegt, und berechnet den Zeitaufwand.

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

Zum Testen müssen Sie die spezifische Server-IP-Adresse und den erweiterten API-Schlüssel der FMZ-Plattform eingeben.

var httpUrl = "http://123.123.123.123:8088/CommandRobot"
var accessKey = "xxx"
var secretKey = "xxx"
  1. Die Funktion serverFunc erstellt einen gleichzeitigen HTTP-Dienst zur Überwachung externer Signale. Zur Überwachung externer Nachrichten, die über die erweiterte API-Schnittstelle empfangen werden, wird die Funktion GetCommand verwendet.
  • Vom HTTP-Dienst-Thread gesendete Nachrichten: Verlassen Sie sich aufvar msg = threading.mainThread().peekMessage(-1)Monitor.

  • Von der erweiterten API-Schnittstelle weitergeleitete Interaktionsnachrichten: Verlassen Sie sich aufvar cmd = GetCommand()Monitor.

  1. Sowohl der Sende- als auch der Empfangssignalprozess sind nicht blockierend. Die Plattform optimiert den zugrunde liegenden Multithread-Ressourcenrecyclingmechanismus.Threadoderexchange.GoGleichzeitige Funktionen müssen nicht mehr explizit auf den Abschluss gleichzeitiger Aufgaben warten (z. B. Verbindungsfunktionen, Wartefunktionen usw.), und das zugrunde liegende System übernimmt automatisch die Ressourcenwiederverwendung (hierzu ist die neueste Version des Hosts erforderlich, um dies zu unterstützen).
    // 摘录代码片段,发送信号
    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,非阻塞

Als nächstes schauen wir uns diesen Testprozess an, bei dem die Informationen direkt im Code annotiert sind:

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

Testergebnisse

Diskussion zum externen Signalempfang der FMZ-Plattform: erweiterte API vs. Strategie integrierter HTTP-Dienst

Diskussion zum externen Signalempfang der FMZ-Plattform: erweiterte API vs. Strategie integrierter HTTP-Dienst

Nach einer Testphase lässt sich feststellen, dass die HTTP-Methode im Durchschnitt weniger Zeit in Anspruch nimmt als die API-Methode.

Die Strategie verfügt über einen integrierten HTTP-Dienst zum Empfangen von Signalen. Diese Testmethode ist nicht sehr streng und die Anforderung sollte von außen kommen. Der Einfachheit halber kann dieser Faktor vernachlässigt werden. Für beide Methoden der Signalerfassung reduziert der integrierte HTTP-Dienst der Strategie immerhin eine Verbindung und sollte über eine schnellere Reaktionsgeschwindigkeit verfügen. Für die Signalstabilität ist es wichtiger, dass das Signal nicht verloren geht oder verpasst wird. Die Testergebnisse zeigen, dass die erweiterte API der FMZ-Plattform ebenfalls stabil ist. Während des Tests wurde kein Signalverlust beobachtet, es ist jedoch nicht ausgeschlossen, dass verschiedene Faktoren wie das Netzwerk Signalprobleme verursachen können. Verwendung des integrierten HTTP-Dienstes auch der direkte Empfang externer Signale ist eine bessere Möglichkeit.

Dieser Artikel ist nur ein Ausgangspunkt. Der im Code integrierte HTTP-Dienst wird nicht überprüft und empfängt lediglich Nachrichtendaten. Im nächsten Artikel werden wir eine verwendbare integrierte HTTP-Dienstvorlage zum Empfang externer Trading View-Signale vollständig implementieren. Willkommen zur Diskussion. Danke fürs Lesen.