avatar of 发明者量化-小小梦 发明者量化-小小梦
Seguir Mensajes Privados
4
Seguir
1271
Seguidores

Discusión sobre la recepción de señales externas de la plataforma FMZ: API extendida vs estrategia de servicio HTTP integrado

Creado el: 2024-12-12 18:33:26, Actualizado el: 2025-05-16 15:53:52
comments   0
hits   583

Discusión sobre la recepción de señales externas de la plataforma FMZ: API extendida vs estrategia de servicio HTTP integrado

Prefacio

En la biblioteca de la plataforma había varios artículos sobre la conexión a los webhooks de Trading View, que permitían que las estrategias impulsaran operaciones basadas en señales de sistemas externos. En ese momento, la plataforma no tenía una función de servicio http integrada que admitiera el lenguaje JavaScript. Se utiliza la interfaz API extendida de la plataforma:CommandRobotEn pocas palabras, 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 y se itera, se han actualizado y mejorado muchas funciones nuevas. También hay nuevas soluciones para recibir señales externas. Cada solución tiene sus propias ventajas. En este artículo, analizaremos este tema juntos.

Utilice la plataforma FMZ para ampliar la interfaz API

Las ventajas de utilizar 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.

El proceso de recepción de señales externas:

Sistema externo (webhook de Trading View) –> Servicio API extendido de FMZ –> Estrategia de mercado real

  1. Sistema externo (webhook de Trading View): por ejemplo, el script PINE que se ejecuta en Trading View puede configurar una alarma, que enviará una solicitud http a la dirección URL del webhook configurado como señal cuando se active.
  2. Servicio API extendido FMZ: después de acceder exitosamente a la interfaz, la plataforma reenvía la información y la envía al mercado real de estrategia como un mensaje interactivo.
  3. Implementación de la estrategia: en la implementación de la estrategia, puede diseñar la función GetCommand para escuchar mensajes interactivos y ejecutar las operaciones especificadas después de detectar los mensajes.

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

Estrategia integrada en el servicio HTTP

Una vez que la plataforma admita la función de servicio Http incorporada del lenguaje JavaScript, puede crear directamente un servicio simultáneo para escuchar señales externas. Las ventajas son: el servicio HTTP creado es un hilo independiente y no afecta a la lógica de la función principal. Puede monitorear mensajes como la función GetCommand y monitorear directamente señales externas. En comparación con el uso de la solución API extendida, elimina el enlace de transferencia. .

El proceso de recepción de señales externas:

Sistema externo (webhook de Trading View) –> Estrategia de trading en vivo

  1. Sistema externo (webhook de Trading View): por ejemplo, el script PINE que se ejecuta en Trading View puede configurar una alarma. Cuando se activa, enviará una solicitud http a la dirección URL del webhook configurado como señal.
  2. Implementación de la estrategia: La estrategia ejecuta simultáneamente un servicio Http para recibir directamente señales externas.

Esta solución ahorra un paso, pero para mejorar la seguridad es mejor configurar el servicio https, lo que requiere cierto esfuerzo. Es un poco más problemático que usar la API extendida.

Código de prueba

Pruebe dos soluciones. La siguiente estrategia enviará 10 solicitudes HTTP/HTTPS simultáneamente en cada ciclo para simular señales externas. Luego, la estrategia monitorea los “mensajes de interacción” y los “mensajes enviados por el hilo del servicio Http”. Luego, el programa de estrategia hace coincidir el mensaje de señal externa con la señal recibida uno por uno, 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("测试开始...", "#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 realiza pruebas, deberá completar la dirección IP del servidor específico 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. Para los mensajes externos recibidos por la interfaz API extendida, se utiliza la función GetCommand para monitorear.
  • Mensajes enviados por el hilo del servicio HTTP: Depender devar msg = threading.mainThread().peekMessage(-1)monitor.

  • Mensajes de interacción reenviados por la interfaz API extendida: Depender devar cmd = GetCommand()monitor.

  1. Tanto el proceso de envío como el de recepción de señales no se bloquean. La plataforma optimiza el mecanismo subyacente de reciclaje de recursos multiproceso.Threadoexchange.GoLas funciones concurrentes ya no necesitan esperar explícitamente a que se completen las tareas concurrentes (como funciones de unión, funciones de espera, etc.) y el sistema subyacente manejará automáticamente el reciclaje de recursos (requiere la última versión del host para soportar esto).
    // 摘录代码片段,发送信号
    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,非阻塞

A continuación, veamos este proceso de prueba, donde la información se anota directamente en el código:

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

Resultados de la prueba

Discusión sobre la recepción de señales externas de la plataforma FMZ: API extendida vs estrategia de servicio HTTP integrado

Discusión sobre la recepción de señales externas de la plataforma FMZ: API extendida vs estrategia de servicio HTTP integrado

Después de un período de prueba, se puede observar que el método Http toma un poco menos de tiempo en promedio que el método API.

La estrategia tiene incorporado un servicio HTTP para recibir señales. Este método de prueba no es muy riguroso y la solicitud debe provenir del exterior. Para simplificar, este factor puede ignorarse. Para ambos métodos de adquisición de señales, el servicio Http integrado de la estrategia reduce un enlace después de todo y debería tener una velocidad de respuesta más rápida. Para la estabilidad de la señal, es más importante que la señal no se pierda ni se pierda. Los resultados de la prueba muestran que la API extendida de la plataforma FMZ también es estable. No se observó pérdida de señal durante la prueba, pero no se descarta que diversos factores, como la red, puedan causar problemas de señal. Uso del servicio Http integrado Recibir señales externas directamente también es una mejor manera de planificar.

Este artículo es solo un punto de partida. El servicio HTTP integrado en el código no está verificado y simplemente recibe datos de mensajes. En el próximo artículo, implementaremos por completo una plantilla de servicio HTTP integrado que se pueda utilizar para recibir señales externas de Trading View. Bienvenido a debatir. Gracias por leer.