MQL5 Discord WebRequest: Логирование Сделок Советника на VPS

MQL5 Discord WebRequest: Логирование Сделок Советника на VPS MQL4 / MQL5 (MetaTrader)
Подробная техническая инструкция по интеграции советника MQL5 с Discord для логирования сделок в реальном времени через WebRequest на VPS. Включает исходный код, настройку Discord Webhook и пошаговый запуск.
Суть: Интеграция советника MQL5 с Discord для автоматического логирования торговых операций осуществляется путем использования функции WebRequest для отправки JSON-сообщений на предварительно настроенный Discord Webhook URL. Это позволяет получать уведомления о сделках в реальном времени прямо в ваш Discord канал.

Исходный код

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


//+------------------------------------------------------------------+
//|                                       DiscordLogger_WebRequest.mq5 |
//|                                          Copyright 2023, FinFluct |
//|                                            https://finfluct.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2023, FinFluct"
#property link      "https://finfluct.com"
#property version   "1.00"
#property description "Интеграция MQL5 советника с Discord для логирования сделок через WebRequest."

//--- Входные параметры
input string Inp_DiscordWebhookURL = "YOUR_DISCORD_WEBHOOK_URL_HERE"; // URL Discord Webhook
input string Inp_DiscordUsername   = "MQL5 Trade Logger";             // Имя пользователя для сообщений в Discord
input string Inp_DiscordAvatarURL  = "";                              // URL аватара для сообщений (опционально)

//+------------------------------------------------------------------+
//| Global variables                                                 |
//+------------------------------------------------------------------+
string last_deal_ticket = ""; // Для отслеживания последней обработанной сделки

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
   // Проверка на корректность URL
   if (StringLen(Inp_DiscordWebhookURL) < 10 || StringFind(Inp_DiscordWebhookURL, "discord.com/api/webhooks") == -1)
     {
      Print("Ошибка: Некорректный URL Discord Webhook. Пожалуйста, проверьте настройки.");
      return INIT_FAILED;
     }
   Print("Discord Logger Initialized. Webhook URL: ", Inp_DiscordWebhookURL);
   SendDiscordMessage("Советник '" + MQL5InfoString(MQL5_PROGRAM_NAME) + "' запущен на " + Symbol() + " " + EnumToString(Period()) + ".");
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   SendDiscordMessage("Советник '" + MQL5InfoString(MQL5_PROGRAM_NAME) + "' остановлен на " + Symbol() + " " + EnumToString(Period()) + ". Причина: " + EnumToString((ENUM_REASON_PROGRAM)reason) + ".");
  }
//+------------------------------------------------------------------+
//| Trade event function                                             |
//+------------------------------------------------------------------+
void OnTrade()
  {
   // Проверяем новые сделки
   HistorySelect(0, TimeCurrent());
   int total_deals = HistoryDealsTotal();

   for (int i = total_deals - 1; i >= 0; i--)
     {
      ulong deal_ticket = HistoryDealGetTicket(i);
      if (deal_ticket == 0) continue;

      // Проверяем, была ли эта сделка уже обработана
      if (LongToString(deal_ticket) == last_deal_ticket) break; // Если нашли последнюю обработанную, выходим

      ENUM_DEAL_TYPE deal_type = (ENUM_DEAL_TYPE)HistoryDealGetInteger(deal_ticket, DEAL_TYPE);
      ENUM_DEAL_ENTRY deal_entry = (ENUM_DEAL_ENTRY)HistoryDealGetInteger(deal_ticket, DEAL_ENTRY);
      string deal_symbol = HistoryDealGetString(deal_ticket, DEAL_SYMBOL);
      double deal_volume = HistoryDealGetDouble(deal_ticket, DEAL_VOLUME);
      double deal_price = HistoryDealGetDouble(deal_ticket, DEAL_PRICE);
      long deal_order = HistoryDealGetInteger(deal_ticket, DEAL_ORDER);
      datetime deal_time = (datetime)HistoryDealGetInteger(deal_ticket, DEAL_TIME);

      // Фильтруем только сделки, относящиеся к текущему символу
      if (deal_symbol != Symbol()) continue;

      // Получаем информацию об ордере, чтобы узнать магик
      long order_magic = 0;
      if (HistoryOrderSelect(deal_order))
        {
         order_magic = HistoryOrderGetInteger(deal_order, ORDER_MAGIC);
        }
      else if (OrderSelect(deal_order, SELECT_BY_TICKET, MODE_TRADES)) // Если ордер еще активен
        {
         order_magic = OrderGetInteger(ORDER_MAGIC);
        }

      // Если магик ордера не совпадает с магиком советника, пропускаем
      if (order_magic != ExpertMagicNumber()) continue;

      string message = "";
      string deal_type_str = "";

      switch (deal_type)
        {
         case DEAL_TYPE_BUY:
           deal_type_str = "Покупка";
           break;
         case DEAL_TYPE_SELL:
           deal_type_str = "Продажа";
           break;
         case DEAL_TYPE_BALANCE:
           deal_type_str = "Баланс";
           break;
         case DEAL_TYPE_CREDIT:
           deal_type_str = "Кредит";
           break;
         case DEAL_TYPE_CHARGE:
           deal_type_str = "Комиссия";
           break;
         case DEAL_TYPE_CORRECTION:
           deal_type_str = "Коррекция";
           break;
         case DEAL_TYPE_COMMISSION:
           deal_type_str = "Комиссия";
           break;
         case DEAL_TYPE_DIVIDEND:
           deal_type_str = "Дивиденд";
           break;
         case DEAL_TYPE_TAX:
           deal_type_str = "Налог";
           break;
         case DEAL_TYPE_AGENT_COMMISSION:
           deal_type_str = "Агентская комиссия";
           break;
         default:
           deal_type_str = "Неизвестно";
           break;
        }

      string deal_entry_str = "";
      switch (deal_entry)
        {
         case DEAL_ENTRY_IN:
           deal_entry_str = "Вход";
           break;
         case DEAL_ENTRY_OUT:
           deal_entry_str = "Выход";
           break;
         case DEAL_ENTRY_INOUT:
           deal_entry_str = "Закрытие/Разворот";
           break;
         case DEAL_ENTRY_STATE:
           deal_entry_str = "Состояние";
           break;
         default:
           deal_entry_str = "Неизвестно";
           break;
        }

      message = StringFormat(
         "**%s** | **%s** | `%s` | Объем: `%.2f` | Цена: `%.5f` | Тикет: `%s` | Время: `%s`",
         deal_entry_str,
         deal_type_str,
         deal_symbol,
         deal_volume,
         deal_price,
         LongToString(deal_ticket),
         TimeToString(deal_time, TIME_DATE|TIME_SECONDS)
      );

      SendDiscordMessage(message);
      last_deal_ticket = LongToString(deal_ticket); // Обновляем последний обработанный тикет
     }
  }

//+------------------------------------------------------------------+
//| Function to send message to Discord                              |
//+------------------------------------------------------------------+
void SendDiscordMessage(string message)
  {
   string headers = "Content-Type: application/json";
   string data = "{\"content\":\"" + message + "\", \"username\":\"" + Inp_DiscordUsername + "\"";

   if (StringLen(Inp_DiscordAvatarURL) > 0)
     {
      data += ", \"avatar_url\":\"" + Inp_DiscordAvatarURL + "\"";
     }
   data += "}";

   char post_data[];
   StringToCharArray(data, post_data);

   char result_headers[];
   char result_data[];
   int  res_code;

   // Домен для WebRequest должен быть добавлен в список разрешенных в настройках терминала (Tools -> Options -> Expert Advisors)
   string domain = StringSubstr(Inp_DiscordWebhookURL, StringFind(Inp_DiscordWebhookURL, "://") + 3);
   domain = StringSubstr(domain, 0, StringFind(domain, "/"));

   int timeout = 5000; // 5 секунд таймаут

   int handle = WebRequest("POST", Inp_DiscordWebhookURL, headers, timeout, post_data, result_headers, result_data);

   if (handle == -1)
     {
      Print("WebRequest failed. Error: ", GetLastError());
     }
   else
     {
      // В MQL5 WebRequest возвращает HTTP статус в первых 4 байтах result_headers
      res_code = CharArrayToInt(result_headers, 0); 
      string response_body = CharArrayToString(result_data);
      if (res_code >= 200 && res_code < 300)
        {
         Print("Сообщение успешно отправлено в Discord. Статус: ", res_code);
        }
      else
        {
         Print("Ошибка отправки сообщения в Discord. Статус: ", res_code, ", Ответ: ", response_body);
        }
     }
  }

Разбор параметров

  • Inp_DiscordWebhookURL: Это критически важный параметр. Сюда необходимо вставить URL вашего Discord Webhook, который вы создадите в настройках канала Discord. Пример: https://discord.com/api/webhooks/123456789012345678/abcdefghijklmnopqrstuvwxyz1234567890abcdefghijklmnopqrstuvwxyz1234567890.
  • Inp_DiscordUsername: Имя, которое будет отображаться в Discord рядом с сообщениями от вашего советника. По умолчанию: MQL5 Trade Logger.
  • Inp_DiscordAvatarURL: (Опционально) URL изображения, которое будет использоваться в качестве аватара для сообщений от советника. Если оставить пустым, будет использоваться стандартный аватар Webhook.

Как запустить

Для успешной интеграции и запуска советника выполните следующие шаги:

  1. Создайте Discord Webhook:
    • Откройте ваш Discord сервер и перейдите в настройки нужного канала (щелкните правой кнопкой мыши по каналу -> 'Редактировать канал' -> 'Интеграции').
    • Нажмите 'Вебхуки' -> 'Новый вебхук'.
    • Дайте ему имя (например, 'MQL5 Logger'), выберите канал, в который будут приходить сообщения, и при желании загрузите изображение.
    • Скопируйте 'URL вебхука'. Это значение вам понадобится для параметра Inp_DiscordWebhookURL в советнике.
  2. Настройте MetaTrader 5 для WebRequest:
    • В MetaTrader 5 перейдите в 'Сервис' -> 'Настройки' (или нажмите Ctrl+O).
    • Перейдите на вкладку 'Советники'.
    • Установите флажок 'Разрешить WebRequest для перечисленных URL'.
    • В поле ниже добавьте домен discord.com. Это необходимо, чтобы MQL5 мог отправлять запросы на серверы Discord.
  3. Установите и настройте советник:
    • Скопируйте предоставленный MQL5 код в новый файл в MetaEditor (Файл -> Создать -> Советник (Шаблон) -> Далее -> Имя: DiscordLogger_WebRequest.mq5 -> Готово).
    • Сохраните и скомпилируйте код (кнопка 'Компилировать' или F7).
    • Перетащите скомпилированный советник (DiscordLogger_WebRequest.ex5) из 'Навигатора' на график торгового инструмента, на котором вы хотите отслеживать сделки.
    • В окне настроек советника (вкладка 'Входные параметры') вставьте скопированный URL вебхука в поле Inp_DiscordWebhookURL. При желании измените имя пользователя и аватар.
    • Убедитесь, что на вкладке 'Общие' установлен флажок 'Разрешить алгоритмическую торговлю'.
    • Нажмите 'ОК'.
  4. Запуск на VPS:

    Для круглосуточного логирования и стабильной работы крайне рекомендуется размещать MetaTrader 5 с советником на виртуальном частном сервере (VPS). Это обеспечит бесперебойную работу терминала и, соответственно, непрерывное логирование сделок в Discord, независимо от состояния вашего локального компьютера.

    Помимо логирования, вы можете использовать WebRequest для интеграции с другими сервисами, например, для получения данных из экономического календаря или для отправки сигналов, которые могут быть дополнительно обработаны, например, с использованием методов K-Means для фильтрации ложных сигналов.

Теперь все сделки, совершенные вашим советником на этом графике, будут автоматически отправляться в ваш Discord канал, предоставляя вам удобный способ мониторинга торговой активности в реальном времени.

Оцените статью
FinFluct