Desenvolvimento de um Cliente MQTT para o MetaTrader 5: Metodologia TDD
Introdução
"A estratégia é a seguinte: primeiro, tudo deve funcionar, depois, tudo deve funcionar corretamente e, finalmente, tudo deve funcionar rapidamente." - Stephen Johnson e Brian Kernighan, "A linguagem C e modelos de programação de sistemas", revista Byte, agosto de 1983.
A troca de dados em tempo real entre dois ou mais instâncias do MetaTrader é uma necessidade comum entre traders e gerentes de contas. Isso é especialmente relevante ao copiar transações de outra conta. Mas também é importante para resolver tarefas como a troca de informações de contas, bem como análises comparativas de símbolos e dados estatísticos para aprendizado de máquina. O funcional necessário pode ser obtido usando soquetes de rede, comunicação entre processos com canais nomeados, serviços da web, troca de arquivos local e, possivelmente, algumas outras soluções.
Como é comum no desenvolvimento de software, cada uma dessas soluções tem suas vantagens e desvantagens em termos de facilidade de uso, estabilidade, confiabilidade e recursos necessários para desenvolvimento e manutenção. Em outras palavras, cada solução representa uma relação diferente entre custos e benefícios, dependendo dos requisitos e do orçamento do usuário.
Este artigo descreve os primeiros passos na implementação da parte do cliente do protocolo MQTT, que é uma tecnologia projetada especificamente para atender a essa necessidade - a troca de dados em tempo real entre máquinas, com alta performance, baixo consumo de tráfego, requisitos de recursos relativamente baixos e baixos custos.
O que é MQTT?
"MQTT é um protocolo de troca de dados no formato 'publicador - assinante'. Ele é leve, aberto, simples e projetado para ser facilmente implementado. Essas características permitem que ele seja eficazmente usado em muitas situações, incluindo ambientes com recursos limitados, como Comunicação Máquina-Máquina (M2M) e Internet das Coisas (IoT), onde é necessário pouco código e/ou largura de banda de rede suficiente."
"Em 2013, a IBM apresentou o MQTT v3.1 à OASIS, com um estatuto que permitia apenas alterações menores na especificação. Assumindo a responsabilidade de suportar o padrão da IBM, a OASIS lançou a versão 3.1.1 em 29 de outubro de 2014. Em 7 de março de 2019, uma atualização mais substancial da MQTT, a versão 5, foi lançada, incluindo várias novas funcionalidades." (Wikipédia em inglês)
"Nós tentamos usar o software intermediário da IBM daquela época - MQ Integrator. Naquela época, minha tarefa na indústria de comunicação era unir linhas comutadas de 1200 e 300 bauds com uma estação VSAT que tinha uma capacidade de largura de banda muito limitada.
Apesar de o protocolo dever ser confiável, rápido e econômico devido a limitações tecnológicas e altos custos de rede, ele precisava garantir a entrega de dados com consciência de sessão contínua, permitindo lidar com uma conexão de Internet não confiável ou até intermitente.
Sendo um protocolo binário, o MQTT é altamente eficiente em termos de requisitos de memória e processamento. Curiosamente, o menor pacote MQTT tem apenas dois bytes!
Como o protocolo opera no modo de "publicador/assinante" (pub/sub), ao contrário dos baseados no modo "solicitação/resposta", ele é bidirecional. Em outras palavras, assim que a conexão cliente/servidor é estabelecida, os dados podem ser transmitidos do cliente para o servidor e do servidor para o cliente a qualquer momento, sem a necessidade de uma solicitação prévia, ao contrário do HTTP WebRequest. Assim que os dados chegam, o servidor os encaminha imediatamente para os destinatários. Essa característica é fundamental para a troca de dados em tempo real, pois permite minimizar os atrasos entre os pontos finais. Alguns provedores afirmam atrasos da ordem de milissegundos.
O tipo, formato, codec ou qualquer outra coisa nos dados não importa. O MQTT é agnóstico em relação aos dados (data-agnostic). Os usuários podem enviar/receber bytes brutos, formatos de texto (como objetos XML, JSON), buffers de protocolo, imagens, trechos de vídeo, etc.
A maior parte da interação entre cliente e servidor pode ser assíncrona, o que significa que o MQTT é altamente escalável. Na indústria da Internet das Coisas, frequentemente estamos lidando com milhares ou até milhões de dispositivos conectados trocando dados em tempo real.
As mensagens entre os pontos finais geralmente são criptografadas, pois o protocolo é compatível com TLS e possui mecanismos embutidos de autenticação e autorização.
Não é surpresa que o MQTT seja não apenas um conjunto de especificações de alto nível, mas também uma tecnologia amplamente utilizada em várias indústrias.
Principais componentes
A publicação/assinatura é um modelo amplamente conhecido de troca de mensagens. O cliente se conecta a um servidor e publica uma mensagem em um tópico específico. Em seguida, todos os clientes inscritos nesse tópico recebem a(s) mensagem(ns). Esse é o mecanismo principal do modelo.
O servidor atua como intermediário, ficando entre os clientes para lidar com inscrições e publicações. O TCP/IP é o protocolo de transporte fundamental, e os clientes podem ser qualquer dispositivo que compreenda TCP/IP e MQTT. As mensagens geralmente consistem em carga útil JSON ou XML, mas podem ser qualquer coisa, incluindo uma sequência de bytes não processados.
Um tópico é uma string codificada em UTF-8 usada para descrever uma estrutura hierárquica semelhante a um namespace.
-
office/machine01/account123456
-
office/machine02/account789012
-
home/machine01/account345678
- home/machine01/#
- office/#
Bem, o MQTT foi desenvolvido para a interação entre máquinas. É amplamente utilizado na Internet das Coisas, sendo confiável, rápido e econômico. Mas você pode se perguntar: qual é o benefício disso para a negociação? Quais são as possíveis aplicações do MQTT no MetaTrader?
Como mencionado anteriormente, a aplicação mais óbvia do MQTT no ambiente de negociação é a cópia de transações. No entanto, você também pode pensar em carregar dados de aprendizado de máquina em tempo real, ajustar o comportamento de um Expert Advisor com base em dados em tempo real obtidos de um serviço da web, ou controlar o MetaTrader remotamente a partir de qualquer dispositivo.
O MQTT pode ser aplicado em qualquer cenário onde haja necessidade de fluxo de dados em tempo real entre máquinas.Aplicação do MQTT no MetaTrader
Existem bibliotecas de cliente gratuitas e de código aberto para as linguagens de programação mais populares, incluindo opções correspondentes para dispositivos móveis e embarcados. Portanto, para usar o MQTT a partir do MQL5, podemos gerar e importar a DLL correspondente de C, C++ ou C#.
Se os dados a serem compartilhados estão limitados a negociações/informações de conta e uma latência relativamente alta é aceitável, você pode usar a biblioteca cliente Python MQTT e o módulo Python como uma "ponte".
No entanto, como sabemos, o uso de DLL tem algumas consequências negativas para o ecossistema do MQL5, sendo a mais evidente delas a não aceitação de Expert Advisors dependentes de DLL no Mercado. Além disso, esses Expert Advisors não podem realizar testes de otimização no histórico na nuvem do MQL5. Para evitar a dependência de DLL e da ponte Python, a solução ideal é desenvolver sua própria biblioteca cliente MQTT para o MetaTrader.
É exatamente isso que estaremos fazendo em breve: implementando o protocolo MQTT-v5.0 do lado do cliente para o MetaTrader 5.
A implementação de um cliente MQTT pode ser considerada "relativamente simples" em comparação com outros protocolos de rede. No entanto, "relativamente simples" não significa necessariamente que tudo ocorrerá sem problemas. Dito isso, começaremos com uma abordagem "de baixo para cima", desenvolvendo através de testes (TDD). Estamos ansiosos por feedback e pelos resultados dos testes da comunidade.
Embora o TDD possa ser usado (e frequentemente seja usado) como apenas mais um termo da moda, significando quase qualquer coisa, essa metodologia é muito apropriada quando temos um conjunto de especificações formalizadas, o que se aplica diretamente a um protocolo de rede padronizado.
Adotando uma abordagem ascendente, podemos lidar com os requisitos técnicos mais rigorosos, dividindo-os em pequenos passos. Os requisitos para o MQTT não são tão estritos, e a parte do cliente é mais simples em comparação com a parte do broker. No entanto, há desafios, especialmente a partir da versão 5.0, que introduz algumas funcionalidades adicionais.
Como nosso tempo não é remunerado e não temos uma grande equipe, a abordagem de pequenos passos parece ser a mais adequada aqui: como enviar uma mensagem? O que devo escrever? Como fazer algo funcionar, depois fazer funcionar bem e finalmente fazer funcionar rapidamente?
Requisitos mais rigorosos, pequenos passos: análise do protocolo MQTT
Como na maioria (se não em todos) os protocolos de rede, o MQTT opera dividindo os dados transmitidos em pacotes. Desse modo, se o receptor souber o que cada tipo de pacote significa, ele pode adotar o comportamento adequado com base no tipo de pacote recebido. No caso do MQTT, o tipo de pacote é chamado de Tipo de Pacote de Controle e pode ter até três partes:
-
Cabeçalho fixo (fixed header) presente em todos os pacotes.
-
Cabeçalho variável (variable header) presente em alguns pacotes.
-
Carga útil (payload) também presente apenas em alguns pacotes.
No MQTT-v5.0, existem quinze tipos de pacotes de controle:
Tabela 1. Tipos de Pacotes de Controle MQTT (tabela da especificação OASIS)
Nome | Valor | Direção do fluxo | Descrição |
---|---|---|---|
Reserved | 0 | Proibido | Reservado |
CONNECT | 1 | Cliente-servidor | Solicitação de conexão |
CONNACK | 2 | Servidor-cliente | Confirmação da conexão |
PUBLISH | 3 | Cliente-servidor ou servidor-cliente | Publicação de mensagem |
PUBREC | 5 | Cliente-servidor ou servidor-cliente | Publicação recebida (entrega QoS 2, parte 1) |
PUBREL | 6 | Cliente-servidor ou servidor-cliente | Publicação emitida (entrega QoS 2, parte 2) |
PUBCOMP | 7 | Cliente-servidor ou servidor-cliente | Publicação concluída (entrega QoS 2, parte 3) |
SUBSCRIBE | 8 | Cliente-servidor | Solicitação de assinatura |
SUBACK | 9 | Servidor-cliente | Confirmação da assinatura |
UNSUBSCRIBE | 10 | Cliente-servidor | Solicitação de cancelamento da assinatura |
UNSUBACK | 11 | Servidor-cliente | Confirmação do cancelamento da assinatura |
PINGREQ | 12 | Cliente-servidor | Solicitação de PING |
PINGRESP | 13 | Servidor-cliente | Resposta do PING |
DISCONNECT | 14 | Cliente-servidor ou servidor-cliente | Notificação de desconexão |
AUTH | 15 | Cliente-servidor ou servidor-cliente | Autenticação do intercâmbio |
Fig. 1. Formato do Cabeçalho Fixo MQTT
Uma vez que não podemos fazer nada até que haja uma conexão entre nosso Cliente e o Servidor, e considerando que no padrão existe uma declaração clara que diz:
"Após o Cliente estabelecer uma conexão de rede com o Servidor, o primeiro pacote enviado do Cliente para o Servidor DEVE ser um pacote CONNECT."
Vamos dar uma olhada em como o cabeçalho fixo do pacote CONNECT deve ser formatado.
Fig. 2. Formato do Cabeçalho Fixo MQTT para o Pacote CONNECT
Isso significa que precisamos preenchê-lo com dois bytes: o primeiro byte deve ter um valor binário de 00010000 e o segundo deve ter o valor da chamada "Remaining Length" (Comprimento Restante).
O padrão define o Comprimento Restante como:
"um número inteiro de bytes variável (Variable Byte Integer) que representa a quantidade de bytes restantes no pacote de controle atual, incluindo os dados no cabeçalho variável e a carga útil. O Comprimento Restante não inclui os bytes usados para sua codificação. O tamanho do pacote é o total de bytes no pacote de controle MQTT, que é igual ao comprimento do cabeçalho fixo mais o Comprimento Restante" (ênfase nossa).
O padrão também define um esquema de codificação para números inteiros de bytes variáveis.
"Os números inteiros de bytes variáveis são codificados usando um esquema de codificação que usa um byte para valores até 127. Valores maiores são tratados da seguinte forma. Os sete bits mais baixos de cada byte codificam os dados, enquanto o bit mais significativo é usado para indicar se há mais bytes na representação. Assim, cada byte codifica 128 valores e um 'bit de continuação'. O número máximo de bytes no campo de número inteiro de bytes variáveis é quatro. O valor codificado DEVE usar o menor número de bytes necessário para representar o valor".
Como podemos ver, esses parágrafos contêm uma quantidade considerável de informações úteis. E nós só estamos preenchendo o segundo byte!
Felizmente, o padrão fornece um "algoritmo para codificar números inteiros não negativos (X) no esquema de codificação de byte variável".
do encodedByte = X MOD 128 X = X DIV 128 // if there are more data to encode, set the top bit of this byte if (X > 0) encodedByte = encodedByte OR 128 endif 'output' encodedByte while (X > 0)
"Onde MOD é o operador de módulo (% no C), DIV é a divisão inteira (/ no C), e OR é a operação bit a bit (| no C)."
Agora temos:
-
uma lista de todos os tipos de pacotes de controle,
-
o formato do cabeçalho fixo do pacote CONNECT com dois bytes,
-
o valor do primeiro byte,
- e um algoritmo para codificar um inteiro de byte variável que preencherá o segundo byte.
Podemos começar a preparar nosso primeiro teste.
Como estamos aplicando a abordagem de TDD ascendente, escreveremos testes antes da implementação. Inicialmente, vamos 1) escrever testes que não funcionam, depois 2) introduzir apenas o código necessário para passar no teste e, em seguida, 3) realizar refatorações, se necessário. Não importa se a implementação inicial é ingênua, desajeitada ou de baixo desempenho. Lidaremos com essas questões assim que tivermos código funcional. O desempenho está no final de nossa lista de tarefas.
Então, abrimos o MetaEditor e criamos um script TestFixedHeader com o seguinte conteúdo.#include <MQTT\mqtt.mqh> //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { //--- Print(TestFixedHeader_Connect()); } //--- bool TestFixedHeader_Connect() { uchar content_buffer[]; //empty //--- uchar expected[2]; expected[0] = 1; //pkt type expected[1] = 0; //remaining length //--- uchar fixed_header[]; //--- GenFixedHeader(CONNECT, content_buffer, fixed_header); //--- if(!ArrayCompare(expected, fixed_header) == 0) { Print(__FUNCTION__); for(uint i = 0; i < expected.Size(); i++) { Print("expected: ", expected[i], " result: ", fixed_header[i]); } return false; } return true; }Também criamos um cabeçalho mqtt.mqh, no qual começaremos a desenvolver nossas funções e o preencheremos com o código abaixo.
void GenFixedHeader(uint pkt_type, uchar& buf[], uchar& head[]) { ArrayFree(head); ArrayResize(head, 2); //--- head[0] = uchar(pkt_type); //--- //Remaining Length uint x; x = ArraySize(buf); do { uint encodedByte = x % 128; x = (uint)(x / 128); if(x > 0) { encodedByte = encodedByte | 128; } head[1] = uchar(encodedByte); } while(x > 0); } //+------------------------------------------------------------------+ enum ENUM_PKT_TYPE { CONNECT = 1, // Connection request CONNACK = 2, // Connect acknowledgment PUBLISH = 3, // Publish message PUBACK = 4, // Publish acknowledgment (QoS 1) PUBREC = 5, // Publish received (QoS 2 delivery part 1) PUBREL = 6, // Publish release (QoS 2 delivery part 2) PUBCOMP = 7, // Publish complete (QoS 2 delivery part 3) SUBSCRIBE = 8, // Subscribe request SUBACK = 9, // Subscribe acknowledgment UNSUBSCRIBE = 10, // Unsubscribe request UNSUBACK = 11, // Unsubscribe acknowledgment PINGREQ = 12, // PING request PINGRESP = 13, // PING response DISCONNECT = 14, // Disconnect notification AUTH = 15, // Authentication exchange };
Ao executar o script, você deve ver o seguinte na guia "Experts".
Fig. 3. Cabeçalho Fixo - Teste Passado
Para ter certeza de que nosso teste está funcionando, precisamos vê-lo quando ele não está funcionando. Sendo assim, recomendamos fortemente alterar o parâmetro de entrada content_buffer, mantendo a variável esperada inalterada. Na guia "Experts", você deve ver algo parecido com o seguinte.
Fig. 4. Cabeçalho Fixo - Teste Falhado
De qualquer forma, partimos do princípio de que nossos testes são atualmente pouco confiáveis, assim como nosso código no cabeçalho mqtt.mqh. Isso não é um problema. Estamos apenas começando, e à medida que avançamos, teremos a oportunidade de torná-los melhores, aprender com nossos erros e aprimorar nossas habilidades.
Agora podemos replicar a função TestFixedHeader_Connect para outros tipos de pacotes. Vamos ignorar aqueles que têm fluxo direcionado apenas do servidor para o cliente. Isso inclui CONNACK, PUBACK, SUBACK, UNSUBACK e PINGRESP. Esses tipos e a resposta ao ping serão gerados pelo servidor. Lidaremos com eles mais tarde.
Para garantir que nossos testes funcionem corretamente, precisamos incluir testes que devem falhar. Esses testes retornam true quando falham.#include <MQTT\mqtt.mqh> //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { //--- Print(TestFixedHeader_Connect()); Print(TestFixedHeader_Connect_RemainingLength1_Fail()); Print(TestFixedHeader_Publish()); Print(TestFixedHeader_Publish_RemainingLength1_Fail()); Print(TestFixedHeader_Puback()); Print(TestFixedHeader_Puback_RemainingLength1_Fail()); Print(TestFixedHeader_Pubrec()); Print(TestFixedHeader_Pubrec_RemainingLength1_Fail()); Print(TestFixedHeader_Pubrel()); Print(TestFixedHeader_Pubrel_RemainingLength1_Fail()); Print(TestFixedHeader_Pubcomp()); Print(TestFixedHeader_Pubcomp_RemainingLength1_Fail()); Print(TestFixedHeader_Subscribe()); Print(TestFixedHeader_Subscribe_RemainingLength1_Fail()); Print(TestFixedHeader_Puback()); Print(TestFixedHeader_Puback_RemainingLength1_Fail()); Print(TestFixedHeader_Unsubscribe()); Print(TestFixedHeader_Unsubscribe_RemainingLength1_Fail()); Print(TestFixedHeader_Pingreq()); Print(TestFixedHeader_Pingreq_RemainingLength1_Fail()); Print(TestFixedHeader_Disconnect()); Print(TestFixedHeader_Disconnect_RemainingLength1_Fail()); Print(TestFixedHeader_Auth()); Print(TestFixedHeader_Auth_RemainingLength1_Fail()); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool TestFixedHeader_Connect() { uchar content_buffer[]; //empty //--- uchar expected[2]; expected[0] = 1; //pkt type expected[1] = 0; //remaining length //--- uchar fixed_header[]; //--- GenFixedHeader(CONNECT, content_buffer, fixed_header); //--- if(!ArrayCompare(expected, fixed_header) == 0) { Print(__FUNCTION__); for(uint i = 0; i < expected.Size(); i++) { Print("expected: ", expected[i], " result: ", fixed_header[i]); } return false; } Print(__FUNCTION__); return true; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool TestFixedHeader_Connect_RemainingLength1_Fail() { uchar content_buffer[]; //empty ArrayResize(content_buffer, 1); content_buffer[0] = 1; //--- uchar expected[2]; expected[0] = 1; //pkt type expected[1] = 0; //remaining length should be 1 //--- uchar fixed_header[]; //--- GenFixedHeader(CONNECT, content_buffer, fixed_header); //--- if(!ArrayCompare(expected, fixed_header) == 0) { Print(__FUNCTION__); for(uint i = 0; i < expected.Size(); i++) { Print("expected: ", expected[i], " result: ", fixed_header[i]); } return true; } Print(__FUNCTION__); return false; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool TestFixedHeader_Publish() { uchar content_buffer[]; //empty //--- uchar expected[2]; expected[0] = 3; //pkt type expected[1] = 0; //remaining length //--- uchar fixed_header[]; //--- GenFixedHeader(PUBLISH, content_buffer, fixed_header); //--- if(!ArrayCompare(expected, fixed_header) == 0) { Print(__FUNCTION__); for(uint i = 0; i < expected.Size(); i++) { Print("expected: ", expected[i], " result: ", fixed_header[i]); } return false; } Print(__FUNCTION__); return true; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool TestFixedHeader_Publish_RemainingLength1_Fail() { uchar content_buffer[]; //empty ArrayResize(content_buffer, 1); content_buffer[0] = 1; //--- uchar expected[2]; expected[0] = 3; //pkt type expected[1] = 0; //remaining length should be 1 //--- uchar fixed_header[]; //--- GenFixedHeader(PUBLISH, content_buffer, fixed_header); //--- if(!ArrayCompare(expected, fixed_header) == 0) { Print(__FUNCTION__); for(uint i = 0; i < expected.Size(); i++) { Print("expected: ", expected[i], " result: ", fixed_header[i]); } return true; } Print(__FUNCTION__); return false; } . . . (окончание не приводится для краткости)
Vemos muito código padrão e repetições.
A longo prazo, isso trará um bom retorno. Esses testes simples (até elementares) permitem construir uma espécie de rede de segurança para nosso desenvolvimento. Eles devem nos ajudar a:
-
Ficar focados na tarefa em questão
-
Evitar complicar demais as coisas
-
Identificar erros de regressão
Recomendo fortemente que você escreva o código por conta própria em vez de apenas usar o arquivo anexado. Você verá quantos pequenos "inofensivos" erros podem ser descobertos desde o início. À medida que nos aprofundamos nas características de trabalho de nosso cliente, esses testes (e alguns outros) provarão seu valor. Além disso, evitamos um problema comum de engenharia de software: adiar a escrita de testes. Normalmente, nesses casos, os testes nunca são escritos.
Fig. 5. Cabeçalho fixo - Todos os Testes Passados
Vamos verificar se nosso cabeçalho MQTT de dois bytes é reconhecido pelo intermediário MQTT como válido.
Instalação do MQTT Broker (e cliente) para desenvolvimento e teste
Existem muitos brokers MQTT preparados para o ambiente de produção disponíveis na Internet, e a maioria deles oferece vários URLs de teste para desenvolvimento e teste. Basta procurar por "MQTT broker" na Internet e você verá muitas opções.
No entanto, nosso cliente está em um estado inicial neste momento. Não podemos receber ou ler respostas sem o uso de um analisador de pacotes para capturar o tráfego de rede. Esta ferramenta será útil mais tarde, mas, por enquanto, é suficiente termos um broker MQTT que esteja em conformidade com as especificações para que possamos verificar seus logs e ver o resultado de nossa interação. Idealmente, ele deve ser instalado em uma máquina virtual para ter um IP diferente do nosso cliente. Ao usar um broker com um IP diferente para desenvolvimento e teste, podemos resolver problemas de conexão e autenticação mais rapidamente.
Novamente, existem várias opções para Windows, Linux e Mac. Eu instalei o Mosquitto no subsistema Windows para Linux (WSL). Além de ser gratuito e de código aberto, o Mosquitto é conveniente porque vem com duas ferramentas de linha de comando muito úteis para desenvolvimento: mosquitto_pub e mosquitto_sub para publicação e subscrição em tópicos MQTT. Também o instalei na minha máquina de desenvolvimento no Windows para poder verificar alguns erros.
Lembre-se de que no MetaTrader você precisa especificar qualquer URL externo no menu "Serviço" > "Configurações" > "Experts" e que você só tem acesso às portas 80 ou 443 no MetaTrader. Portanto, se você instalar o broker no WSL, não se esqueça de especificar o endereço IP do host e redirecionar o tráfego de rede que chega na porta 80 para a porta 1883, que é a porta padrão do MQTT (e do Mosquitto). A ferramenta "redir" permite fazer esse redirecionamento de forma rápida e confiável.
Fig. 6. Diálogo do MetaTrader 5 - Permitir URL da solicitação da web
Para obter o endereço IP do WSL, execute o comando abaixo.
Fig. 7. WSL - Obtenção do nome do host
Após a instalação, o Mosquitto será configurado automaticamente para ser executado como um "serviço" na inicialização. Assim, basta reiniciar o WSL para iniciar o Mosquitto na porta padrão 1883.
Para redirecionar o tráfego de rede da porta 80 para a porta 1883 usando o "redir", execute o comando abaixo.
Fig. 8. Redirecionamento de tráfego de rede com o "redir"
Finalmente, podemos verificar se nosso cabeçalho MQTT de dois bytes é reconhecido como um cabeçalho válido pelo broker MQTT, conforme especificado. Vamos criar um script temporário e inserir o código a seguir. (Não esqueça de alterar o endereço IP na variável "broker_ip" de acordo com os resultados do comando "get hostname -I".)
#include <MQTT\mqtt.mqh> string broker_ip = "172.20.155.236"; int broker_port = 80; //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { int socket = SocketCreate(); if(socket != INVALID_HANDLE) { if(SocketConnect(socket, broker_ip, broker_port, 1000)) { Print("Connected ", broker_ip); //--- uchar fixed_header[]; uchar content_buffer[]; //empty //--- GenFixedHeader(CONNECT, content_buffer, fixed_header); //--- if(SocketSend(socket, fixed_header, ArraySize(fixed_header)) < 0) { Print("Failed sending fixed header ", GetLastError()); } } } }
Na guia "Experts", você deve ver algo parecido com o seguinte.
Fig. 9. Conexão ao broker local
Nos logs do Mosquitto, veremos o seguinte.
Fig. 10. Conexão ao broker local - logs do Mosquitto
Nosso cabeçalho CONNECT de dois bytes foi reconhecido pelo Mosquitto, mas um cliente desconhecido ("<unknown>") foi desconectado imediatamente "devido a um erro de protocolo". Isso ocorreu porque ainda não incluímos o cabeçalho variável com o nome do protocolo, o nível do protocolo e outros metadados relacionados. Vamos corrigir isso no próximo passo.
Como você pode ver no início do comando acima, estamos usando o comando "tail -f {pathToLogFile}". Podemos usá-lo durante o desenvolvimento para acompanhar as atualizações do log do Mosquitto sem a necessidade de abrir e reiniciar o arquivo.
No próximo estágio, implementaremos o cabeçalho variável CONNECT (e outros) para manter uma conexão estável com nosso broker. Também publicaremos (PUBLISH) uma mensagem e lidaremos com os pacotes CONNACK retornados pelo broker e seus respectivos códigos de razão (Reason Codes). O próximo passo incluirá várias operações de bits interessantes para preencher nossos sinalizadores de conexão (Connect Flags). O próximo passo também exigirá que melhoremos substancialmente nossos testes para lidar com as complexidades resultantes da interação entre o cliente e o broker.
Conclusão
Neste artigo, foi apresentada uma breve visão geral do protocolo de troca de mensagens em tempo real no modo "publicador-assinante" MQTT, suas origens e componentes essenciais. Também destacamos algumas possíveis aplicações do MQTT para troca de mensagens em tempo real no contexto da negociação e como usá-lo para operações automatizadas no MetaTrader 5, seja por meio da importação de DLLs geradas a partir de C, C++ ou C#, ou usando a biblioteca MQTT Python por meio do módulo Python para o MetaTrader 5.
Levando em consideração as restrições impostas ao uso de DLLs na plataforma de negociação MetaQuotes e no MetaQuotes Cloud Tester, também propusemos e descrevemos nossos primeiros passos para implementar um cliente MQTT nativo em MQL5 usando a abordagem de desenvolvimento orientado por testes (TDD).
Links Úteis
Não é necessário reinventar a roda. Muitas soluções para os problemas mais comuns enfrentados pelos desenvolvedores ao escrever clientes MQTT em outros idiomas estão disponíveis na forma de bibliotecas/SDK de código aberto.
Se você é um desenvolvedor MQL5 experiente e tem sugestões, deixe um comentário abaixo.
Traduzido do Inglês pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/en/articles/12857
- Aplicativos de negociação gratuitos
- 8 000+ sinais para cópia
- Notícias econômicas para análise dos mercados financeiros
Você concorda com a política do site e com os termos de uso