English Русский 中文 Español Deutsch 日本語
preview
Desenvolvimento de um Cliente MQTT para o MetaTrader 5: Metodologia TDD

Desenvolvimento de um Cliente MQTT para o MetaTrader 5: Metodologia TDD

MetaTrader 5Integração | 7 novembro 2023, 14:26
337 0
Jocimar Lopes
Jocimar Lopes

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.

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

A IBM iniciou o desenvolvimento do protocolo em 1999 para atender à necessidade industrial de monitorar oleodutos usando sensores e enviar dados via satélite para centros de controle remoto. Segundo Arlen Nipper, coautor do protocolo junto com o Dr. Andy Stanford-Clark, o objetivo era fornecer fluxo de dados em tempo real para esses centros.

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

"Hoje, o MQTT é usado em diversas indústrias, como a automobilística, manufatura, telecomunicações, petróleo e gás, entre outras." (mqtt.org).


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

Também podemos usar o caractere # como um curinga para se inscrever em tópicos. Por exemplo, para se inscrever em todas as contas em machine01 em casa:
  • home/machine01/# 
Ou para se inscrever em todas as máquinas no escritório:
  • 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

O cabeçalho fixo de todos os pacotes de controle tem o mesmo formato.

Fig. 1. Formato do Cabeçalho Fixo MQTT

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

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

Output Test Fixed Header - True

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

Output Test Fixed Header - 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

Output Test Fixed Header - 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

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

Рис. 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"

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

Conexão ao broker local

Nos logs do Mosquitto, veremos o seguinte.

Fig. 10. Conexão ao broker local - logs do Mosquitto

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.

  • Programas incluindo brokers, bibliotecas e ferramentas.
  • Recursos relacionados ao MQTT no GitHub.

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

Arquivos anexados |
TestFixedHeader.mq5 (19.48 KB)
mqtt.mqh (2.19 KB)
Melhore seus gráficos de negociação com uma GUI interativa baseada em MQL5 (Parte I): GUI móvel (II) Melhore seus gráficos de negociação com uma GUI interativa baseada em MQL5 (Parte I): GUI móvel (II)
Libere todo o poder da representação de dados dinâmicos em suas estratégias de negociação ou utilitários com o nosso guia detalhado para desenvolver uma GUI móvel em MQL5. Mergulhe nos princípios fundamentais da programação orientada a objetos e aprenda a desenvolver e usar de forma fácil e eficiente uma ou mais GUIs móveis em um único gráfico.
Desenvolvendo um agente de Aprendizado por Reforço em MQL5 com Integração RestAPI(Parte 1): Usando RestAPIs em MQL5 Desenvolvendo um agente de Aprendizado por Reforço em MQL5 com Integração RestAPI(Parte 1): Usando RestAPIs em MQL5
Este artigo aborda a importância das APIs (Interfaces de Programação de Aplicativos) na comunicação entre diferentes aplicativos e sistemas de software. Ele destaca o papel das APIs na simplificação da interação entre aplicativos, permitindo que eles compartilhem dados e funcionalidades de maneira eficiente.
Pode o Heiken-Ashi em combinação com médias móveis oferecer bons sinais? Pode o Heiken-Ashi em combinação com médias móveis oferecer bons sinais?
Combinar estratégias pode aumentar a eficácia da negociação. Podemos combinar indicadores e padrões para obter confirmações adicionais. As médias móveis nos ajudam a confirmar a tendência e a segui-la. Este é o indicador técnico mais conhecido, o que se explica pela sua simplicidade e eficácia comprovada na análise.
Desenvolvendo um sistema de Replay (Parte 34): Sistema de Ordens (III) Desenvolvendo um sistema de Replay (Parte 34): Sistema de Ordens (III)
Vamos neste artigo concluir a primeira fase da construção. Será algo relativamente rápido, mas explicarei detalhes que podem não ter sido comentados no passado. Mas ainda assim aqui explicarei algumas coisas que muitos não entender por que são como são. Um destes casos é o Mouse. Você sabe o motivo de ter que pressionar a tecla Shift ou Ctrl no teclado ?!?!