En la carga de los recursos... Cargando...

Discusión sobre la recepción de señales externas de la plataforma FMZ: API extendida VS estrategia Servicio HTTP incorporado

El autor:FMZ~Lydia, Creado: 2024-12-13 13:12:31, Actualizado: 2024-12-16 11:32:35

img

Prefacio

Hay varios artículos en la plataforma Digest sobre la conexión a los webhooks de Trading View, lo que permite a las estrategias conducir las operaciones con señales de sistemas externos.CommandRobot, en términos simples, la solicitud http/https de la señal externa se envía a la plataforma FMZ, y la plataforma reenvía la señal como un mensaje de interacción de estrategia al programa de estrategia.

A medida que la plataforma se desarrolla e itera, muchas nuevas características se han actualizado y actualizado. También hay nuevas soluciones para recibir señales externas. Cada solución tiene sus propias ventajas.

Utilice la plataforma FMZ para expandir la interfaz API

Las ventajas de usar este método para conectarse a sistemas externos son que es relativamente simple, altamente seguro y depende de la estabilidad de la interfaz API extendida de la plataforma.

Proceso de recepción de señales externas:

Sistema externo (Trading View webhook) > Servicio de API ampliado de FMZ > Trading en vivo de la estrategia

  1. Sistema externo (Trading View webhook): Por ejemplo, el script PINE que se ejecuta en Trading View puede establecer una alarma y, cuando se activa, enviará una solicitud http a la dirección de url del webhook establecida como una señal.
  2. Servicio de API ampliado de FMZ: Después de acceder con éxito a la interfaz, la plataforma reenvía la información y la envía a la estrategia de negociación en vivo como un mensaje interactivo.
  3. Negociación en vivo de estrategia: en la estrategia de negociación en vivo, puede diseñar la función GetCommand para escuchar el mensaje interactivo y ejecutar la operación especificada después de detectar el mensaje.

En comparación con el uso del servicio HTTP incorporado para crear un servicio para recibir señales directamente, hay un paso adicional en el medio (transferencia de plataforma).

Estrategia Servicio HTTP incorporado

Después de que la plataforma admita la función de servicio HTTP incorporada del lenguaje JavaScript, puede crear un servicio concurrente para escuchar señales externas directamente. Las ventajas son: el servicio HTTP creado es un hilo separado y no afecta la lógica de la función principal. Puede escuchar mensajes como la función GetCommand y escuchar señales externas directamente. En comparación con el uso de la solución de API extendida, elimina la necesidad de tránsito.

Proceso de recepción de señales externas:

Sistema externo (webhook de la vista de operaciones) > Operación de negociación en vivo de estrategia

  1. Sistema externo (Trading View webhook): por ejemplo, el script PINE que se ejecuta en Trading View puede establecer una alarma, que enviará una solicitud http a la dirección de url del webhook establecida como una señal cuando se activa.
  2. Estrategia de comercio en vivo: La estrategia ejecuta un servicio HTTP simultáneamente para recibir señales externas directamente.

Esta solución ahorra un paso, pero con el fin de mejorar la seguridad, es mejor configurar el servicio https, que requiere cierto esfuerzo.

Código de ensayo

Prueba dos soluciones. La siguiente estrategia enviará 10 solicitudes Http/Https en paralelo en cada ciclo para simular señales externas. Luego la estrategia supervisa mensajes de interacción y mensajes empujados por hilos de servicio Http. Luego el programa de estrategia compara mensajes de señal externa y señales recibidas una por una, detecta si hay pérdida de señal y calcula el consumo de tiempo.

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 está probando, debe rellenar la dirección IP específica del servidor y la clave API extendida de la plataforma FMZ.

var httpUrl = "http://123.123.123.123:8088/CommandRobot"
var accessKey = "xxx"
var secretKey = "xxx"
  1. La función serverFunc crea un servicio Http concurrente para monitorear señales externas.
  • Mensajes enviados por el hilo del servicio HTTP: Monitoreado porvar msg = threading.mainThread().peekMessage(-1).

  • Mensajes de interacción enviados por la interfaz API extendida: Monitoreado porvar cmd = GetCommand().

  1. Los procesos de envío y recepción de señales no son bloqueantes. La plataforma optimiza el mecanismo de recuperación de recursos subyacente multi-threaded.Threado bienexchange.Go, no hay necesidad de esperar explícitamente para que las tareas concurrentes se completen (como funciones de unión, funciones de espera, etc.).
    // 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

A continuación, vamos a ver el proceso de prueba, donde la información se anota directamente en el código:

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

Resultados de las pruebas

img

img

Después de un período de pruebas, se puede observar que el método HTTP tarda un poco menos de tiempo en promedio que el método API.

La estrategia tiene un servicio HTTP incorporado para recibir señales. Este método de prueba no es muy riguroso, y la solicitud debe provenir del exterior. Por el bien de la comprensión simple, este factor puede ignorarse. Para los dos métodos de adquisición de señales, el servicio HTTP incorporado de la estrategia reduce un enlace después de todo, y debe responder más rápido. Para la estabilidad de la señal, es más importante que la señal no se pierda o se pierda. Los resultados de la prueba muestran que la API extendida de la plataforma FMZ también es estable, y no se observó ninguna pérdida de señal en la prueba, pero no se puede descartar que factores como la red puedan causar problemas de señal.

Este artículo es sólo un punto de partida, bienvenido a discutir, gracias por leer.


Más.