
MQL5-советник, интегрированный в Telegram (Часть 7): Анализ команд для автоматизации индикаторов на графиках
Введение
В предыдущей статье серии (Часть 6) мы интегрировали адаптивные кнопки для улучшения взаимодействия с ботом. Теперь автоматизируем добавление индикаторов на графики MetaTrader 5, используя команды, отправленные из Telegram. Мы создадим систему, в которой советник фиксирует заданные пользователем параметры индикатора через Telegram, анализирует данные и применяет указанные индикаторы к торговым графикам в реальном времени.
Мы рассмотрим следующие темы:
- Обзор торговли на основе индикаторов с помощью Telegram: мы рассмотрим, как трейдеры могут использовать команды из Telegram для управления индикаторами в MetaTrader 5.
- Анализ и обработка команд индикатора из Telegram: В этом разделе подробно описывается, как правильно извлекать и обрабатывать параметры индикатора из Telegram-сообщений.
- Выполнение команд MQL5-индикаторов: Мы покажем, как использовать проанализированные команды для добавления и автоматизации индикаторов непосредственно в MetaTrader 5.
- Тестирование системы торговли на основе индикаторов: Тщательное тестирование обеспечит бесперебойную работу системы для точной автоматизации индикаторов.
- Заключение: Наконец, мы подведем итоги всего процесса и обсудим основные выводы.
К концу статьи у вас будет полностью функциональная система автоматического добавления индикаторов с помощью Telegram в MetaTrader 5, способная принимать и обрабатывать команды из Telegram для беспрепятственного применения технических индикаторов в MQL5.
Обзор торговли на основе индикаторов с помощью Telegram
В этом разделе мы рассмотрим использование Telegram для отправки команд индикатора, которые могут автоматизировать анализ графика. Многие трейдеры используют Telegram для взаимодействия с ботами и советниками, которые позволяют им добавлять, изменять или удалять технические индикаторы непосредственно в MetaTrader 5. Эти команды обычно включают в себя ключевую информацию, - такую как тип индикатора, таймфрейм, период и ценовое действие, - необходимую для анализа графика. Однако при ручном применении этих индикаторов возможны задержки и ошибки, особенно на быстро меняющихся рынках.
Автоматизировав применение индикаторов через Telegram-команды, трейдеры могут улучшить свой технический анализ без необходимости ручного управления графиками. При правильной интеграции эти Telegram-команды можно анализировать и преобразовывать в исполняемые инструкции для MetaTrader 5, что позволяет добавлять индикаторы на графики в реальном времени. Это обеспечивает не только точность, но и более эффективный торговый процесс, позволяя трейдерам сосредоточиться на интерпретации результатов, а не на управлении настройками. Типичная визуализация команд индикатора показана ниже:
Результатом является система, которая устраняет разрыв между Telegram и MetaTrader 5, предоставляя трейдерам возможность оптимизировать анализ графиков, минимизировать ошибки и в полной мере использовать рыночные возможности в реальном времени с помощью автоматизированного управления индикаторами.
Анализ и обработка Telegram-команд индикатора
Первое, что нам нужно сделать, это захватить Telegram-команды индикатора, после чего следует их кодирование, анализ и обработка в MetaQuotes Language 5 (MQL5), чтобы мы могли их интерпретировать и применять соответствующие индикаторы к графикам в MetaTrader 5. Для кодирования и анализа мы уже представили необходимые классы в части 5 этой серии. Здесь мы еще раз рассмотрим эти классы, чтобы обеспечить ясность, особенно с учетом того, что часть 6 была посвящена различным функциям, таким как встроенные кнопки. Фрагмент кода, отвечающий за анализ и обработку Telegram-команд индикатора, представлен ниже:
//+------------------------------------------------------------------+ //| Class_Bot_EA | //+------------------------------------------------------------------+ class Class_Bot_EA{ private: string member_token; //--- Stores the bot’s token. string member_name; //--- Stores the bot’s name. long member_update_id; //--- Stores the last update ID processed by the bot. CArrayString member_users_filter; //--- An array to filter users. bool member_first_remove; //--- A boolean to indicate if the first message should be removed. protected: CList member_chats; //--- A list to store chat objects. public: void Class_Bot_EA(); //--- Declares the constructor. ~Class_Bot_EA(){}; //--- Declares the destructor. int getChatUpdates(); //--- Declares a function to get updates from Telegram. void ProcessMessages(); //--- Declares a function to process incoming messages. }; void Class_Bot_EA::Class_Bot_EA(void){ //--- Constructor member_token=NULL; //--- Initialize the bot's token as NULL. member_token=getTrimmedToken(InpToken); //--- Assign the trimmed bot token from InpToken. member_name=NULL; //--- Initialize the bot's name as NULL. member_update_id=0; //--- Initialize the last update ID to 0. member_first_remove=true; //--- Set the flag to remove the first message to true. member_chats.Clear(); //--- Clear the list of chat objects. member_users_filter.Clear(); //--- Clear the user filter array. } //+------------------------------------------------------------------+ int Class_Bot_EA::getChatUpdates(void){ //--- Check if the bot token is NULL if(member_token==NULL){ Print("ERR: TOKEN EMPTY"); //--- Print an error message if the token is empty return(-1); //--- Return with an error code } string out; //--- Variable to store the response from the request string url=TELEGRAM_BASE_URL+"/bot"+member_token+"/getUpdates"; //--- Construct the URL for the Telegram API request string params="offset="+IntegerToString(member_update_id); //--- Set the offset parameter to get updates after the last processed ID //--- Send a POST request to get updates from Telegram int res=postRequest(out, url, params, WEB_TIMEOUT); // THIS IS THE STRING RESPONSE WE GET // "ok":true,"result":[]} //--- If the request was successful if(res==0){ //Print(out); //--- Optionally print the response //--- Create a JSON object to parse the response CJSONValue obj_json(NULL, jv_UNDEF); //--- Deserialize the JSON response bool done=obj_json.Deserialize(out); //--- If JSON parsing failed // Print(done); if(!done){ Print("ERR: JSON PARSING"); //--- Print an error message if parsing fails return(-1); //--- Return with an error code } //--- Check if the 'ok' field in the JSON is true bool ok=obj_json["ok"].ToBool(); //--- If 'ok' is false, there was an error in the response if(!ok){ Print("ERR: JSON NOT OK"); //--- Print an error message if 'ok' is false return(-1); //--- Return with an error code } //--- Create a message object to store message details Class_Message obj_msg; //--- Get the total number of updates in the JSON array 'result' int total=ArraySize(obj_json["result"].m_elements); //--- Loop through each update for(int i=0; i<total; i++){ //--- Get the individual update item as a JSON object CJSONValue obj_item=obj_json["result"].m_elements[i]; //--- Extract message details from the JSON object obj_msg.update_id=obj_item["update_id"].ToInt(); //--- Get the update ID obj_msg.message_id=obj_item["message"]["message_id"].ToInt(); //--- Get the message ID obj_msg.message_date=(datetime)obj_item["message"]["date"].ToInt(); //--- Get the message date obj_msg.message_text=obj_item["message"]["text"].ToStr(); //--- Get the message text obj_msg.message_text=decodeStringCharacters(obj_msg.message_text); //--- Decode any HTML entities in the message text //--- Extract sender details from the JSON object obj_msg.from_id=obj_item["message"]["from"]["id"].ToInt(); //--- Get the sender's ID obj_msg.from_first_name=obj_item["message"]["from"]["first_name"].ToStr(); //--- Get the sender's first name obj_msg.from_first_name=decodeStringCharacters(obj_msg.from_first_name); //--- Decode the first name obj_msg.from_last_name=obj_item["message"]["from"]["last_name"].ToStr(); //--- Get the sender's last name obj_msg.from_last_name=decodeStringCharacters(obj_msg.from_last_name); //--- Decode the last name obj_msg.from_username=obj_item["message"]["from"]["username"].ToStr(); //--- Get the sender's username obj_msg.from_username=decodeStringCharacters(obj_msg.from_username); //--- Decode the username //--- Extract chat details from the JSON object obj_msg.chat_id=obj_item["message"]["chat"]["id"].ToInt(); //--- Get the chat ID obj_msg.chat_first_name=obj_item["message"]["chat"]["first_name"].ToStr(); //--- Get the chat's first name obj_msg.chat_first_name=decodeStringCharacters(obj_msg.chat_first_name); //--- Decode the first name obj_msg.chat_last_name=obj_item["message"]["chat"]["last_name"].ToStr(); //--- Get the chat's last name obj_msg.chat_last_name=decodeStringCharacters(obj_msg.chat_last_name); //--- Decode the last name obj_msg.chat_username=obj_item["message"]["chat"]["username"].ToStr(); //--- Get the chat's username obj_msg.chat_username=decodeStringCharacters(obj_msg.chat_username); //--- Decode the username obj_msg.chat_type=obj_item["message"]["chat"]["type"].ToStr(); //--- Get the chat type //--- Update the ID for the next request member_update_id=obj_msg.update_id+1; //--- If it's the first update, skip processing if(member_first_remove){ continue; } //--- Filter messages based on username if(member_users_filter.Total()==0 || //--- If no filter is applied, process all messages (member_users_filter.Total()>0 && //--- If a filter is applied, check if the username is in the filter member_users_filter.SearchLinear(obj_msg.from_username)>=0)){ //--- Find the chat in the list of chats int index=-1; for(int j=0; j<member_chats.Total(); j++){ Class_Chat *chat=member_chats.GetNodeAtIndex(j); if(chat.member_id==obj_msg.chat_id){ //--- Check if the chat ID matches index=j; break; } } //--- If the chat is not found, add a new chat to the list if(index==-1){ member_chats.Add(new Class_Chat); //--- Add a new chat to the list Class_Chat *chat=member_chats.GetLastNode(); chat.member_id=obj_msg.chat_id; //--- Set the chat ID chat.member_time=TimeLocal(); //--- Set the current time for the chat chat.member_state=0; //--- Initialize the chat state chat.member_new_one.message_text=obj_msg.message_text; //--- Set the new message text chat.member_new_one.done=false; //--- Mark the new message as not processed } //--- If the chat is found, update the chat message else{ Class_Chat *chat=member_chats.GetNodeAtIndex(index); chat.member_time=TimeLocal(); //--- Update the chat time chat.member_new_one.message_text=obj_msg.message_text; //--- Update the message text chat.member_new_one.done=false; //--- Mark the new message as not processed } } } //--- After the first update, set the flag to false member_first_remove=false; } //--- Return the result of the POST request return(res); }
Здесь мы представляем Class_Bot_EA и реализуем его функцию getChatUpdates для обработки входящих обновлений от Telegram. В конструкторе мы инициализируем токен бота, имя и другие необходимые переменные. Мы также устанавливаем флаг, чтобы определить, следует ли удалить первое сообщение и очистить некоторые старые данные, включая список чатов и любые пользовательские фильтры.
Функция getChatUpdates создает URL-адрес для API Telegram, который предоставляет нам обновления для указанного бота. Идентификатор последнего обработанного обновления используется в качестве смещения в URL-адресе, что означает, что мы не получим никаких обновлений, которые уже обработаны. После создания URL-адреса мы отправляем POST-запрос к API и обрабатываем ответ сервера. Так как мы ожидаем получить данные в формате JavaScript Object Notation (JSON) обратно с сервера, мы проверяем наличие ошибок, пытаясь проанализировать данные. Если анализ не удался или поле "ok" в ответе JSON имеет значение false, мы выводим сообщение об ошибке и возвращаем ее код.
После успешного ответа мы извлекаем из сообщения соответствующую информацию — идентификатор обновления, идентификатор сообщения, информацию об отправителе и сведения о чате, — которая позволяет нам узнать, что произошло в разговоре. Затем мы просматриваем список чатов, которые у нас есть на данный момент, и смотрим, какое место в нем занимает эта новая информация. Если чата, связанного с этим новым сообщением, нет в списке, мы его добавляем. Если он есть в списке, мы обновляем его информацию новым сообщением.
Теперь мы можем заботиться о пользовательской сортировке сообщений и обработке состояния каждого чата. После завершения необходимых обновлений мы гарантируем, что последнее обработанное сообщение каждого чата является актуальным. Наконец, мы возвращаем результат нашего запроса POST, указывающий либо на успех, либо на соответствующим образом описанную ошибку.
Это все, что нам нужно для обработки полученных команд. Затем нам необходимо интерпретировать полученные команды индикатора, идентифицировать запрошенный индикатор и автоматически добавить его на график для дальнейшего анализа. Сделаем это в следующем разделе.
Выполнение команд MQL5-индикаторов
Для обработки полученных команд индикатора мы вызовем функцию, отвечающую за обработку сообщений, чтобы обработать сообщения целиком, а затем интерпретировать детали сообщения по сегментам. Применима следующая функция.
void Class_Bot_EA::ProcessMessages(void){ //... }
Именно в этой функции начинается настоящая обработка. Первое, что нам нужно сделать, это просмотреть все полученные сообщения и обработать их по отдельности. Это важно, поскольку поставщик мог отправлять сигналы одновременно для нескольких торговых символов, например, "AUDUSD, EURUSD, GBPUSD, XAUUSD, XRPUSD, USDKES, USDJPY, EURCHF" и многих других. Мы достигаем этого с помощью следующей логики.
//--- Loop through all chats for(int i=0; i<member_chats.Total(); i++){ Class_Chat *chat=member_chats.GetNodeAtIndex(i); //--- Get the current chat if(!chat.member_new_one.done){ //--- Check if the message has not been processed yet chat.member_new_one.done=true; //--- Mark the message as processed string text=chat.member_new_one.message_text; //--- Get the message text //... } }
Сначала мы просматриваем сохраненные чаты в списке member_chats. Каждый объект чата извлекается с помощью функции GetNodeAtIndex. Мы проверяем, было ли обработано сообщение, связанное с этим чатом, оценивая флаг в структуре member_new_one. Если сообщение еще не обработано, флаг done (выполнено) устанавливается в значение true, что означает, что одно и то же сообщение не будет обрабатываться несколько раз.
Далее мы извлекаем содержимое сообщения. Оно хранится в поле message_text структуры member_new_one. Таким образом, мы можем работать с текстом напрямую, не беспокоясь о том, что уже обработано.
Первое, что нам нужно сделать сейчас, — это получить данные команды для анализа. Логика представлена ниже.
string user_text = text; Print("USER'S PLAIN TEXT IS AS BELOW:\n",user_text); StringToUpper(user_text); Print("USER'S TRANSFORMED UPPERCASE TEXT IS AS BELOW:\n",user_text);
Здесь мы сначала сохраняем текст входящего сообщения из переменной text в новую переменную user_text. Это позволяет нам работать с содержимым сообщения, не изменяя исходную переменную. Затем мы печатаем текст сообщения пользователя с помощью функции Print, которая выводит сообщение в терминале MetaTrader 5 для регистрации событий.
Далее мы преобразуем всю строку user_text в верхний регистр с помощью функции StringToUpper. Это преобразует все символы в сообщении в верхний регистр. Это необходимо, так как мы выровняем символы сообщения и нам будет легче работать. После преобразования мы снова выводим обновленное сообщение в терминал, отображая преобразованную версию ввода пользователя в верхнем регистре. Это позволяет нам видеть как исходную, так и измененную версию сообщения для дальнейшей обработки или ответа. При повторном запуске программы в разделе журнала мы получаем следующее.
После преобразования сообщения сигнала в верхний регистр нам необходимо инициализировать переменные, которые будут хранить наши данные, как показано ниже:
// MOVING AVERAGE //--- Initialize variables to hold extracted data string indicator_type = NULL; string indicator_symbol = NULL; string indicator_timeframe = NULL; long indicator_period = 0; long indicator_shift = 0; string indicator_method = NULL; string indicator_app_price = NULL;
Начнем с индикатора скользящей средней. Мы инициализируем несколько переменных, которые будут содержать извлеченные данные для настройки индикатора скользящей средней (MA) в MetaTrader 5 на основе пользовательского ввода из Telegram. Вот что представляет каждая переменная:
- indicator_type - тип индикатора, в данном случае - скользящая средняя.
- indicator_symbol - символ (валютная пара или актив), к которому будет применен индикатор.
- indicator_timeframe - таймфрейм графика (например, M1, H1, D1), на котором будет построен индикатор.
- indicator_period - количество периодов (или баров), которые скользящая средняя будет учитывать при расчете.
- indicator_shift - смещение или сдвиг для перемещения индикатора вперед или назад на графике.
- indicator_method - метод расчета скользящей средней (например, SMA, EMA).
- indicator_app_price - цена расчета скользящей средней (например, цена закрытия, цена открытия).
Чтобы извлечь элементы данных, относящиеся к индикатору, нам нужно будет разбить сообщение по строкам и пройтись по каждой строке в поисках подробностей. Эта реализация достигается с помощью следующей логики.
//--- Split the message by lines string lines[]; StringSplit(user_text,'\n',lines); Print("SPLIT TEXT SEGMENTS IS AS BELOW:"); ArrayPrint(lines,0,",");
Здесь мы разбиваем преобразованное сообщение пользователя на отдельные строки, чтобы упростить извлечение соответствующей информации об индикаторе. Сначала мы объявляем массив с именем lines для хранения каждой строки сообщения после его разделения. Затем мы применяем функцию StringSplit, используя символ новой строки ('\n') в качестве разделителя для разбиения сообщения на отдельные строки. Эта функция навсегда заполняет массив lines каждой частью текста, которая была отделена новой строкой.
После того, как сообщение разделено, мы печатаем полученные сегменты с помощью функции ArrayPrint, которая выводит каждую строку как отдельный элемент. Этот шаг обязателен для визуализации структуры сообщения и корректной работы разделения. Организовав сообщение таким образом, мы можем легче обрабатывать каждую строку для извлечения важных элементов, таких как торговый символ, тип индикатора и другие сведения. Чтобы получить детали, нам необходимо пройтись по каждой строке.
//--- Iterate over each line to extract information for (int i=0; i<ArraySize(lines); i++){ StringTrimLeft(lines[i]); StringTrimRight(lines[i]); string selected_line = lines[i]; Print(i,". ",selected_line); //... }
Мы перебираем массив lines, чтобы извлечь конкретную индикаторную информацию из каждой строки разделенного сообщения. Мы используем цикл for для прохождения каждого элемента массива, гарантируя, что каждая строка обрабатывается индивидуально. В начале цикла мы применяем функции StringTrimLeft и StringTrimRight для каждой строки, чтобы удалить все начальные и конечные символы пробела. Это гарантирует, что никакие лишние пробелы не помешают анализу.
Затем мы присваиваем каждую обрезанную строку переменной selected_line, которая содержит текущую обрабатываемую строку. Аккуратно обрезав каждую строку и сохранив ее в переменной selected_line, мы можем выполнять дальнейшие операции, например, проверять, содержит ли строка определенные торговые сигналы или команды. Чтобы убедиться, что у нас верная информация, мы выводим каждую строку.
Всё работает как надо. Мы можем приступить к поиску конкретных деталей в выбранной строке. Начнем с поиска типа технического индикатора. Сначала мы найдем текст типа индикатора скользящей средней, то есть INDICATOR TYPE.
if (StringFind(selected_line,"INDICATOR TYPE") >= 0){ indicator_type = StringSubstr(selected_line,16); Print("Line @ index ",i," Indicator Type = ",indicator_type); //--- Print the extracted details }
Здесь мы обрабатываем сообщение пользователя Telegram, чтобы извлечь тип индикатора. Мы используем функцию StringFind для поиска в текущей строке ("selected_line") текста INDICATOR TYPE. Если этот текст найден, функция возвращает начальную позицию совпадения, которая является значением большим или равным нулю. После обнаружения совпадения мы извлекаем тип индикатора с помощью функции StringSubstr, которая извлекает подстроку с позиции после INDICATOR TYPE (начиная с индекса 16) до конца строки. Извлеченное значение сохраняется в переменной indicator_type. Наконец, мы выводим индекс строки и извлеченный indicator_type с помощью функции Print, чтобы подтвердить, что данные были успешно извлечены. При запуске мы получим следующий результат.
Мы видим, что мы успешно прошли по всем сегментам команды и определили имя индикатора. Далее нам необходимо определить символ. Будет использоваться похожая, но немного более сложная логика.
//--- Check for symbol in the list of available symbols and assign it for(int k = 0; k < SymbolsTotal(true); k++) { //--- Loop through all available symbols string selected_symbol = SymbolName(k, true); //--- Get the symbol name if (StringCompare(selected_line,selected_symbol,false) == 0){ //--- Compare the line with the symbol name indicator_symbol = selected_symbol; //--- Assign the symbol if a match is found Print("Line @ index ",i," SYMBOL = ",indicator_symbol); //--- Print the found symbol } }
В этом блоке кода мы проверяем введенные пользователем данные по списку доступных торговых символов и присваиваем правильный символ переменной indicator_symbol. Во-первых, мы используем функцию SymbolsTotal, которая возвращает общее количество символов, доступных в данный момент на платформе. Аргумент true указывает, что нам нужно количество видимых символов. Затем мы перебираем все доступные символы, используя цикл for с переменной k в качестве индекса.
Внутри цикла мы используем функцию SymbolName для получения имени символа по индексу k. Второй аргумент, true, указывает, что нам нужно имя символа в его краткой форме. После извлечения имени символа и сохранения его в переменной selected_symbol, мы используем функцию StringCompare для сравнения selected_symbol с введенными пользователем данными (selected_line). Аргумент false указывает на то, что сравнение должно быть без учета регистра.
Если функция возвращает ноль, это означает, что две строки совпадают, и мы присваиваем selected_symbol переменной indicator_symbol. Наконец, мы выводим индекс строки и соответствующий indicator_symbol с помощью функции Print для подтверждения того, что мы правильно идентифицировали и назначили символ, введенный пользователем. Он не содержит никакого лишнего текста, поэтому мы осуществляем прямой поиск. При запуске текущего фрагмента кода мы не получим никаких результатов, поскольку извлеченный текст и символы по умолчанию не похожи друг на друга, поскольку они чувствительны к регистру, то есть "XAUUSDM" не равно "XAUUSDm". Вот имя символа, которое у нас есть:
Таким образом, для проведения сравнения нам необходимо преобразовать системный символ по умолчанию в верхний регистр. Обновленный фрагмент кода выглядит так:
//--- Check for symbol in the list of available symbols and assign it for(int k = 0; k < SymbolsTotal(true); k++) { //--- Loop through all available symbols string selected_symbol = SymbolName(k, true); //--- Get the symbol name StringToUpper(selected_symbol); if (StringCompare(selected_line,selected_symbol,false) == 0){ //--- Compare the line with the symbol name indicator_symbol = selected_symbol; //--- Assign the symbol if a match is found Print("Line @ index ",i," SYMBOL = ",indicator_symbol); //--- Print the found symbol } }
С помощью нового преобразования мы можем приступить к сравнению и получаем следующие результаты:
Всё работает как надо. Чтобы получить другие данные, мы используем похожую логику.
if (StringFind(selected_line,"TIMEFRAME") >= 0){ indicator_timeframe = StringSubstr(selected_line,12); Print("Line @ index ",i," Indicator Timeframe = ",indicator_timeframe); //--- Print the extracted details } if (StringFind(selected_line,"PERIOD") >= 0){ indicator_period = StringToInteger(StringSubstr(selected_line,9)); Print("Line @ index ",i," Indicator Period = ",indicator_period); } if (StringFind(selected_line,"SHIFT") >= 0){ indicator_shift = StringToInteger(StringSubstr(selected_line,8)); Print("Line @ index ",i," Indicator Shift = ",indicator_shift); } if (StringFind(selected_line,"METHOD") >= 0){ indicator_method = StringSubstr(selected_line,9); Print("Line @ index ",i," Indicator Method = ",indicator_method); } if (StringFind(selected_line,"APPLIED PRICE") >= 0){ indicator_app_price = StringSubstr(selected_line,16); Print("Line @ index ",i," Indicator Applied Price = ",indicator_app_price); } }
При запуске мы получим следующий результат.
Чтобы просмотреть данные в более структурированном виде, мы можем распечатать всю полученную извлеченную информацию, как показано ниже:
//--- Final data Print("\nFINAL EXTRACTED DATA:"); //--- Print the final data for confirmation Print("Type = ",indicator_type); Print("Symbol = ",indicator_symbol); Print("Timeframe = ",indicator_timeframe); Print("Period = ",indicator_period); Print("Shift = ",indicator_shift); Print("Method = ",indicator_method); Print("Applied Price = ",indicator_app_price);
Мы печатаем окончательные извлеченные данные, чтобы подтвердить, что переменные были заполнены правильно. Сначала мы используем функцию «Печать» для отображения заголовка сообщения: "\nFINAL EXTRACTED DATA:". Это служит визуальной подсказкой в журналах, отмечающей, где будут показаны обработанные данные.
После этого мы последовательно выводим значения каждой из ключевых переменных — indicator_type, indicator_symbol, indicator_timeframe, indicator_period, indicator_shift, indicator_method и indicator_app_price. В каждом случае обращение к Print выводит имя переменной и ее текущее значение. Это важно для отладки и проверки, поскольку гарантирует, что данные, проанализированные на основе введенных пользователем данных (например, тип индикатора, символ, таймфрейм и т. д.), будут точно зафиксированы, прежде чем система продолжит добавление индикатора на график в MetaTrader 5. Результат представлен ниже:
Отлично! Теперь, когда у нас есть все необходимые данные для индикатора скользящей средней, мы можем приступить к его добавлению на график. Однако перед тем, как добавить его, мы можем перепроверить, что у нас правильный индикатор, как указано в Telegram. Мы делаем это с помощью оператора if.
if (indicator_type=="MOVING AVERAGE"){ //... }
После подтверждения индикатора мы можем приступить к преобразованию извлеченных входных данных в соответствующие им структуры типов данных. Обратите внимание, что текущие значения представлены либо строковыми, либо целочисленными типами данных. Например, система не поймет, что введенный пользователем метод скользящей средней "SMA"означает простую скользящую среднюю в соответствии с перечислением ENUM_MA_METHOD, как показано ниже.
Таким образом, нам необходимо более подробно объяснить это программе. Чтобы идти по порядку, начнем с самого очевидного — с таймфрейма.
//--- Convert timeframe to ENUM_TIMEFRAMES ENUM_TIMEFRAMES timeframe_enum = _Period; if (indicator_timeframe == "M1") { timeframe_enum = PERIOD_M1; } else if (indicator_timeframe == "M5") { timeframe_enum = PERIOD_M5; } else if (indicator_timeframe == "M15") { timeframe_enum = PERIOD_M15; } else if (indicator_timeframe == "M30") { timeframe_enum = PERIOD_M30; } else if (indicator_timeframe == "H1") { timeframe_enum = PERIOD_H1; } else if (indicator_timeframe == "H4") { timeframe_enum = PERIOD_H4; } else if (indicator_timeframe == "D1") { timeframe_enum = PERIOD_D1; } else if (indicator_timeframe == "W1") { timeframe_enum = PERIOD_W1; } else if (indicator_timeframe == "MN1") { timeframe_enum = PERIOD_MN1; } else { Print("Invalid timeframe: ", indicator_timeframe); }
В этом разделе мы преобразуем предоставленный пользователем таймфрейм из извлеченной строки indicator_timeframe в соответствующее перечисление MetaTrader 5 ENUM_TIMEFRAMES. Этот шаг имеет решающее значение, поскольку MetaTrader 5 использует предопределенные таймфреймы, такие как PERIOD_M1 для 1-минутных графиков, PERIOD_H1 для 1-часового графика и так далее. Эти временные рамки определены как часть типа ENUM_TIMEFRAMES.
Начнем с инициализации переменной timeframe_enum в соответствии с текущим таймфреймом графика, который представлен как _Period. Это служит запасным вариантом по умолчанию на случай, если предоставленный пользователем таймфрейм окажется недействительным.
Далее мы используем ряд условных операторов if-else для проверки значения строки indicator_timeframe. Каждое условие сравнивает извлеченную строку с известными идентификаторами временных рамок, такими как M1 (1 минута), H1 (1 час) и так далее. Если совпадение найдено, соответствующее значение ENUM_TIMEFRAMES (например, PERIOD_M1 или PERIOD_H1) присваивается переменной timeframe_enum.
Если ни одно из условий не совпадает, срабатывает финальный блок else, печатая в журнале сообщение об ошибке "Invalid timeframe" (неверный таймфрейм) со значением indicator_timeframe. Это помогает гарантировать, что в процессе создания индикатора в MetaTrader 5 передаются только допустимые таймфреймы. Аналогично мы преобразуем остальные переменные в соответствующие им формы.
//--- Convert MA method to ENUM_MA_METHOD ENUM_MA_METHOD ma_method = MODE_SMA; if (indicator_method == "SMA") { ma_method = MODE_SMA; } else if (indicator_method == "EMA") { ma_method = MODE_EMA; } else if (indicator_method == "SMMA") { ma_method = MODE_SMMA; } else if (indicator_method == "LWMA") { ma_method = MODE_LWMA; } else { Print("Invalid MA method: ", indicator_method); } //--- Convert applied price to ENUM_APPLIED_PRICE ENUM_APPLIED_PRICE app_price_enum = PRICE_CLOSE; if (indicator_app_price == "CLOSE") { app_price_enum = PRICE_CLOSE; } else if (indicator_app_price == "OPEN") { app_price_enum = PRICE_OPEN; } else if (indicator_app_price == "HIGH") { app_price_enum = PRICE_HIGH; } else if (indicator_app_price == "LOW") { app_price_enum = PRICE_LOW; } else if (indicator_app_price == "MEDIAN") { app_price_enum = PRICE_MEDIAN; } else if (indicator_app_price == "TYPICAL") { app_price_enum = PRICE_TYPICAL; } else if (indicator_app_price == "WEIGHTED") { app_price_enum = PRICE_WEIGHTED; } else { Print("Invalid applied price: ", indicator_app_price); }
После завершения процесса преобразования мы можем приступить к созданию хэндла индикатора, который будем использовать для добавления индикатора на график.
int handle_ma = iMA(indicator_symbol,timeframe_enum,(int)indicator_period,(int)indicator_shift,ma_method,app_price_enum);
Здесь мы создаем хэндл для индикатора скользящей средней (MA), используя функцию iMA. Эта функция генерирует хэндл индикатора на основе параметров, которые мы извлекли и обработали ранее. Хэндл позволит нам ссылаться на индикатор на графике и управлять им позже в коде. Мы передаем несколько аргументов функции iMA, каждый из которых соответствует параметру, который мы получили из команды Telegram:
- indicator_symbol - финансовый инструмент (например, EURUSD), к которому будет применяться индикатор.
- timeframe_enum - этот аргумент относится к временному интервалу (например, M1 - 1 минута, H1 - 1 час), который был ранее преобразован из введенных пользователем данных.
- (int)indicator_period - преобразует извлеченный indicator_period из типа данных long в целое число, представляющее количество периодов скользящей средней.
- (int)indicator_shift - аналогично преобразует indicator_shift в целое число, определяя, на сколько баров следует сместить индикатор на графике.
- ma_method - метод расчета скользящей средней, например, простая скользящая (SMA) или экспоненциальная скользящая средняя (EMA), на основе введенных пользователем данных.
- app_price_enum - применяемый тип цены (например, цена закрытия или цена открытия), на основе которого будет рассчитываться скользящая средняя.
Результат функции iMA сохраняется в переменной handle_ma. Если функция успешно создает хэндл индикатора, handle_ma будет содержать действительную ссылку. Если создание не удалось, возвращается INVALID_HANDLE, что указывает на наличие проблемы с одним или несколькими параметрами. Так как мы знаем, что INVALID HANDLE указывает на неудачу, мы можем продолжить его использование для дальнейшей обработки.
if (handle_ma != INVALID_HANDLE){ Print("Successfully created the indicator handle!"); //... } else if (handle_ma == INVALID_HANDLE){ Print("Failed to create the indicator handle!"); }
Здесь мы проверяем, был ли успешно создан хэндл индикатора скользящей средней, проверяя значение handle_ma. Если хэндл действителен, функция iMA вернет значение, которое не равно INVALID_HANDLE. В этом случае мы выводим сообщение об успешном завершении: "Successfully created the indicator handle!" (Успешно создан хэндл индикатора!). Это означает, что все параметры (символ, таймфрейм, период, метод и прочие) были правильно интерпретированы и индикатор скользящей средней готов к использованию на графике.
Если создание хэндла не удалось, значение handle_ma равно INVALID_HANDLE, выводим сообщение об ошибке: "Failed to create the indicator handle!" (Не удалось создать хэндл индикатора!). Это состояние указывает на то, что в процессе создания индикатора что-то пошло не так — например, недопустимый символ, неправильный таймфрейм или любой другой неверный параметр. Такая обработка ошибок помогает нам гарантировать, что система сможет обнаруживать проблемы и предоставлять информативную обратную связь в процессе настройки индикатора. Затем мы можем открыть график с указанным символом и таймфреймом, убедиться, что он синхронизирован с последними данными, и настроить его параметры для ясности.
long chart_id=ChartOpen(indicator_symbol,timeframe_enum); ChartSetInteger(ChartID(),CHART_BRING_TO_TOP,true); // update chart int wait=60; while(--wait>0){//decrease the value of wait by 1 before loop condition check if(SeriesInfoInteger(indicator_symbol,timeframe_enum,SERIES_SYNCHRONIZED)){ break; // if prices up to date, terminate the loop and proceed } } ChartSetInteger(chart_id,CHART_SHOW_GRID,false); ChartSetInteger(chart_id,CHART_SHOW_PERIOD_SEP,false); ChartRedraw(chart_id); Sleep(7000);
Во-первых, мы используем функцию ChartOpen для открытия нового графика на основе indicator_symbol и timeframe_enum, которые мы ранее извлекли из Telegram-команды. Идентификатор графика возвращается и сохраняется в переменной сhart_id. Чтобы вывести этот график на передний план в MetaTrader 5, мы используем функцию ChartSetInteger, передающую идентификатор графика вместе с константой CHART_BRING_TO_TOP, чтобы гарантировать видимость графика для взаимодействия.
Далее мы реализуем проверку синхронизации, чтобы убедиться, что ценовые данные для графика полностью обновлены. Это делается путем повторения цикла до 60 раз с использованием функции SeriesInfoInteger для проверки синхронизированности ряда ценовых данных. Если синхронизация происходит до завершения цикла, мы выходим из него раньше времени. Убедившись, что данные актуальны, переходим к настройке внешнего вида графика. Разделители сетки и периодов скрываются с помощью функции ChartSetInteger, в которой мы передаем CHART_SHOW_GRID и CHART_SHOW_PERIOD_SEP как false, создавая более чистый вид графика.
После этих корректировок график принудительно обновляется визуально с помощью функции ChartRedraw. Наконец, добавляется 7-секундная пауза с помощью функции Sleep, позволяющая полностью загрузить график и данные, прежде чем приступать к дальнейшим операциям. Процесс гарантирует, что график готов к взаимодействию и отображению с обновленными данными и четкой визуальной настройкой. Теперь мы можем продолжить добавлять индикатор на график.
if (ChartIndicatorAdd(chart_id,0,handle_ma)) { Print("SUCCESS. Indicator added to the chart."); isIndicatorAddedToChart = true; } else { Print("FAIL: Indicator not added to the chart."); }
Здесь мы добавляем ранее созданный индикатор скользящей средней к графику, который мы открыли ранее. Мы достигаем этого с помощью функции ChartIndicatorAdd, в которую мы передаем chart_id, ноль (который указывает на то, что мы добавляем индикатор к основному графику) и handle_ma, который представляет собой хэндл созданного нами индикатора скользящей средней. Если добавление прошло успешно, мы выводим сообщение об успешном завершении: "SUCCESS. Indicator added to the chart" (индикатор успешно добавлен на график), и устанавливаем логическую переменную isIndicatorAddedToChart на true, указывая, что индикатор теперь активен на графике. Мы определили логическую переменную и инициализировали ее значением false вне оператора if, как показано ниже.
bool isIndicatorAddedToChart = false;
И наоборот, если добавление не удалось, мы выводим сообщение об ошибке: "FAIL: Indicator not added to the chart" (индикатор не добавлен на график). Эта проверка имеет решающее значение, поскольку она гарантирует, что мы можем подтвердить, успешно ли применен индикатор к графику, что необходимо для последующих торговых операций и визуального анализа. Обрабатывая оба результата, мы сохраняем прозрачность нашего процесса и получаем информацию о состоянии индикатора на графике. Если мы создадим индикатор и добавим его на график, то сможем сообщить пользователю об успехе в той же структуре кода.
if (isIndicatorAddedToChart){ string message = "\nSUCCESS! THE INDICATOR WAS ADDED TO THE CHART WITH THE FOLLOWING DETAILS:\n"; //--- Prepare success message message += "\nType = "+indicator_type+"\n"; message += "Symbol = "+indicator_symbol+"\n"; message += "Timeframe = "+indicator_timeframe+"\n"; message += "Period = "+IntegerToString(indicator_period)+"\n"; message += "Shift = "+IntegerToString(indicator_shift)+"\n"; message += "Applied Price = "+indicator_app_price+"\n"; message+="\nHAPPY TRADING!"; //--- Add final message line Print(message); //--- Print the message in the terminal sendMessageToTelegram(chat.member_id,message,NULL); //--- Send the success message to Telegram }
Здесь мы проверяем, был ли индикатор успешно добавлен на график, оценивая логическую переменную isIndicatorAddedToChart. При true мы приступаем к подготовке сообщения об успешном завершении, в котором подробно описывается конфигурация индикатора. Начнем с инициализации строковой переменной с именем message с заголовком сообщения об успешном выполнении: "\nSUCCESS! THE INDICATOR WAS ADDED TO THE CHART WITH THE FOLLOWING DETAILS:\n". Затем мы объединяем различные данные об индикаторе, включая его тип, символ, временные рамки, период, сдвиг и примененную цену. Для числовых значений мы используем функцию IntegerToString, обеспечивающую их преобразование в строковый формат для правильной конкатенации.
Собрав всю эту информацию, мы добавляем к сообщению последнюю строку, начиная с "\nHAPPY TRADING!" (Удачной торговли!), чтобы передать позитивный настрой. Затем мы используем функцию Print для вывода полного сообщения в терминал, обеспечивающего четкое подтверждение добавления индикатора и его параметров. Наконец, мы вызываем функцию sendMessageToTelegram, чтобы отправить то же самое сообщение об успешном выполнении в Telegram, гарантируя, что соответствующий чат, идентифицированный по chat.member_id, будет уведомлен об успешной операции. При запуске мы получим следующий результат.
Мы видим, что, несмотря на наличие символа в обзоре рынка, мы все равно возвращаем сообщение об ошибке. Это произошло потому, что мы нарушили правильную структуру имени символа, преобразовав все буквы в верхний регистр. Чтобы восстановить правильную структуру, сохранив при этом правильную интерпретацию и сравнение символов, мы можем использовать множество вариантов, но самый простой из них — напрямую добавить исходное имя символа обратно к переменной-держателю, как показано ниже.
//--- Check for symbol in the list of available symbols and assign it for(int k = 0; k < SymbolsTotal(true); k++) { //--- Loop through all available symbols string selected_symbol = SymbolName(k, true); //--- Get the symbol name StringToUpper(selected_symbol); if (StringCompare(selected_line,selected_symbol,false) == 0){ //--- Compare the line with the symbol name indicator_symbol = SymbolName(k, true); //--- Assign the symbol if a match is found Print("Line @ index ",i," SYMBOL = ",indicator_symbol); //--- Print the found symbol } }
Единственное изменение, произошедшее во фрагменте, показано и выделено желтым цветом. При повторном запуске мы получаем правильный вывод, как показано ниже:
Мы успешно добавили индикатор на график. В Telegram мы получаем ответ об успешном завершении, как показано ниже:
В торговом терминале открывается новый график и добавляется индикатор.
Мы достигли своей цели по автоматическому добавлению технических индикаторов скользящей средней на графики. Аналогичную методологию можно применить и к другим индикаторам, например, к Awesome Oscillator. Используя один и тот же формат для идентификации команды, извлечения соответствующих параметров и выполнения добавления индикатора, мы можем легко интегрировать различные индикаторы в нашу торговую систему, сохраняя согласованность и эффективность на протяжении всего процесса внедрения. Однако нам необходимо протестировать реализацию и убедиться, что все работает нормально.
Тестирование торговой системы на основе индикатора
В этом разделе мы сосредоточимся на проверке функциональности нашей торговой системы на основе индикатора. Тестирование включает в себя проверку того, что индикаторы правильно настроены и адекватно реагируют на команды, получаемые от Telegram. Мы рассмотрим процесс добавления индикаторов на график, убедившись, что все параметры заданы правильно и индикаторы корректно отображаются на графиках.
Для наглядности я добавил видео, демонстрирующее установку и настройку индикаторов в MetaTrader 5, в частности скользящей средней, подчеркивающее, как функционирует система, и подтверждающее ее готовность к реальным торговым сценариям, как показано ниже.
Заключение
В статье мы продемонстрировали, как анализировать и обрабатывать команды индикатора, полученные через Telegram для автоматизации добавления индикаторов в графики MetaTrader 5. Мы оптимизировали процесс, повысили эффективность торговли и снизили вероятность человеческих ошибок.
Благодаря знаниям, полученным в результате этого внедрения, вы будете готовы разрабатывать более сложные системы, включающие дополнительные индикаторы и команды. Созданная основа позволит вам усовершенствовать свои торговые стратегии, адаптироваться к меняющимся рыночным условиям и в конечном итоге улучшить результаты торговли. Надеюсь, статья оказалась для вас понятной и дала идеи, необходимые для улучшения ваших торговых систем. Если у вас возникнут какие-либо вопросы или потребуются дополнительные разъяснения, не стесняйтесь изучать дополнительные источники или экспериментировать с концепциями, представленными в этой серии. Удачной торговли!
Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/15962





- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования