avatar of 发明者量化-小小梦 发明者量化-小小梦
focar em Mensagem privada
4
focar em
1271
Seguidores

Discussão sobre recepção de sinal externo da plataforma FMZ: API estendida vs estratégia de serviço HTTP integrado

Criado em: 2024-12-12 18:33:26, atualizado em: 2025-05-16 15:53:52
comments   0
hits   583

Discussão sobre recepção de sinal externo da plataforma FMZ: API estendida vs estratégia de serviço HTTP integrado

Prefácio

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.

Use a plataforma FMZ para expandir a interface da API

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

  1. Sistema externo (webhook do Trading View): Por exemplo, o script PINE em execução no Trading View pode definir um alarme, que enviará uma solicitação http para o endereço de URL do webhook definido como um sinal quando acionado.
  2. Serviço de API estendido da FMZ: Após acessar a interface com sucesso, a plataforma encaminha as informações e as envia ao mercado real de estratégia como uma mensagem interativa.
  3. Implementação da estratégia: Na implementação da estratégia, você pode projetar a função GetCommand para ouvir mensagens interativas e executar as operações especificadas após detectar as mensagens.

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

Estratégia de serviço Http integrado

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

  1. Sistema externo (Trading View webhook): Por exemplo, o script PINE em execução no Trading View pode definir um alarme. Quando acionado, ele enviará uma solicitação http para o endereço de URL do webhook definido como um sinal.
  2. Implementação da estratégia: A estratégia executa simultaneamente um serviço HTTP para receber diretamente sinais externos.

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.

Código de teste

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"
  1. A função serverFunc cria um serviço HTTP simultâneo para monitorar sinais externos. Para mensagens externas recebidas pela interface API estendida, a função GetCommand é usada para monitorar.
  • 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.

  1. Tanto o processo de envio quanto o de recebimento de sinais são não-bloqueantes. A plataforma otimiza o mecanismo de reciclagem de recursos multithread subjacente.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)
    }
}

Resultados dos testes

Discussão sobre recepção de sinal externo da plataforma FMZ: API estendida vs estratégia de serviço HTTP integrado

Discussão sobre recepção de sinal externo da plataforma FMZ: API estendida vs estratégia de serviço HTTP integrado

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.