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


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.