
Havia vários artigos na biblioteca da plataforma sobre conexão com webhooks do Trading View, que permitiam que estratégias conduzissem negociações com base em sinais de sistemas externos. Naquela época, a plataforma não tinha uma função de serviço http integrada que suportasse a linguagem JavaScript. A interface API estendida da plataforma é usada:CommandRobotSimplificando, a solicitação http/https do sinal externo é enviada para a plataforma FMZ, e a plataforma encaminha o sinal como uma mensagem de interação de estratégia para o programa de estratégia.
À medida que a plataforma se desenvolve e itera, muitos novos recursos foram aprimorados e atualizados. Há também novas soluções para receber sinais externos. Cada solução tem suas próprias vantagens. Neste artigo, discutiremos esse tópico juntos.
As vantagens de usar esse método para se conectar a sistemas externos são que ele é relativamente simples, altamente seguro e depende da estabilidade da interface de API estendida da plataforma.
O processo de recebimento de sinais externos:
Sistema externo (webhook Trading View) –> Serviço de API estendida FMZ –> Estratégia de mercado real
Comparado ao uso do serviço HTTP integrado para criar diretamente um serviço para receber sinais, há uma etapa extra no meio (transferência de plataforma).
Depois que a plataforma suportar a função de serviço HTTP integrada da linguagem JavaScript, você poderá criar diretamente um serviço simultâneo para ouvir sinais externos. As vantagens são: o serviço Http criado é um thread separado e não afeta a lógica da função principal. Ele pode monitorar mensagens como a função GetCommand e monitorar diretamente sinais externos. Comparado com o uso da solução de API estendida, ele elimina o link de transferência.
O processo de recebimento de sinais externos:
Sistema externo (Trading View webhook) –> Estratégia de negociação ao vivo
Essa solução economiza uma etapa, mas para melhorar a segurança, é melhor configurar o serviço https, o que exige algum esforço. É um pouco mais problemático do que usar a API estendida.
Teste duas soluções. A estratégia a seguir enviará 10 solicitações HTTP/HTTPS simultaneamente em cada ciclo para simular sinais externos. Em seguida, a estratégia monitora “mensagens de interação” e “mensagens enviadas pelo thread de serviço HTTP”. Em seguida, o programa de estratégia compara a mensagem de sinal externo com o sinal recebido, um por um, detecta se há perda de sinal e calcula o consumo de tempo.
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)
}
}
Ao testar, você precisa preencher o endereço IP específico do servidor e a CHAVE de API estendida da plataforma FMZ.
var httpUrl = "http://123.123.123.123:8088/CommandRobot"
var accessKey = "xxx"
var secretKey = "xxx"
Mensagens enviadas pelo thread do serviço Http:
Depende devar msg = threading.mainThread().peekMessage(-1)monitor.
Mensagens de interação encaminhadas pela interface API estendida:
Depende devar cmd = GetCommand()monitor.
Threadouexchange.GoFunções simultâneas não precisam mais esperar explicitamente que tarefas simultâneas sejam concluídas (como funções de junção, funções de espera, etc.), e o sistema subjacente manipulará automaticamente a reciclagem de recursos (requer a versão mais recente do host para oferecer suporte a isso). // 摘录代码片段,发送信号
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 seguir, vamos dar uma olhada neste processo de teste, onde as informações são anotadas diretamente no 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)
}
}


Após um período de testes, pode-se observar que o método Http leva, em média, um pouco menos de tempo que o método API.
A estratégia tem um serviço Http embutido para receber sinais. Esse método de teste não é muito rigoroso e a solicitação deve vir de fora. Por uma questão de simplicidade, esse fator pode ser ignorado. Para ambos os métodos de aquisição de sinal, o serviço HTTP integrado à estratégia reduz um link e deve ter uma velocidade de resposta mais rápida. Para a estabilidade do sinal, é mais importante que o sinal não seja perdido ou ignorado. Os resultados do teste mostram que a API estendida da plataforma FMZ também é estável. Nenhuma perda de sinal foi observada durante o teste, mas não está descartado que vários fatores, como a rede, possam causar problemas de sinal. Usando o serviço Http integrado receber sinais externos diretamente também é uma maneira melhor. plano.
Este artigo é apenas um ponto de partida. O serviço Http integrado no código não é verificado e simplesmente recebe dados de mensagem. No próximo artigo, implementaremos completamente um modelo de serviço Http integrado utilizável para receber sinais externos do Trading View. Bem-vindo para discutir. Obrigado por ler.