English 中文 Español Deutsch 日本語 Português
preview
MQL5-советник, интегрированный в Telegram (Часть 4): Модуляризация функций кода для улучшенного повторного использования

MQL5-советник, интегрированный в Telegram (Часть 4): Модуляризация функций кода для улучшенного повторного использования

MetaTrader 5Торговые системы |
470 4
Allan Munene Mutiiria
Allan Munene Mutiiria

Введение

В предыдущей статье серии мы рассмотрели отправку скриншотов графиков с подписями из MetaTrader 5 в Telegram. Наш подход, хотя и эффективный, был довольно прямолинейным и не слишком гибким. Мы объединили компоненты, необходимые для захвата снимка экрана, конвертировали или кодировали его в удобную для отправки форму и отправили его в Telegram. Это сработало, но привело к большому объему повторяющегося кода, с которым сложно управляться. Что мы можем сделать, чтобы улучшить реализацию? Перейти к модульной базе кодов! Это первый шаг на пути к более гибкой и удобной в обслуживании системе.

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

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

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

  1. Понимание необходимости модуляризации
  2. Рефакторинг кода отправки сообщений
  3. Модуляризация функций скриншотов
  4. Тестирование и реализация модульных функций
  5. Заключение

Результатом должен стать аккуратный и эффективный советник MetaQuotes Language 5 (MQL5)-Telegram. Модульный и хорошо структурированный код обеспечивает легкую интеграцию, отличается высокой гибкостью и требует гораздо меньше усилий для обновления и добавления новых функций, чем если бы код был написан обычным способом. Он не только обрабатывает задачи отправки сообщений, но и выполняет функции захвата снимков экрана и хранения данных, делая это управляемым и масштабируемым образом и тем самым закладывая прочную основу для будущих улучшений.


Понимание необходимости модуляризации

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

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

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

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


Рефакторинг кода отправки сообщений

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

//+------------------------------------------------------------------+
//|    FUNCTION TO SEND SIMPLE MESSAGE                               |
//+------------------------------------------------------------------+

void sendSimpleMessage(){

//...

}

Здесь мы инкапсулируем отправку Telegram-сообщения в функцию под названием sendSimpleMessage. Эта модульная структура упрощает обслуживание, управление и повторное использование кода в советнике. Мы используем функцию void, которая не возвращает значение. Вместо этого она отправляет сообщение в Telegram. Функция также способна незаметно обрабатывать успех и неудачу операции, поэтому код не становится слишком запутанным из-за всевозможных операторов if. Такая инкапсуляция позволяет основной программе вызывать функцию, когда ей нужно отправить сообщение, не увязая в деталях того, как это сделать с помощью Telegram API.

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

void sendSimpleMessage(string custom_message,const string api_url,
                       const string bot_token,const string chat_id,
                       int timeout=10000){

//...

}

Здесь мы определяем void-функцию sendSimpleMessage, которая отправляет простое сообщение в Telegram без сложных вложений или обработки данных. Затем она заполняется четырьмя обязательными входными параметрами custom_message, api_url, bot_token и chat_id, а также одним необязательным входным параметром timeout. Давайте структурируем параметры для более легкого понимания.

  • custom_message - строковый параметр, содержащий фактическое текстовое сообщение, которое мы хотим отправить в Telegram.
  • api_url - строковый параметр, содержащий базовый URL-адрес API Telegram-бота. Этот URL-адрес используется для запроса правильной конечной точки API.
  • bot_token - еще один строковый параметр, содержащий уникальный токен бота, необходимый для аутентификации бота и его авторизации для отправки сообщений.
  • chat_id - строковый параметр, указывающий уникальный идентификатор Telegram-чата или канала, куда будет отправлено сообщение.
  • timeout - необязательный параметр типа int, который задает время (в миллисекундах), в течение которого функция должна ожидать ответа от API Telegram, прежде чем считать запрос истекшим по времени. Значение по умолчанию составляет 10 000 миллисекунд (10 секунд), но при необходимости пользователь может указать собственное значение.

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

   char data[];  // Array to hold data to be sent in the web request (empty in this case)
   char res[];  // Array to hold the response data from the web request
   string resHeaders;  // String to hold the response headers from the web request

   string message = custom_message;
   
   const string url = api_url + "/bot" + bot_token + "/sendmessage?chat_id=" + chat_id +
      "&text=" + message;
   
   // Send the web request to the Telegram API
   int send_res = WebRequest("POST", url, "", timeout, data, res, resHeaders);

Начнем с объявления пары массивов: data и res. Массив data пуст, поскольку мы не отправляем никому данные в нашем веб-запросе — мы отправляем только сообщение в качестве параметра URL. Массив res будет содержать данные ответа от сервера после того, как мы сделаем запрос. Также объявляем строку resHeaders, которая будет содержать все заголовки ответов, отправленные Telegram API.

Далее мы берем custom_message из входного параметра и присваиваем его переменной message. По сути, это дает нам возможность работать с сообщением или передавать его внутри функции, если нам это необходимо.

Мы создаем URL-адрес API-запроса, объединяя несколько компонентов: базовый api_url, конечную точку /bot, аутентификационный bot_token и chat_id чата получателя. К этому мы добавляем текст сообщения как URL-параметр: "&text=". Результатом является полный URL-адрес, содержащий все необходимые данные для вызова API.

Наконец, мы передаем логику веб-запроса функции WebRequest. Функция отвечает за отправку HTTP-запроса POST в API Telegram. Она использует URL-адрес, который мы только что создали для API. Значение тайм-аута запроса, по умолчанию равное 10 секундам (или другому значению, указанному пользователем), определяет, как долго запрос будет ожидать ответа. Запрос отправляется с пустым массивом данных (который также может быть просто пустым объектом в формате JavaScript Object Notation (JSON)), и любой ответ, который API отправляет нам обратно, сохраняется в массиве результатов и строке заголовков результатов.

Наконец, мы просто добавляем логику проверки статуса ответа веб-запроса.

   // Check the response status of the web request
   if (send_res == 200) {
      // If the response status is 200 (OK), print a success message
      Print("TELEGRAM MESSAGE SENT SUCCESSFULLY");
   }
   else if (send_res == -1) {
      // If the response status is -1 (error), check the specific error code
      if (GetLastError() == 4014) {
         // If the error code is 4014, it means the Telegram API URL is not allowed in the terminal
         Print("PLEASE ADD THE ", api_url, " TO THE TERMINAL");
      }
      // Print a general error message if the request fails
      Print("UNABLE TO SEND THE TELEGRAM MESSAGE");
   }
   else if (send_res != 200) {
      // If the response status is not 200 or -1, print the unexpected response code and error code
      Print("UNEXPECTED RESPONSE ", send_res, " ERR CODE = ", GetLastError());
   }

Окончательный код функции для отправки простого сообщения из MetaTrader 5 в Telegram выглядит следующим образом:

//+------------------------------------------------------------------+
//|    FUNCTION TO SEND SIMPLE MESSAGE                               |
//+------------------------------------------------------------------+

void sendSimpleMessage(string custom_message,const string api_url,
                       const string bot_token,const string chat_id,
                       int timeout=10000){
   
   char data[];  // Array to hold data to be sent in the web request (empty in this case)
   char res[];  // Array to hold the response data from the web request
   string resHeaders;  // String to hold the response headers from the web request

   string message = custom_message;
   
   const string url = api_url + "/bot" + bot_token + "/sendmessage?chat_id=" + chat_id +
      "&text=" + message;
   
   // Send the web request to the Telegram API
   int send_res = WebRequest("POST", url, "", timeout, data, res, resHeaders);
   
   // Check the response status of the web request
   if (send_res == 200) {
      // If the response status is 200 (OK), print a success message
      Print("TELEGRAM MESSAGE SENT SUCCESSFULLY");
   }
   else if (send_res == -1) {
      // If the response status is -1 (error), check the specific error code
      if (GetLastError() == 4014) {
         // If the error code is 4014, it means the Telegram API URL is not allowed in the terminal
         Print("PLEASE ADD THE ", api_url, " TO THE TERMINAL");
      }
      // Print a general error message if the request fails
      Print("UNABLE TO SEND THE TELEGRAM MESSAGE");
   }
   else if (send_res != 200) {
      // If the response status is not 200 or -1, print the unexpected response code and error code
      Print("UNEXPECTED RESPONSE ", send_res, " ERR CODE = ", GetLastError());
   }
   
}

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

   string msg = "EA INITIALIZED ON CHART " + _Symbol;  // Message to send, including the chart symbol
   sendSimpleMessage(msg,TG_API_URL,botTkn,chatID,10000);

Здесь мы вызываем функцию sendSimpleMessage для доставки сообщения в чат Telegram. Сначала мы создаем строку (msg), которая представляет собой простое объединение слов "EA INITIALIZED ON CHART" (советник инициализирован на графике) с символом текущего графика (_Symbol). Будущий получатель этого сообщения будет уведомлен о том, что советник был инициализирован на определенном графике.

После того, как мы определили текстовое сообщение, которое хотим отправить, мы вызываем функцию sendSimpleMessage. Мы передаем четыре аргумента функции. Первый аргумент — это просто текстовое сообщение, которое мы хотим отправить, поэтому оно называется msg. Второй аргумент — это константа с именем TG_API_URL, которая является базовым URL для API Telegram-бота. Третий аргумент — токен доступа бота (botTkn), а четвертый (chatID) — идентификатор Telegram-чата или канала, куда бот отправит сообщение. Наконец, мы указываем значение тайм-аута 10 секунд (10 000 миллисекунд). Если сервер Telegram не отвечает в течение этого времени, функция возвращает код ошибки. При тестировании получаем следующее сообщение:

Простое TELEGRAM-сообщение

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

   string new_msg = "THE CURRENT TIMEFRAME IS "+EnumToString(_Period);
   sendSimpleMessage(new_msg,TG_API_URL,botTkn,chatID,10000);

Здесь мы определяем новую строковую переменную с именем new_msg. Новая переменная создается путем слияния текста "THE CURRENT TIMEFRAME IS " (текущий таймфрейм) со строковой версией значения _Period (таймфрейм текущего графика). Это делается с помощью функции EnumToString, которая переводит значение _Period в удобочитаемую форму. Например, если график настроен на 1-часовой таймфрейм, new_msg будет содержать текст "THE CURRENT TIMEFRAME IS PERIOD_H1" (текущий таймфрейм - H1). После этого вызывается та же функция для отправки простых сообщений, и на этом всё. Как видите, всё очень просто. После запуска теста мы получаем следующий результат:

Новое простое сообщение

Мы видим, насколько легко было отправить код. Мы использовали всего две строки кода. Далее мы переходим к отправке сложного закодированного сообщения. Больших изменений в функции не будет. Она наследует ту же логику. Однако поскольку мы отправляем сложные сообщения, мы хотим обрабатывать ошибки, которые могут возникнуть. Таким образом, вместо того, чтобы просто объявить функцию void, у нас будет функция типа целочисленных данных, которая будет возвращать определенные коды, иллюстрирующие неудачу или успех при вызове функции. Таким образом, в глобальном масштабе нам придется определить коды ошибок.

#define FAILED_CODE -1
#define SUCCEEDED_CODE +1

Здесь мы определяем две константы, FAILED_CODE и SUCCEEDED_CODE, используя директиву препроцессора #define. Мы назначаем целочисленные значения в зависимости от констант для представления результатов операций: FAILED_CODE устанавливается равным -1 и означает неудачу, а SUCCEEDED_CODE равно +1 и означает успех. Эти константы могут быть любыми, какие вы сочтете подходящими. После их объявления мы приступаем к построению нашей функции.

int sendEncodedMessage(string custom_message,const string api_url,
                       const string bot_token,const string chat_id,
                       int timeout=10000){

//...

}

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

   // Check the response status of the web request
   if (send_res == 200) {
      // If the response status is 200 (OK), print a success message
      Print("TELEGRAM MESSAGE SENT SUCCESSFULLY");
      return (SUCCEEDED_CODE);
   }

Здесь, если статус ответа успешный и сообщение было успешно отправлено, мы возвращаем SUCCEEDED_CODE. В противном случае, если статус ответа — неудача, мы возвращаем FAILED_CODE.

   else if (send_res == -1) {
      // If the response status is -1 (error), check the specific error code
      if (GetLastError() == 4014) {
         // If the error code is 4014, it means the Telegram API URL is not allowed in the terminal
         Print("PLEASE ADD THE ", api_url, " TO THE TERMINAL");
      }
      // Print a general error message if the request fails
      Print("UNABLE TO SEND THE TELEGRAM MESSAGE");
      return (FAILED_CODE);
   }
   else if (send_res != 200) {
      // If the response status is not 200 or -1, print the unexpected response code and error code
      Print("UNEXPECTED RESPONSE ", send_res, " ERR CODE = ", GetLastError());
      return (FAILED_CODE);
   }

Наконец, нам нужно вернуть успешный код, если до этого момента все прошло успешно, следующим образом:

   return (SUCCEEDED_CODE);

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

Сообщение об ошибке функции возврата

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

//+------------------------------------------------------------------+
//|    FUNCTION TO SEND ENCODED MESSAGE                              |
//+------------------------------------------------------------------+

//#define FAILED_CODE -1
//#define SUCCEEDED_CODE +1

int sendEncodedMessage(string custom_message,const string api_url,
                       const string bot_token,const string chat_id,
                       int timeout=10000){
   char data[];  // Array to hold data to be sent in the web request (empty in this case)
   char res[];  // Array to hold the response data from the web request
   string resHeaders;  // String to hold the response headers from the web request

   string message = custom_message;
   
   const string url = api_url + "/bot" + bot_token + "/sendmessage?chat_id=" + chat_id +
      "&text=" + message;
   
   // Send the web request to the Telegram API
   int send_res = WebRequest("POST", url, "", timeout, data, res, resHeaders);
   
   // Check the response status of the web request
   if (send_res == 200) {
      // If the response status is 200 (OK), print a success message
      Print("TELEGRAM MESSAGE SENT SUCCESSFULLY");
      return (SUCCEEDED_CODE);
   }
   else if (send_res == -1) {
      // If the response status is -1 (error), check the specific error code
      if (GetLastError() == 4014) {
         // If the error code is 4014, it means the Telegram API URL is not allowed in the terminal
         Print("PLEASE ADD THE ", api_url, " TO THE TERMINAL");
      }
      // Print a general error message if the request fails
      Print("UNABLE TO SEND THE TELEGRAM MESSAGE");
      return (FAILED_CODE);
   }
   else if (send_res != 200) {
      // If the response status is not 200 or -1, print the unexpected response code and error code
      Print("UNEXPECTED RESPONSE ", send_res, " ERR CODE = ", GetLastError());
      return (FAILED_CODE);
   }
   
   return (SUCCEEDED_CODE);
}

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

   ////--- Account Status Update:
   double accountEquity = AccountInfoDouble(ACCOUNT_EQUITY);
   double accountFreeMargin = AccountInfoDouble(ACCOUNT_MARGIN_FREE);
   string complex_msg = "\xF680 EA INITIALIZED ON CHART " + _Symbol + "\xF680"
                +"\n\xF4CA Account Status \xF4CA"
                +"\nEquity: $"
                +DoubleToString(accountEquity,2)
                +"\nFree Margin: $"
                +DoubleToString(accountFreeMargin,2);
   
   string encloded_msg = UrlEncode(complex_msg);
   complex_msg = encloded_msg;
   sendEncodedMessage(complex_msg,TG_API_URL,botTkn,chatID,10000);


Начнем с доступа к балансу счета и свободной марже, используя AccountInfoDouble и передавая аргументы ACCOUNT_EQUITY и ACCOUNT_MARGIN_FREE соответственно. Они считывают данные о состоянии счета и свободной марже в реальном времени. Мы создаем подробное сообщение complex_msg, в котором используем символ графика, а также остаток на счете и свободную маржу, и форматируем его с помощью эмодзи. Мы отправляем сообщение с помощью API Telegram, но сначала нам нужно убедиться, что его передача по HTTP безопасна. Мы делаем это, кодируя сообщение с помощью функции UrlEncode. Вот что мы получаем в Telegram после отправки сообщения.

Сложное сообщение

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


Модуляризация функций скриншотов

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

int sendScreenShot(string screenshot_name,const string telegram_url,
                   const string bot_token,const string chat_id,
                   string caption=""){

//...

}

Объявим целочисленную функцию sendScreenShot, которая будет возвращать целочисленное значение. Функция принимает несколько параметров, обеспечивая гибкость и модульность.

  • Параметр screenshot_name относится к имени файла скриншота, который будет отправлен, что позволяет нам указывать разные скриншоты.
  • telegram_url, bot_token и chat_id — основные входные данные, необходимые для взаимодействия с API Telegram, что позволяет адаптировать функцию к различным конфигурациям ботов и Telegram-аккаунтам.
  • Необязательный параметр caption позволяет нам добавлять к скриншоту подпись.

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

   int screenshot_Handle = INVALID_HANDLE;
   screenshot_Handle = FileOpen(screenshot_name,FILE_READ|FILE_BIN);
   if(screenshot_Handle == INVALID_HANDLE){
      Print("INVALID SCREENSHOT HANDLE. REVERTING NOW!");
      return(FAILED_CODE);
   }

Здесь, если нам не удается открыть данные скриншота для отправки в Telegram, мы возвращаем FAILED_CODE из функции, сигнализируя о том, что операция по отправке скриншота не может быть продолжена из-за проблемы с доступом к файлу. Далее мы просто наследуем ту же логику для проверки статуса ответа и соответствующих сообщений и кодов статуса, как показано ниже:

   // Send the web request to the Telegram API
   int send_res = WebRequest("POST",URL,HEADERS,10000, DATA, res, resHeaders);

   // Check the response status of the web request
   if (send_res == 200) {
      // If the response status is 200 (OK), print a success message
      Print("TELEGRAM SCREENSHOT FILE SENT SUCCESSFULLY");
      return (SUCCEEDED_CODE);
   }
   else if (send_res == -1) {
      // If the response status is -1 (error), check the specific error code
      if (GetLastError() == 4014) {
         // If the error code is 4014, it means the Telegram API URL is not allowed in the terminal
         Print("PLEASE ADD THE ", telegram_url, " TO THE TERMINAL");
      }
      // Print a general error message if the request fails
      Print("UNABLE TO SEND THE TELEGRAM SCREENSHOT FILE");
      return (FAILED_CODE);
   }
   else if (send_res != 200) {
      // If the response status is not 200 or -1, print the unexpected response code and error code
      Print("UNEXPECTED RESPONSE ", send_res, " ERR CODE = ", GetLastError());
      return (FAILED_CODE);
   }
   return (SUCCEEDED_CODE);

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

//+------------------------------------------------------------------+
//|    FUNCTION TO SEND CHART SCREENSHOT FILES                       |
//+------------------------------------------------------------------+

int sendScreenShot(string screenshot_name,const string telegram_url,
                   const string bot_token,const string chat_id,
                   string caption=""){
   
   int screenshot_Handle = INVALID_HANDLE;
   screenshot_Handle = FileOpen(screenshot_name,FILE_READ|FILE_BIN);
   if(screenshot_Handle == INVALID_HANDLE){
      Print("INVALID SCREENSHOT HANDLE. REVERTING NOW!");
      return(FAILED_CODE);
   }
   
   else if (screenshot_Handle != INVALID_HANDLE){
      Print("SCREENSHOT WAS SAVED & OPENED SUCCESSFULLY FOR READING.");
      Print("HANDLE ID = ",screenshot_Handle,". IT IS NOW READY FOR ENCODING.");
   }
   
   int screenshot_Handle_Size = (int)FileSize(screenshot_Handle);
   if (screenshot_Handle_Size > 0){
      Print("CHART SCREENSHOT FILE SIZE = ",screenshot_Handle_Size);
   }
   uchar photoArr_Data[];
   ArrayResize(photoArr_Data,screenshot_Handle_Size);
   FileReadArray(screenshot_Handle,photoArr_Data,0,screenshot_Handle_Size);
   if (ArraySize(photoArr_Data) > 0){
      Print("READ SCREENSHOT FILE DATA SIZE = ",ArraySize(photoArr_Data));
   }
   FileClose(screenshot_Handle);
   
   //ArrayPrint(photoArr_Data);
   
   //--- create boundary: (data -> base64 -> 1024 bytes -> md5)
   //Encodes the photo data into base64 format
   //This is part of preparing the data for transmission over HTTP.
   uchar base64[];
   uchar key[];
   CryptEncode(CRYPT_BASE64,photoArr_Data,key,base64);
   if (ArraySize(base64) > 0){
      Print("Transformed BASE-64 data = ",ArraySize(base64));
      //Print("The whole data is as below:");
      //ArrayPrint(base64);
   }
   
   //Copy the first 1024 bytes of the base64-encoded data into a temporary array
   uchar temporaryArr[1024]= {0};
   //Print("FILLED TEMPORARY ARRAY WITH ZERO (0) IS AS BELOW:");
   //ArrayPrint(temporaryArr);
   ArrayCopy(temporaryArr,base64,0,0,1024);
   //Print("FIRST 1024 BYTES OF THE ENCODED DATA IS AS FOLLOWS:");
   //ArrayPrint(temporaryArr);
      
   //Create an MD5 hash of the temporary array
   //This hash will be used as part of the boundary in the multipart/form-data
   uchar md5[];
   CryptEncode(CRYPT_HASH_MD5,temporaryArr,key,md5);
   if (ArraySize(md5) > 0){
      Print("SIZE OF MD5 HASH OF TEMPORARY ARRAY = ",ArraySize(md5));
      Print("MD5 HASH boundary in multipart/form-data is as follows:");
      ArrayPrint(md5);
   }

   //Format MD5 hash as a hexadecimal string &
   //truncate it to 16 characters to create the boundary.
   string HexaDecimal_Hash=NULL;//Used to store the hexadecimal representation of MD5 hash
   int total=ArraySize(md5);
   for(int i=0; i<total; i++){
      HexaDecimal_Hash+=StringFormat("%02X",md5[i]);
   }
   Print("Formatted MD5 Hash String is: \n",HexaDecimal_Hash);
   HexaDecimal_Hash=StringSubstr(HexaDecimal_Hash,0,16);//truncate HexaDecimal_Hash string to its first 16 characters
   //done to comply with a specific length requirement for the boundary
   //in the multipart/form-data of the HTTP request.
   Print("Final Truncated (16 characters) MD5 Hash String is: \n",HexaDecimal_Hash);
   
   //--- WebRequest
   char DATA[];
   string URL = NULL;
   URL = telegram_url+"/bot"+bot_token+"/sendPhoto";
   //--- add chart_id
   //Append a carriage return and newline character sequence to the DATA array.
   //In the context of HTTP, \r\n is used to denote the end of a line
   //and is often required to separate different parts of an HTTP request.
   ArrayAdd(DATA,"\r\n");
   //Append a boundary marker to the DATA array.
   //Typically, the boundary marker is composed of two hyphens (--)
   //followed by a unique hash string and then a newline sequence.
   //In multipart/form-data requests, boundaries are used to separate
   //different pieces of data.
   ArrayAdd(DATA,"--"+HexaDecimal_Hash+"\r\n");
   //Add a Content-Disposition header for a form-data part named chat_id.
   //The Content-Disposition header is used to indicate that the following data
   //is a form field with the name chat_id.
   ArrayAdd(DATA,"Content-Disposition: form-data; name=\"chat_id\"\r\n");
   //Again, append a newline sequence to the DATA array to end the header section
   //before the value of the chat_id is added.
   ArrayAdd(DATA,"\r\n");
   //Append the actual chat ID value to the DATA array.
   ArrayAdd(DATA,chat_id);
   //Finally, Append another newline sequence to the DATA array to signify
   //the end of the chat_id form-data part.
   ArrayAdd(DATA,"\r\n");

   // EXAMPLE OF USING CONVERSIONS
   //uchar array[] = { 72, 101, 108, 108, 111, 0 }; // "Hello" in ASCII
   //string output = CharArrayToString(array,0,WHOLE_ARRAY,CP_ACP);
   //Print("EXAMPLE OUTPUT OF CONVERSION = ",output); // Hello
   
   Print("CHAT ID DATA:");
   ArrayPrint(DATA);
   string chatID_Data = CharArrayToString(DATA,0,WHOLE_ARRAY,CP_UTF8);
   Print("SIMPLE CHAT ID DATA IS AS FOLLOWS:",chatID_Data);   


   //--- Caption
   string CAPTION_STRING = NULL;
   CAPTION_STRING = caption;
   if(StringLen(CAPTION_STRING) > 0){
      ArrayAdd(DATA,"--"+HexaDecimal_Hash+"\r\n");
      ArrayAdd(DATA,"Content-Disposition: form-data; name=\"caption\"\r\n");
      ArrayAdd(DATA,"\r\n");
      ArrayAdd(DATA,CAPTION_STRING);
      ArrayAdd(DATA,"\r\n");
   }
   //---
   
   ArrayAdd(DATA,"--"+HexaDecimal_Hash+"\r\n");
   ArrayAdd(DATA,"Content-Disposition: form-data; name=\"photo\"; filename=\"Upload_ScreenShot.jpg\"\r\n");
   ArrayAdd(DATA,"\r\n");
   ArrayAdd(DATA,photoArr_Data);
   ArrayAdd(DATA,"\r\n");
   ArrayAdd(DATA,"--"+HexaDecimal_Hash+"--\r\n");
   
   Print("FINAL FULL PHOTO DATA BEING SENT:");
   ArrayPrint(DATA);
   string final_Simple_Data = CharArrayToString(DATA,0,WHOLE_ARRAY,CP_ACP);
   Print("FINAL FULL SIMPLE PHOTO DATA BEING SENT:",final_Simple_Data);

   string HEADERS = NULL;
   HEADERS = "Content-Type: multipart/form-data; boundary="+HexaDecimal_Hash+"\r\n";
   
   Print("SCREENSHOT SENDING HAS BEEN INITIATED SUCCESSFULLY.");
   
   //char data[];  // Array to hold data to be sent in the web request (empty in this case)
   char res[];  // Array to hold the response data from the web request
   string resHeaders;  // String to hold the response headers from the web request
   //string msg = "EA INITIALIZED ON CHART " + _Symbol;  // Message to send, including the chart symbol
   
   //const string url = TG_API_URL + "/bot" + botTkn + "/sendmessage?chat_id=" + chatID +
   //   "&text=" + msg;
      
   // Send the web request to the Telegram API
   int send_res = WebRequest("POST",URL,HEADERS,10000, DATA, res, resHeaders);

   // Check the response status of the web request
   if (send_res == 200) {
      // If the response status is 200 (OK), print a success message
      Print("TELEGRAM SCREENSHOT FILE SENT SUCCESSFULLY");
      return (SUCCEEDED_CODE);
   }
   else if (send_res == -1) {
      // If the response status is -1 (error), check the specific error code
      if (GetLastError() == 4014) {
         // If the error code is 4014, it means the Telegram API URL is not allowed in the terminal
         Print("PLEASE ADD THE ", telegram_url, " TO THE TERMINAL");
      }
      // Print a general error message if the request fails
      Print("UNABLE TO SEND THE TELEGRAM SCREENSHOT FILE");
      return (FAILED_CODE);
   }
   else if (send_res != 200) {
      // If the response status is not 200 or -1, print the unexpected response code and error code
      Print("UNEXPECTED RESPONSE ", send_res, " ERR CODE = ", GetLastError());
      return (FAILED_CODE);
   }
   return (SUCCEEDED_CODE);
}

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

//+------------------------------------------------------------------+
//|    FUNCTION TO GET SCREENSHOT OF CURRENT CHART                   |
//+------------------------------------------------------------------+

int getScreenshot_of_Current_Chart(string screenshot_name){
   
   //--- First delete an instance of the screenshot file if it already exists
   if(FileIsExist(screenshot_name)){
      FileDelete(screenshot_name);
      Print("Chart Screenshot was found and deleted.");
      ChartRedraw(0);
   }
   
   ChartScreenShot(0,screenshot_name,1366,768,ALIGN_RIGHT);
   
   // Wait for 30 secs to save screenshot if not yet saved
   int wait_loops = 60;
   while(!FileIsExist(screenshot_name) && --wait_loops > 0){
      Sleep(500);
   }
   
   if(!FileIsExist(screenshot_name)){
      Print("THE SPECIFIED SCREENSHOT DOES NOT EXIST (WAS NOT SAVED). REVERTING NOW!");
      return (FAILED_CODE);
   }
   else if(FileIsExist(screenshot_name)){
      Print("THE CHART SCREENSHOT WAS SAVED SUCCESSFULLY TO THE DATA-BASE.");
      return (SUCCEEDED_CODE);
   }
   return (SUCCEEDED_CODE);
}

Здесь мы объявляем целочисленную функцию getScreenshot_of_Current_Chart для захвата и сохранения снимка экрана текущего графика в MetaTrader 5. Функция принимает один параметр screenshot_name, который содержит желаемое имя файла, в который будет сохранен снимок экрана. Мы начинаем функцию с проверки того, существует ли уже файл с именем screenshot_name. Если существуем, удаляем существующий файл. Этот шаг имеет решающее значение, поскольку, если бы мы не удалили существующий файл, у нас неизбежно возникла бы проблема перезаписи — ситуация, когда сохраненный снимок экрана оказался бы с тем же именем, что и файл, который был недавно удален.

Мы также вызываем функцию ChartRedraw, чтобы обновить отображение графика перед тем, как сделать снимок экрана. Затем, чтобы получить снимок экрана графика в его текущем состоянии, мы вызываем функцию ChartScreenShot, сообщая ему имя, которое мы хотим дать файлу, желаемые размеры и желаемое выравнивание. После этого нам пришлось использовать цикл while для проверки существования файла. Мы ждали появления файла до 30 секунд, прежде чем перейти к следующему шагу, и мы никоим образом не хотели замедлять процесс из-за появления скриншота на следующем шаге.

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

//+------------------------------------------------------------------+
//|    FUNCTION TO GET SCREENSHOT OF A NEW CHART                     |
//+------------------------------------------------------------------+

int getScreenshot_of_New_Chart(string screenshot_name,string symbol_name,
                               ENUM_TIMEFRAMES period_name){
   
   //--- First delete an instance of the screenshot file if it already exists
   if(FileIsExist(screenshot_name)){
      FileDelete(screenshot_name);
      Print("Chart Screenshot was found and deleted.");
      ChartRedraw(0);
   }
   
   long chart_id=ChartOpen(symbol_name,period_name);
   ChartSetInteger(chart_id,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(symbol_name,period_name,SERIES_SYNCHRONIZED)){
         break; // if prices up to date, terminate the loop and proceed
      }
   }

   ChartRedraw(chart_id);
   ChartSetInteger(chart_id,CHART_SHOW_GRID,false);
   ChartSetInteger(chart_id,CHART_SHOW_PERIOD_SEP,false);
   ChartSetInteger(chart_id,CHART_COLOR_CANDLE_BEAR,clrRed);
   ChartSetInteger(chart_id,CHART_COLOR_CANDLE_BULL,clrBlue);
   ChartSetInteger(chart_id,CHART_COLOR_BACKGROUND,clrLightSalmon);

   ChartScreenShot(chart_id,screenshot_name,1366,768,ALIGN_RIGHT);
   Print("OPENED CHART PAUSED FOR 10 SECONDS TO TAKE SCREENSHOT.");
   Sleep(10000); // sleep for 10 secs to see the opened chart
   ChartClose(chart_id);
   
   // Wait for 30 secs to save screenshot if not yet saved
   int wait_loops = 60;
   while(!FileIsExist(screenshot_name) && --wait_loops > 0){
      Sleep(500);
   }
   
   if(!FileIsExist(screenshot_name)){
      Print("THE SPECIFIED SCREENSHOT DOES NOT EXIST (WAS NOT SAVED). REVERTING NOW!");
      return (FAILED_CODE);
   }
   else if(FileIsExist(screenshot_name)){
      Print("THE CHART SCREENSHOT WAS SAVED SUCCESSFULLY TO THE DATA-BASE.");
      return (SUCCEEDED_CODE);
   }
   return (SUCCEEDED_CODE);
}

Здесь мы объявляем целочисленную функцию, которая принимает три параметра: имя изображения, имя символа и период открываемого графика. На данный момент у нас есть все необходимые функции для получения скриншотов и их отправки в Telegram. Давайте приступим к получению и отправке скриншота текущего графика и посмотрим, что у нас получится. Для этого применим следующий фрагмент кода.

   getScreenshot_of_Current_Chart(SCREENSHOT_FILE_NAME);
   sendScreenShot(SCREENSHOT_FILE_NAME,TG_API_URL,botTkn,chatID,NULL);

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

Первое изображение текущего графика

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

   int get_screenshot_new_chart_result = getScreenshot_of_New_Chart(SCREENSHOT_FILE_NAME,_Symbol,_Period);
   if (get_screenshot_new_chart_result == FAILED_CODE){
      string result_msg = "NEW CHART SCREENSHOT COULDN'T BE SAVED. REVERT NOW.";
      Print(result_msg);
      sendSimpleMessage(result_msg,TG_API_URL,botTkn,chatID,10000);
      return (INIT_FAILED);
   }
   else if (get_screenshot_new_chart_result == SUCCEEDED_CODE){
      string result_msg = "SUCCESS. NEW CHART SCREENSHOT WAS SAVED. CONTINUE NOW.";
      Print(result_msg);
      sendSimpleMessage(result_msg,TG_API_URL,botTkn,chatID,10000);
      string sending_msg = "\x2705\SCREENSHOT SENDING HAS BEEN INITIATED SUCCESSFULLY.";
      sending_msg += "\n\x270C\YOU SHOULD RECEIVE THE IMAGE FILE WITHIN 10 SECONDS";
      string encoded_sending_msg = UrlEncode(sending_msg);
      Print(encoded_sending_msg);
      sendEncodedMessage(encoded_sending_msg,TG_API_URL,botTkn,chatID,10000);
      
   }


Здесь мы управляем выводом функции getScreenshot_of_New_Chart. Функция делает и сохраняет скриншот нового графика. Вызываем функцию с четкими параметрами: желаемое имя для файла скриншота, текущий символ графика и таймфрейм. Результат функции сохраняется в переменной, которую мы называем get_screenshot_new_chart_result. В случае успеха переходим к следующей части нашей программы. Если что-то не получается, решаем проблему наиболее разумным образом.

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

SUCCEEDED_CODE означает, что скриншот был успешно сохранен. Мы отображаем сообщение о том, что терминал отправил команду успешного завершения после создания скриншота. Затем мы переходим к использованию функции sendSimpleMessage, чтобы сообщить пользователю, что его скриншот сохранен и что ему следует ожидать получения файла в ближайшее время. Процесс отправки сообщения пользователю ясен, лаконичен и выполнен правильно. Команда на отправку скриншота пользователю выполнена успешно, и он должен получить файл примерно через 10 секунд. В журнале мы получаем следующую запись:

Запись в журнале

В Telegram-чате видим следующее:

Входящий файл изображения

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

   sendScreenShot(SCREENSHOT_FILE_NAME,TG_API_URL,botTkn,chatID,NULL);

После запуска кода получаем следующие результаты:

Новый скриншот графика

Это успех. Вы можете заметить, что полученное нами изображение не имеет подписи. Это потому, что в поле caption у нас стоит NULL, что означает, что никаких подписей не будет. Чтобы включить подпись, нам просто нужно определить поле caption и передать его функции. Используем нашу подпись по умолчанию:

   //--- Caption
   string CAPTION = NULL;
   CAPTION = "Screenshot of Symbol: "+Symbol()+
             " ("+EnumToString(ENUM_TIMEFRAMES(_Period))+
             ") @ Time: "+TimeToString(TimeCurrent());
   
   sendScreenShot(SCREENSHOT_FILE_NAME,TG_API_URL,botTkn,chatID,CAPTION);

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

Скриншот с подписью

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


Тестирование и реализация модульных функций

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

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

      // BUY POSITION OPENED. GET READY TO SEND MESSAGE TO TELEGRAM
      Print("BUY POSITION OPENED. SEND MESSAGE TO TELEGRAM NOW.");
      
      //char data[];  // Array to hold data to be sent in the web request (empty in this case)
      //char res[];  // Array to hold the response data from the web request
      //string resHeaders;  // String to hold the response headers from the web request
      
      
      ushort MONEYBAG = 0xF4B0;
      string MONEYBAG_Emoji_code = ShortToString(MONEYBAG);
      string msg =  "\xF680 Opened Buy Position."
             +"\n===================="
             +"\n"+MONEYBAG_Emoji_code+"Price = "+DoubleToString(openPrice,_Digits)
             +"\n\xF412\Time = "+TimeToString(iTime(_Symbol,_Period,0),TIME_SECONDS)
             +"\n\xF551\Time Current = "+TimeToString(TimeCurrent(),TIME_SECONDS)
             +"\n\xF525 Lotsize = "+DoubleToString(lotSize,2)
             +"\n\x274E\Stop loss = "+DoubleToString(stopLoss,_Digits)
             +"\n\x2705\Take Profit = "+DoubleToString(takeProfit,_Digits)
             +"\n_________________________"
             +"\n\xF5FD\Time Local = "+TimeToString(TimeLocal(),TIME_DATE)
             +" @ "+TimeToString(TimeLocal(),TIME_SECONDS)
             ;
      string encloded_msg = UrlEncode(msg);
      msg = encloded_msg;
      
      sendEncodedMessage(msg,TG_API_URL,botTkn,chatID,10000);
      
      int get_screenshot_current_chart_result = getScreenshot_of_Current_Chart(SCREENSHOT_FILE_NAME);
      if (get_screenshot_current_chart_result == FAILED_CODE){
         string result_msg = "CURRENT CHART SCREENSHOT COULDN'T BE SAVED. REVERT NOW.";
         Print(result_msg);
         sendSimpleMessage(result_msg,TG_API_URL,botTkn,chatID,10000);
         return;
      }
      else if (get_screenshot_current_chart_result == SUCCEEDED_CODE){
         string result_msg = "SUCCESS. CURRENT CHART SCREENSHOT WAS SAVED. CONTINUE NOW.";
         Print(result_msg);
         sendSimpleMessage(result_msg,TG_API_URL,botTkn,chatID,10000);
         string sending_msg = "\x2705\SCREENSHOT SENDING HAS BEEN INITIATED SUCCESSFULLY.";
         sending_msg += "\n\x270C\YOU SHOULD RECEIVE THE IMAGE FILE WITHIN 10 SECONDS";
         string encoded_sending_msg = UrlEncode(sending_msg);
         Print(encoded_sending_msg);
         sendEncodedMessage(encoded_sending_msg,TG_API_URL,botTkn,chatID,10000);
         
      }
      
      //--- Caption
      string CAPTION = NULL;
      CAPTION = "Screenshot of Symbol: "+Symbol()+
                " ("+EnumToString(ENUM_TIMEFRAMES(_Period))+
                ") @ Time: "+TimeToString(TimeCurrent());
      
      sendScreenShot(SCREENSHOT_FILE_NAME,TG_API_URL,botTkn,chatID,CAPTION);

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

      // SELL POSITION OPENED. GET READY TO SEND MESSAGE TO TELEGRAM
      Print("SELL POSITION OPENED. SEND MESSAGE TO TELEGRAM NOW.");
      
      //char data[];  // Array to hold data to be sent in the web request (empty in this case)
      //char res[];  // Array to hold the response data from the web request
      //string resHeaders;  // String to hold the response headers from the web request
   
      ushort MONEYBAG = 0xF4B0;
      string MONEYBAG_Emoji_code = ShortToString(MONEYBAG);
      string msg =  "\xF680 Opened Sell Position."
             +"\n===================="
             +"\n"+MONEYBAG_Emoji_code+"Price = "+DoubleToString(openPrice,_Digits)
             +"\n\xF412\Time = "+TimeToString(iTime(_Symbol,_Period,0),TIME_SECONDS)
             +"\n\xF551\Time Current = "+TimeToString(TimeCurrent(),TIME_SECONDS)
             +"\n\xF525 Lotsize = "+DoubleToString(lotSize,2)
             +"\n\x274E\Stop loss = "+DoubleToString(stopLoss,_Digits)
             +"\n\x2705\Take Profit = "+DoubleToString(takeProfit,_Digits)
             +"\n_________________________"
             +"\n\xF5FD\Time Local = "+TimeToString(TimeLocal(),TIME_DATE)
             +" @ "+TimeToString(TimeLocal(),TIME_SECONDS)
             ;
      string encloded_msg = UrlEncode(msg);
      msg = encloded_msg;
      
      sendEncodedMessage(msg,TG_API_URL,botTkn,chatID,10000);
      
      int get_screenshot_current_chart_result = getScreenshot_of_Current_Chart(SCREENSHOT_FILE_NAME);
      if (get_screenshot_current_chart_result == FAILED_CODE){
         string result_msg = "CURRENT CHART SCREENSHOT COULDN'T BE SAVED. REVERT NOW.";
         Print(result_msg);
         sendSimpleMessage(result_msg,TG_API_URL,botTkn,chatID,10000);
         return;
      }
      else if (get_screenshot_current_chart_result == SUCCEEDED_CODE){
         string result_msg = "SUCCESS. CURRENT CHART SCREENSHOT WAS SAVED. CONTINUE NOW.";
         Print(result_msg);
         sendSimpleMessage(result_msg,TG_API_URL,botTkn,chatID,10000);
         string sending_msg = "\x2705\SCREENSHOT SENDING HAS BEEN INITIATED SUCCESSFULLY.";
         sending_msg += "\n\x270C\YOU SHOULD RECEIVE THE IMAGE FILE WITHIN 10 SECONDS";
         string encoded_sending_msg = UrlEncode(sending_msg);
         Print(encoded_sending_msg);
         sendEncodedMessage(encoded_sending_msg,TG_API_URL,botTkn,chatID,10000);
         
      }
      
      //--- Caption
      string CAPTION = NULL;
      CAPTION = "Screenshot of Symbol: "+Symbol()+
                " ("+EnumToString(ENUM_TIMEFRAMES(_Period))+
                ") @ Time: "+TimeToString(TimeCurrent());
      
      sendScreenShot(SCREENSHOT_FILE_NAME,TG_API_URL,botTkn,chatID,CAPTION);

Чтобы убедиться в наличии генерируемых сигналов, перейдем на минутный таймфрейм и дождемся ответных сигналов. Первый сигнал, который мы получаем, — это сигнал на продажу. Он генерируется в торговом терминале, как показано ниже:

Продажа

Запись о сигнале на продажу в журнале MetaTrader 5:

Запись о продаже в журнале

Сообщение о продаже в Telegram-чате:

Подтверждение продажи в Telegram

Из представленных выше изображений видно, что настройка сигнала на продажу подтверждается в торговом терминале, важные сообщения заносятся в журнал, а соответствующие данные о позициях и скриншоты отправляются в Telegram-чат. Теперь мы ожидаем, что при возникновении бычьего пересечения мы закроем существующую позицию на продажу, откроем позицию на покупку и также отправим информацию в Telegram. Настройка подтверждения выглядит так:

Покупка

Запись о сигнале на покупку в журнале MetaTrader 5:

Запись о покупке в журнале

Сообщение о покупке в Telegram-чате:

Подтверждение покупки в Telegram

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


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

Прикрепленные файлы |
Последние комментарии | Перейти к обсуждению на форуме трейдеров (4)
majid namnabat
majid namnabat | 28 нояб. 2024 в 08:34
Отправка сообщений работает, но sendPhoto не работает, почему?
Allan Munene Mutiiria
Allan Munene Mutiiria | 28 нояб. 2024 в 14:43
matrix2010 #:
Отправка сообщений работает, но sendPhoto не работает, почему?
Попробуйте внимательно проследить
Sittiporn Somboon
Sittiporn Somboon | 12 янв. 2025 в 14:02
Большое спасибо за ваши усилия и доброту, с которой вы делитесь, эта серия статей так полезна для меня и других. Желаю вам всего наилучшего, мира, здоровья и богатства.
Allan Munene Mutiiria
Allan Munene Mutiiria | 12 янв. 2025 в 21:23
Sittiporn Somboon #:
Большое спасибо за ваши усилия и доброту, с которой вы делитесь, эта серия статей так полезна для меня и других. Желаю вам мира, здоровья и богатства.
@Sittiporn Somboon спасибо за добрый отзыв и обзор. Очень рады.
Нейросети в трейдинге: Адаптивное обнаружение рыночных аномалий (DADA) Нейросети в трейдинге: Адаптивное обнаружение рыночных аномалий (DADA)
Предлагаем познакомиться с фреймворком DADA — инновационным методом выявления аномалий во временных рядах. Он помогает отличить случайные колебания от подозрительных отклонений. В отличие от традиционных методов, DADA гибко подстраивается под разные данные. Вместо фиксированного уровня сжатия он использует несколько вариантов и выбирает наиболее подходящий для каждого случая.
Пример сетевого анализа причинно-следственных связей (CNA) и модели векторной авторегресси для прогнозирования рыночных событий Пример сетевого анализа причинно-следственных связей (CNA) и модели векторной авторегресси для прогнозирования рыночных событий
В настоящей статье представлено подробное руководство по реализации сложной торговой системы с использованием сетевого анализа причинно-следственных связей (CNA) и векторной авторегрессии (VAR) в MQL5. В ней излагаются теоретические основы этих методов, предлагаются подробные объяснения ключевых функций торгового алгоритма, а также приводится пример кода для реализации.
Арбитражный трейдинг Forex: Простой бот-маркетмейкер синтетиков для старта Арбитражный трейдинг Forex: Простой бот-маркетмейкер синтетиков для старта
Сегодня разберем моего первого робота в сфере арбитража — поставщика ликвидности (если его можно так назвать) на синетических активах. Сегодня данный бот успешно работает как модуль в большой системе на машинном обучении, но я поднял старый арбитражный робот на Форекс из облака, и давайте посмотрим на него, и подумаем, что мы можем с ним сделать сегодня?
От начального до среднего уровня: Массив (I) От начального до среднего уровня: Массив (I)
Данная статья является переходом между тем, что рассматривалось до этого, и новым этапом исследований. Чтобы понять эту статью, необходимо прочитать предыдущие. Представленные здесь материалы предназначены только для обучения. Ни в коем случае нельзя рассматривать это приложение как окончательное, цели которого будут иные, кроме изучения представленных концепций.