English Русский 中文 Español Deutsch 日本語
preview
Criando um Expert Advisor Integrado MQL5-Telegram (Parte 7): Análise de Comandos para Automação de Indicadores em Gráficos

Criando um Expert Advisor Integrado MQL5-Telegram (Parte 7): Análise de Comandos para Automação de Indicadores em Gráficos

MetaTrader 5Sistemas de negociação |
15 0
Allan Munene Mutiiria
Allan Munene Mutiiria

Introdução

Neste artigo, vamos dar continuidade ao progresso obtido na parte anterior (Parte 6), onde integramos botões inline responsivos para aprimorar a interação do bot. Agora, nosso foco se volta para a automação da adição de indicadores nos gráficos do MetaTrader 5 usando comandos enviados a partir do Telegram. Criaremos um sistema onde o Expert Advisor captura parâmetros de indicadores definidos pelo usuário via Telegram, analisa os dados e aplica os indicadores especificados aos gráficos de negociação em tempo real. 

Os seguintes tópicos irão nos guiar passo a passo na implementação deste processo de automação de indicadores:

  1. Visão geral da negociação baseada em indicadores via Telegram: exploraremos como os traders podem usar comandos do Telegram para controlar indicadores no MetaTrader 5.
  2. Analisando e processando comandos de indicadores do Telegram: esta seção detalhará como extrair e processar corretamente os parâmetros de indicadores a partir das mensagens do Telegram.
  3. Executando indicadores no MQL5: demonstraremos como usar os comandos analisados para adicionar e automatizar indicadores diretamente no MetaTrader 5.
  4. Testando o sistema de negociação com indicadores: um processo de testes completo garantirá o funcionamento adequado do sistema para automação precisa de indicadores.
  5. Conclusão: por fim, faremos um resumo de todo o processo e discutiremos os principais aprendizados.

Ao final deste artigo, você terá um sistema de automação de indicadores do Telegram para o MetaTrader 5 totalmente funcional, capaz de receber e processar comandos do Telegram para aplicar indicadores técnicos de forma integrada no MQL5. Vamos começar então!


Visão geral da negociação baseada em indicadores via Telegram

Nesta seção, abordaremos o uso do Telegram para envio de comandos de indicadores que podem automatizar a análise de gráficos. Muitos traders estão aproveitando o Telegram para interagir com bots e Expert Advisors (EAs) que permitem adicionar, modificar ou remover indicadores técnicos diretamente no MetaTrader 5. Esses comandos geralmente incluem informações-chave, como o tipo de indicador, período gráfico (timeframe), período de cálculo e aplicação de preço — essenciais para a análise de gráficos. No entanto, quando feitos manualmente, aplicar esses indicadores pode gerar atrasos ou erros, especialmente em mercados voláteis.

Ao automatizar o processo de aplicação de indicadores por meio de comandos do Telegram, os traders podem aprimorar sua análise técnica sem o incômodo do gerenciamento manual de gráficos. Quando devidamente integrados, esses comandos do Telegram podem ser analisados e convertidos em instruções executáveis no MetaTrader 5, permitindo que indicadores sejam adicionados aos gráficos em tempo real. Isso garante não apenas precisão, mas também um fluxo de trabalho de negociação mais eficiente, permitindo que os traders se concentrem na interpretação dos resultados em vez de gerenciar a configuração. Uma visualização típica dos comandos de indicadores é mostrada abaixo:

FORMATO DE COMANDO DE INDICADOR VIA TELEGRAM

O resultado é um sistema que conecta o Telegram ao MetaTrader 5, permitindo que os traders agilizem sua análise gráfica, minimizem erros e aproveitem totalmente as oportunidades de mercado em tempo real por meio da gestão automatizada de indicadores.


Analisando e processando comandos de indicadores do Telegram

A primeira coisa que precisamos fazer é capturar os comandos de indicadores enviados pelo Telegram, e em seguida codificá-los, analisá-los e processá-los no MetaQuotes Language 5 (MQL5) para que possamos interpretá-los e aplicar os indicadores correspondentes aos gráficos do MetaTrader 5. Para a parte de codificação e análise, já apresentamos as classes necessárias na parte 5 desta série. No entanto, nesta parte, revisitaremos essas classes para garantir clareza, especialmente porque a parte 6 focou em funcionalidades diferentes, como botões inline. O trecho de código responsável por analisar e processar comandos de indicadores do Telegram é apresentado 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.
};


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.
}
//+------------------------------------------------------------------+
int Class_Bot_EA::getChatUpdates(void){
   //--- 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
   }
   
   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);
   // THIS IS THE STRING RESPONSE WE GET // "ok":true,"result":[]}
   
   //--- If the request was successful
   if(res==0){
      //Print(out); //--- Optionally print the response
      
      //--- Create a JSON object to parse the response
      CJSONValue obj_json(NULL, jv_UNDEF);
      //--- Deserialize the JSON response
      bool done=obj_json.Deserialize(out);
      //--- If JSON parsing failed
      // Print(done);
      if(!done){
         Print("ERR: JSON PARSING"); //--- Print an error message if parsing fails
         return(-1); //--- Return with an error code
      }
      
      //--- 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
      }
      
      //--- Create a message object to store message details
      Class_Message obj_msg;
      
      //--- 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++){
         //--- Get the individual update item as a JSON object
         CJSONValue obj_item=obj_json["result"].m_elements[i];
         
         //--- 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
         
         //--- 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
         
         //--- 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
         
         //--- Update the ID for the next request
         member_update_id=obj_msg.update_id+1;
         
         //--- If it's the first update, skip processing
         if(member_first_remove){
            continue;
         }

         //--- 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
            }
         }
         
      }
      //--- After the first update, set the flag to false
      member_first_remove=false;
   }
   //--- Return the result of the POST request
   return(res);
}

Aqui, apresentamos a classe "Class_Bot_EA" e implementamos sua função "getChatUpdates" para lidar com as atualizações recebidas do Telegram. No construtor, inicializamos o token do bot, o nome e outras variáveis pertinentes. Também definimos uma flag para determinar se devemos excluir a primeira mensagem e limpar alguns dados antigos, incluindo a lista de chats e quaisquer filtros de usuário.

A função "getChatUpdates" constrói uma URL para a API do Telegram que nos fornece as atualizações para o bot especificado. O último ID de atualização processado é usado como um offset na URL, o que significa que não receberemos atualizações que já tenham sido processadas. Após construirmos a URL, enviamos uma requisição POST para a API e tratamos a resposta do servidor. Como esperamos receber dados no formato JavaScript Object Notation (JSON) do servidor, verificamos erros tentando fazer o parse dos dados. Se a análise falhar ou se o campo "ok" na resposta JSON for falso, imprimimos uma mensagem de erro e retornamos um código de erro.

Depois de responder com sucesso, extraímos as informações relevantes da mensagem — ID de atualização, ID da mensagem, informações do remetente e detalhes do chat — que nos permitem entender o que ocorreu na conversa. Em seguida, verificamos a lista de chats que temos até agora para ver onde esta nova informação se encaixa. Se o chat associado a essa nova mensagem não estiver na lista, nós o adicionamos. Se ele já estiver na lista, atualizamos suas informações com a nova mensagem.

Por fim, cuidamos da filtragem de mensagens definida pelo usuário e do gerenciamento do estado de cada chat. Após as atualizações necessárias terem sido concluídas, garantimos que a última mensagem processada de cada chat esteja atualizada. Finalmente, retornamos o resultado da requisição POST, indicando sucesso ou um erro devidamente descrito.

Isso é tudo o que precisamos para processar os comandos recebidos. Depois, precisamos interpretar os comandos de indicadores recebidos, identificar o indicador solicitado e adicioná-los ao gráfico automaticamente para análise posterior. Isso será feito na próxima seção.



Executando Indicadores no MQL5

Para processar os comandos de indicadores recebidos, chamaremos a função responsável pelo processamento de mensagens para que possamos processá-las como um todo e, em seguida, interpretar os detalhes da mensagem em segmentos. A função aplicável é a seguinte:

void Class_Bot_EA::ProcessMessages(void){

//...

}

É nesta função que o processamento real começa. A primeira coisa que precisamos fazer é percorrer todas as mensagens recebidas e processá-las individualmente. Isso é importante porque o provedor pode ter enviado os sinais simultaneamente e em lote para vários símbolos de negociação, como por exemplo: "AUDUSD, EURUSD, GBPUSD, XAUUSD, XRPUSD, USDKES, USDJPY, EURCHF" e muitos outros. Fazemos isso por meio da seguinte lógica:

   //--- 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

         //...

      }
   }

Primeiro, percorremos os chats armazenados dentro da lista "member_chats". Cada objeto de chat é recuperado com a função "GetNodeAtIndex". Verificamos se a mensagem associada a esse chat já foi tratada avaliando uma flag na estrutura "member_new_one". Se a mensagem ainda não tiver sido processada, a flag "done" é definida como "true", o que significa que a mesma mensagem não será tratada várias vezes.

Em seguida, extraímos o conteúdo da mensagem. Eles estão armazenados no campo "message_text" da estrutura "member_new_one". Assim, podemos trabalhar diretamente com o texto sem nos preocupar com o que já foi processado.

A primeira coisa que precisamos fazer agora é obter os detalhes do comando para análise. Segue a lógica:

         string user_text = text;
         Print("USER'S PLAIN TEXT IS AS BELOW:\n",user_text);
         
         StringToUpper(user_text);
         Print("USER'S TRANSFORMED UPPERCASE TEXT IS AS BELOW:\n",user_text);

Aqui, primeiro armazenamos o texto da mensagem recebida da variável "text" em uma nova variável chamada "user_text". Isso nos permite trabalhar com o conteúdo da mensagem sem modificar a variável original. Em seguida, imprimimos o texto simples da mensagem do usuário usando a função "Print", que exibe a mensagem no terminal do MetaTrader 5 para fins de registro (log).

Depois, convertemos toda a string "user_text" para letras maiúsculas usando a função StringToUpper. Isso transforma todos os caracteres da mensagem em suas versões maiúsculas equivalentes. Essa padronização é necessária porque iremos uniformizar os caracteres da mensagem, facilitando o trabalho de processamento. Após a transformação, imprimimos novamente a mensagem atualizada no terminal, mostrando a versão em letras maiúsculas da entrada do usuário. Esse processo nos permite visualizar tanto a versão original quanto a modificada da mensagem para manipulação ou resposta posterior. Ao executar o programa novamente, obtemos a seguinte saída na seção de log:

TEXTO TRANSFORMADO EM MAIÚSCULAS

Após transformar a mensagem do sinal para maiúsculas, precisamos inicializar as variáveis que irão armazenar nossos dados, conforme abaixo:

         // MOVING AVERAGE
         //--- Initialize variables to hold extracted data
         string indicator_type = NULL;
         string indicator_symbol = NULL;
         string indicator_timeframe = NULL;
         long indicator_period = 0;
         long indicator_shift = 0;
         string indicator_method = NULL;
         string indicator_app_price = NULL;

Começamos com o indicador Média Móvel. Inicializamos diversas variáveis que armazenarão os dados extraídos para configurar uma Média Móvel (MA) no MetaTrader 5, com base nas informações recebidas do usuário via Telegram. Segue o que cada variável representa:

  • indicator_type: O tipo do indicador, neste caso, uma Média Móvel.
  • indicator_symbol: O símbolo (par de moedas ou ativo) no qual o indicador será aplicado.
  • indicator_timeframe: O período gráfico (ex.: M1, H1, D1) no qual o indicador será plotado.
  • indicator_period: O número de períodos (ou barras) que a Média Móvel considerará em seu cálculo.
  • indicator_shift: O deslocamento (offset) usado para mover o indicador para frente ou para trás no gráfico.
  • indicator_method: O método de cálculo da MA (ex.: SMA, EMA).
  • indicator_app_price: O preço aplicado usado para o cálculo da MA (ex.: preço de fechamento, preço de abertura).

Para extrair os elementos de dados relacionados a um indicador, precisaremos dividir a mensagem por linhas e percorrer cada uma delas procurando os detalhes. Essa implementação é feita com a lógica a seguir:

         //--- Split the message by lines
         string lines[];
         StringSplit(user_text,'\n',lines);
         Print("SPLIT TEXT SEGMENTS IS AS BELOW:");
         ArrayPrint(lines,0,",");

Aqui, dividimos a mensagem transformada do usuário em linhas individuais para facilitar a extração das informações relevantes do indicador. Primeiro, declaramos um array chamado "lines" para armazenar cada linha da mensagem após a divisão. Em seguida, aplicamos a função StringSplit, usando o caractere de nova linha ('\n') como delimitador para separar a mensagem em linhas distintas. Essa função preenche o array "lines" com cada parte do texto que estava separada por uma quebra de linha.

Depois que a mensagem é dividida, imprimimos os segmentos resultantes usando a função ArrayPrint, que exibe cada linha como um elemento individual. Essa etapa é fundamental para visualizar a estrutura da mensagem e garantir que o processo de divisão funcionou corretamente. Ao organizar a mensagem dessa forma, conseguimos processar cada linha com mais facilidade para extrair elementos críticos, como o símbolo de negociação, o tipo de indicador e outros detalhes. Para obter os detalhes, precisamos percorrer cada linha:

         //--- Iterate over each line to extract information
         for (int i=0; i<ArraySize(lines); i++){
            StringTrimLeft(lines[i]);
            StringTrimRight(lines[i]);
            
            string selected_line = lines[i];
            Print(i,". ",selected_line);

            //...

         }

Percorremos o array "lines" para extrair informações específicas sobre o indicador técnico de cada linha da mensagem dividida. Usamos um for loop para iterar sobre todos os elementos do array, garantindo que cada linha seja processada individualmente. No início do loop, aplicamos as funções StringTrimLeft e StringTrimRight a cada linha para remover espaços em branco à esquerda e à direita. Isso garante que espaços extras não interfiram no processo de análise.

A seguir, atribuímos cada linha tratada à variável "selected_line", que armazena a linha atualmente em processamento. Com cada linha devidamente limpa e armazenada em "selected_line", podemos realizar outras operações, como verificar se a linha contém sinais de negociação ou comandos específicos. Para confirmar que temos as informações corretas, imprimimos cada linha, e abaixo está a saída:

ITERAÇÕES DE COMANDOS

Isso foi um sucesso. Podemos prosseguir para buscar detalhes específicos na linha selecionada. Vamos começar procurando pelo tipo de indicador técnico de negociação. Primeiro, verificaremos se a linha contém o texto referente ao tipo de indicador Média Móvel, ou seja, "INDICATOR TYPE". 

            if (StringFind(selected_line,"INDICATOR TYPE") >= 0){
               indicator_type = StringSubstr(selected_line,16);
               Print("Line @ index ",i," Indicator Type = ",indicator_type); //--- Print the extracted details
            }

Aqui, processamos a mensagem do usuário no Telegram para extrair o tipo de indicador. Usamos a função StringFind para procurar na linha atual ("selected_line") o texto "INDICATOR TYPE". Se esse texto for encontrado, a função retorna a posição inicial da correspondência, que será um valor maior ou igual a zero. Uma vez detectada a correspondência, extraímos o tipo de indicador usando a função StringSubstr, que recupera uma substring a partir da posição logo após "INDICATOR TYPE" (iniciando no índice 16) até o final da linha. O valor extraído é armazenado na variável "indicator_type". Por fim, imprimimos o índice da linha e o valor extraído de "indicator_type" usando a função Print para confirmar que os dados foram obtidos com sucesso. Ao executar, obtemos a seguinte saída:

CONFIRMAÇÃO DO INDICADOR

Podemos ver que percorremos com sucesso todos os segmentos do comando e identificamos o nome do indicador. Agora, precisamos identificar o símbolo. Uma lógica semelhante, porém um pouco mais complexa, será utilizada. 

            //--- Check for symbol in the list of available symbols and assign it
            for(int k = 0; k < SymbolsTotal(true); k++) { //--- Loop through all available symbols
               string selected_symbol = SymbolName(k, true); //--- Get the symbol name
               if (StringCompare(selected_line,selected_symbol,false) == 0){ //--- Compare the line with the symbol name
                  indicator_symbol = selected_symbol; //--- Assign the symbol if a match is found
                  Print("Line @ index ",i," SYMBOL = ",indicator_symbol); //--- Print the found symbol
               }
            }            

Neste bloco de código, verificamos a entrada do usuário em relação à lista de símbolos de negociação disponíveis e atribuimos o símbolo correto à variável "indicator_symbol". Primeiro, usamos a função SymbolsTotal, que retorna o número total de símbolos atualmente disponíveis na plataforma. O argumento "true" especifica que queremos o número de símbolos visíveis. Em seguida, percorremos todos os símbolos disponíveis usando um loop for com a variável "k" como índice.

Dentro do loop, usamos a função SymbolName para obter o nome do símbolo no índice "k". O segundo argumento, "true", indica que queremos o nome curto do símbolo. Após obter o nome do símbolo e armazená-lo na variável "selected_symbol", usamos a função StringCompare para comparar esse "selected_symbol" com a entrada do usuário ("selected_line"). O argumento "false" indica que a comparação não diferencia maiúsculas de minúsculas.

Se a função retornar zero, significa que as duas strings coincidem, e atribuimos "selected_symbol" à variável "indicator_symbol". Por fim, imprimimos o índice da linha e o símbolo encontrado usando a função Print para confirmar que identificamos e atribuímos corretamente o símbolo a partir da entrada do usuário. Este trecho não contém texto extra, por isso buscamos diretamente. Ao executar, não teremos resultados com o código atual, porque o texto extraído e os símbolos padrão não coincidem em termos de caixa de caracteres (case-sensitive), ou seja, "XAUUSDM" não é igual a "XAUUSDm". Segue o nome do símbolo que temos:

ESTRUTURA PADRÃO

Portanto, precisamos converter o símbolo padrão do sistema para maiúsculas antes de fazer a comparação. O novo trecho de código atualizado está abaixo:

            //--- Check for symbol in the list of available symbols and assign it
            for(int k = 0; k < SymbolsTotal(true); k++) { //--- Loop through all available symbols
               string selected_symbol = SymbolName(k, true); //--- Get the symbol name
               StringToUpper(selected_symbol);
               if (StringCompare(selected_line,selected_symbol,false) == 0){ //--- Compare the line with the symbol name
                  indicator_symbol = selected_symbol; //--- Assign the symbol if a match is found
                  Print("Line @ index ",i," SYMBOL = ",indicator_symbol); //--- Print the found symbol
               }
            }            

Com a nova transformação, podemos prosseguir com a comparação, e os resultados obtidos são os seguintes:

SÍMBOLO SELECIONADO

Isso foi um sucesso. Para obter os demais detalhes, aplicamos uma lógica semelhante.

            if (StringFind(selected_line,"TIMEFRAME") >= 0){
               indicator_timeframe = StringSubstr(selected_line,12);
               Print("Line @ index ",i," Indicator Timeframe = ",indicator_timeframe); //--- Print the extracted details
            }
            if (StringFind(selected_line,"PERIOD") >= 0){
               indicator_period = StringToInteger(StringSubstr(selected_line,9));
               Print("Line @ index ",i," Indicator Period = ",indicator_period);
            }
            if (StringFind(selected_line,"SHIFT") >= 0){
               indicator_shift = StringToInteger(StringSubstr(selected_line,8));
               Print("Line @ index ",i," Indicator Shift = ",indicator_shift);
            }
            if (StringFind(selected_line,"METHOD") >= 0){
               indicator_method = StringSubstr(selected_line,9);
               Print("Line @ index ",i," Indicator Method = ",indicator_method);
            }
            if (StringFind(selected_line,"APPLIED PRICE") >= 0){
               indicator_app_price = StringSubstr(selected_line,16);
               Print("Line @ index ",i," Indicator Applied Price = ",indicator_app_price);
            }
         }

Ao executar, obtemos a seguinte saída:

DETALHES DO INDICADOR

Para visualizar os dados de forma mais estruturada, podemos imprimir todas as informações extraídas conforme abaixo:

         //--- Final data
         Print("\nFINAL EXTRACTED DATA:"); //--- Print the final data for confirmation
         
         Print("Type = ",indicator_type);
         Print("Symbol = ",indicator_symbol);
         Print("Timeframe = ",indicator_timeframe);
         Print("Period = ",indicator_period);
         Print("Shift = ",indicator_shift);
         Print("Method = ",indicator_method);
         Print("Applied Price = ",indicator_app_price);

Imprimimos os dados finais extraídos para confirmar que as variáveis foram preenchidas corretamente. Primeiro, usamos a função "Print" para exibir uma mensagem de cabeçalho: "\nFINAL EXTRACTED DATA:". Isso serve como um marcador visual nos logs, indicando o ponto onde os dados processados serão exibidos.

Em seguida, imprimimos sequencialmente os valores de cada uma das variáveis principais — "indicator_type", "indicator_symbol", "indicator_timeframe", "indicator_period", "indicator_shift", "indicator_method" e "indicator_app_price". Cada chamada à função Print exibe o nome da variável e seu valor atual. Isso é importante para depuração e verificação, garantindo que os dados extraídos da entrada do usuário (por exemplo, tipo de indicador, símbolo, período gráfico, etc.) tenham sido capturados corretamente antes que o sistema prossiga com a adição do indicador ao gráfico no MetaTrader 5. A saída obtida é visualizada abaixo:

LOG ORGANIZADO

Perfeito! Agora que temos todos os detalhes necessários para um indicador de Média Móvel, podemos prosseguir para adicioná-lo ao gráfico. Entretanto, antes de adicioná-lo, podemos verificar se realmente temos o indicador correto conforme instruído no Telegram. Fazemos isso usando uma instrução if.

         if (indicator_type=="MOVING AVERAGE"){
                //...
         }

Depois de confirmar o indicador, podemos prosseguir para converter as entradas extraídas em suas respectivas estruturas de tipo de dados. Vale lembrar que os valores atuais estão em tipos string ou inteiro. O sistema não entenderá, por exemplo, que a entrada "SMA" do usuário para o método da média móvel significa "SIMPLE MOVING AVERAGE" dentro da enumeração ENUM_MA_METHOD, como mostrado abaixo.

ENUM_MA_METHOD

Portanto, precisamos explicar isso ao programa. Seguindo de forma sistemática, começaremos pelo mais óbvio: o período gráfico (timeframe).

            //--- Convert timeframe to ENUM_TIMEFRAMES
            ENUM_TIMEFRAMES timeframe_enum = _Period;
            if (indicator_timeframe == "M1") {
               timeframe_enum = PERIOD_M1;
            } else if (indicator_timeframe == "M5") {
               timeframe_enum = PERIOD_M5;
            } else if (indicator_timeframe == "M15") {
               timeframe_enum = PERIOD_M15;
            } else if (indicator_timeframe == "M30") {
               timeframe_enum = PERIOD_M30;
            } else if (indicator_timeframe == "H1") {
               timeframe_enum = PERIOD_H1;
            } else if (indicator_timeframe == "H4") {
               timeframe_enum = PERIOD_H4;
            } else if (indicator_timeframe == "D1") {
               timeframe_enum = PERIOD_D1;
            } else if (indicator_timeframe == "W1") {
               timeframe_enum = PERIOD_W1;
            } else if (indicator_timeframe == "MN1") {
               timeframe_enum = PERIOD_MN1;
            } else {
               Print("Invalid timeframe: ", indicator_timeframe);
            }

Nesta seção, convertemos o período gráfico fornecido pelo usuário, contido na string "indicator_timeframe", na enumeração correspondente do MetaTrader 5 "ENUM_TIMEFRAMES". Essa etapa é fundamental, pois o MetaTrader 5 utiliza períodos gráficos predefinidos, como "PERIOD_M1" para gráficos de 1 minuto, "PERIOD_H1" para gráficos de 1 hora, e assim por diante. Esses períodos são definidos como parte do tipo "ENUM_TIMEFRAMES".

Começamos inicializando a variável "timeframe_enum" com o período gráfico atual do gráfico, representado por _Period. Isso funciona como um valor padrão caso o período informado pelo usuário seja inválido.

Em seguida, usamos uma série de instruções condicionais if-else para verificar o valor da string "indicator_timeframe". Cada condição compara a string extraída com identificadores conhecidos de períodos, como "M1" (1 minuto), "H1" (1 hora), etc. Se uma correspondência for encontrada, o valor equivalente da enumeração "ENUM_TIMEFRAMES" (como "PERIOD_M1" ou "PERIOD_H1") é atribuído à variável "timeframe_enum".

Se nenhuma das condições for atendida, o bloco final else é acionado, imprimindo uma mensagem de erro no log informando "Invalid timeframe" junto com o valor de "indicator_timeframe". Isso garante que apenas períodos válidos sejam passados ao MetaTrader 5 durante a criação do indicador. Da mesma forma, transformamos as demais variáveis para suas respectivas formas.

            //--- Convert MA method to ENUM_MA_METHOD
            ENUM_MA_METHOD ma_method = MODE_SMA;
            if (indicator_method == "SMA") {
               ma_method = MODE_SMA;
            } else if (indicator_method == "EMA") {
               ma_method = MODE_EMA;
            } else if (indicator_method == "SMMA") {
               ma_method = MODE_SMMA;
            } else if (indicator_method == "LWMA") {
               ma_method = MODE_LWMA;
            } else {
               Print("Invalid MA method: ", indicator_method);
            }
            
            //--- Convert applied price to ENUM_APPLIED_PRICE
            ENUM_APPLIED_PRICE app_price_enum = PRICE_CLOSE;
            
            if (indicator_app_price == "CLOSE") {
               app_price_enum = PRICE_CLOSE;
            } else if (indicator_app_price == "OPEN") {
               app_price_enum = PRICE_OPEN;
            } else if (indicator_app_price == "HIGH") {
               app_price_enum = PRICE_HIGH;
            } else if (indicator_app_price == "LOW") {
               app_price_enum = PRICE_LOW;
            } else if (indicator_app_price == "MEDIAN") {
               app_price_enum = PRICE_MEDIAN;
            } else if (indicator_app_price == "TYPICAL") {
               app_price_enum = PRICE_TYPICAL;
            } else if (indicator_app_price == "WEIGHTED") {
               app_price_enum = PRICE_WEIGHTED;
            } else {
               Print("Invalid applied price: ", indicator_app_price);
            }

Uma vez concluído o processo de transformação, podemos prosseguir para criar o handle do indicador, que usaremos para adicioná-lo ao gráfico.

            int handle_ma = iMA(indicator_symbol,timeframe_enum,(int)indicator_period,(int)indicator_shift,ma_method,app_price_enum);

Aqui, criamos um handle para o indicador Média Móvel (MA) usando a função iMA. Essa função gera um identificador de indicador com base nos parâmetros extraídos e processados anteriormente. O handle permitirá que façamos referência e manipulemos o indicador no gráfico mais adiante no código. Passamos vários argumentos para a função "iMA", cada um correspondendo a um parâmetro obtido do comando do Telegram:

  • "indicator_symbol": Especifica o ativo financeiro (por exemplo, EURUSD) no qual o indicador será aplicado.
  • "timeframe_enum": Refere-se ao período gráfico (por exemplo, M1 para 1 minuto, H1 para 1 hora) convertido anteriormente a partir da entrada do usuário.
  • "(int)indicator_period": Converte o "indicator_period" extraído de tipo long para inteiro, representando o número de períodos da MA.
  • "(int)indicator_shift": Converte o "indicator_shift" para inteiro, definindo quantos candles o indicador será deslocado no gráfico.
  • "ma_method": Especifica o método de cálculo da MA (por exemplo, SMA ou EMA), conforme informado pelo usuário.
  • "app_price_enum": Indica o tipo de preço aplicado (por exemplo, preço de fechamento ou de abertura) usado para calcular a MA.

O resultado da função "iMA" é armazenado na variável "handle_ma". Se a função criar com sucesso o identificador do indicador, "handle_ma" conterá uma referência válida. Se a criação falhar, ela retornará INVALID_HANDLE, indicando problema com um ou mais parâmetros. Como sabemos que INVALID_HANDLE representa falha, podemos utilizá-lo no processamento seguinte.

            if (handle_ma != INVALID_HANDLE){
               Print("Successfully created the indicator handle!");

                //...

            }
            else if (handle_ma == INVALID_HANDLE){
               Print("Failed to create the indicator handle!");
            }

Aqui, verificamos se o identificador do indicador de Média Móvel (MA) foi criado com sucesso, verificando o valor de "handle_ma". Se o identificador for válido, a função iMA retornará um valor diferente de INVALID_HANDLE. Nesse caso, imprimimos uma mensagem indicando sucesso: "O identificador do indicador foi criado com sucesso!". Isso significa que todos os parâmetros (símbolo, período, método, etc.) foram interpretados corretamente e o indicador MA está pronto para ser usado no gráfico.

Se a criação do identificador falhar, ou seja, "handle_ma" for igual a INVALID_HANDLE, imprimiremos uma mensagem de erro: "Falha ao criar o identificador do indicador!". Essa condição indica que algo deu errado durante o processo de criação do indicador, como um símbolo inválido, período incorreto ou qualquer outro parâmetro incorreto. Esse tratamento de erros nos ajuda a garantir que o sistema possa detectar problemas e fornecer feedback informativo durante o processo de configuração do indicador. Podemos então abrir um gráfico com o símbolo e o período especificados, garantir que ele esteja sincronizado com os dados mais recentes e ajustar suas configurações para maior clareza.

               long chart_id=ChartOpen(indicator_symbol,timeframe_enum);
               ChartSetInteger(ChartID(),CHART_BRING_TO_TOP,true);
               // update chart
               int wait=60;
               while(--wait>0){//decrease the value of wait by 1 before loop condition check
                  if(SeriesInfoInteger(indicator_symbol,timeframe_enum,SERIES_SYNCHRONIZED)){
                     break; // if prices up to date, terminate the loop and proceed
                  }
               }
               
               ChartSetInteger(chart_id,CHART_SHOW_GRID,false);
               ChartSetInteger(chart_id,CHART_SHOW_PERIOD_SEP,false);
               ChartRedraw(chart_id);
               
               Sleep(7000);

Primeiro, usamos a função ChartOpen para abrir um novo gráfico com base no "indicator_symbol" e no "timeframe_enum", que extraímos anteriormente do comando do Telegram. O ID do gráfico é retornado e armazenado na variável "chart_id". Para trazer esse gráfico para a frente no MetaTrader 5, usamos a função ChartSetInteger, passando o ID do gráfico junto com a constante "CHART_BRING_TO_TOP" para garantir que o gráfico fique visível para interação.

Em seguida, implementamos uma verificação de sincronização para garantir que os dados de preço do gráfico estejam totalmente atualizados. Isso é feito repetindo até 60 vezes usando a função SeriesInfoInteger para verificar se a série de dados de preço está sincronizada. Se a sincronização ocorrer antes do término do loop, encerramos antecipadamente. Após confirmar que os dados estão atualizados, partimos para personalizar a aparência do gráfico. A grade e os separadores de período são ocultados usando a função ChartSetInteger, na qual passamos “CHART_SHOW_GRID” e “CHART_SHOW_PERIOD_SEP” como false, criando uma visualização de gráfico mais limpa.

Depois desses ajustes, o gráfico é forçado a atualizar visualmente usando a função ChartRedraw. Por fim, é adicionada uma pausa de 7 segundos com a função Sleep para dar tempo de o gráfico e os dados carregarem completamente antes de prosseguir com quaisquer outras operações. Todo esse processo garante que o gráfico esteja pronto para interação e exibição, com dados atualizados e um visual limpo. Depois de tudo isso, podemos então continuar e adicionar o indicador ao gráfico.

               if (ChartIndicatorAdd(chart_id,0,handle_ma)) {
                  Print("SUCCESS. Indicator added to the chart.");
                  isIndicatorAddedToChart = true;
               }
               else {
                  Print("FAIL: Indicator not added to the chart.");
               }   

Aqui, adicionamos ao gráfico que abrimos anteriormente o indicador de Média Móvel (MA) criado anteriormente. Fazemos isso usando a função ChartIndicatorAdd, na qual passamos o “chart_id”, um zero (que indica que estamos adicionando o indicador ao gráfico principal) e o “handle_ma”, que representa o handle do indicador MA que criamos. Se a adição for bem-sucedida, imprimimos uma mensagem de sucesso dizendo “SUCCESS. Indicator added to the chart” e definimos a variável booleana “isIndicatorAddedToChart” como true, indicando que o indicador agora está ativo no gráfico. Definimos a variável booleana e a inicializamos como false fora da instrução “if”, conforme mostrado.

         bool isIndicatorAddedToChart = false;

Por outro lado, se a adição falhar, imprimimos uma mensagem de falha dizendo “FAIL: Indicator not added to the chart”. Essa verificação é crucial porque garante que possamos confirmar se o indicador foi aplicado com sucesso ao gráfico, o que é essencial para operações de trading subsequentes e análise visual. Ao tratar ambos os resultados, mantemos transparência no processo e sabemos o estado do indicador no gráfico. Se criarmos e adicionarmos o indicador ao gráfico, então podemos informar o sucesso ao usuário na mesma estrutura de código.

         if (isIndicatorAddedToChart){
            string message = "\nSUCCESS! THE INDICATOR WAS ADDED TO THE CHART WITH THE FOLLOWING DETAILS:\n"; //--- Prepare success message
            message += "\nType = "+indicator_type+"\n";
            message += "Symbol = "+indicator_symbol+"\n";
            message += "Timeframe = "+indicator_timeframe+"\n";
            message += "Period = "+IntegerToString(indicator_period)+"\n";
            message += "Shift = "+IntegerToString(indicator_shift)+"\n";
            message += "Applied Price = "+indicator_app_price+"\n";
            message+="\nHAPPY TRADING!"; //--- Add final message line
            Print(message); //--- Print the message in the terminal
            sendMessageToTelegram(chat.member_id,message,NULL); //--- Send the success message to Telegram
         }
 

Aqui, verificamos se o indicador foi adicionado com sucesso ao gráfico avaliando a variável booleana “isIndicatorAddedToChart”. Se essa condição for verdadeira, prosseguimos para preparar uma mensagem de sucesso que detalha a configuração do indicador. Começamos inicializando uma variável string chamada “message” com um cabeçalho de mensagem de sucesso: “\nSUCCESS! O INDICADOR FOI ADICIONADO AO GRÁFICO COM OS SEGUINTES DETALHES:\n”. Em seguida, concatenamos várias informações sobre o indicador, incluindo seu tipo, símbolo, período gráfico (timeframe), período, deslocamento (shift) e preço aplicado. Para os valores numéricos, usamos a função IntegerToString para garantir que sejam convertidos em formato de string para concatenação adequada.

Após compilar todas essas informações, adicionamos uma linha final à mensagem dizendo “\nHAPPY TRADING!” para transmitir um sentimento positivo. Depois, usamos a função Print para enviar a mensagem completa ao terminal, fornecendo uma confirmação clara da adição do indicador e de seus detalhes. Por fim, chamamos a função “sendMessageToTelegram” para enviar a mesma mensagem de sucesso ao Telegram, garantindo que o chat relevante, identificado por “chat.member_id”, seja notificado sobre a operação bem-sucedida. Ao executar, obtemos a seguinte saída:

SAÍDA DE ERRO

Podemos ver que, apesar de termos o símbolo na janela de Observação do Mercado (Market Watch), ainda retornamos uma mensagem de erro. Isso ocorre porque interferimos na estrutura correta do nome do símbolo ao transformar tudo em maiúsculas. Para recuperar a estrutura correta, mantendo a interpretação e a comparação do símbolo, podemos usar várias opções, mas a mais simples é anexar diretamente o nome inicial do símbolo de volta à variável que o armazena, conforme mostrado.

            //--- Check for symbol in the list of available symbols and assign it
            for(int k = 0; k < SymbolsTotal(true); k++) { //--- Loop through all available symbols
               string selected_symbol = SymbolName(k, true); //--- Get the symbol name
               StringToUpper(selected_symbol);
               if (StringCompare(selected_line,selected_symbol,false) == 0){ //--- Compare the line with the symbol name
                  indicator_symbol = SymbolName(k, true); //--- Assign the symbol if a match is found
                  Print("Line @ index ",i," SYMBOL = ",indicator_symbol); //--- Print the found symbol
               }
            }            

A única mudança que ocorreu no trecho está mostrada e destacada em amarelo. Ao executar novamente, obtemos a saída correta abaixo:

SAÍDA CORRETA

Até aqui, adicionamos com sucesso o indicador ao gráfico. No Telegram, recebemos a resposta de sucesso conforme visualizado abaixo:

CONFIRMAÇÃO NO TELEGRAM

No terminal de negociação, um novo gráfico é aberto e o indicador é adicionado. Abaixo está uma confirmação visual.

CONFIRMAÇÃO NO MT5

Até este ponto, podemos dizer que atingimos nosso objetivo de adicionar automaticamente indicadores técnicos de Média Móvel aos gráficos. Uma metodologia semelhante pode ser aplicada a outros indicadores, como o Awesome Oscillator. Seguindo o mesmo formato de identificação do comando, extração dos parâmetros relevantes e execução da adição do indicador, podemos integrar diversos indicadores ao nosso sistema de trading de forma contínua, mantendo consistência e eficiência em toda a implementação. Ainda precisamos testar a implementação e confirmar que tudo funciona corretamente. Isso será feito na próxima seção.


Testando o Sistema de Indicadores

Nesta seção, vamos nos concentrar em validar a funcionalidade do nosso Sistema de Indicadores. O teste envolve verificar se os indicadores estão configurados corretamente e respondem adequadamente aos comandos recebidos do Telegram. Examinaremos o processo de adicionar indicadores ao gráfico, garantindo que todos os parâmetros sejam definidos com precisão e que os indicadores sejam exibidos corretamente nos gráficos.

Para fornecer uma visão clara desse processo, incluímos um vídeo demonstrando a configuração e o ajuste de indicadores no MetaTrader 5 — em especial a Média Móvel — destacando como o sistema funciona e confirmando sua prontidão para cenários reais de negociação, conforme abaixo.



Conclusão

Em conclusão, neste artigo demonstramos como analisar e processar comandos de indicadores recebidos via Telegram para automatizar a adição de indicadores aos seus gráficos no MetaTrader 5. Ao implementar essas técnicas, simplificamos o processo, aumentando a eficiência da negociação e reduzindo o potencial de erro humano.

Com o conhecimento adquirido com esta implementação, você está apto a desenvolver sistemas mais sofisticados que incorporem indicadores e comandos adicionais. Essa base permitirá refinar suas estratégias de negociação, adaptar-se às condições de mercado em mudança e, em última análise, melhorar seu desempenho de negociação. Esperamos que você tenha achado o artigo fácil de entender e que ele tenha fornecido os insights necessários para aprimorar seus sistemas de negociação. Se você tiver alguma dúvida ou precisar de mais esclarecimentos, sinta-se à vontade para explorar recursos adicionais ou experimentar os conceitos compartilhados ao longo desta série. Bons trades!

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

Data Science e ML (Parte 30): O Casal Poderoso para Prever o Mercado de Ações, Redes Neurais Convolucionais (CNNs) e Redes Neurais Recorrentes (RNNs) Data Science e ML (Parte 30): O Casal Poderoso para Prever o Mercado de Ações, Redes Neurais Convolucionais (CNNs) e Redes Neurais Recorrentes (RNNs)
Neste artigo, exploramos a integração dinâmica das Redes Neurais Convolucionais (CNNs) e das Redes Neurais Recorrentes (RNNs) na previsão do mercado de ações. Aproveitando a capacidade das CNNs de extrair padrões e a proficiência das RNNs em lidar com dados sequenciais. Vamos ver como essa combinação poderosa pode aumentar a precisão e eficiência dos algoritmos de negociação.
Construindo um Modelo de Restrição de Tendência com Candlestick (Parte 9): Expert Advisor de Múltiplas Estratégias (I) Construindo um Modelo de Restrição de Tendência com Candlestick (Parte 9): Expert Advisor de Múltiplas Estratégias (I)
Hoje, vamos explorar as possibilidades de incorporar múltiplas estratégias em um Expert Advisor (EA) usando MQL5. Os Expert Advisors oferecem capacidades mais amplas do que apenas indicadores e scripts, permitindo abordagens de negociação mais sofisticadas que podem se adaptar às mudanças das condições do mercado. Confira mais na discussão deste artigo.
MQL5 Trading Toolkit (Parte 3): Desenvolvimento de uma biblioteca EX5 para gerenciamento de ordens pendentes MQL5 Trading Toolkit (Parte 3): Desenvolvimento de uma biblioteca EX5 para gerenciamento de ordens pendentes
Você aprenderá como desenvolver e implementar uma biblioteca EX5 abrangente para ordens pendentes em seu código ou projetos MQL5. Vamos analisar como importar e implementar essa biblioteca como parte de um painel de negociação ou interface gráfica do usuário (GUI). O painel de ordens do EA permitirá aos usuários abrir, acompanhar e excluir ordens pendentes por número mágico diretamente na interface gráfica exibida na janela do gráfico.
Repensando estratégias clássicas (Parte X): A IA pode operar o MACD? Repensando estratégias clássicas (Parte X): A IA pode operar o MACD?
Junte-se a nós em uma análise empírica do indicador MACD para verificar se a aplicação da inteligência artificial à estratégia que inclui esse indicador pode aumentar a precisão da previsão do par EURUSD. Avaliamos simultaneamente se é mais fácil prever o próprio indicador do que o preço, bem como se o valor do indicador permite prever os níveis futuros de preço. Forneceremos as informações necessárias para que você decida se vale a pena investir seu tempo integrando o MACD às suas estratégias de trading com o uso de inteligência artificial.