English
preview
Отправка сообщений из MQL5 в Discord, создание бота Discord-MetaTrader 5

Отправка сообщений из MQL5 в Discord, создание бота Discord-MetaTrader 5

MetaTrader 5Интеграция |
67 0
Omega J Msigwa
Omega J Msigwa

Содержание


Введение

Мы уже не живем на ранних стадиях развития Интернета и в первобытную цифровую эпоху; сегодня практически все можно связать с чем угодно в Интернете; все зависит только от готовности человека выполнить необходимую работу.

Наличие API (интерфейсов прикладного программирования), представляющих собой набор правил и протоколов, позволяющих различным программным приложениям взаимодействовать друг с другом, упростило подключение нескольких программ или приложений друг к другу, и, следовательно, сделало Интернет наиболее всеобъемлющим, таким, каким мы его видим сегодня.

Чтобы использовать любой предоставленный API, вы должны соблюдать правила и протоколы, не говоря уже о том, что поставщик API должен предоставить вам безопасный способ (если таковой имеется) доступа к нему.

Организация связи между MetaTrader 5 и внешними приложениями не является чем-то новым; она уже была реализована в нескольких приложениях, предлагающих надежные API, которые разработчики MQL5 используют для отправки и получения информации с помощью веб-запросов. Наиболее распространенным приложением, которое MQL5-трейдеры используют для таких сообщений, является Telegram.

В данной статье мы обсудим, как отправлять сообщения и торговую информацию (сигналы) из MetaTrader 5 в Discord с помощью языка программирования MQL5.


Создание вебхук на Discord

Для тех, кто еще не знаком с Discord:

Discord — это бесплатная коммуникационная платформа, позволяющая пользователям общаться с помощью текстовых сообщений, голоса и видео, а также делиться скриншотами. Он популярен для общения с друзьями и сообществами, особенно в онлайн-играх, но также используется и для различных других групп, например, книжных клубов или кружков шитья.

Подобно Telegram, это коммуникационная платформа, позволяющая пользователям общаться друг с другом. Обе эти платформы предоставляют API, позволяющие общаться даже за пределами своих платформ, но Discord значительно опережает Telegram в вопросах интеграции и автоматизации.

Эта платформа позволяет пользователям создавать гибкие сообщества и управлять приложениями (также известными как боты) разных типов.

Существуют различные способы создания ботов, автоматизации коммуникации и процесса обмена информацией в Discord, но самый простой способ — использование вебхука в одном из каналов вашего сообщества. Так создается один из них.

 

Рисунок 0.

Из вашего сообщества перейдите в раздел Настройки сервера (server settings).

 

Рисунок 02.

Из настроек сервера перейдите в Integrations > Webhooks.

 

Рисунок 03.

Для создания вебхука нажмите кнопку Новый вебхук (New Webhook). Будет создан вебхук с именем по умолчанию «Капитан Крюк» (Captain Hook). Можно свободно изменить это имя на любое другое, какое захотите.

 

Рисунок 04.

После изменения имени можно выбрать канал, к которому хотите применить этот вебхук. На этом сервере у меня есть канал под названием торговые сигналы, к которому я и хочу применить этот вебхук. Смотрите на рисунок 01.

Наконец, нам нужно скопировать URL-адрес вебхука. Это наш API-шлюз, который мы можем использовать с веб-запросами на MQL5.

 

Рисунок 05.



Отправление первого сообщения из MetaTrader 5 в Discord

Первое, что нам нужно сделать, это добавить  https://discord.com  в список разрешенных URL-адресов в MetaTrader 5, иначе все, что мы обсудим дальше, работать не будет.

В MetaTrader 5 перейдите в Сервис  (Tools)>  Настройки  Options > Советники (Expert Advisors)>  Убедитесь, что флажок Разрешить WebRequest для перечисленных URL установлен (Ensure Allow WebRequest for listed URL is checked), затем перейдите к добавить URL-адрес в форму ниже (add a URL on the form below).

С помощью URL-адреса вебхука мы можем отправить запрос POST к этой конкретной конечной точке API, получив данные в формате JSON.

Имя файла: 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);
  }

Вся информация и сообщения, которые необходимо отправить в Discord, всегда должны быть в формате JSON.

Все, что находится под кнопкой content в объекте JSON, представляет собой основное текстовое тело сообщения, отправляемого в Discord.

Запуск кода выше отправляет простое сообщение в Discord.

Отлично! Однако это был утомительный процесс обработки JSON-данных и всего остального только для того, чтобы отправить простое сообщение в Discord. Давайте объединим все в класс, чтобы упростить отправку сообщений и не беспокоиться каждый раз о технических деталях.


Создание класса Discord на MQL5

Чтобы обеспечить наличие стандартного класса функциональности, добавим в него ведение лога для отслеживания ошибок, создаваемых конечной точкой API и всем процессом отправки запросов.

Имя файла: 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__);
 }

Мы оборачиваем повторяющиеся процессы отправки POST-запросов и преобразования текстов в формат 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;
 }

Ниже представлена простая функция отправки сообщений в 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);
 }

Применение показано ниже.

Имя файла: 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!");
  }

Эта простая и мощная функция, она может использоваться для отправки сообщений разного типа, поскольку Discord использует текст в формате Markdown.

Пример сообщения в формате 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.

И это еще не самое крутое, что можно сделать с помощью текстового редактора Markdown, предоставляемого Discord. Давайте поделимся несколькими строками кода с 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"
                        "```");

Результат.

Это сделано для того, чтобы показать вам, насколько мощными являются текстовые редакторы Markdown.

Добавление эмодзи в ваши сообщения

Эмодзи весьма полезны в текстовых сообщениях: они добавляют элегантности, улучшают читаемость, не говоря уже о том, что привносят толику юмора.

В отличие от других коммуникационных платформ, где эмодзи создаются из определенных числовых кодов, Discord использует уникальный синтаксис для идентификации и отображения эмодзи непосредственно в текстовых сообщениях.

Все, что вам нужно сделать, это поместить название эмодзи между двумя двоеточиями — одно двоеточие в начале, а другое в конце названия эмодзи.

При вставке кодов этих эмодзи в текстовое сообщение они будут отображены как эмодзи в Discord.

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

Сообщение.

Существуют тысячи эмодзи и уследить за всеми ними довольно сложно. Вот шпаргалка —  https://github.com/ikatyang/emoji-cheat-sheet/blob/master/README.md.  Я создал простой файл библиотеки с некоторыми кодами эмодзи и синтаксисом. Не стесняйтесь добавлять больше кодов эмодзи по вашему желанию.

Имя файла: 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:"       

Отправка сообщения.

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

Результат сообщения.

Упоминание пользователей и ролей

Это простое сообщение может содержать упоминания и роли, которые имеют решающее значение для эффективной доставки сообщения.

  • @everyone — Это оповещает всех пользователей на канале, даже если они находятся офлайн.
  • @here — Это оповестит всех активных пользователей в чате. Все пользователи, находящиеся в сети.
  • <@user_id> — Уведомление отправляется определенному пользователю или пользователям.
  • <@&role_id> Это уведомляет всех пользователей, которым назначена определенная роль. Например, отправка торговых сигналов всем пользователям, которым назначена роль ручных трейдеров.

Пример использования.

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

Результат сообщения.


Идентификация бота Discord

Преимущество вебхука состоит в том, что он не ограничивается внешним видом по умолчанию (имя и аватар); вы можете изменить эти значения в любой момент, когда отправляете запрос на публикацию.

Эта возможность весьма удобна, поскольку вы можете иметь несколько торговых роботов, которые отправляют какую-либо информацию одному или двум сообществам, при этом все они будут использовать один вебхук. Возможность использовать разные идентификации помогает различать отправителей и полученные сообщения.

Мы можем сделать эту идентификацию обязательной при каждом вызове (инициализации) класса Discord, чтобы сделать идентификацию глобальной.

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);
 }

Мы объявляем эти переменные публично, поскольку пользователь может захотеть изменить их или получить к ним доступ в процессе (после инициализации класса).

Это еще больше упрощает отслеживание логов для каждого имени пользователя, назначенного этому мессенджеру 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__);
 }

Теперь нам необходимо добавить эту идентификационную информацию (имя и аватар) везде, где встречается строка JSON.

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);
 }

Внутри функции SendJSON() нам нужно сделать эту идентификацию необязательной, поскольку у пользователя может быть изначально другое мнение, поэтому он решил использовать функцию, которая принимает необработанную строку JSON, дающую ему контроль над тем, какую информацию он хочет отправить.

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);
 }

Давайте установим другую идентификацию для нашего бота.

#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);
  }

Результат.


Работа с эмбедами (embed) и файлами изображений

Несмотря на то, что при создании класса Discord в предыдущем разделе к сообщению было прикреплено изображение, существует надлежащий способ прикреплять файлы и встраивать элементы в сообщение.

Мы можем сделать это с помощью независимого JSON-запроса.

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

Результаты.

К сожалению, вы не можете отправлять изображение напрямую из MQL5 с помощью Web Request, несмотря на то, что Discord способен принимать файлы.  В настоящее время лучшее, что вы можете сделать, — это поделиться изображением по какой-либо ссылке, где файл размещен в сети.

В предыдущем примере мне пришлось использовать imgur.com .


Отправка торговых уведомлений из MetaTrader 5 в Discord

Трейдеры часто используют возможность отправки информации из MetaTrader 5 на внешние платформы для отправки обновлений о своей торговой деятельности. Давайте используем этого бота (советника) для такой задачи.

Начнем с уведомлений об открытии сделки.

01: Отправка уведомлений об открытии сделок

Для этой задачи удобна функция OnTradeTransaction.

Ниже приведены средства проверки состояния для проверки состояния недавней позиции и сделки.

#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
  //...     
 }

Примечание:

При форматировании рыночных значений, таких как цена входа, стоп-лосс, тейк-профит и т. д., я заключаю каждое из них в одну обратную кавычку (`). Это создает встроенный блок кода в Discord, который помогает визуально разделять цифры и упрощает их копирование пользователями.

Виды использования Discord.

  • Одинарная открывающая кавычка (`) для форматирования встроенного кода — отлично подходит для коротких значений, таких как цены.
  • Тройные обратные кавычки (```) для форматирования многострочных блоков кода — используются для более крупных блоков кода или сообщений.

Я открыл две противоположные сделки вручную, используя «Торговлю в один клик» (One-Click Trading) в MetaTrader 5, ниже представлен результат.

02: Отправка уведомлений об изменении сделок

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);      
  }

В этом сообщении все то же самое, что и при отправке торговых уведомлений, изменены только названия полей стоп-лосса и тейк-профита.

03: Отправка уведомлений о закрытии сделок

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);
  }

В сообщении о закрытии сделки мы добавляем эмодзи с поднятым вверх большим пальцем, если сделка закрыта с прибылью, и эмодзи с опущенным большим пальцем, если сделка закрыта с убытком. В отличие от открытия и изменения позиции, где у нас был номер тикета позиции. Закрытая позиция больше не является позицией — это сделка, поэтому для нее мы используем номер тикета сделки.

Эта сделка закрыта вручную, поэтому ее причиной становится Client (Desktop Terminal), это соответствует 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";
   }
}


Заключение

Вебхуки программы Discord являются хорошей начальной точкой для интеграции MetaTrader 5 и Discord, обеспечивая сообщение между этими двумя мощными платформами. Однако, как я уже говорил ранее, у всех API есть правила и ограничения — API вебхука Discord ничем не отличается, поскольку запросы вебхука ограничены по частоте.

  • Используя один вебхук можно отправлять только 5 сообщений для 2 каждые 2 секунды.
  • Для одного вебхук допускается не более 30 сообщений в минуту на канал.

При превышении этого ограничения Discord вернет HTTP 429 (Слишком много запросов). Чтобы решить эту проблему, вы можете добавить задержки или очереди в свой код MQL5.

Кроме того, на протяжении всей статьи я говорю о боте (ботах) Discord, но Webhook сильно отличается от бота Discord. Весь рабочий процесс между MQL5 и Discord я называю ботом.

По сути, боты — это программы, работающие в Discord и предлагающие широкий спектр функций: от базовых команд до сложной автоматизации. С другой стороны, вебхуки — это простые URL-адреса, которые позволяют внешним приложениям отправлять автоматические сообщения на определенный канал Discord.

Ниже приведена таблица различий между вебхуком программы Discord и Discord-ботом.


Вебхуки Боты
Функция
  • Могут отправлять сообщения только на заданный канал
  • Они могут только отправлять сообщения. Не могут просматривать их.
  • Могут отправлять до 10 эмбедов в одно сообщение.

  • Гораздо более гибкие, поскольку могут выполнять более сложные действия, похожие на те, что может делать обычный пользователь.
  • Боты могут просматривать и отправлять сообщения.
  • В одно сообщение допускается только один эмбед.
Возможность пользовательской настройки
  • Можно создать 10 вебхуков на сервер  с возможностью настройки каждого аватара и имени.
  • Возможность создания гиперссылки на любой текст за пределами эмбеда

  • Публичные боты часто имеют предустановленный аватар и имя, которые конечные пользователи не могут изменить.
  • Невозможно создать гиперссылку на текст в обычном сообщении, необходимо использовать эмбед.
Нагрузка и безопасность
  • Просто конечная точка для отправки данных, фактический хостинг не требуется.
  • Нет аутентификации, подтверждающей, что отправленные на вебхук данные получены из надежного источника.
  • Нет аутентификации, подтверждающей, что отправленные на вебхук данные получены из надежного источника. Если URL-адрес вебхука будет раскрыт, могут возникнуть только непостоянные проблемы (например, рассылка спама).
  • При необходимости URL-адрес вебхука можно легко изменить.

  • Боты должны размещаться в безопасной среде, которая должна постоянно поддерживаться в режиме онлайн, что требует дополнительных ресурсов.
  • Аутентификация ботов осуществляется с помощью токена; скомпрометированный токен может нанести серьезный ущерб из-за своих возможностей, если у них есть разрешения, предоставленные им владельцем сервера.
  • Однако при необходимости можно сбросить токен бота.

Бот Discord довольно сложен и не очень практичен для программистов MQL5, поскольку нам часто нужна функция, позволяющая оповещать наших коллег-трейдеров только о ходе торговли. Не говоря уже о том, что для создания бота Discord вам понадобится пара  модулей на Python.

Однако при желании вы можете разработать полноценный Discord-бот, работающий с MetaTrader 5.

Поскольку у нас есть пакет MetaTrader 5-Python, из среды Python вы можете добиться полной интеграции между этими двумя платформами.

Всем удачи!


Таблица вложений

Имя файла Описание и назначение
Include\discord emojis.mqh Содержит коды эмодзи и синтаксис, совместимый с Discord.
Include\discord.mqh Имеет класс CDiscord для отправки сообщений в формате JSON на платформу Discord.
Include\errordescription.mqh Содержит описания всех кодов ошибок, выдаваемых MetaTrader 5, на языке MQL5.
Include\jason.mqh Библиотека для сериализации и десериализации протокола JSON.
Include\logging.mqh  Библиотека для регистрации всей информации и ошибок, создаваемых классом CDiscord (отправитель вебхук). 
Experts\Discord EA.mq5  Советник (EA) для тестирования вебхука Discord и отправки торговых оповещений на назначенный URL-адрес вебхука Discord. 

Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/18550

Прикрепленные файлы |
Attachments.zip (19.26 KB)
Особенности написания Пользовательских Индикаторов Особенности написания Пользовательских Индикаторов
Написание пользовательских индикаторов в торговой системе MetaTrader 4
Нейросети в трейдинге: Распутывание структурных компонентов (Энкодер) Нейросети в трейдинге: Распутывание структурных компонентов (Энкодер)
Предлагаем познакомиться с продолжением реализации фреймворка SCNN, который сочетает в себе гибкость и интерпретируемость, позволяя точно выделять структурные компоненты временного ряда. В статье подробно раскрываются механизмы адаптивной нормализации и внимания, что обеспечивает устойчивость модели к изменяющимся рыночным условиям.
Особенности написания экспертов Особенности написания экспертов
Написание и тестирование экспертов в торговой системе MetaTrader 4.
Торгуем опционы без опционов (Часть 2): Использование в реальной торговле Торгуем опционы без опционов (Часть 2): Использование в реальной торговле
В статье рассматриваются простые опционные стратегии и их реализация на MQL5. Пишем базовый эксперт, который будет модернизироваться и усложняться.