O recurso está a ser carregado... Carregamento...

Discussão sobre a recepção de sinais externos da plataforma FMZ: API estendida VS estratégia Serviço HTTP integrado

Autora:FMZ~Lydia, Criado: 2024-12-13 13:12:31, Atualizado: 2024-12-16 11:32:35

img

Prefácio

Há vários artigos na plataforma Digest sobre a conexão com os webhooks do Trading View, o que permite que as estratégias conduzam as negociações com sinais de sistemas externos.CommandRobot, em termos simples, 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 estratégica para o programa de estratégia.

Como a plataforma se desenvolve e itera, muitos novos recursos foram atualizados e atualizados. Há também novas soluções para receber sinais externos. Cada solução tem suas próprias vantagens.

Utilize a plataforma FMZ para expandir a interface API

As vantagens de usar este método para se conectar a sistemas externos são que é relativamente simples, altamente seguro e depende da estabilidade da interface API estendida da plataforma.

Processo de recepção de sinais externos:

Sistema externo (Trading View webhook) > Serviço FMZ API estendido > Trading ao vivo da estratégia

  1. Sistema externo (Trading View webhook): Por exemplo, o script PINE executado no Trading View pode definir um alarme e, quando ativado, enviará uma solicitação http para o endereço de url do webhook definido como um sinal.
  2. Serviço de API estendida FMZ: Após acessar a interface com sucesso, a plataforma encaminha as informações e as envia para a estratégia de negociação ao vivo como uma mensagem interativa.
  3. Estratégia de negociação ao vivo: na estratégia de negociação ao vivo, você pode projetar a função GetCommand para ouvir a mensagem interativa e executar a operação especificada após detectar a mensagem.

Em comparação com o uso do serviço Http incorporado para criar um serviço para receber sinais diretamente, há um passo extra no meio (transferência de plataforma).

Estratégia Serviço HTTP integrado

Após a plataforma suportar a função de serviço Http incorporada da linguagem JavaScript, você pode criar um serviço simultâneo para ouvir sinais externos diretamente. As vantagens são: o serviço Http criado é um tópico separado e não afeta a lógica da função principal. Ele pode ouvir mensagens como a função GetCommand e ouvir sinais externos diretamente. Em comparação com o uso da solução API estendida, elimina a necessidade de trânsito.

Processo de recepção 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 executado 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 ativado.
  2. Estratégia de negociação ao vivo: A estratégia executa um serviço HTTP simultaneamente para receber sinais externos diretamente.

Esta solução economiza um passo, mas para melhorar a segurança, é melhor configurar o serviço https, que requer algum esforço.

Código de ensaio

Teste duas soluções. A estratégia seguinte enviará 10 solicitações Http/Https em paralelo em cada ciclo para simular sinais externos. Em seguida, a estratégia monitora mensagens de interação e mensagens empurradas por threads de serviço Http. Em seguida, o programa de estratégia corresponde a mensagens de sinal externo e sinais recebidos 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("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)
    }
}

Se estiver a testar, é necessário preencher o endereço IP do servidor específico e a chave 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.
  • Mensagens enviadas pelo thread do serviço Http: monitorizados porvar msg = threading.mainThread().peekMessage(-1).

  • Mensagens de interação encaminhadas pela interface API estendida: monitorizados porvar cmd = GetCommand().

  1. A plataforma otimiza o mecanismo de recuperação de recursos subjacente multi-threaded.Threadouexchange.Go, não há necessidade de esperar explicitamente que as tarefas simultâneas sejam concluídas (como funções de junção, funções 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

Em seguida, vamos olhar para o processo de teste, onde as informações são anotadas diretamente no 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 dos ensaios

img

img

Após um período de testes, pode-se observar que o método Http leva um pouco menos de tempo em média do que o método API.

A estratégia tem um serviço HTTP embutido para receber sinais. Este método de teste não é muito rigoroso, e o pedido deve vir de fora. Por uma simples compreensão, esse fator pode ser ignorado. Para os dois métodos de aquisição de sinal, o serviço HTTP embutido da estratégia reduz um link, afinal, e deve responder mais rapidamente. Para a estabilidade do sinal, é mais importante que o sinal não possa ser perdido ou perdido. Os resultados do teste mostram que a API estendida da plataforma FMZ também é estável, e nenhuma perda de sinal foi observada no teste, mas não pode ser descartado que fatores como a rede possam causar problemas de sinal. Usar o serviço HTTP embutido para receber sinais externos diretamente também é uma solução melhor.

Este artigo é apenas um ponto de partida, bem-vindo a discutir, obrigado por ler.


Mais.