English Русский 中文 Español Deutsch 日本語
preview
Criando um Expert Advisor Integrado MQL5-Telegram (Parte 5): Enviando Comandos do Telegram para o MQL5 e Recebendo Respostas em Tempo Real

Criando um Expert Advisor Integrado MQL5-Telegram (Parte 5): Enviando Comandos do Telegram para o MQL5 e Recebendo Respostas em Tempo Real

MetaTrader 5Sistemas de negociação |
634 6
Allan Munene Mutiiria
Allan Munene Mutiiria

Introdução

Neste artigo, parte 5 da nossa série, continuamos a integração da Linguagem MetaQuotes 5 (MQL5) com o Telegram, focando no refinamento da interação entre o MetaTrader 5 (MT5) e o Telegram. Anteriormente, na parte 4 da série, estabelecemos as bases para o envio de mensagens complexas e imagens de gráficos do MQL5 para o Telegram, criando a ponte de comunicação entre essas plataformas. Agora, buscamos expandir essa base permitindo que o Expert Advisor receba e interprete comandos diretamente de usuários do Telegram. Em vez do Expert Advisor se controlar gerando sinais, abrindo posições de mercado e enviando mensagens predefinidas ao nosso chat do Telegram, nós o controlaremos diretamente do chat do Telegram, enviando comandos ao Advisor, que por sua vez os decodificará, interpretará e responderá com solicitações e respostas apropriadas e inteligentes.

Começaremos configurando o ambiente necessário para facilitar essa comunicação, garantindo que tudo esteja pronto para uma interação fluida. O núcleo deste artigo envolverá a criação de classes que automaticamente recuperam atualizações de chat a partir de dados JSON (JavaScript Object Notation), que neste caso são os comandos e requisições do Telegram, o que permitirá ao Expert Advisor entender e processar os comandos dos usuários do Telegram. Essa etapa é crucial para estabelecer uma comunicação bidirecional dinâmica, onde o bot não apenas envia mensagens, mas também responde inteligentemente às entradas dos usuários.

Além disso, focaremos na decodificação e interpretação dos dados recebidos, garantindo que o Expert Advisor consiga lidar eficazmente com diversos tipos de comandos da API (Interface de Programação de Aplicações) do Telegram. Para demonstrar esse processo, fornecemos um guia visual detalhado que ilustra o fluxo de comunicação entre o Telegram, o MetaTrader 5 e o editor de código MQL5, tornando mais fácil entender como esses componentes trabalham juntos.

FLUXO DO PROCESSO DE INTEGRAÇÃO

A ilustração fornecida deve ser clara para demonstrar os componentes da integração. Assim, o fluxo será o seguinte: o Telegram envia comandos ao terminal de negociação onde o Expert Advisor está anexado, o Advisor envia os comandos ao MQL5 que decodifica, interpreta as mensagens e prepara as respectivas respostas, que por sua vez são enviadas ao terminal de negociação e ao Telegram como respostas. Para facilitar o entendimento, subdividiremos todo o processo nos seguintes tópicos:

  1. Configurando o Ambiente
  2. Criando Classes para Obter Atualizações do Chat via JSON
  3. Decodificando e Interpretando Dados da API do Telegram
  4. Tratando Respostas
  5. Testando a Implementação
  6. Conclusão

Ao final do artigo, teremos um Expert Advisor totalmente integrado que envia comandos e requisições do Telegram para o MQL5 e recebe respostas supervisionadas no chat do Telegram. Vamos começar então.


Configurando o Ambiente

É fundamental estabelecer um ambiente que permita ao nosso Expert Advisor (EA) interagir com o Telegram antes de iniciar efetivamente o trabalho de criação das classes e funções. Nosso EA precisará acessar diversas bibliotecas essenciais que facilitam o gerenciamento de negociações, arrays e strings no MQL5. Ao disponibilizar essas bibliotecas essenciais, garantimos que nosso EA tenha acesso a um conjunto completo de funções e classes, o que facilita bastante o processo de implementação. Isso é mostrado a seguir:

#include <Trade/Trade.mqh>
#include <Arrays/List.mqh>
#include <Arrays/ArrayString.mqh>
    

Aqui, a biblioteca "<Trade/Trade.mqh>" fornece um conjunto completo de funções de negociação. Essa biblioteca permite ao EA executar negociações, gerenciar posições e realizar outras tarefas relacionadas ao mercado. É um componente crítico de qualquer EA que deseja interagir com o mercado. As bibliotecas "<Arrays/List.mqh>" e "<Arrays/ArrayString.mqh>" que seguem são incluídas para facilitar o gerenciamento de estruturas de dados. A primeira dessas bibliotecas é usada para gerenciar listas dinâmicas. A segunda é utilizada para trabalhar com arrays de strings. Ambas são especialmente úteis ao lidar com os sinais de negociação que recebemos do Telegram. Sabemos que isso foi muito jargão técnico. Os capítulos seguintes irão detalhar isso um pouco mais e tentaremos explicar melhor o que cada um desses componentes faz. Para acessar a biblioteca "Arrays", abra o navegador, expanda a pasta "includes" e verifique qualquer uma das duas, como ilustrado abaixo.

BIBLIOTECA ARRAYS

Por fim, precisamos definir a URL base do Telegram, o tempo limite (timeout) e o token do bot, como mostrado abaixo.

#define TELEGRAM_BASE_URL  "https://api.telegram.org"
#define WEB_TIMEOUT        5000
//#define InpToken "7456439661:AAELUurPxI1jloZZl3Rt-zWHRDEvBk2venc"
#define InpToken "7456439661:AAELUurPxI1jloZZl3Rt-zWHRDEvBk2venc"

Após incluir as bibliotecas e compilar seu programa, você estará pronto com o ambiente necessário para lidar com estruturas de dados complexas recebidas dos comandos do Telegram, e podemos agora prosseguir com a implementação. 


Criando Classes para Obter Atualizações do Chat via JSON

Esta é a seção onde focamos no desenvolvimento da funcionalidade central que permite ao nosso Expert Advisor (EA) receber atualizações do Telegram em tempo real. Especificamente, precisaremos criar classes que façam o parse dos dados JSON retornados pela API do Telegram e extraiam as informações necessárias, como atualizações de chat e comandos de usuários. Essa etapa é essencial para estabelecer um ciclo de comunicação responsiva entre o Telegram e o MetaTrader 5. Primeiro, vamos simular o processo. Novamente, vamos carregar a função padrão para obter atualizações do chat conforme abaixo em nosso navegador, para que possamos visualizar a estrutura de dados necessária para implementar as classes.

DADOS VAZIOS

Após carregar, retornamos verdadeiro, indicando que o processo foi bem-sucedido, mas a estrutura de dados está vazia. Isso ocorre porque não foram enviadas mensagens pelo chat do Telegram nas últimas 24 horas. Portanto, precisamos enviar uma mensagem para obter uma atualização. Para isso, enviamos uma mensagem de inicialização a partir do chat do Telegram, como mostrado abaixo.

MENSAGEM DE INICIALIZAÇÃO DO TELEGRAM

Assim que enviamos a mensagem, agora temos uma atualização e podemos recarregar o link no navegador para obter a estrutura dos dados enviados.

ESTRUTURA DE DADOS 1

A partir da imagem acima, podemos ver os detalhes corretos na estrutura de dados, que é construída a partir da mensagem que enviamos. Esse é exatamente o dado que precisamos copiar para nossas classes e percorrer a cada nova atualização de mensagem. Portanto, vamos construir uma classe que conterá todas as variáveis membro. Vamos primeiro construir o modelo geral da classe.

//+------------------------------------------------------------------+
//|        Class_Message                                             |
//+------------------------------------------------------------------+
class Class_Message : public CObject{//Defines a class named Class_Message that inherits from CObject.
   public:
      Class_Message(); // constructor
      ~Class_Message(){}; // Declares a destructor for the class, which is empty.
};

Vamos nos concentrar no protótipo da classe que declaramos acima para que tudo flua corretamente depois. Para declarar uma classe, usamos a palavra-chave "class" seguida pelo nome da classe, que no nosso caso é "Class_Message". Como receberemos muitas estruturas de dados semelhantes, herdamos outra classe chamada "CObject", e tornamos os membros herdados da classe externa públicos usando a palavra-chave "public". Em seguida, declaramos os primeiros membros da classe como "public". Antes de continuar, vamos explicar em detalhe o que tudo isso significa. A palavra-chave é um dos 4 qualificadores, comumente chamados de especificadores de acesso, e eles definem como o compilador pode acessar variáveis, membros de estruturas ou classes. Os quatro são: public, protected, private e virtual.

Vamos detalhá-los e explicar cada um separadamente.

  • Public: Membros declarados com o especificador de acesso "public" são acessíveis de qualquer parte do código onde a classe seja visível. Isso significa que funções, variáveis ou outros objetos fora da classe podem acessar e usar diretamente os membros públicos. Eles são frequentemente usados para funções ou variáveis que precisam ser acessadas por outras classes, funções ou scripts.
  • Protected: Membros declarados com o especificador de acesso "protected" não são acessíveis fora da classe, mas são acessíveis dentro da própria classe, por classes derivadas (ou seja, subclasses que herdam desta classe) e por classes/funções amigas Isso é útil para encapsular dados que devem estar disponíveis para subclasses, mas não para o restante do programa. Eles são normalmente usados para permitir que subclasses acessem ou modifiquem certas variáveis ou funções da classe base, ao mesmo tempo em que ocultam esses membros do restante do programa.
  • Private: Membros declarados com o especificador de acesso "private" são acessíveis apenas dentro da própria classe. Nem classes derivadas, nem qualquer outra parte do programa pode acessar ou modificar diretamente os membros privados. Esse é o nível de acesso mais restritivo e é comumente usado para variáveis e funções auxiliares que não devem ser acessadas ou modificadas fora da classe. Eles são frequentemente usados para implementar ocultação de dados, garantindo que o estado interno de um objeto só possa ser modificado por meio de interfaces públicas bem definidas (métodos).
  • Virtual: Aplica-se apenas a métodos de classe (mas não a métodos de estruturas) e informa ao compilador que esse método deve ser colocado na tabela de funções virtuais da classe.

Dos exemplos de sintaxe apresentados acima, apenas os três primeiros são comumente usados. Agora, voltando ao nosso protótipo de classe, vamos destrinchar o que cada parte faz.

  • Declaração de Classe:

class Class_Message : public CObject{...};: Aqui, declaramos uma nova classe chamada "Class_Message". Esta classe herda de "CObject", que é uma classe base no MQL5 usada para criar objetos personalizados. Assim, "Class_Message" pode utilizar os recursos fornecidos pelo framework do MQL5, como gerenciamento de memória e outros benefícios da programação orientada a objetos, para exibir mensagens convenientemente em nosso programa.

  • Construtor:

Class_Message();: O construtor da classe "Class_Message" é declarado aqui. Um construtor é uma função especial chamada automaticamente quando uma instância (ou objeto) da classe é criada. O trabalho do construtor é inicializar as variáveis membro da classe e realizar qualquer configuração que precise ser feita ao criar o objeto. No caso da "Class_Message", ele inicializa as variáveis membro.

  • Destrutor:

~Class_Message(){};: A classe "Class_Message" declara um destrutor. Um destrutor é chamado automaticamente quando uma instância de uma classe é explicitamente deletada ou sai do escopo. Normalmente, um destrutor é definido para realizar a limpeza e é conceitualmente o oposto de um construtor, que é chamado quando uma instância da classe é criada. Neste caso, o destrutor da classe "Class_Message" não faz nada (não executa tarefas de limpeza), pois isso não é necessário no momento. 

Observe que tanto o construtor quanto o destrutor possuem o mesmo nome da classe base, com a diferença de que o destrutor possui um til (~) como prefixo. Com isso, agora podemos continuar definindo os membros da nossa classe. Esses membros são os mesmos recebidos na nossa estrutura de dados. Assim, visualizaremos a estrutura de dados e os membros que precisamos extrair dela como abaixo.

DETALHES CLAROS DA MENSAGEM

Pela imagem acima, podemos ver que precisamos de no mínimo 14 membros em nossa classe. Nós os definimos conforme abaixo:

      bool              done; //A boolean member variable TO INDICATE if a message has been processed.
      long              update_id; //Store the update ID from Telegram.
      long              message_id;//Stores the message ID.
      //---
      long              from_id;//Stores the sender’s ID.
      string            from_first_name;
      string            from_last_name;
      string            from_username;
      //---
      long              chat_id;
      string            chat_first_name;
      string            chat_last_name;
      string            chat_username;
      string            chat_type;
      //---
      datetime          message_date;
      string            message_text;

Agora temos todos os membros da classe que precisamos. A estrutura final da classe é como a mostrada abaixo. Adicionamos comentários para tornar todo o processo autoexplicativo.

//+------------------------------------------------------------------+
//|        Class_Message                                             |
//+------------------------------------------------------------------+
class Class_Message : public CObject{//--- Defines a class named Class_Message that inherits from CObject.
   public:
      Class_Message(); //--- Constructor declaration.
      ~Class_Message(){}; //--- Declares an empty destructor for the class.
      
      //--- Member variables to track the status of the message.
      bool              done; //--- Indicates if a message has been processed.
      long              update_id; //--- Stores the update ID from Telegram.
      long              message_id; //--- Stores the message ID.

      //--- Member variables to store sender-related information.
      long              from_id; //--- Stores the sender’s ID.
      string            from_first_name; //--- Stores the sender’s first name.
      string            from_last_name; //--- Stores the sender’s last name.
      string            from_username; //--- Stores the sender’s username.

      //--- Member variables to store chat-related information.
      long              chat_id; //--- Stores the chat ID.
      string            chat_first_name; //--- Stores the chat first name.
      string            chat_last_name; //--- Stores the chat last name.
      string            chat_username; //--- Stores the chat username.
      string            chat_type; //--- Stores the chat type.

      //--- Member variables to store message-related information.
      datetime          message_date; //--- Stores the date of the message.
      string            message_text; //--- Stores the text of the message.
};

Após definir a classe de mensagens, precisamos inicializar seus membros para que fiquem prontos para receber dados. Fazemos isso chamando o construtor da classe.

//+------------------------------------------------------------------+
//|      Constructor to initialize class members                     |
//+------------------------------------------------------------------+
Class_Message::Class_Message(void){
   //--- Initialize the boolean 'done' to false, indicating the message is not processed.
   done = false;
   
   //--- Initialize message-related IDs to zero.
   update_id = 0;
   message_id = 0;
   
   //--- Initialize sender-related information.
   from_id = 0;
   from_first_name = NULL;
   from_last_name = NULL;
   from_username = NULL;
   
   //--- Initialize chat-related information.
   chat_id = 0;
   chat_first_name = NULL;
   chat_last_name = NULL;
   chat_username = NULL;
   chat_type = NULL;
   
   //--- Initialize the message date and text.
   message_date = 0;
   message_text = NULL;
}

Primeiro, chamamos a classe base e definimos o construtor utilizando o "operador de escopo" (::). Em seguida, inicializamos as variáveis membro com seus valores padrão. O booleano "done" é definido como "false", significando que a mensagem ainda não foi processada. Tanto "message_id" quanto "update_id" são inicializados como 0, o que representa os IDs padrão para mensagem e atualização. Para informações relacionadas ao remetente, "from_id" é definido como 0, e as variáveis "from_first_name", "from_last_name" e "from_username" são inicializadas como NULL, significando que os dados do remetente não estão definidos. Da mesma forma, variáveis relacionadas ao chat, ou seja, "chat_id", "chat_first_name", "chat_last_name", "chat_username" e "chat_type", também são inicializadas como 0 ou NULL, conforme seu tipo de dado, indicando que as informações do chat ainda não estão disponíveis. Por fim, "message_date" é definido como 0, e "message_text" é inicializado como NULL, o que significa que o conteúdo e a data da mensagem ainda não estão especificados. Tecnicamente, inicializamos variáveis do tipo "inteiro" com 0 e strings com NULL.

Da mesma forma, precisamos definir outra instância de classe que será usada para armazenar chats individuais do Telegram. Usaremos esses dados para fazer uma comparação entre os dados analisados e os dados recebidos do Telegram. Por exemplo, quando enviamos o comando "get Ask price", analisamos os dados, obtemos atualizações do JSON e verificamos se algum dos dados recebidos e armazenados no JSON corresponde ao nosso comando e, se sim, tomamos a ação necessária. Esperamos que isso esclareça algumas coisas, mas ficará mais claro à medida que avançarmos. O trecho de código da classe está abaixo:

//+------------------------------------------------------------------+
//|        Class_Chat                                                |
//+------------------------------------------------------------------+
class Class_Chat : public CObject{
   public:
      Class_Chat(){}; //Declares an empty constructor.
      ~Class_Chat(){}; // deconstructor
      long              member_id;//Stores the chat ID.
      int               member_state;//Stores the state of the chat.
      datetime          member_time;//Stores the time related to the chat.
      Class_Message     member_last;//An instance of Class_Message to store the last message.
      Class_Message     member_new_one;//An instance of Class_Message to store the new message.
};

Definimos uma classe chamada "Class_Chat" para lidar e armazenar informações de chats individuais do Telegram. Essa classe contém um construtor e um destrutor vazios, e vários membros: "member_id" armazena o ID único do chat; "member_state" indica o estado do chat; e "member_time" mantém qualquer informação relacionada ao tempo do chat. A classe possui duas instâncias da classe base que já definimos, "Class_Message", que armazenam respectivamente a última e a nova mensagem. Precisamos dessas instâncias para armazenar as mensagens e processá-las individualmente quando o usuário envia múltiplos comandos. Para ilustrar isso, enviaremos uma mensagem de inicialização como abaixo:

SEGUNDA MENSAGEM DE INICIALIZAÇÃO

Ao lermos as atualizações do chat, obtemos a seguinte estrutura de dados.

ESTRUTURA DE DADOS DA SEGUNDA MENSAGEM

A partir da estrutura de dados da segunda mensagem recebida, podemos ver que os IDs de atualização e de mensagem da primeira mensagem são 794283239 e 664 respectivamente, enquanto a segunda mensagem possui 794283240 e 665, com uma diferença de 1. Esperamos que isso esclareça a necessidade de uma classe diferente. Agora podemos prosseguir para criar a última classe padrão que utilizaremos para controlar o fluxo de interação de forma fluida. Sua estrutura está descrita abaixo.

//+------------------------------------------------------------------+
//|   Class_Bot_EA                                                    |
//+------------------------------------------------------------------+
class Class_Bot_EA{
   private:
      string            member_token;         //--- Stores the bot’s token.
      string            member_name;          //--- Stores the bot’s name.
      long              member_update_id;     //--- Stores the last update ID processed by the bot.
      CArrayString      member_users_filter;  //--- An array to filter users.
      bool              member_first_remove;  //--- A boolean to indicate if the first message should be removed.
   
   protected:
      CList             member_chats;         //--- A list to store chat objects.

   public:
      void Class_Bot_EA();   //--- Declares the constructor.
      ~Class_Bot_EA(){};    //--- Declares the destructor.
      int getChatUpdates(); //--- Declares a function to get updates from Telegram.
      void ProcessMessages(); //--- Declares a function to process incoming messages.
};

Definimos uma classe chamada "Class_Bot_EA" para gerenciar as interações entre o bot do Telegram e o ambiente MQL5. Ela possui vários membros privados, como "member_token", que armazena o token de autenticação do bot, e "member_name", que contém o nome do bot. Outro membro é o "member_update_id", que mantém o controle da última atualização processada. Diversos outros membros gerenciam e filtram interações com o usuário. A classe tem um membro protegido, "member_chats", que mantém uma lista de objetos de chat. Entre seus membros públicos, os mais notáveis são o construtor e o destrutor, que realizam a inicialização e limpeza necessárias das instâncias. Há também duas funções de destaque entre os membros públicos: "getChatUpdates", que recupera atualizações do Telegram, e "ProcessMessages", que lida com o processamento das mensagens recebidas. Essas são as funções mais cruciais que usaremos para obter as atualizações de chat e processar os comandos recebidos. Inicializaremos esses membros usando um formato semelhante ao que fizemos com a primeira classe, conforme abaixo.

void Class_Bot_EA::Class_Bot_EA(void){ //--- Constructor
   member_token=NULL; //--- Initialize the bot's token as NULL.
   member_token=getTrimmedToken(InpToken); //--- Assign the trimmed bot token from InpToken.
   member_name=NULL; //--- Initialize the bot's name as NULL.
   member_update_id=0; //--- Initialize the last update ID to 0.
   member_first_remove=true; //--- Set the flag to remove the first message to true.
   member_chats.Clear(); //--- Clear the list of chat objects.
   member_users_filter.Clear(); //--- Clear the user filter array.
}

Aqui, invocamos o construtor da classe "Class_Bot_EA" e inicializamos as variáveis membro para configurar o ambiente do bot. Inicialmente, "member_token" é definido como NULL como espaço reservado. Depois, atribuímos a ele a versão ajustada de "InpToken". Esse valor é muito importante, pois rege a autenticação do bot. Se o espaço reservado ajustado for deixado no código, o bot simplesmente não funcionará. O "member_name" também é inicializado como NULL, e "member_update_id" é definido como 0, o que indica que nenhuma atualização foi processada ainda. A variável "member_first_remove" é definida como verdadeira. Isso significa que o bot está configurado para remover a primeira mensagem que processar. Por fim, tanto "member_chats" quanto "member_users_filter" são limpos, para garantir que comecem vazios. Você pode ter notado que usamos uma função diferente para obter o token do bot. A função está abaixo.

//+------------------------------------------------------------------+
//|        Function to get the Trimmed Bot's Token                   |
//+------------------------------------------------------------------+
string getTrimmedToken(const string bot_token){
   string token=getTrimmedString(bot_token); //--- Trim the bot_token using getTrimmedString function.
   if(token==""){ //--- Check if the trimmed token is empty.
      Print("ERR: TOKEN EMPTY"); //--- Print an error message if the token is empty.
      return("NULL"); //--- Return "NULL" if the token is empty.
   }
   return(token); //--- Return the trimmed token.
}

//+------------------------------------------------------------------+
//|        Function to get a Trimmed string                          |
//+------------------------------------------------------------------+
string getTrimmedString(string text){
   StringTrimLeft(text); //--- Remove leading whitespace from the string.
   StringTrimRight(text); //--- Remove trailing whitespace from the string.
   return(text); //--- Return the trimmed string.
}

Aqui, definimos duas funções que trabalham em conjunto para limpar e validar a string do token do bot. A primeira função, "getTrimmedToken", acessa o "bot_token" como entrada. Ela então chama outra função, "getTrimmedString", para remover qualquer espaço em branco à esquerda ou à direita do token. Após a limpeza, a função verifica se o token está vazio. Se o token estiver vazio após o ajuste, uma mensagem de erro é impressa e a função retorna "NULL", indicando que o bot não pode continuar com esse token. Por outro lado, se o token não estiver vazio, ele é retornado como um token válido e ajustado.

A segunda função, "getTrimmedString", realiza de fato o trabalho de remover espaços em branco das extremidades de uma string. Ela utiliza StringTrimLeft para remover espaços à esquerda e StringTrimRight para remover espaços à direita, e então retorna a string ajustada como um token válido.

Até este ponto, já temos as estruturas de dados necessárias para organizar os metadados recebidos. Precisamos então prosseguir para obter as atualizações do chat e processá-las simultaneamente. Para garantir uma comunicação clara, primeiro chamaremos as funções da classe. Para acessar os membros da classe, teremos que criar um objeto com base na classe para obter o acesso necessário. Isso é feito conforme abaixo:

Class_Bot_EA obj_bot; //--- Create an instance of the Class_Bot_EA class

Após declarar o objeto da classe como "obj_bot", podemos acessar os membros da classe usando o operador ponto. Precisaremos verificar as atualizações e processar as mensagens em um intervalo de tempo definido. Assim, ao invés de usar o manipulador de eventos OnTick, que seria mais demorado por contar os ticks (e poderia consumir recursos do computador), optamos pela função OnTimer, que faz a contagem automaticamente. Para usar esse manipulador de eventos, precisaremos configurá-lo e inicializá-lo no manipulador OnInit, conforme abaixo.

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit(){
   EventSetMillisecondTimer(3000); //--- Set a timer event to trigger every 3000 milliseconds (3 seconds)
   OnTimer(); //--- Call OnTimer() immediately to get the first update
   return(INIT_SUCCEEDED); //--- Return initialization success
}

Aqui, inicializamos o Expert Advisor configurando um evento de timer usando a função EventSetMillisecondTimer para disparar a cada 3000 milissegundos (3 segundos). Isso garante que o Expert Advisor verifique atualizações continuamente em intervalos regulares. Em seguida, chamamos imediatamente o manipulador OnTimer para obter a primeira atualização logo após a inicialização, garantindo que o processo comece sem atraso. Por fim, retornamos "INIT_SUCCEEDED" para indicar que a inicialização foi bem-sucedida. Como definimos o timer, quando o programa for desinicializado, precisamos destruir o timer definido para liberar os recursos do computador.

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason){
   EventKillTimer(); //--- Kill the timer event to stop further triggering
   ChartRedraw(); //--- Redraw the chart to reflect any changes
}

Aqui, quando o Expert Advisor é removido ou interrompido, a primeira coisa que fazemos no manipulador OnDeinit é parar o evento de timer. Isso é feito usando a função EventKillTimer, que é o equivalente lógico de EventSetMillisecondTimer. Não queremos que o timer continue executando se o Expert Advisor não estiver mais funcionando. Após parar o timer, chamamos a função ChartRedraw. Chamar essa função não é estritamente necessário, mas pode ajudar em algumas situações onde é preciso atualizar o gráfico para aplicar alterações. Por fim, chamamos o manipulador de evento do timer para cuidar do processo de contagem.

//+------------------------------------------------------------------+
//| Timer function                                                   |
//+------------------------------------------------------------------+
void OnTimer(){
   obj_bot.getChatUpdates(); //--- Call the function to get chat updates from Telegram
   obj_bot.ProcessMessages(); //--- Call the function to process incoming messages
}

Por fim, chamamos o manipulador de eventos OnTimer. Dentro dele, chamamos nossas duas funções cruciais que são necessárias para obter as atualizações de chat e processar as mensagens, respectivamente, usando o objeto "obj_bot" que criamos e o operador ponto para acessar as funções da classe. Até este ponto, tudo foi um sucesso e agora podemos nos concentrar nas funções. Isso será feito nas próximas seções.


Decodificando e Interpretando Dados da API do Telegram

A primeira coisa que precisamos fazer é obter as atualizações de chat, que usaremos para fazer uma comparação com o texto recebido do Telegram e, se houver correspondência, executar a resposta necessária. Portanto, faremos isso na função responsável por obter as atualizações.

//+------------------------------------------------------------------+
int Class_Bot_EA::getChatUpdates(void){

//--- ....

}

Após chamar a função, a primeira coisa que fazemos é garantir que temos um token válido, e se não for o caso, imprimimos uma mensagem de erro no log e retornamos -1, indicando que não podemos prosseguir sem o token. Isso é mostrado a seguir:

   //--- Check if the bot token is NULL
   if(member_token==NULL){
      Print("ERR: TOKEN EMPTY"); //--- Print an error message if the token is empty
      return(-1); //--- Return with an error code
   }

Se o token não estiver vazio, podemos prosseguir para preparar uma requisição a ser enviada para a API do Telegram para recuperar atualizações de um chat específico.

   string out; //--- Variable to store the response from the request
   string url=TELEGRAM_BASE_URL+"/bot"+member_token+"/getUpdates"; //--- Construct the URL for the Telegram API request
   string params="offset="+IntegerToString(member_update_id); //--- Set the offset parameter to get updates after the last processed ID
   
   //--- Send a POST request to get updates from Telegram
   int res=postRequest(out, url, params, WEB_TIMEOUT);

Começamos declarando uma variável chamada "out" para armazenar a resposta retornada da requisição à API. Para construir a URL da requisição, combinamos a URL base da API ("TELEGRAM_BASE_URL"), o token do bot ("member_token") e o método que queremos chamar ("/getUpdates"). Esse método recupera atualizações enviadas ao bot por usuários, permitindo que vejamos o que aconteceu desde a última verificação de atualizações. Incluímos então um único parâmetro em nossa requisição. O parâmetro "offset" garante que apenas atualizações que ocorreram após a última atualização recuperada sejam retornadas. Por fim, emitimos uma requisição POST para a API, com o resultado da requisição armazenado na variável "out" e indicado pelo campo "res" da resposta. Utilizamos uma função personalizada chamada "postRequest". Abaixo está seu trecho de código e explicação. É similar ao que fizemos nas partes anteriores, mas agora com comentários para explicar as variáveis utilizadas.

//+------------------------------------------------------------------+
//| Function to send a POST request and get the response             |
//+------------------------------------------------------------------+
int postRequest(string &response, const string url, const string params,
                const int timeout=5000){
   char data[]; //--- Array to store the data to be sent in the request
   int data_size=StringLen(params); //--- Get the length of the parameters
   StringToCharArray(params, data, 0, data_size); //--- Convert the parameters string to a char array

   uchar result[]; //--- Array to store the response data
   string result_headers; //--- Variable to store the response headers

   //--- Send a POST request to the specified URL with the given parameters and timeout
   int response_code=WebRequest("POST", url, NULL, NULL, timeout, data, data_size, result, result_headers);
   if(response_code==200){ //--- If the response code is 200 (OK)
      //--- Remove Byte Order Mark (BOM) if present
      int start_index=0; //--- Initialize the starting index for the response
      int size=ArraySize(result); //--- Get the size of the response data array
      // Loop through the first 8 bytes of the 'result' array or the entire array if it's smaller
      for(int i=0; i<fmin(size,8); i++){
         // Check if the current byte is part of the BOM
         if(result[i]==0xef || result[i]==0xbb || result[i]==0xbf){
            // Set 'start_index' to the byte after the BOM
            start_index=i+1;
         }
         else {break;}
      }
      //--- Convert the response data from char array to string, skipping the BOM
      response=CharArrayToString(result, start_index, WHOLE_ARRAY, CP_UTF8);
      //Print(response); //--- Optionally print the response for debugging

      return(0); //--- Return 0 to indicate success
   }
   else{
      if(response_code==-1){ //--- If there was an error with the WebRequest
         return(_LastError); //--- Return the last error code
      }
      else{
         //--- Handle HTTP errors
         if(response_code>=100 && response_code<=511){
            response=CharArrayToString(result, 0, WHOLE_ARRAY, CP_UTF8); //--- Convert the result to string
            Print(response); //--- Print the response for debugging
            Print("ERR: HTTP"); //--- Print an error message indicating an HTTP error
            return(-1); //--- Return -1 to indicate an HTTP error
         }
         return(response_code); //--- Return the response code for other errors
      }
   }
   return(0); //--- Return 0 in case of an unexpected error
}

Aqui, cuidamos do envio de uma requisição POST e do processamento da resposta. Começamos pegando os parâmetros de entrada e os convertendo para uma forma que possa ser enviada — usando StringToCharArray para criar um array de caracteres a partir da string de parâmetros. Em seguida, definimos dois arrays que capturarão os dados da resposta e os cabeçalhos da resposta. Por fim, usamos a função WebRequest para enviar a requisição POST para a URL de destino, com os parâmetros que deve utilizar e um tempo limite configurado.

Quando nossa requisição tem sucesso (o que determinamos ao receber um código de resposta 200), garantimos que nada interfira com o processamento logo no início dos dados de resposta. Especificamente, verificamos se há algum Byte Order Mark (BOM). Se encontrarmos um, tratamos como uma substring que não deveria estar ali e tomamos medidas para não incluí-la nos dados que usaremos posteriormente. Depois disso, convertemos os dados de array de caracteres para uma string. Se passarmos por todas essas etapas sem problemas, retornamos 0 para indicar que tudo correu bem.

Quando nossa requisição não é bem-sucedida, lidamos com o erro verificando o código retornado com a resposta. Se o problema estiver na função WebRequest, informamos ao usuário qual foi o último código de erro definido — essa é a única forma de identificar o que houve. Se estivermos lidando com um erro HTTP, fazemos o possível para interpretar a mensagem de erro que veio com a resposta HTTP e informamos ao usuário o que encontramos. Por fim, para quaisquer outros códigos de resposta que possamos receber, apenas retornamos o código.

Antes de prosseguir, podemos verificar os dados que estão sendo enviados conferindo a resposta e imprimindo os dados. Fazemos isso com a seguinte lógica.

   //--- If the request was successful
   if(res==0){
      Print(out); //--- Optionally print the response
   }

Aqui, verificamos se o resultado do post é igual a zero e, se for, imprimimos os dados para depuração e verificação. Ao executar, temos os seguintes resultados.

DADOS DA RESPOSTA

Aqui, podemos ver que a resposta é verdadeira, o que significa que o processo para obter as atualizações foi bem-sucedido. Agora precisamos obter os dados da resposta e, para isso, usaremos o parseamento de JSON. Não entraremos em muitos detalhes sobre o código responsável pela análise, mas o incluiremos como um arquivo e também o adicionaremos ao escopo global do nosso programa. Após essa adição, prosseguimos para criar um objeto JSON como abaixo.

      //--- Create a JSON object to parse the response
      CJSONValue obj_json(NULL, jv_UNDEF);

Depois de criar o objeto, usamos ele para desserializar a resposta conforme abaixo. 

      //--- Deserialize the JSON response
      bool done=obj_json.Deserialize(out);

Declaramos uma variável booleana chamada "done" para armazenar os resultados. É aqui que armazenamos os indicadores de se a resposta foi corretamente analisada ou não. Podemos imprimi-la para fins de depuração como mostrado abaixo.

      Print(done);

Após a impressão, obtemos a seguinte resposta.

RESPOSTA DA DESSERIALIZAÇÃO

Aqui, podemos ver que analisamos corretamente a resposta. Precisamos que a resposta seja verdadeira para podermos prosseguir. Caso a resposta não seja positiva, precisamos interromper o processo e retornar, pois não acessaremos o restante das atualizações de mensagens. Por esse motivo, garantimos que se a resposta for negativa, encerramos o processo.

      if(!done){
         Print("ERR: JSON PARSING"); //--- Print an error message if parsing fails
         return(-1); //--- Return with an error code
      }

Aqui, verificamos se a análise do JSON foi bem-sucedida ao avaliar a variável booleana "done". Se a análise falhar (ou seja, "done" for falso), imprimimos uma mensagem de erro "ERR: JSON PARSING" para indicar que houve um problema ao interpretar a resposta JSON. Em seguida, retornamos -1 para sinalizar que ocorreu um erro durante o processo de análise do JSON. Depois disso, garantimos que a resposta seja processada com sucesso através da lógica a seguir.

      //--- Check if the 'ok' field in the JSON is true
      bool ok=obj_json["ok"].ToBool();
      //--- If 'ok' is false, there was an error in the response
      if(!ok){
         Print("ERR: JSON NOT OK"); //--- Print an error message if 'ok' is false
         return(-1); //--- Return with an error code
      }

Primeiro, verificamos o valor do campo 'ok' no JSON que é recuperado da resposta. Isso nos informa se a solicitação foi processada com sucesso. Extraímos esse campo e o armazenamos em uma variável booleana chamada "ok". Se o valor de "ok" for falso, isso indica que houve um erro ou algum tipo de problema com a resposta, mesmo que a solicitação em si tenha sido bem-sucedida. Nesse caso, imprimimos "ERR: JSON NOT OK" para sinalizar que houve algum problema e retornamos -1 para indicar que também houve um problema ao processar a resposta JSON. Se tudo correu bem, significa que temos atualizações de mensagens e podemos prosseguir para recuperá-las. Assim, precisaremos declarar um objeto com base na classe de mensagens conforme abaixo:

      //--- Create a message object to store message details
      Class_Message obj_msg;

Agora podemos percorrer todas as atualizações de mensagens e armazená-las na classe usando o objeto criado. Primeiro, precisamos obter o número total de atualizações, o que é feito por meio da seguinte lógica.

      //--- Get the total number of updates in the JSON array 'result'
      int total=ArraySize(obj_json["result"].m_elements);
      //--- Loop through each update
      for(int i=0; i<total; i++){

      }

A cada iteração, precisamos recuperar um item individual de atualização da resposta JSON com o qual iremos trabalhar.

         //--- Get the individual update item as a JSON object
         CJSONValue obj_item=obj_json["result"].m_elements[i];

Podemos então prosseguir para obter as atualizações individuais do chat. Primeiro, vamos às atualizações de mensagens.

         //--- Extract message details from the JSON object
         obj_msg.update_id=obj_item["update_id"].ToInt(); //--- Get the update ID
         obj_msg.message_id=obj_item["message"]["message_id"].ToInt(); //--- Get the message ID
         obj_msg.message_date=(datetime)obj_item["message"]["date"].ToInt(); //--- Get the message date
         
         obj_msg.message_text=obj_item["message"]["text"].ToStr(); //--- Get the message text
         obj_msg.message_text=decodeStringCharacters(obj_msg.message_text); //--- Decode any HTML entities in the message text

Aqui, pegamos os detalhes da mensagem individual do item de atualização indicado por "obj_item". Começamos extraindo o ID da atualização do objeto JSON e o armazenamos em "obj_msg.update_id". Depois disso, extraímos o ID da mensagem e o armazenamos em "obj_msg.message_id". A data da mensagem, que vem em um formato não tão legível, também está incluída no item, e a armazenamos como um objeto "datetime" em "obj_msg.message_date", que depois "convertido" para um formato legível. Em seguida, analisamos o texto da mensagem. Na maioria das vezes, podemos simplesmente capturar o texto e colocá-lo em "obj_msg.message_text". No entanto, às vezes os caracteres HTML estão codificados; outras vezes, há caracteres especiais também codificados. Para esses casos, tratamos isso em uma função chamada "decodeStringCharacters". Essa é uma função que explicamos anteriormente e que chamamos aqui para fazer seu trabalho. Depois, de forma semelhante, extraímos os detalhes do remetente.

         //--- Extract sender details from the JSON object
         obj_msg.from_id=obj_item["message"]["from"]["id"].ToInt(); //--- Get the sender's ID
         obj_msg.from_first_name=obj_item["message"]["from"]["first_name"].ToStr(); //--- Get the sender's first name
         obj_msg.from_first_name=decodeStringCharacters(obj_msg.from_first_name); //--- Decode the first name
         obj_msg.from_last_name=obj_item["message"]["from"]["last_name"].ToStr(); //--- Get the sender's last name
         obj_msg.from_last_name=decodeStringCharacters(obj_msg.from_last_name); //--- Decode the last name
         obj_msg.from_username=obj_item["message"]["from"]["username"].ToStr(); //--- Get the sender's username
         obj_msg.from_username=decodeStringCharacters(obj_msg.from_username); //--- Decode the username

Após extrair os detalhes do remetente, extraímos também os detalhes do chat da mesma maneira.

         //--- Extract chat details from the JSON object
         obj_msg.chat_id=obj_item["message"]["chat"]["id"].ToInt(); //--- Get the chat ID
         obj_msg.chat_first_name=obj_item["message"]["chat"]["first_name"].ToStr(); //--- Get the chat's first name
         obj_msg.chat_first_name=decodeStringCharacters(obj_msg.chat_first_name); //--- Decode the first name
         obj_msg.chat_last_name=obj_item["message"]["chat"]["last_name"].ToStr(); //--- Get the chat's last name
         obj_msg.chat_last_name=decodeStringCharacters(obj_msg.chat_last_name); //--- Decode the last name
         obj_msg.chat_username=obj_item["message"]["chat"]["username"].ToStr(); //--- Get the chat's username
         obj_msg.chat_username=decodeStringCharacters(obj_msg.chat_username); //--- Decode the username
         obj_msg.chat_type=obj_item["message"]["chat"]["type"].ToStr(); //--- Get the chat type

Até este ponto, você já deve ter notado que a estrutura é exatamente a mesma que fornecemos na estrutura de dados do navegador. Então podemos prosseguir para atualizar o ID da atualização para garantir que a próxima solicitação de atualizações do Telegram comece do ponto certo.

         //--- Update the ID for the next request
         member_update_id=obj_msg.update_id+1;

Aqui, atualizamos o "member_update_id" para garantir que a próxima solicitação de atualizações do Telegram comece do ponto correto. Ao atribuir o valor "obj_msg.update_id + 1", definimos o deslocamento para que a próxima solicitação não inclua a atualização atual e, com isso, apenas receba novas atualizações que ocorrerem após esse ID. Isso é importante porque não queremos tratar a mesma atualização mais de uma vez, e também queremos manter o bot o mais responsivo possível. Em seguida, verificamos por novas atualizações.

         //--- If it's the first update, skip processing
         if(member_first_remove){
            continue;
         }

Aqui, determinamos se a atualização atual é a primeira a ser processada após a inicialização, verificando a flag "member_first_remove". Se "member_first_remove" for verdadeiro, isso indica que estamos processando a primeira atualização - a atualização inicial - após tudo ter sido inicializado. Então ignoramos o processamento dessa atualização apenas continuando para a próxima. Por fim, filtramos e gerenciamos as mensagens do chat com base em se há ou não um filtro de nome de usuário aplicado.

         //--- Filter messages based on username
         if(member_users_filter.Total()==0 || //--- If no filter is applied, process all messages
            (member_users_filter.Total()>0 && //--- If a filter is applied, check if the username is in the filter
            member_users_filter.SearchLinear(obj_msg.from_username)>=0)){

            //--- Find the chat in the list of chats
            int index=-1;
            for(int j=0; j<member_chats.Total(); j++){
               Class_Chat *chat=member_chats.GetNodeAtIndex(j);
               if(chat.member_id==obj_msg.chat_id){ //--- Check if the chat ID matches
                  index=j;
                  break;
               }
            }

            //--- If the chat is not found, add a new chat to the list
            if(index==-1){
               member_chats.Add(new Class_Chat); //--- Add a new chat to the list
               Class_Chat *chat=member_chats.GetLastNode();
               chat.member_id=obj_msg.chat_id; //--- Set the chat ID
               chat.member_time=TimeLocal(); //--- Set the current time for the chat
               chat.member_state=0; //--- Initialize the chat state
               chat.member_new_one.message_text=obj_msg.message_text; //--- Set the new message text
               chat.member_new_one.done=false; //--- Mark the new message as not processed
            }
            //--- If the chat is found, update the chat message
            else{
               Class_Chat *chat=member_chats.GetNodeAtIndex(index);
               chat.member_time=TimeLocal(); //--- Update the chat time
               chat.member_new_one.message_text=obj_msg.message_text; //--- Update the message text
               chat.member_new_one.done=false; //--- Mark the new message as not processed
            }
         }

Primeiro, determinamos se um filtro de nome de usuário está ativo verificando o "member_users_filter.Total()". Se não houver filtro ("Total() == 0"), tratamos todas as mensagens normalmente. Se houver um filtro ("Total() > 0"), verificamos se o nome de usuário do remetente ("obj_msg.from_username") está no filtro, usando "member_users_filter.SearchLinear()". Se encontrarmos o nome de usuário, então tratamos a mensagem.

Em seguida, procuramos o chat na lista "member_chats" iterando por ela e comparando com o ID do chat ("obj_msg.chat_id"). Se o chat não for encontrado (índice == -1), adicionamos um novo objeto "Class_Chat" à lista. Inicializamos o objeto com o ID do chat, a hora atual, um estado inicial 0, e o texto da nova mensagem. Também marcamos a nova mensagem como não processada (done = false).

Se o chat já estiver na lista, atualizamos o objeto de chat existente com o novo texto da mensagem e a hora atual, marcando a mensagem como não processada. Isso garante que a mensagem mais recente de cada chat seja registrada e atualizada corretamente. Depois de tudo feito, definimos a flag de primeira atualização como falsa.

      //--- After the first update, set the flag to false
      member_first_remove=false;

Por fim, retornamos o resultado da solicitação POST.

   //--- Return the result of the POST request
   return(res);

Com essa função, podemos ter certeza de que recuperamos as atualizações do chat e as armazenamos a cada intervalo de tempo definido, podendo processá-las sempre que necessário. O processamento das mensagens é feito na próxima seção.


Tratando Respostas

Após obter as atualizações do chat, podemos prosseguir para acessar as mensagens recuperadas, fazer comparações e enviar respostas de volta para Telegram. Isso é feito por meio da função "ProcessMessages" da classe.

void Class_Bot_EA::ProcessMessages(void){

//---

}

A primeira coisa que precisamos fazer é processar os chats individuais.

   //--- Loop through all chats
   for(int i=0; i<member_chats.Total(); i++){
      Class_Chat *chat=member_chats.GetNodeAtIndex(i); //--- Get the current chat
      if(!chat.member_new_one.done){ //--- Check if the message has not been processed yet
         chat.member_new_one.done=true; //--- Mark the message as processed
         string text=chat.member_new_one.message_text; //--- Get the message text

         //---

      }
   }

Aqui, iteramos pela coleção "member_chats" e recuperamos o objeto de chat correspondente a cada chat usando a variável de índice "i" de "member_chats". Para cada chat, verificamos a mensagem associada ao chat atual para ver se já foi processada, avaliando a flag done na estrutura "member_new_one". Se a mensagem ainda não tiver sido processada, definimos essa flag como verdadeira, marcando a mensagem como tratada para evitar o processamento duplicado. Por fim, extraímos o texto da mensagem da estrutura "member_new_one". Utilizaremos esse texto para determinar que tipo de resposta ou ação, se houver, deve ser tomada com base no conteúdo da mensagem. Primeiro, vamos definir um exemplo onde o usuário envia um texto de saudação "Hello" a partir do Telegram.

         //--- Process the command based on the message text
         
         //--- If the message is "Hello"
         if(text=="Hello"){
            string message="Hello world! You just sent a 'Hello' text to MQL5 and has been processed successfully.";
            
         }

Aqui, verificamos se o texto da mensagem é "Hello". Se for, criamos uma resposta para informar ao usuário que o sistema recebeu e processou o texto "Hello". Essa resposta serve como uma confirmação de que a entrada foi corretamente tratada pelo código MQL5. Em seguida, enviamos esse reconhecimento de volta ao usuário para que ele saiba que sua mensagem foi processada com sucesso. Para enviar a resposta, precisaremos criar outra função para lidar com as respostas.

//+------------------------------------------------------------------+
//| Send a message to Telegram                                      |
//+------------------------------------------------------------------+
int sendMessageToTelegram(const long chat_id,const string text,
                const string reply_markup=NULL){
   string output; //--- Variable to store the response from the request
   string url=TELEGRAM_BASE_URL+"/bot"+getTrimmedToken(InpToken)+"/sendMessage"; //--- Construct the URL for the Telegram API request

   //--- Construct parameters for the API request
   string params="chat_id="+IntegerToString(chat_id)+"&text="+UrlEncode(text); //--- Set chat ID and message text
   if(reply_markup!=NULL){ //--- If a reply markup is provided
      params+="&reply_markup="+reply_markup; //--- Add reply markup to parameters
   }
   params+="&parse_mode=HTML"; //--- Set parse mode to HTML (can also be Markdown)
   params+="&disable_web_page_preview=true"; //--- Disable web page preview in the message

   //--- Send a POST request to the Telegram API
   int res=postRequest(output,url,params,WEB_TIMEOUT); //--- Call postRequest to send the message
   return(res); //--- Return the response code from the request
}

Aqui, definimos a função "sendMessageToTelegram", que envia uma mensagem para um chat específico do Telegram usando a API do Bot Telegram. Primeiro, construímos a URL para a requisição da API combinando a URL base do Telegram, o token do bot (obtido usando "getTrimmedToken") e o método específico para envio de mensagens ("sendMessage"). Essa URL é essencial para direcionar a requisição da API para o endpoint correto. Em seguida, construímos os parâmetros da requisição. Esses parâmetros incluem:

  • chat_id: O ID do chat onde a mensagem será enviada.
  • text: O conteúdo da mensagem, que é codificado na URL para garantir sua transmissão correta.

Se um teclado de resposta personalizado ("reply_markup") for fornecido, ele será adicionado aos parâmetros. Isso permite botões interativos na mensagem. Parâmetros adicionais incluem:

  • parse_mode=HTML: Especifica que a mensagem deve ser interpretada como HTML, permitindo texto formatado.
  • disable_web_page_preview=true: Garante que quaisquer pré-visualizações de páginas da web sejam desativadas na mensagem.

Por fim, a função envia a requisição usando a função "postRequest", que lida com a comunicação real com a API do Telegram. O código de resposta dessa requisição é retornado para indicar se a mensagem foi enviada com sucesso ou se ocorreu algum erro.

Podemos então chamar essa função com os parâmetros respectivos conforme abaixo para enviar a resposta.

            //--- Send the response message 
            sendMessageToTelegram(chat.member_id,message,NULL);
            continue;

Aqui, utilizamos primeiro a função "sendMessageToTelegram" para enviar a mensagem de resposta para o chat do Telegram apropriado. Chamamos a função com o "chat.member_id" para direcionar a mensagem ao chat correto. O parâmetro "reply_markup" é definido como NULL, significando que a mensagem enviada não possui teclado ou elementos interativos. Após o envio da mensagem, usamos o comando "continue". Ele ignora qualquer código restante no loop em processamento e passa para a próxima iteração do loop. A lógica aqui é simples: tratamos e encaminhamos a resposta para a mensagem atual. Depois disso, simplesmente seguimos em frente, não processando mais nada para o chat ou mensagem atuais nessa iteração. Após a compilação, temos o seguinte resultado:

HELLO WORLD

Podemos ver que a mensagem foi recebida e processada em poucos segundos. Vamos então adicionar um teclado de resposta personalizado à nossa função.

//+------------------------------------------------------------------+
//| Create a custom reply keyboard markup for Telegram               |
//+------------------------------------------------------------------+
string customReplyKeyboardMarkup(const string keyboard, const bool resize,
                           const bool one_time){
   // Construct the JSON string for the custom reply keyboard markup.
   // 'keyboard' specifies the layout of the custom keyboard.
   // 'resize' determines whether the keyboard should be resized to fit the screen.
   // 'one_time' specifies if the keyboard should disappear after being used once.
   
   // 'resize' > true: Resize the keyboard to fit the screen.
   // 'one_time' > true: The keyboard will disappear after the user has used it once.
   // 'selective' > false: The keyboard will be shown to all users, not just specific ones.
   
   string result = "{"
                   "\"keyboard\": " + UrlEncode(keyboard) + ", " //--- Encode and set the keyboard layout
                   "\"one_time_keyboard\": " + convertBoolToString(one_time) + ", " //--- Set whether the keyboard should disappear after use
                   "\"resize_keyboard\": " + convertBoolToString(resize) + ", " //--- Set whether the keyboard should be resized to fit the screen
                   "\"selective\": false" //--- Keyboard will be shown to all users
                   "}";
   
   return(result); //--- Return the JSON string for the custom reply keyboard
}

Aqui, definimos a função "customReplyKeyboardMarkup", que cria um teclado de resposta personalizado para o Telegram. Essa função recebe três parâmetros: teclado, resize e one_time. O parâmetro teclado especifica o layout do teclado personalizado no formato JSON. O parâmetro resize determina se o teclado será redimensionado para se ajustar à tela do dispositivo do usuário. Se o parâmetro resize for verdadeiro, o teclado será redimensionado para se ajustar à tela do dispositivo do usuário. O parâmetro one_time especifica se o teclado será um "teclado de uso único", desaparecendo após o usuário interagir com ele.

Dentro da função, uma string JSON é construída para representar o teclado de resposta personalizado. Para garantir que o parâmetro teclado esteja formatado corretamente para a requisição da API, usamos a função "UrlEncode" para codificá-lo. Em seguida, utilizamos a função "convertBoolToString" para converter os valores booleanos de resize e one_time (que determinam se esses valores devem ser considerados verdadeiros ou falsos) em suas representações em string. Por fim, a string construída é retornada pela função e pode ser usada em requisições para a API do Telegram. A função personalizada que utilizamos é a seguinte.

//+------------------------------------------------------------------+
//| Convert boolean value to string                                 |
//+------------------------------------------------------------------+
string convertBoolToString(const bool _value){
   if(_value)
      return("true"); //--- Return "true" if the boolean value is true
   return("false"); //--- Return "false" if the boolean value is false
}

Por fim, para ocultar e forçar resposta nos teclados personalizados, usamos as seguintes funções.

//+------------------------------------------------------------------+
//| Create JSON for hiding custom reply keyboard                    |
//+------------------------------------------------------------------+
string hideCustomReplyKeyboard(){
   return("{\"hide_keyboard\": true}"); //--- JSON to hide the custom reply keyboard
}

//+------------------------------------------------------------------+
//| Create JSON for forcing a reply to a message                    |
//+------------------------------------------------------------------+
string forceReplyCustomKeyboard(){
   return("{\"force_reply\": true}"); //--- JSON to force a reply to the message
}

Aqui, as funções "hideCustomReplyKeyboard" e "forceReplyCustomKeyboard" geram strings JSON que especificam ações específicas a serem realizadas pelo recurso de teclado personalizado do Telegram.

Para a função "hideCustomReplyKeyboard", a string JSON gerada é: "{"hide_keyboard": true}". Essa configuração JSON informa ao Telegram para ocultar o teclado de resposta após o usuário enviar uma mensagem. Em essência, essa função serve para fazer o teclado desaparecer assim que uma mensagem for enviada.

Para a função "forceReplyCustomKeyboard", a string JSON gerada é: "{"force_reply": true}". Essa string diz ao Telegram que uma resposta do usuário é necessária antes que ele possa interagir com qualquer outro elemento da interface no chat. Essa string serve para manter o foco do usuário na mensagem que acabou de ser enviada.

Com a função de teclado de resposta personalizado pronta, vamos então chamá-la para construir o teclado de resposta no Telegram.

         //--- If the message is "Hello"
         if(text=="Hello"){
            string message="Hello world! You just sent a 'Hello' text to MQL5 and has been processed successfully.";
            
            //--- Send the response message 
            sendMessageToTelegram(chat.member_id,message,customReplyKeyboardMarkup("[[\"Hello\"]]",false,false));
            continue;
         }

Quando enviamos a mensagem no Telegram, temos o seguinte resultado.

TECLADO DE RESPOSTA PERSONALIZADO HELLO

Podemos ver que foi um sucesso. Agora podemos enviar a mensagem apenas clicando no botão. Porém, ele é bem grande. Agora podemos adicionar vários botões. Primeiro, vamos adicionar os botões no formato de linhas.

            string message="Hello world! You just sent a 'Hello' text to MQL5 and has been processed successfully.";
            string buttons_rows = "[[\"Hello 1\"],[\"Hello 2\"],[\"Hello 3\"]]";
            //--- Send the response message 
            sendMessageToTelegram(chat.member_id,message,customReplyKeyboardMarkup(buttons_rows,false,false));
            continue;

Aqui, definimos um layout de teclado de resposta personalizado com a variável "buttons_rows". Essa string "[["Hello 1"],["Hello 2"],["Hello 3"]]" representa um teclado com três botões, cada um com o rótulo "Hello 1", "Hello 2" e "Hello 3". O formato dessa string é JSON, que é utilizado pelo Telegram para renderizar o teclado. Ao executar, temos os seguintes resultados.

LAYOUT EM LINHAS

Para visualizar o layout do teclado no formato de colunas, implementamos a seguinte lógica.

            string message="Hello world! You just sent a 'Hello' text to MQL5 and has been processed successfully.";
            string buttons_rows = "[[\"Hello 1\",\"Hello 2\",\"Hello 3\"]]";
            //--- Send the response message 
            sendMessageToTelegram(chat.member_id,message,customReplyKeyboardMarkup(buttons_rows,false,false));

Ao executar o programa, recebemos a seguinte saída:

LAYOUT EM COLUNAS

Podemos ver que o layout recebido está no formato de colunas, o que significa que o processo foi bem-sucedido. Agora podemos continuar criando comandos mais complexos. Primeiramente, vamos definir uma lista personalizada de comandos que o usuário pode utilizar rapidamente.

         //--- If the message is "/start", "/help", "Start", or "Help"
         if(text=="/start" || text=="/help" || text=="Start" || text=="Help"){
            //chat.member_state=0; //--- Reset the chat state
            string message="I am a BOT \xF680 and I work with your MT5 Forex trading account.\n";
            message+="You can control me by sending these commands \xF648 :\n";
            message+="\nInformation\n";
            message+="/name - get EA name\n";
            message+="/info - get account information\n";
            message+="/quotes - get quotes\n";
            message+="/screenshot - get chart screenshot\n";
            message+="\nTrading Operations\n";
            message+="/buy - open buy position\n";
            message+="/close - close a position\n";
            message+="\nMore Options\n";
            message+="/contact - contact developer\n";
            message+="/join - join our MQL5 community\n";
            
            //--- Send the response message with the main keyboard
            sendMessageToTelegram(chat.member_id,message,customReplyKeyboardMarkup(KEYB_MAIN,false,false));
            continue;
         }

Aqui, verificamos se a mensagem recebida é um dos comandos pré-determinados "/start", "/help", "Start" ou "Help". Se for um desses comandos, preparamos uma mensagem de boas-vindas que apresenta o bot ao usuário e fornece uma lista de comandos que podem ser enviados ao bot para interagir com ele. Omitimos partes dessa lista e categorizamos outras para dar ao usuário uma visão geral do que pode ser feito com o bot. Por fim, enviamos essa mensagem junto com um teclado personalizado de volta ao usuário, que é mais adequado para interagir com o bot do que a linha de comando. Também definimos o teclado personalizado da seguinte forma.

   #define EMOJI_CANCEL "\x274C" //--- Cross mark emoji
   #define KEYB_MAIN    "[[\"Name\"],[\"Account Info\"],[\"Quotes\"],[\"More\",\"Screenshot\",\""+EMOJI_CANCEL+"\"]]" //--- Main keyboard layout

Usamos a macro #define para definir dois elementos que serão usados na interface do usuário do bot do Telegram. Primeiro, definimos "EMOJI_CANCEL" como um emoji de marca de cruz usando sua representação Unicode "\x274C". Usaremos esse emoji no layout do teclado para indicar uma opção de "Cancelar". A representação Unicode do emoji é mostrada abaixo:

UNICODE DA MARCA DE CRUZ

Em seguida, definimos "KEYB_MAIN", que representa o layout principal do teclado para o bot. O teclado é estruturado como um array JSON com linhas de botões. O layout inclui opções contidas na lista de comandos, que são "Name", "Account Info", "Quotes" e uma linha com "More", "Screenshot" e o botão "Cancel" representado por "EMOJI_CANCEL". Este teclado será exibido ao usuário, permitindo que ele interaja com o bot pressionando esses botões, em vez de digitar comandos manualmente. Quando executamos o programa, obtemos a seguinte saída:

INTERFACE JSON DO TELEGRAM 1

Agora temos o teclado personalizado em formato JSON e a lista de comandos que podemos enviar ao bot. O que resta agora é a criação das respectivas respostas conforme os comandos recebidos do Telegram. Começaremos respondendo ao comando "/name". 

         //--- If the message is "/name" or "Name"
         if (text=="/name" || text=="Name"){
            string message = "The file name of the EA that I control is:\n";
            message += "\xF50B"+__FILE__+" Enjoy.\n";
            sendMessageToTelegram(chat.member_id,message,NULL);
         }

Aqui, verificamos se a mensagem recebida do usuário é "/name" ou "Name". Caso o resultado da verificação seja positivo, começamos a construir uma resposta para o usuário contendo o nome do arquivo do Expert Advisor (EA) que está sendo usado no momento. Inicializamos uma variável string chamada "message", que começa com o texto "O nome do arquivo do EA que eu controlo é:\n". Seguimos essa declaração inicial com um emoji de livro (representado pelo código "\xF50B") e o nome do arquivo EA.

Usamos a macro interna do MQL5 "FILE" para obter o nome do arquivo. A macro retorna o nome e o caminho do arquivo. Em seguida, construímos uma mensagem a ser enviada ao usuário. A mensagem consiste no nome do arquivo EA e o caminho para ele. Enviamos a mensagem construída usando a função "sendMessageToTelegram". Essa função recebe três parâmetros: o primeiro é o ID do chat do usuário para o qual queremos enviar a mensagem; o segundo é a própria mensagem; e o terceiro parâmetro, definido como "NULL", indica que não estamos enviando nenhum teclado personalizado ou comando de botão com a mensagem. Isso é importante, já que não queremos criar um teclado adicional. Quando clicamos no comando "/name" ou no botão correspondente, recebemos a seguinte resposta:

COMANDO NAME

Isso foi um sucesso. Da mesma forma, construímos as respostas para os comandos de informações de conta e cotações de preços. Isso é feito com o seguinte trecho de código:

         //--- If the message is "/info" or "Account Info"
         ushort MONEYBAG = 0xF4B0; //--- Define money bag emoji
         string MONEYBAGcode = ShortToString(MONEYBAG); //--- Convert emoji to string
         if(text=="/info" || text=="Account Info"){
            string currency=AccountInfoString(ACCOUNT_CURRENCY); //--- Get the account currency
            string message="\x2733\Account No: "+(string)AccountInfoInteger(ACCOUNT_LOGIN)+"\n";
            message+="\x23F0\Account Server: "+AccountInfoString(ACCOUNT_SERVER)+"\n";
            message+=MONEYBAGcode+"Balance: "+(string)AccountInfoDouble(ACCOUNT_BALANCE)+" "+currency+"\n";
            message+="\x2705\Profit: "+(string)AccountInfoDouble(ACCOUNT_PROFIT)+" "+currency+"\n";
            
            //--- Send the response message
            sendMessageToTelegram(chat.member_id,message,NULL);
            continue;
         }

         //--- If the message is "/quotes" or "Quotes"
         if(text=="/quotes" || text=="Quotes"){
            double Ask = SymbolInfoDouble(_Symbol,SYMBOL_ASK); //--- Get the current ask price
            double Bid = SymbolInfoDouble(_Symbol,SYMBOL_BID); //--- Get the current bid price
            string message="\xF170 Ask: "+(string)Ask+"\n";
            message+="\xF171 Bid: "+(string)Bid+"\n";
            
            //--- Send the response message
            sendMessageToTelegram(chat.member_id,message,NULL);
            continue;
         }

Para os comandos de operação de negociação, mais especificamente para abrir uma posição de compra, usamos a seguinte lógica:

         //--- If the message is "/buy" or "Buy"
         if (text=="/buy" || text=="Buy"){
            CTrade obj_trade; //--- Create a trade object
            double Ask = SymbolInfoDouble(_Symbol,SYMBOL_ASK); //--- Get the current ask price
            double Bid = SymbolInfoDouble(_Symbol,SYMBOL_BID); //--- Get the current bid price
            obj_trade.Buy(0.01,NULL,0,Bid-300*_Point,Bid+300*_Point); //--- Open a buy position
            double entry=0,sl=0,tp=0,vol=0;
            ulong ticket = obj_trade.ResultOrder(); //--- Get the ticket number of the new order
            if (ticket > 0){
               if (PositionSelectByTicket(ticket)){ //--- Select the position by ticket
                  entry=PositionGetDouble(POSITION_PRICE_OPEN); //--- Get the entry price
                  sl=PositionGetDouble(POSITION_SL); //--- Get the stop loss price
                  tp=PositionGetDouble(POSITION_TP); //--- Get the take profit price
                  vol=PositionGetDouble(POSITION_VOLUME); //--- Get the volume
               }
            }
            string message="\xF340\Opened BUY Position:\n";
            message+="Ticket: "+(string)ticket+"\n";
            message+="Open Price: "+(string)entry+"\n";
            message+="Lots: "+(string)vol+"\n";
            message+="SL: "+(string)sl+"\n";
            message+="TP: "+(string)tp+"\n";
            
            //--- Send the response message
            sendMessageToTelegram(chat.member_id,message,NULL);
            continue;
         }

Aqui, tratamos um cenário em que o usuário envia a mensagem "/buy" ou "Buy". Nosso primeiro passo é criar um objeto CTrade chamado "obj_trade", que usaremos para realizar a operação de negociação. Em seguida, obtemos os preços atuais de ask e bid chamando a função SymbolInfoDouble. Para abrir nossa posição de compra, usamos a função Buy do objeto CTrade. Definimos o volume da negociação em 0.01 lotes. Para o SL (stop loss) e TP (take profit), definimos como o preço bid menos 300 pontos e o preço bid mais 300 pontos, respectivamente.

Uma vez que a posição esteja aberta, determinamos o número do ticket da nova ordem via função "ResultOrder". Com o ticket em mãos, usamos a função PositionGetInteger para selecionar a posição por ticket. Em seguida, recuperamos estatísticas importantes como o preço de entrada, volume, stop loss e take profit. Usando esses números, construímos uma mensagem para informar ao usuário que ele abriu uma posição de compra. Para lidar com o encerramento da posição e o comando de contato, usamos uma lógica semelhante.

         //--- If the message is "/close" or "Close"
         if (text=="/close" || text=="Close"){
            CTrade obj_trade; //--- Create a trade object
            int totalOpenBefore = PositionsTotal(); //--- Get the total number of open positions before closing
            obj_trade.PositionClose(_Symbol); //--- Close the position for the symbol
            int totalOpenAfter = PositionsTotal(); //--- Get the total number of open positions after closing
            string message="\xF62F\Closed Position:\n";
            message+="Total Positions (Before): "+(string)totalOpenBefore+"\n";
            message+="Total Positions (After): "+(string)totalOpenAfter+"\n";
            
            //--- Send the response message
            sendMessageToTelegram(chat.member_id,message,NULL);
            continue;
         }

         //--- If the message is "/contact" or "Contact"
         if (text=="/contact" || text=="Contact"){
            string message="Contact the developer via link below:\n";
            message+="https://t.me/Forex_Algo_Trader";
            
            //--- Send the contact message
            sendMessageToTelegram(chat.member_id,message,NULL);
            continue;
         }

Agora está claro que conseguimos responder aos comandos enviados pelo Telegram. Até agora, apenas enviamos mensagens de texto simples. Vamos tornar isso um pouco mais sofisticado e formatar nossas mensagens usando a entidade Hypertext Markup Language (HTML), que também pode ser Markdown. Você escolhe!

         //--- If the message is "/join" or "Join"
         if (text=="/join" || text=="Join"){
            string message="You want to be part of our MQL5 Community?\n";
            message+="Welcome! <a href=\"https://t.me/forexalgo_trading\">Click me</a> to join.\n";
            message+="<s>Civil Engineering</s> Forex AlgoTrading\n";//strikethrough
            message+="<pre>This is a sample of our MQL5 code</pre>\n";//preformat
            message+="<u><i>Remember to follow community guidelines!\xF64F\</i></u>\n";//italic, underline
            message+="<b>Happy Trading!</b>\n";//bold
            
            //--- Send the join message
            sendMessageToTelegram(chat.member_id,message,NULL);
            continue;
         }

Aqui, respondemos ao usuário quando ele envia a mensagem "/join" ou "Join". Começamos redigindo uma mensagem que convida o usuário a entrar na Comunidade MQL5. A mensagem inclui um hiperlink que os usuários podem clicar para entrar na comunidade, bem como vários exemplos de como o texto pode ser formatado usando tags HTML no Telegram:

  • Texto Tachado: Usamos a tag <s> para riscar as palavras "Civil Engineering" e enfatizar que focamos em "Forex AlgoTrading".
  • Texto Pré-formatado: A tag <pre> é usada para exibir um trecho de código MQL5 em um bloco de texto pré-formatado.
  • Texto Itálico e Sublinhado: As tags <u> e <i> são combinadas para sublinhar e colocar em itálico um lembrete para que os usuários sigam as diretrizes da comunidade, adicionando um emoji Unicode para ênfase.
  • Texto em Negrito: A tag <b> é usada para colocar em negrito a declaração final "Happy Trading!"

Por fim, enviamos essa mensagem formatada ao usuário via Telegram utilizando a função "sendMessageToTelegram", garantindo que o usuário receba um convite bem formatado e envolvente para participar da comunidade MQL5. Ao executar, obtemos o seguinte resultado:

ENTIDADE HTML

Agora que esgotamos a lista de comandos, vamos continuar modificando o teclado de resposta e gerar um novo quando o botão "more" for clicado. A lógica a seguir é implementada:

         //--- If the message is "more" or "More"
         if (text=="more" || text=="More"){
            chat.member_state=1; //--- Update chat state to show more options
            string message="Choose More Options Below:";
            
            //--- Send the more options message with the more options keyboard
            sendMessageToTelegram(chat.member_id,message,customReplyKeyboardMarkup(KEYB_MORE,false,true));
            continue;
         }

Quando recebemos a mensagem "more" ou "More" do usuário, interpretamos como um sinal para atualizar o contexto da conversa atual. No mundo dos chatbots, essa mensagem indica que o usuário não está satisfeito com o número atual de opções ou ainda não encontrou o que procura. Nossa resposta ao usuário deve, portanto, fornecer uma variedade diferente de seleções. Na prática, isso significa enviar ao usuário uma nova mensagem com um novo layout de teclado. O "KEYB_MORE" é como mostrado abaixo:

   #define EMOJI_UP    "\x2B06" //--- Upwards arrow emoji
   #define KEYB_MORE "[[\""+EMOJI_UP+"\"],[\"Buy\",\"Close\",\"Next\"]]" //--- More options keyboard layout

Quando executamos o programa, obtemos a seguinte saída:

GIF DO MORE

Isso foi um sucesso. Podemos lidar da mesma forma com os outros comandos.

         //--- If the message is the up emoji
         if(text==EMOJI_UP){
            chat.member_state=0; //--- Reset chat state
            string message="Choose a menu item:";
            
            //--- Send the message with the main keyboard
            sendMessageToTelegram(chat.member_id,message,customReplyKeyboardMarkup(KEYB_MAIN,false,false));
            continue;
         }

         //--- If the message is "next" or "Next"
         if(text=="next" || text=="Next"){
            chat.member_state=2; //--- Update chat state to show next options
            string message="Choose Still More Options Below:";
            
            //--- Send the next options message with the next options keyboard
            sendMessageToTelegram(chat.member_id,message,customReplyKeyboardMarkup(KEYB_NEXT,false,true));
            continue;
         }

         //--- If the message is the pistol emoji
         if (text==EMOJI_PISTOL){
            if (chat.member_state==2){
               chat.member_state=1; //--- Change state to show more options
               string message="Choose More Options Below:";
               
               //--- Send the message with the more options keyboard
               sendMessageToTelegram(chat.member_id,message,customReplyKeyboardMarkup(KEYB_MORE,false,true));
            }
            else {
               chat.member_state=0; //--- Reset chat state
               string message="Choose a menu item:";
               
               //--- Send the message with the main keyboard
               sendMessageToTelegram(chat.member_id,message,customReplyKeyboardMarkup(KEYB_MAIN,false,false));
            }
            continue;
         }

         //--- If the message is the cancel emoji
         if (text==EMOJI_CANCEL){
            chat.member_state=0; //--- Reset chat state
            string message="Choose /start or /help to begin.";
            
            //--- Send the cancel message with hidden custom reply keyboard
            sendMessageToTelegram(chat.member_id,message,hideCustomReplyKeyboard());
            continue;
         }

Aqui, lidamos com mensagens diversas dos usuários para controlar a interface do chat. Quando um usuário envia o emoji de seta para cima, interpretamos isso como um sinal e redefinimos o estado do chat para 0, solicitando ao usuário que escolha novamente um item de menu, acompanhado do layout principal do teclado. Quando o usuário envia "next" ou "Next", atualizamos o estado do chat para 2 e instruímos o usuário a escolher novamente um item de menu, desta vez com um layout de teclado que apresenta opções adicionais.

Para o emoji de pistola, ajustamos o estado do chat com base em seu valor atual: se o estado for 2, mudamos para 1 e mostramos o teclado de mais opções; caso contrário, mudamos para 0 e apresentamos o teclado do menu principal. Para o emoji de cancelamento, redefinimos o estado do chat para 0 e enviamos ao usuário uma mensagem dizendo para escolher "/start" ou "/help" para começar. Enviamos essa mensagem com o teclado personalizado oculto para limpar quaisquer teclados ativos para o usuário. Os layouts personalizados extras usados são os seguintes:

   #define EMOJI_PISTOL   "\xF52B" //--- Pistol emoji
   #define KEYB_NEXT "[[\""+EMOJI_UP+"\",\"Contact\",\"Join\",\""+EMOJI_PISTOL+"\"]]" //--- Next options keyboard layout

Até aqui, tudo está completo. Resta apenas lidar com os comandos de captura de tela, e isso será tudo. A seguinte lógica é implementada para lidar com o modo de recebimento das imagens dos gráficos. O layout do teclado será usado para esse fim, evitando a digitação manual.

         //--- If the message is "/screenshot" or "Screenshot"
         static string symbol = _Symbol; //--- Default symbol
         static ENUM_TIMEFRAMES period = _Period; //--- Default period
         if (text=="/screenshot" || text=="Screenshot"){
            chat.member_state = 10; //--- Set state to screenshot request
            string message="Provide a symbol like 'AUDUSDm'";
            
            //--- Send the message with the symbols keyboard
            sendMessageToTelegram(chat.member_id,message,customReplyKeyboardMarkup(KEYB_SYMBOLS,false,false));
            continue;
         }

         //--- Handle state 10 (symbol selection for screenshot)
         if (chat.member_state==10){
            string user_symbol = text; //--- Get the user-provided symbol
            if (SymbolSelect(user_symbol,true)){ //--- Check if the symbol is valid
               chat.member_state = 11; //--- Update state to period request
               string message = "CORRECT: Symbol is found\n";
               message += "Now provide a Period like 'H1'";
               symbol = user_symbol; //--- Update symbol
               
               //--- Send the message with the periods keyboard
               sendMessageToTelegram(chat.member_id,message,customReplyKeyboardMarkup(KEYB_PERIODS,false,false));
            }
            else {
               string message = "WRONG: Symbol is invalid\n";
               message += "Provide a correct symbol name like 'AUDUSDm' to proceed.";
               
               //--- Send the invalid symbol message with the symbols keyboard
               sendMessageToTelegram(chat.member_id,message,customReplyKeyboardMarkup(KEYB_SYMBOLS,false,false));
            }
            continue;
         }

         //--- Handle state 11 (period selection for screenshot)
         if (chat.member_state==11){
            bool found=false; //--- Flag to check if period is valid
            int total=ArraySize(periods); //--- Get the number of defined periods
            for(int k=0; k<total; k++){
               string str_tf=StringSubstr(EnumToString(periods[k]),7); //--- Convert period enum to string
               if(StringCompare(str_tf,text,false)==0){ //--- Check if period matches
                  ENUM_TIMEFRAMES user_period=periods[k]; //--- Set user-selected period
                  period = user_period; //--- Update period
                  found=true;
                  break;
               }
            }
            if (found){
               string message = "CORRECT: Period is valid\n";
               message += "Screenshot sending process initiated \xF60E";
               
               //--- Send the valid period message with the periods keyboard
               sendMessageToTelegram(chat.member_id,message,customReplyKeyboardMarkup(KEYB_PERIODS,false,false));
               string caption = "Screenshot of Symbol: "+symbol+
                                " ("+EnumToString(ENUM_TIMEFRAMES(period))+
                                ") @ Time: "+TimeToString(TimeCurrent());
               
               //--- Send the screenshot to Telegram
               sendScreenshotToTelegram(chat.member_id,symbol,period,caption);
            }
            else {
               string message = "WRONG: Period is invalid\n";
               message += "Provide a correct period like 'H1' to proceed.";
               
               //--- Send the invalid period message with the periods keyboard
               sendMessageToTelegram(chat.member_id,message,customReplyKeyboardMarkup(KEYB_PERIODS,false,false));
            }
            continue;
         }

Aqui, lidamos com os pedidos do usuário por uma captura de tela de um gráfico, gerenciando os diferentes estados do fluxo do chat. Quando o usuário envia o comando "/screenshot" ou "Screenshot", definimos o estado do chat como 10 e solicitamos ao usuário um símbolo, exibindo um teclado com os símbolos disponíveis. É importante observar que o estado do chat pode ser qualquer número, até mesmo 1000. Ele serve apenas como um identificador ou quantificador para armazenar o estado que lembramos durante o processamento da resposta. Se o usuário fornecer um símbolo, verificamos sua validade. Se for válido, pedimos ao usuário um período (um "tempo" válido para o gráfico) exibindo um teclado com as opções disponíveis de períodos. Se o usuário fornecer um símbolo inválido, notificamos e solicitamos um símbolo válido.

Quando o usuário fornece um período de tempo, verificamos se ele é válido. Se o período for uma das opções válidas predefinidas, prosseguimos para atualizar o estado do chat e encaminhar o pedido de captura de tela do símbolo fornecido na última legenda válida, com os detalhes de execução necessários e iniciamos o processo de captura no backend. Caso o usuário forneça um período de tempo que não corresponda a uma das opções válidas predefinidas, apenas o notificamos que a entrada foi incorreta, repetindo as opções válidas conforme exibidas na solicitação inicial. Os teclados personalizados de resposta para símbolos, períodos e o array de timeframes que usamos são definidos abaixo.

   #define KEYB_SYMBOLS "[[\""+EMOJI_UP+"\",\"AUDUSDm\",\"AUDCADm\"],[\"EURJPYm\",\"EURCHFm\",\"EURUSDm\"],[\"USDCHFm\",\"USDCADm\",\""+EMOJI_PISTOL+"\"]]" //--- Symbol selection keyboard layout
   #define KEYB_PERIODS "[[\""+EMOJI_UP+"\",\"M1\",\"M15\",\"M30\"],[\""+EMOJI_CANCEL+"\",\"H1\",\"H4\",\"D1\"]]" //--- Period selection keyboard layout

   //--- Define timeframes array for screenshot requests
   const ENUM_TIMEFRAMES periods[] = {PERIOD_M1,PERIOD_M15,PERIOD_M30,PERIOD_H1,PERIOD_H4,PERIOD_D1};

Até este ponto, estamos prontos com nosso teclado e respostas totalmente personalizadas. Para confirmar isso, executamos o programa. Aqui estão os resultados de saída que obtemos:

GIF DA CAPTURA DE TELA

Aqui, podemos ver que o processo de envio da captura de tela foi iniciado e concluído. Qualquer comando ou entrada inválida é tratada de forma a garantir que apenas comandos válidos sejam enviados pelo usuário. Para garantir que tudo funcione conforme o esperado e identificar eventuais limitações, será necessário testar a implementação de forma abrangente. Isso será feito na próxima seção.


Testando a Implementação

Testar é uma fase crucial para validar que o programa que criamos funciona conforme o esperado. Portanto, precisaremos verificar se ele está funcionando corretamente. A primeira coisa que fazemos é ativar a visualização de páginas da web nas nossas respostas com links. Permitir visualizações de páginas da web nos links permite que os usuários tenham uma prévia do conteúdo antes de clicarem. Eles veem um título e uma imagem que frequentemente transmitem uma boa ideia do conteúdo da página vinculada. Isso é ótimo do ponto de vista da experiência do usuário, especialmente quando consideramos que geralmente é difícil avaliar a qualidade de um link apenas pelo texto apresentado. Dessa forma, vamos ativar a visualização, definindo a opção de desativação como falsa, conforme mostrado abaixo.

//+------------------------------------------------------------------+
//| Send a message to Telegram                                      |
//+------------------------------------------------------------------+
int sendMessageToTelegram( ... ){
   
   //--- ...

   params+="&disable_web_page_preview=false"; //--- Enable web page preview in the message

   //--- ...
   
}

Ao executarmos isso, obtemos a seguinte saída:

VISUALIZAÇÃO DE PÁGINA ATIVADA

Agora podemos receber as prévias de páginas da web, como mostrado. Isso foi um sucesso. Em seguida, podemos mudar a entidade de formatação ou modo de análise de Hypertext Markup Language (HTML) para Markdown, como segue:

//+------------------------------------------------------------------+
//| Send a message to Telegram                                      |
//+------------------------------------------------------------------+
int sendMessageToTelegram( ... ){

   //--- ...

   params+="&parse_mode=Markdown"; //--- Set parse mode to Markdown (can also be HTML)

   //--- ...

}

No modo de análise Markdown, precisaremos alterar toda a estrutura de formatação do nosso código inicial para utilizar entidades Markdown. A forma correta será conforme abaixo: 

      //--- If the message is "/join" or "Join"
      if (text=="/join" || text=="Join"){
         string message = "You want to be part of our MQL5 Community?\n";
         message += "Welcome! [Click me](https://t.me/forexalgo_trading) to join.\n"; // Link
         message += "~Civil Engineering~ Forex AlgoTrading\n"; // Strikethrough
         message += "```\nThis is a sample of our MQL5 code\n```"; // Preformatted text
         message += "*_Remember to follow community guidelines! \xF64F_*"; // Italic and underline
         message += "**Happy Trading!**\n"; // Bold
      
         //--- Send the join message
         sendMessageToTelegram(chat.member_id, message, NULL);
         continue;
      }

Aqui está o que mudamos:

  • Link: Em Markdown, links são criados com texto em vez de <a href="URL">texto</a>.
  • Tachado: Use texto para tachado em vez de <s>texto</s>.
  • Texto pré-formatado: Use três crases (```) para formatar texto pré-formatado em vez de <pre>texto</pre>.
  • Itálico e sublinhado: Markdown não suporta sublinhado de forma nativa. O mais próximo possível é itálico com texto ou texto. O efeito de sublinhado do HTML não é diretamente suportado no Markdown, então ele é incluído com um marcador ou anotação, se necessário.
  • Negrito: Use dois asteriscos texto para negrito em vez de <b>texto</b>.

Ao executarmos o programa, obtemos a seguinte saída:

SAÍDA EM MARKDOWN

Para demonstrar o processo de testes, preparamos um vídeo que mostra o programa em funcionamento. Este vídeo ilustra os diferentes casos de teste que executamos e destaca como o programa respondeu a diversas entradas e como desempenhou suas tarefas necessárias. Ao assistir ao vídeo, você terá uma visão muito clara do processo de teste e poderá ver, sem dúvida, que a implementação atende aos requisitos esperados. O vídeo está apresentado abaixo.

Em resumo, a execução e verificação bem-sucedidas da implementação, conforme demonstrado no vídeo anexo, confirmam que o programa está funcionando conforme o esperado.


Conclusão

Para resumir, o Expert Advisor que criamos integra a linguagem MetaQuotes Language 5 (MQL5) — juntamente com a plataforma de negociação MetaTrader 5 — ao aplicativo de mensagens Telegram, permitindo que os usuários literalmente conversem com seus robôs de negociação. E por que não? O Telegram surgiu como uma maneira poderosa e amigável de controlar sistemas de negociação automatizados. Usando-o, é possível enviar comandos e receber respostas do sistema em tempo real.

No nosso caso, garantimos que, em vez de esperar que o Expert Advisor se comunique com o bot do Telegram, que por sua vez retransmite a comunicação para o usuário, conectamos os dois bots e nos comunicamos com o Expert Advisor sempre que quisermos, sem a necessidade de esperar pela geração de sinais. Estabelecemos uma série de conversas entre o usuário e o bot. Garantimos que os comandos MQL5 enviados pelo usuário via Telegram fossem interpretados corretamente. Após muitos testes, podemos afirmar com confiança que nosso Expert Advisor é tanto confiável quanto robusto.

Traduzido do Inglês pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/en/articles/15750

Arquivos anexados |
Últimos Comentários | Ir para discussão (6)
Allan Munene Mutiiria
Allan Munene Mutiiria | 2 out. 2024 em 16:29
Extratimber Alpha #:

Trabalho muito impressionante!!!

Isso permite que as seguintes funções sejam realizadas:

Alerta de visualização de negociação para telegrama

telegrama para MQL5

OBRIGADO!

@Extratimber Alpha, muito obrigado pelo seu feedback. Ficamos felizes que tenha sido útil.
Oluwatosin Michael Akinyemi
Oluwatosin Michael Akinyemi | 23 mar. 2025 em 08:35
obj_msg.update_id=obj_item["update_id"].ToInt(); //--- Obter a ID de atualização
         obj_msg.message_id=obj_item["message"]["message_id"].ToInt(); //--- Obter a ID da mensagem
         obj_msg.message_date=(datetime)obj_item["message"]["date"].ToInt(); //--- Obter a data da mensagem

Olá Allan, obrigado por esse excelente artigo.

Infelizmente, o código parece estar quebrado a partir da linha 1384 ao extrair os detalhes da mensagem do objeto JSON. O primeiro código na linha 1383

obj_msg.update_id=obj_item["update_id"].ToInt(); //--- Obter a ID de atualização

funciona bem quando impresso no diário. O id da atualização retorna um id válido, mas message_id, message_date e todas as outras instâncias retornam um valor vazio. Devido a esses problemas, nada parece funcionar no código como deveria ser esperado.

Você pode ajudar a resolver esses problemas?

Mais uma vez, obrigado por dedicar seu tempo a este artigo.

Oluwatosin Michael Akinyemi
Oluwatosin Michael Akinyemi | 25 mar. 2025 em 11:38
Oluwatosin Michael Akinyemi #:

Olá Allan, obrigado por esse excelente artigo.

Infelizmente, o código parece estar quebrado a partir da linha 1384 ao extrair os detalhes da mensagem do objeto JSON. O primeiro código na linha 1383

funciona bem quando impresso no diário. O id da atualização retorna um id válido, mas message_id, message_date e todas as outras instâncias retornam um valor vazio. Devido a esses problemas, nada parece funcionar no código como deveria ser esperado.

Você pode me ajudar a resolver esses problemas?

Mais uma vez, obrigado por dedicar seu tempo a este artigo.

Olá Allan, finalmente descobri que o problema era meu. Obrigado por esse excelente artigo!

Allan Munene Mutiiria
Allan Munene Mutiiria | 25 mar. 2025 em 20:24
Oluwatosin Michael Akinyemi #:

Olá Allan, finalmente descobri que o problema era do meu lado. Obrigado por esse excelente artigo!

@Oluwatosin Michael Akinyemi, obrigado pela indicação. Seja bem-vindo.
Tai Tran
Tai Tran | 30 jun. 2025 em 15:18
Olá Allan, obrigado pelo tutorial útil.

Quando compilo, recebo o seguinte erro:


----------------------------------------------------------------------------------------------------------------------------

'ArrayAdd' - nenhuma das sobrecargas pode ser aplicada à chamada de função TELEGRAM_MQL5_COMMANDS_PART5.mq5 1151 4

poderia ser uma de 2 funções TELEGRAM_MQL5_COMMANDS_PART5.mq5 1151 4

void ArrayAdd(uchar&[],const uchar&[]) TELEGRAM_MQL5_COMMANDS_PART5.mq5 1186 6

void ArrayAdd(char&[],const string) TELEGRAM_MQL5_COMMANDS_PART5.mq5 1200 6


'ArrayAdd' - nenhuma das sobrecargas pode ser aplicada à chamada de função TELEGRAM_MQL5_COMMANDS_PART5.mq5 1223 7

poderia ser uma de 2 funções TELEGRAM_MQL5_COMMANDS_PART5.mq5 1223 7

void ArrayAdd(uchar&[],const uchar&[]) TELEGRAM_MQL5_COMMANDS_PART5.mq5 1186 6

void ArrayAdd(char&[],const string) TELEGRAM_MQL5_COMMANDS_PART5.mq5 1200 6


2 erros, 0 avisos 2 0

----------------------------------------------------------------------------------------------------------------------------


Você pode me ajudar a corrigir isso?

Desde já, obrigado!
Do básico ao intermediário: Objetos (II) Do básico ao intermediário: Objetos (II)
Neste artigo veremos como controlar de forma simples via código algumas propriedades de objetos. Vermos como podemos colocar mais de um objeto em um mesmo gráfico, usando para isto uma aplicação. E além disto, começaremos a ver a importância de definir um nome curto, para todo e qualquer indicador que venhamos a implementar.
Critérios de tendência no trading Critérios de tendência no trading
As tendências são parte importante de muitas estratégias de negociação. Neste artigo, examinaremos algumas das ferramentas usadas para identificar tendências e suas características. Compreender e interpretar corretamente as tendências pode aumentar significativamente o desempenho do trading e minimizar riscos.
Exemplo de Otimização Estocástica e Controle Ótimo Exemplo de Otimização Estocástica e Controle Ótimo
Este Expert Advisor, chamado SMOC (provavelmente abreviação de Stochastic Model Optimal Control), é um exemplo simples de um sistema de negociação algorítmica avançado para o MetaTrader 5. Ele utiliza uma combinação de indicadores técnicos, controle preditivo baseado em modelos e gerenciamento dinâmico de risco para tomar decisões de negociação. O EA incorpora parâmetros adaptativos, dimensionamento de posição baseado em volatilidade e análise de tendências para otimizar seu desempenho em diferentes condições de mercado.
Expert Advisor Autônomo com MQL5 e Python (Parte III): Decifrando o Algoritmo do Boom 1000 Expert Advisor Autônomo com MQL5 e Python (Parte III): Decifrando o Algoritmo do Boom 1000
Nesta série de artigos, discutimos como podemos construir Expert Advisors capazes de se ajustarem autonomamente às condições dinâmicas do mercado. No artigo de hoje, tentaremos ajustar uma rede neural profunda aos mercados sintéticos da Deriv.