English Русский 中文 Español Deutsch 日本語
preview
Envio de mensagens de MQL5 para o Discord, criação de um bot Discord-MetaTrader 5

Envio de mensagens de MQL5 para o Discord, criação de um bot Discord-MetaTrader 5

MetaTrader 5Integração |
18 0
Omega J Msigwa
Omega J Msigwa

Sumário


Introdução

Já não vivemos nos estágios iniciais do desenvolvimento da Internet nem na era digital primitiva; hoje, praticamente qualquer serviço pode ser integrado a outro pela Internet; tudo depende apenas da disposição de alguém para fazer o esforço necessário.

A existência de APIs (...), conjuntos de regras e protocolos que permitem a interação entre diferentes aplicações de software, simplificou a integração entre programas e aplicações e ajudou a tornar a Internet tão abrangente quanto é hoje.

Para usar qualquer API disponibilizada, é preciso cumprir suas regras e protocolos, sem falar que o provedor da API deve oferecer um meio seguro (se houver) de acessá-la.

A comunicação entre o MetaTrader 5 e aplicações externas não é algo novo; ela já foi implementada com várias aplicações que oferecem APIs confiáveis, que os desenvolvedores MQL5 usam para enviar e receber informações por meio de requisições web. O aplicativo mais comum usado por traders que trabalham com MQL5 para esse tipo de comunicação é o Telegram.

Neste artigo, discutiremos como enviar mensagens e informações de trading (sinais) do MetaTrader 5 para o Discord usando a linguagem de programação MQL5.


Criação de um webhook no Discord

Para quem ainda não conhece o Discord:

O Discord é uma plataforma gratuita de comunicação que permite que os usuários troquem mensagens de texto, conversem por voz e vídeo, além de compartilhar capturas de tela. Ele é popular como ferramenta de comunicação entre amigos e comunidades, especialmente em jogos online, mas também é usado por diversos outros grupos, como clubes de leitura ou grupos de costura.

Assim como o Telegram, o Discord permite que os usuários interajam entre si. Ambas as plataformas fornecem APIs que possibilitam a comunicação até mesmo fora de suas próprias plataformas, mas o Discord está bem à frente do Telegram em termos de integração e automação.

Essa plataforma permite que os usuários criem comunidades flexíveis e gerenciem aplicativos (também conhecidos como bots) de vários tipos.

Existem várias maneiras de criar bots e automatizar a comunicação e a troca de informações no Discord, mas a forma mais simples é usar um webhook em um dos canais da sua comunidade. Veja como criar um deles.

 

Figura 0.

A partir da sua comunidade, acesse a seção Configurações do servidor (server settings).

 

Figura 02.

Nas configurações do servidor, acesse Integrations > Webhooks.

 

Figura 03.

Para criar um webhook, clique no botão Novo webhook (New Webhook). Será criado um webhook com o nome padrão "Capitão Gancho" (Captain Hook). Você pode alterar livremente esse nome para qualquer outro que desejar.

 

Figura 04.

Depois de alterar o nome, é possível escolher o canal ao qual deseja associar esse webhook. Neste servidor, tenho um canal chamado sinais de trading, ao qual quero associar esse webhook. Veja a figura 01.

Por fim, precisamos copiar a URL do webhook. Esse é o nosso gateway da API, que podemos usar com requisições web em MQL5.

 

Figura 05.



Envio da primeira mensagem do MetaTrader 5 para o Discord

A primeira coisa que precisamos fazer é adicionar https://discord.com à lista de URLs permitidas no MetaTrader 5; caso contrário, tudo o que discutiremos a seguir não funcionará.

No MetaTrader 5, acesse Ferramentas (Tools) > Opções (Options) > EAs (Expert Advisors) > verifique se a caixa Permitir WebRequest para as URLs listadas está marcada (Ensure Allow WebRequest for listed URL is checked) e, em seguida, adicione a URL ao formulário abaixo (add a URL on the form below).

Com a URL do webhook, podemos enviar uma requisição POST para esse endpoint específico da API, passando os dados em formato JSON.

Nome do arquivo: Discord EA.mq5

#include <jason.mqh> //https://www.mql5.com/ru/code/13663

string discord_webhook_url = "https://discord.com/api/webhooks/1384809399767269527/105Kp27yKnQDpKD01VdEb01GS5P-KH5o5rYKuJb_xD_D8O23GPkGLXGn9pHBB1aOt4wR";
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
     
   CJAVal js(NULL, jtUNDEF); //Creating a json object
   
   string raw_json = "{\"content\": \"Hello world from MetaTrader5!\"}";
   
   bool b = js.Deserialize(raw_json); //Parse JSON string into a structured object
   
   string json;
   js.Serialize(json); //Convert the object to a valid JSON string
   
//--- Sending a Post webrequest

   char data[]; 
   ArrayResize(data, StringToCharArray(json, data, 0, StringLen(json))); //--- serialize to string
   
//--- send data

   char res_data[]; //For storing the body of the response
   string res_headers=NULL; //For storing response headers (if needed)
   
   string headers="Content-Type: application/json; charset=utf-8";
   uint timeout=10000; //10 seconds timeout for the HTTP request
   
   int ret = WebRequest("POST", 
                         discord_webhook_url, 
                         headers, 
                         timeout, 
                         data, 
                         res_data, 
                         res_headers); //Send a post request
   
   if (ret==-1)
     {
       printf("func=%s line=%d, Failed to send a webrequest. Error = %s",__FUNCTION__,__LINE__,ErrorDescription(GetLastError()));
       return false;
     }
   
//--- Check if the post request was successful or not

   if (ret==204)
     {
       if (MQLInfoInteger(MQL_DEBUG))
         Print("Message sent to discord successfully");
     }
   else
     {
       printf("Failed to send message to discord. Json response Error = %s",CharArrayToString(res_data));
     }
  
//---
   return(INIT_SUCCEEDED);
  }

Todas as informações e mensagens que precisam ser enviadas ao Discord devem estar sempre em formato JSON.

Tudo o que fica sob a chave content no objeto JSON representa o corpo principal de texto da mensagem enviada ao Discord.

A execução do código acima envia uma mensagem simples ao Discord.

Ótimo! No entanto, esse foi um fluxo trabalhoso de processamento de dados JSON e de todo o restante apenas para enviar uma mensagem simples ao Discord. Vamos reunir tudo em uma classe para simplificar o envio de mensagens e não precisar nos preocupar sempre com os detalhes técnicos.


Criação da classe Discord em MQL5

Para termos uma classe com funcionalidade padrão, adicionaremos a ela o registro em log para rastrear os erros gerados pelo endpoint da API e pelo fluxo de envio de requisições.

Nome do arquivo: Discord.mqh.

#include <errordescription.mqh>
#include <logging.mqh>
#include <jason.mqh>

class CDiscord
  {
protected:
   string m_webhook_url;
   string m_headers;
   uint m_timeout;
   CLogging logging;
   
   bool PostRequest(const string json);
   string GetFormattedJson(const string raw_json);
   
public:
                     CDiscord(const string webhook_url, const string headers="Content-Type: application/json; charset=utf-8", const uint timeout=10000);
                    ~CDiscord(void);
                     
                     bool SendMessage(const string message);
                     bool SendEmbeds(const string title, const string description_, color clr, const string footer_text, const datetime time);
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CDiscord::CDiscord(const string webhook_url, string headers="Content-Type: application/json; charset=utf-8", uint timeout=10000):
 m_webhook_url(webhook_url),
 m_headers(headers),
 m_timeout(timeout)
 {
//--- Initialize the logger

   logging.Config("Discord server");
   if (!logging.init())
      return;
      
   logging.info("Initialized",MQLInfoString(MQL_PROGRAM_NAME),__LINE__);
 }

Encapsulamos as rotinas repetitivas de envio de requisições POST e de conversão de textos para o formato JSON.

string CDiscord::GetFormattedJson(const string raw_json)
 {
   CJAVal js(NULL, jtUNDEF);
   bool b = js.Deserialize(raw_json);
   
   string json;
   js.Serialize(json); 
   
   return json;
 }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CDiscord::PostRequest(const string json)
 {
   char res[];
    
//--- serialize to string

   char data[]; 
   ArrayResize(data, StringToCharArray(json, data, 0, StringLen(json)));
   
//--- send data

   char res_data[];
   string res_headers=NULL;

   int ret = WebRequest("POST", m_webhook_url, m_headers, m_timeout, data, res_data, res_headers); //Send a post request
   
   if (ret==-1)
     {
       printf("func=%s line=%d, Failed to send a webrequest. Error = %s",__FUNCTION__,__LINE__,ErrorDescription(GetLastError()));
       return false;
     }
   
//--- Check if the post request was successful or not

   if (ret==204)
     {
       if (MQLInfoInteger(MQL_DEBUG))
         Print("Message sent to discord successfully");
         logging.info("Message sent to discord successfully",MQLInfoString(MQL_PROGRAM_NAME), __LINE__);
     }
   else
     {
       printf("Failed to send message to discord. Json response Error = %s",CharArrayToString(res_data));
       logging.error(CharArrayToString(res_data), MQLInfoString(MQL_PROGRAM_NAME), __LINE__);
     }

  return true;
 }

A seguir, temos uma função simples para enviar mensagens ao Discord.

bool CDiscord::SendMessage(const string message)
 {
   string raw_json = StringFormat("{\"content\": \"%s\"}",message);   
   string json = GetFormattedJson(raw_json); //Deserialize & Serialize the message in JSON format
   
   return PostRequest(json);
 }

Seu uso é mostrado a seguir.

Nome do arquivo: Discord EA.mq5

#include <Discord.mqh>
CDiscord *discord;

string discord_webhook_url = "https://discord.com/api/webhooks/1384809399767269527/105Kp27yKnQDpKD01VdEb01GS5P-KH5o5rYKuJb_xD_D8O23GPkGLXGn9pHBB1aOt4wR";
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
     
    discord = new CDiscord(discord_webhook_url);  
    discord.SendMessage("Hello from MetaTrader5!");
  }

Essa é uma função simples e poderosa, que pode ser usada para enviar mensagens de diferentes tipos, pois o Discord aceita mensagens com formatação Markdown.

Exemplo de mensagem em formato Markdown

    discord.SendMessage(
                         "# h1 header\n"
                         "## h2 header\n"
                         "### h3 header\n\n"
                         "**bold text** normal text\n"
                         "[MQL5](https://www.mql5.com)\n"
                         "![image](https://i.imgur.com/4M34hi2.png)"
                       );

Outputs.

E isso ainda não é o mais interessante que se pode fazer com o recursos de formatação Markdown oferecidos pelo Discord. Vamos compartilhar algumas linhas de código no Discord.

    discord.SendMessage("```MQL5\n" //we put the type of language for markdown highlighters after three consecutive backticks
                        "if (CheckPointer(discord)!=POINTER_INVALID)\n"
                        "  delete discord;\n"
                        "```");
                        
    discord.SendMessage("```Python\n"
                        "while(True):\n"
                        "  break\n"
                        "```");

Resultado.

A ideia é mostrar o quanto os recursos de formatação Markdown são poderosos.

Adição de emojis às suas mensagens

Os emojis são muito úteis em mensagens de texto: acrescentam elegância, melhoram a legibilidade e, sem falar que, trazem uma pitada de humor.

Diferentemente de outras plataformas de comunicação, nas quais os emojis são criados a partir de determinados códigos numéricos, o Discord usa uma sintaxe própria para identificar e exibir emojis diretamente em mensagens de texto.

Tudo o que você precisa fazer é colocar o nome do emoji entre dois sinais de dois-pontos: um no início e outro no final do nome.

Ao inserir os códigos desses emojis em uma mensagem de texto, eles serão exibidos como emojis no Discord.

discord.SendMessage(":rotating_light: Trade Alert!");

Mensagem.

Existem milhares de emojis, e acompanhar todos eles é bastante difícil. Aqui está um guia de referência rápida: https://github.com/ikatyang/emoji-cheat-sheet/blob/master/README.md.  Criei um arquivo simples de biblioteca com alguns códigos de emojis e a sintaxe correspondente. Fique à vontade para adicionar mais códigos de emojis conforme desejar.

Nome do arquivo: discord emojis.mqh

#define DISCORD_EMOJI_ROCKET                   ":rocket:"               
#define DISCORD_EMOJI_CHART_UP                 ":chart_with_upwards_trend:"   
#define DISCORD_EMOJI_CHART_DOWN               ":chart_with_downwards_trend:" 
#define DISCORD_EMOJI_BAR_CHART                ":bar_chart:"            
#define DISCORD_EMOJI_BOMB                     ":bomb:"                 
#define DISCORD_EMOJI_THUMBS_UP                ":thumbsup:"             
#define DISCORD_EMOJI_THUMBS_DOWN              ":thumbsdown:"           
#define DISCORD_EMOJI_WARNING                  ":warning:"              
#define DISCORD_EMOJI_JOY                      ":joy:"                  
#define DISCORD_EMOJI_SOB                      ":sob:"                  
#define DISCORD_EMOJI_SMILE                    ":smile:"                
#define DISCORD_EMOJI_FIRE                     ":fire:"                 
#define DISCORD_EMOJI_STAR                     ":star:"                 
#define DISCORD_EMOJI_BLUSH                    ":blush:"                
#define DISCORD_EMOJI_THINKING                 ":thinking:"             
#define DISCORD_EMOJI_ROTATING_LIGHT           ":rotating_light:"
#define DISCORD_EMOJI_X                        ":x:"
#define DISCORD_EMOJI_WHITE_CHECK_MARK         ":white_check_mark:"
#define DISCORD_EMOJI_BALLOT_BOX_WITH_CHECK    ":ballot_box_with_check:"

#define DISCORD_EMOJI_HASH                     ":hash:"               
#define DISCORD_EMOJI_ASTERISK                 ":asterisk:"           

#define DISCORD_EMOJI_ZERO                     ":zero:"               
#define DISCORD_EMOJI_ONE                      ":one:"                
#define DISCORD_EMOJI_TWO                      ":two:"                
#define DISCORD_EMOJI_THREE                    ":three:"              
#define DISCORD_EMOJI_FOUR                     ":four:"               
#define DISCORD_EMOJI_FIVE                     ":five:"               
#define DISCORD_EMOJI_SIX                      ":six:"                
#define DISCORD_EMOJI_SEVEN                    ":seven:"              
#define DISCORD_EMOJI_EIGHT                    ":eight:"              
#define DISCORD_EMOJI_NINE                     ":nine:"               
#define DISCORD_EMOJI_TEN                      ":keycap_ten:"       
  
#define DISCORD_EMOJI_RED_CIRCLE               ":red_circle:"               
#define DISCORD_EMOJI_GREEN_CIRCLE             ":green_circle:"       

Envio da mensagem.

discord.SendMessage(DISCORD_EMOJI_ROTATING_LIGHT " Trade Alert! " DISCORD_EMOJI_JOY);

Resultado da mensagem.

Menção a usuários e cargos

Essa mensagem simples pode conter menções e cargos, que são essenciais para a entrega eficiente da mensagem.

  • @everyone: notifica todos os usuários no canal, mesmo que estejam offline.
  • @here: notifica todos os usuários ativos no chat. ou seja, todos os usuários que estão online.
  • <@user_id>: a notificação é enviada a um usuário específico ou a usuários específicos.
  • <@&role_id> notifica todos os usuários aos quais foi atribuído um determinado cargo. Por exemplo, o envio de sinais de trading a todos os usuários a todos os usuários com o cargo de traders manuais.

Exemplo de uso.

discord.SendMessage("@everyone Это информация о торговом счете");
discord.SendMessage("@here Это быстрые торговые сигналы для всех вас, кто активен.);

Resultado da mensagem.


Identidade do bot Discord

A vantagem do webhook é que ele não fica limitado à aparência padrão (nome e avatar); você pode alterar esses valores a qualquer momento ao enviar uma requisição POST.

Esse recurso é bastante conveniente, pois você pode ter vários robôs de trading enviando informações para uma ou duas comunidades, enquanto todos eles usam um único webhook. A possibilidade de usar identidades diferentes ajuda a distinguir os remetentes e as mensagens recebidas.

Podemos tornar essa identidade obrigatória a cada chamada (inicialização) da classe Discord, para que a identidade seja global.

class CDiscord
  {
protected:

//...
//...
   
public:
                     string m_name;
                     string m_avatar_url;
                     
                     CDiscord(const string webhook_url, 
                              const string name, 
                              const string avatar_url, 
                              const string headers="Content-Type: application/json; charset=utf-8", 
                              const uint timeout=10000);
 }

Declaramos essas variáveis como públicas, pois o usuário pode querer alterá-las ou acessá-las durante a execução (após a inicialização da classe).

Isso facilita ainda mais o rastreamento dos logs para cada nome de usuário atribuído a esse mensageiro do Discord.

CDiscord::CDiscord(const string webhook_url, 
                   const string name, 
                   const string avatar_url, 
                   const string headers="Content-Type: application/json; charset=utf-8", 
                   const uint timeout=10000):
                   
 m_webhook_url(webhook_url),
 m_headers(headers),
 m_timeout(timeout),
 m_name(name),
 m_avatar_url(avatar_url)
 {
//--- Initialize the logger

   logging.Config(m_name);
   if (!logging.init())
      return;
      
   logging.info("Initialized",MQLInfoString(MQL_PROGRAM_NAME),__LINE__);
 }

Agora precisamos adicionar essas informações de identidade (nome e avatar) em todos os pontos em que a string JSON aparece.

bool CDiscord::SendMessage(const string message)
 {
   string raw_json = StringFormat("{"
                                    "\"username\": \"%s\","
                                    "\"avatar_url\": \"%s\","
                                    "\"content\": \"%s\""
                                  "}",
                                  m_name, m_avatar_url, message);   
                                  
   string json = GetFormattedJson(raw_json); //Deserialize & Serialize the message in JSON format
   
   return PostRequest(json);
 }

Dentro da função SendJSON(), precisamos tornar essa identificação opcional, pois o usuário pode ter uma preferência diferente desde o início e decidir usar a função que aceita uma string JSON bruta, dando-lhe controle sobre as informações que deseja enviar.

bool CDiscord::SendJSON(const string raw_json, bool use_id=true)
 {
   CJAVal js;                                 
   
   js.Deserialize(raw_json);
   
   if (use_id) //if a decides to use the ids assigned to the class constructor, we append that information to the json object
     {
       js["username"] = m_name;
       js["avatar_url"] = m_avatar_url;
     }
     
   string json;
   js.Serialize(json);
   
   return PostRequest(json);
 }

Vamos definir uma identidade diferente para o nosso bot.

#include <Discord.mqh>
CDiscord *discord;

input string discord_webhook_url = "https://discord.com/api/webhooks/1384809399767269527/105Kp27yKnQDpKD01VdEb01GS5P-KH5o5rYKuJb_xD_D8O23GPkGLXGn9pHBB1aOt4wR";
input string avatar_url_ = "https://imgur.com/m7sVf51.jpeg";
input string bots_name = "Signals Bot";
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
     
    discord = new CDiscord(discord_webhook_url,
                           bots_name,
                           avatar_url_);  
    
    discord.SendMessage("Same webhook but, different Identity :smile: cheers!");
            
//---
   return(INIT_SUCCEEDED);
  }

Resultado.


Uso de embeds (embed) e arquivos de imagem

Embora uma imagem tenha sido anexada à mensagem durante a criação da classe Discord na seção anterior, existe uma forma apropriada de anexar arquivos e incorporar elementos à mensagem.

Podemos fazer isso usando uma requisição JSON independente.

discord.SendJSON(
    "{"
        "\"content\": \"Here's the latest chart update:\","
        "\"embeds\": ["
            "{"
                "\"title\": \"Chart Update\","
                "\"image\": {"
                    "\"url\": \"https://imgur.com/SBsomI7.png\""
                "}"
            "}"
        "]"
    "}"
);

Resultados.

Infelizmente, você não pode enviar uma imagem diretamente pelo MQL5 usando WebRequest, apesar de o Discord ser capaz de receber arquivos.  No momento, o melhor que você pode fazer é compartilhar a imagem por meio de um link para o local em que o arquivo esteja hospedado online.

No exemplo anterior, precisei usar imgur.com.


Envio de notificações de trading do MetaTrader 5 para o Discord

Os traders costumam enviar informações do MetaTrader 5 para plataformas externas a fim de compartilhar atualizações sobre sua atividade de trading. Vamos usar esse bot (EA) para essa tarefa.

Começaremos com as notificações de abertura de operação.

01: Envio de notificações de abertura de operações

Para essa tarefa, a função OnTradeTransaction é bastante útil.

Abaixo estão as funções de verificação usadas para conferir o estado da posição e da operação mais recentes.

#define IS_TRANSACTION_POSITION_OPENED         (trans.type == TRADE_TRANSACTION_DEAL_ADD && HistoryDealSelect(trans.deal) && (ENUM_DEAL_ENTRY)HistoryDealGetInteger(trans.deal, DEAL_ENTRY) == DEAL_ENTRY_IN)
#define IS_TRANSACTION_POSITION_CLOSED         (trans.type == TRADE_TRANSACTION_DEAL_ADD && HistoryDealSelect(trans.deal) && (ENUM_DEAL_ENTRY)HistoryDealGetInteger(trans.deal, DEAL_ENTRY) == DEAL_ENTRY_OUT && ((ENUM_DEAL_REASON)HistoryDealGetInteger(trans.deal, DEAL_REASON) != DEAL_REASON_SL && (ENUM_DEAL_REASON)HistoryDealGetInteger(trans.deal, DEAL_REASON) != DEAL_REASON_TP))
#define IS_TRANSACTION_POSITION_MODIFIED       (trans.type == TRADE_TRANSACTION_POSITION)
void OnTradeTransaction(const MqlTradeTransaction& trans,
                        const MqlTradeRequest& request,
                        const MqlTradeResult& result)
  {
     ulong deal_ticket = trans.deal;
     ulong position_ticket = trans.position;

//---
     
     m_deal.Ticket(deal_ticket); //select a deal by it's ticket
     
     if (IS_TRANSACTION_POSITION_OPENED)
       {
         if (deal_ticket==0)
           return;
          
         if (!m_position.SelectByTicket(position_ticket)) //select a position by ticket
            return;
              
         if (!m_symbol.Name(m_deal.Symbol())) //select a symbol from a position
           {
             printf("line=%d Failed to select symbol %s. Error = %s",__LINE__,m_deal.Symbol(),ErrorDescription(GetLastError()));
             return;
           }
         
         string message = 
            DISCORD_EMOJI_ROTATING_LIGHT " **TRADE OPENED ALERT** \n\n"
            "- **Symbol:**"+m_position.Symbol()+"\n"
            "- **Trade Type:** "+ (m_position.PositionType()==POSITION_TYPE_BUY ? DISCORD_EMOJI_GREEN_CIRCLE: DISCORD_EMOJI_RED_CIRCLE) +" "+m_position.TypeDescription()+"\n"
            "- **Entry Price:** `"+DoubleToString(m_deal.Price(), m_symbol.Digits())+"`\n" 
            "- **Stop Loss:** `"+DoubleToString(m_position.StopLoss(), m_symbol.Digits())+"`\n"
            "- **Take Profit:** `"+DoubleToString(m_position.TakeProfit(), m_symbol.Digits())+"`\n"
            "- **Time UTC:** `"+TimeToString(TimeGMT())+"`\n"
            "- **Position Ticket:** `"+(string)position_ticket+"`\n"
            "> "DISCORD_EMOJI_WARNING" *Risk what you can afford to lose.*";
               
         discord.SendMessage(message);
       }

  //...
  //... Other lines of code
  //...     
 }

Observação:

Ao formatar valores de mercado, como preço de entrada, stop loss, take profit etc., coloco cada um deles entre acentos graves (). Isso cria um bloco de código inline no Discord, o que ajuda a separar visualmente os números e facilita a cópia pelos usuários.

Exemplos de uso no Discord.

  • Acento grave simples () para formatar código inline: ótima para valores curtos, como preços.
  • Três crases (```) para formatar blocos de código multilinha: usadas para blocos maiores de código ou mensagens.

Abri duas operações opostas manualmente usando a Negociação com Um Clique (One-Click Trading) no MetaTrader 5; veja o resultado abaixo.

02: Envio de notificações de alteração de operações

if (IS_TRANSACTION_POSITION_MODIFIED)
  {
    if (!m_position.SelectByTicket(position_ticket))
      {
        printf("Failed to modify a position. Erorr = %s",ErrorDescription(GetLastError()));
        return;
      }
              
    if (!m_symbol.Name(m_deal.Symbol()))
      {
        printf("line=%d Failed to select symbol %s. Error = %s",__LINE__,m_deal.Symbol(),ErrorDescription(GetLastError()));
        return;
      }
         
    string message = 
       DISCORD_EMOJI_BAR_CHART " **TRADE MODIFIED ALERT** \n\n"
       "- **Symbol:** `"+m_position.Symbol()+"`\n"
       "- **Trade Type:** "+ (m_position.PositionType()==POSITION_TYPE_BUY ? DISCORD_EMOJI_GREEN_CIRCLE: DISCORD_EMOJI_RED_CIRCLE) +" "+m_position.TypeDescription()+"\n"
       "- **Entry Price:** `"+DoubleToString(m_position.PriceOpen(), m_symbol.Digits())+"`\n" 
       "- **New Stop Loss:** `"+DoubleToString(m_position.StopLoss(), m_symbol.Digits())+"`\n"
       "- **New Take Profit:** `"+DoubleToString(m_position.TakeProfit(), m_symbol.Digits())+"`\n"
       "- **Time UTC:** `"+TimeToString(TimeGMT())+"`\n"
       "- **Position Ticket:** `"+(string)position_ticket+"`\n"
       "> "DISCORD_EMOJI_WARNING" *Risk what you can afford to lose.*";
            
    discord.SendMessage(message);      
  }

Nesta mensagem, tudo é igual ao exemplo de envio de notificações de trading; apenas os nomes dos campos de stop loss e take profit foram alterados.

03: Envio de notificações de fechamento de operações

if (IS_TRANSACTION_POSITION_CLOSED)
   {         
    if (!m_symbol.Name(m_deal.Symbol()))
      {
        printf("line=%d Failed to select symbol %s. Error = %s",__LINE__,m_deal.Symbol(),ErrorDescription(GetLastError()));
        return;
      }
         
    m_symbol.RefreshRates(); //Get recent ask and bid prices
         
    long reason_integer;
    m_deal.InfoInteger(DEAL_REASON, reason_integer);
         
    string reason_text = GetDealReasonText(reason_integer);
            
    string message = 
          DISCORD_EMOJI_X " **TRADE CLOSED ALERT**\n\n"
          "- **Symbol:** `" + m_deal.Symbol() + "`\n"
          "- **Trade Type:** " + (m_position.PositionType() == POSITION_TYPE_BUY ? DISCORD_EMOJI_GREEN_CIRCLE : DISCORD_EMOJI_RED_CIRCLE) + " " + m_position.TypeDescription() + "\n"
          "- **Entry Price:** `" + DoubleToString(m_deal.Price(), m_symbol.Digits()) + "`\n"
          "- **Exit Price:** `" + (m_position.PositionType() == POSITION_TYPE_BUY ? (DoubleToString(m_symbol.Bid(), m_symbol.Digits())) : (DoubleToString(m_symbol.Ask(), m_symbol.Digits()))) + "`\n"
          "- **Profit:** " + (m_deal.Profit() >= 0 ? DISCORD_EMOJI_THUMBS_UP : DISCORD_EMOJI_THUMBS_DOWN) + " `" + DoubleToString(m_deal.Profit(), 2) + "`\n"
          "- **Close Reason:** `" + reason_text+ "`\n"
          "- **Commission:** `" + DoubleToString(m_position.Commission(), 2) + "`\n"
          "- **Swap:** `" + DoubleToString(m_position.Swap(), 2)+ "`\n"
          "- **Time (UTC):** `" + TimeToString(TimeGMT()) + "`\n"
          "- **Deal Ticket:** `" + string(deal_ticket) + "`\n"
          "> "DISCORD_EMOJI_WARNING" *Risk what you can afford to lose.*";
               
    discord.SendMessage(message);
  }

Na mensagem de fechamento da operação, adicionamos um emoji de polegar para cima se a operação foi fechada com lucro e um emoji de polegar para baixo se a operação foi fechada com prejuízo. Diferentemente da abertura e da alteração da posição, em que tínhamos o número do ticket da posição. uma posição fechada já não é uma posição, mas uma operação. Portanto, usamos o número do ticket da operação.

Essa operação foi fechada manualmente, portanto seu motivo passa a ser Client (Desktop Terminal), o que corresponde a ENUM_DEAL_REASON.

string GetDealReasonText(long reason)
{
   switch((int)reason)
   {
      case DEAL_REASON_CLIENT:            return "Client (Desktop Terminal)";
      case DEAL_REASON_MOBILE:            return "Mobile App";
      case DEAL_REASON_WEB:               return "Web Platform";
      case DEAL_REASON_EXPERT:            return "Expert Advisor";
      case DEAL_REASON_SL:                return "Stop Loss Hit";
      case DEAL_REASON_TP:                return "Take Profit Hit";
      case DEAL_REASON_SO:                return "Stop Out (Margin)";
      case DEAL_REASON_ROLLOVER:          return "Rollover Execution";
      case DEAL_REASON_VMARGIN:           return "Variation Margin Charged";
      case DEAL_REASON_SPLIT:             return "Stock Split Adjustment";
      case DEAL_REASON_CORPORATE_ACTION:  return "Corporate Action";
      default:                            return "Unknown Reason";
   }
}


Conclusão

Os webhooks do Discord são um bom ponto de partida para integrar o MetaTrader 5 ao Discord, viabilizando a comunicação entre essas duas plataformas robustas. No entanto, como eu já disse antes, todas as APIs têm regras e limitações. A API de webhook do Discord não é diferente, pois suas requisições estão sujeitas a limite de taxa.

  • Usando um único webhook, é possível enviar apenas 5 mensagens a cada 2 segundos.
  • Para um único webhook, são permitidas no máximo 30 mensagens por minuto por canal.

Ao exceder esse limite, o Discord retornará HTTP 429 (Too Many Requests, "requisições demais"). Para resolver esse problema, você pode adicionar atrasos ou filas ao seu código MQL5.

Além disso, ao longo de todo o artigo eu falo sobre bot(s) do Discord, mas um webhook é bem diferente de um bot do Discord. Eu chamo de bot todo o fluxo operacional entre MQL5 e Discord.

Em essência, bots são programas que rodam no Discord e oferecem uma ampla gama de recursos: desde comandos básicos até automação complexa. Por outro lado, webhooks são URLs simples que permitem que aplicações externas enviem mensagens automáticas para um canal específico do Discord.

Abaixo está uma tabela com as diferenças entre um webhook do Discord e um bot do Discord.


Webhooks Bots
Função
  • Podem enviar mensagens apenas para um canal definido.
  • Eles só podem enviar mensagens. Não podem lê-las.
  • Podem enviar até 10 embeds em uma única mensagem.

  • São muito mais flexíveis, pois podem executar ações mais complexas, parecidas com as que um usuário comum pode realizar.
  • Bots podem visualizar e enviar mensagens.
  • Só é permitido um embed por mensagem.
Possibilidade de personalização
  • É possível criar 10 webhooks em um servidor e configurar o avatar e o nome de cada um.
  • Possibilidade de criar link em qualquer texto fora do embed.

  • Bots públicos geralmente têm avatar e nome predefinidos, que os usuários finais não podem alterar.
  • Não é possível criar link em texto de uma mensagem comum; é necessário usar um embed.
Carga operacional e segurança
  • Apenas um endpoint para envio de dados, sem necessidade de hospedagem propriamente dita.
  • Não há autenticação que confirme que os dados enviados para o webhook vêm de uma fonte confiável.
  • Não há autenticação que confirme que os dados enviados para o webhook vêm de uma fonte confiável. Se a URL do webhook for exposta, podem surgir apenas problemas pontuais, como envio de spam (por exemplo, envio de spam).
  • Se necessário, a URL do webhook pode ser alterada facilmente.

  • Bots devem ser hospedados em um ambiente seguro, que precisa permanecer online continuamente, o que exige recursos adicionais.
  • A autenticação dos bots é feita por meio de um token; um token comprometido pode causar danos sérios, dependendo das permissões concedidas ao bot pelo proprietário do servidor.
  • No entanto, se necessário, é possível redefinir o token do bot.

Um bot do Discord é bastante complexo e pouco prático para desenvolvedores MQL5, pois geralmente precisamos apenas de uma função que permita notificar nossos colegas traders sobre o andamento das atividades de trading. Sem falar que, para criar um bot do Discord, você precisará de alguns módulos em Python.

No entanto, se desejar, você pode desenvolver um bot do Discord completo que funcione com o MetaTrader 5.

Como temos o pacote MetaTrader 5-Python, a partir do ambiente Python, você pode implementar uma integração completa entre essas duas plataformas.

Boa sorte a todos!


Tabela de anexos

Nome do arquivo Descrição e finalidade
Include\discord emojis.mqh Contém códigos de emojis e sintaxe compatível com o Discord.
Include\discord.mqh Contém a classe CDiscord para enviar mensagens em formato JSON para a plataforma Discord.
Include\errordescription.mqh Contém descrições de todos os códigos de erro gerados pelo MetaTrader 5 na linguagem MQL5.
Include\jason.mqh Biblioteca para serialização e desserialização do formato JSON.
Include\logging.mqh  Biblioteca para o registro em log de todas as informações e erros gerados pela classe CDiscord (envio via webhook). 
Experts\Discord EA.mq5  EA para testar o webhook do Discord e enviar alertas de trading para a URL definida do webhook do Discord. 

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

Arquivos anexados |
Attachments.zip (19.26 KB)
Automatização de estratégias de negociação em MQL5 (Parte 20): estratégia multissímbolo usando CCI e AO Automatização de estratégias de negociação em MQL5 (Parte 20): estratégia multissímbolo usando CCI e AO
Neste artigo, desenvolveremos uma estratégia de negociação multissímbolo usando os indicadores CCI e AO para identificar reversões de tendência. Veremos o projeto, a implementação em MQL5 e os testes da estratégia em dados históricos. Na conclusão, são apresentadas recomendações para melhorar o desempenho.
Criação de classes Python para trading no MetaTrader 5, análogas às apresentadas em MQL5 Criação de classes Python para trading no MetaTrader 5, análogas às apresentadas em MQL5
O pacote Python MetaTrader 5 oferece uma maneira simples de criar aplicativos de trading para a plataforma MetaTrader 5 na linguagem Python. Embora seja um módulo poderoso e útil, ele não é tão simples quanto a linguagem de programação MQL5 quando se trata de desenvolver soluções para trading algorítmico. Neste artigo, criaremos classes para trading análogas às oferecidas pela linguagem MQL5, a fim de criar uma sintaxe semelhante e tornar o desenvolvimento de robôs de trading em Python tão simples quanto em MQL5.
Está chegando o novo MetaTrader 5 e MQL5 Está chegando o novo MetaTrader 5 e MQL5
Esta é apenas uma breve resenha do MetaTrader 5. Eu não posso descrever todos os novos recursos do sistema por um período tão curto de tempo - os testes começaram em 09.09.2009. Esta é uma data simbólica, e tenho certeza que será um número de sorte. Alguns dias passaram-se desde que eu obtive a versão beta do terminal MetaTrader 5 e MQL5. Eu ainda não consegui testar todos os seus recursos, mas já estou impressionado.
Do iniciante ao especialista: Sistema de análise autogeométrica Do iniciante ao especialista: Sistema de análise autogeométrica
Os padrões geométricos oferecem aos traders uma forma concisa de interpretar o movimento dos preços. Muitos analistas desenham linhas de tendência, retângulos e outras figuras manualmente e, em seguida, baseiam suas decisões de negociação nas formações que enxergam. Neste artigo, examinaremos uma alternativa automatizada: o uso de MQL5 para detectar e analisar os padrões geométricos mais populares. Vamos detalhar a metodologia, discutir os detalhes da implementação e mostrar como o reconhecimento automático de padrões pode aprimorar a compreensão do mercado pelo trader.