Трейдинг с экономическим календарем MQL5 (Часть 6): Автоматизация входа в сделку с анализом новостей и таймерами обратного отсчета
Введение
В этой статье мы сделаем следующий шаг в нашей серии статей об экономическом календаре на MQL5, автоматизируя вход в сделки на основе анализа новостей в реальном времени. Опираясь на предыдущие улучшения панели (Часть 5), мы теперь интегрируем торговую логику, которая сканирует новости, используя настраиваемые фильтры и временные смещения, сравнивает прогнозные и предыдущие значения и автоматически исполняет ордера на покупку или продажу в зависимости от ожиданий рынка. Мы также внедрим динамические таймеры обратного отсчета, которые отображают оставшееся время до выхода новостей и сбрасывают систему после исполнения, гарантируя, что наша торговая стратегия будет реагировать на изменяющиеся условия. В статье будут рассмотрены следующие темы:
- Понимание требований торговой логики
- Реализация торговой логики на MQL5
- Создание и управление таймерами обратного отсчета
- Тестирование торговой логики
- Заключение
Давайте углубимся и рассмотрим, как эти компоненты работают вместе, чтобы автоматизировать точные и надежные входы в рынок.
Понимание требований торговой логики
Для нашей автоматизированной торговой системы первым шагом будет определение того, какие новостные события являются подходящими кандидатами для сделки. Мы определяем событие-кандидат как событие, которое попадает в конкретный временной интервал (задаваемый смещением) относительно его запланированного времени выхода. Затем мы включим входные данные для торговых режимов, например, торговлю до выхода новостей. Например, в режиме trade before (торговать до) событие будет учитываться только в том случае, если текущее время находится между запланированным временем выхода новости за вычетом смещения (например, 5 минут) и фактическим временем появления новости. Таким образом, мы начнем торговать за 5 минут до фактического выхода новости.
Сортировка имеет решающее значение для поиска лишь необходимых нам новостей. Таким образом, наша система будет использовать несколько фильтров: фильтр валют, чтобы сосредоточиться на выбранных валютных парах, фильтр воздействия, чтобы ограничить события теми, которые имеют выбранный уровень значимости, и временной фильтр, который ограничивает события теми, которые находятся в предопределенном общем диапазоне. Пользователь может настраивать их на панели. Такая многоуровневая сортировка поможет минимизировать шум и гарантировать, что будут обрабатываться только самые важные новости.
Как только новость проходит критерии сортировки, торговая логика сравнивает ее ключевые точки данных, в частности, прогнозируемое значение с предыдущим значением. Если оба значения доступны и не равны нулю, и если прогноз выше предыдущего значения, система откроет ордер на покупку; если прогноз ниже, она откроет ордер на продажу. Если какое-либо из значений отсутствует или они равны, новость будет пропущена. Этот процесс принятия решений позволит советнику преобразовывать необработанные новостные данные в четкие торговые сигналы, автоматизируя точный вход в сделки. Процесс принятия решений и направление торговли полностью зависят от пользователя, но в целях демонстрации мы воспользуемся приведенной выше схемой.
Для визуализации процессов мы воспользуемся данными отладки, а также создадим кнопки и метки на графике прямо над панелью управления для отображения торгуемых новостей и времени, оставшегося до их выхода. Вот как это всё выглядит.

Реализация торговой логики на MQL5
Для реализации торговой логики в MQL5, нам придется включить торговые файлы, которые содержат торговые методы и определить некоторые входные данные, которые позволят пользователю управлять системой, и глобальные переменные, которые мы будем использовать повторно на протяжении всей программы. Чтобы добиться этого, мы определяем их в глобальном масштабе.
#include <Trade\Trade.mqh> // Trading library for order execution CTrade trade; // Global trade object //================== Trade Settings ==================// // Trade mode options: enum ETradeMode { TRADE_BEFORE, // Trade before the news event occurs TRADE_AFTER, // Trade after the news event occurs NO_TRADE, // Do not trade PAUSE_TRADING // Pause trading activity (no trades until resumed) }; input ETradeMode tradeMode = TRADE_BEFORE; // Choose the trade mode // Trade offset inputs: input int tradeOffsetHours = 12; // Offset hours (e.g., 12 hours) input int tradeOffsetMinutes = 5; // Offset minutes (e.g., 5 minutes before) input int tradeOffsetSeconds = 0; // Offset seconds input double tradeLotSize = 0.01; // Lot size for the trade //================== Global Trade Control ==================// // Once a trade is executed for one news event, no further trades occur. bool tradeExecuted = false; // Store the traded event’s scheduled news time for the post–trade countdown. datetime tradedNewsTime = 0; // Global array to store event IDs that have already triggered a trade. int triggeredNewsEvents[];
В глобальном масштабе мы включаем библиотеку Trade\Trade.mqh, library using #include ь для включения исполнения ордеров и объявления глобального объекта CTrade с именем trade для обработки сделок. Мы определяем один из перечисленных типов ETradeMode с опциями TRADE_BEFORE (торговать до), TRADE_AFTER (торговать после), NO_TRADE (не торговать) и PAUSE_TRADING (пауза в торговле), и используем входную переменную tradeMode (по умолчанию TRADE_BEFORE для программы), чтобы определить, когда следует открывать сделки относительно новостей. Кроме того, мы настраиваем "входные" переменные tradeOffsetHours, tradeOffsetMinutes, tradeOffsetSeconds и tradeLotSize, чтобы указать смещение времени и размер сделки, в то время, как глобальные переменные tradeExecuted (логическое значение), tradedNewsTime (дата и время) и массив triggeredNewsEvents (массив int) помогают нам управлять сделками и предотвращать повторную торговлю на одной и той же новости. Затем мы можем включить торговую логику в функцию.
//--- Function to scan for news events and execute trades based on selected criteria //--- It handles both pre-trade candidate selection and post-trade countdown updates void CheckForNewsTrade() { //--- Log the call to CheckForNewsTrade with the current server time Print("CheckForNewsTrade called at: ", TimeToString(TimeTradeServer(), TIME_SECONDS)); //--- If trading is disabled (either NO_TRADE or PAUSE_TRADING), remove countdown objects and exit if(tradeMode == NO_TRADE || tradeMode == PAUSE_TRADING) { //--- Check if a countdown object exists on the chart if(ObjectFind(0, "NewsCountdown") >= 0) { //--- Delete the countdown object from the chart ObjectDelete(0, "NewsCountdown"); //--- Log that the trading is disabled and the countdown has been removed Print("Trading disabled. Countdown removed."); } //--- Exit the function since trading is not allowed return; } //--- Begin pre-trade candidate selection section //--- Define the lower bound of the event time range based on the user-defined start time offset datetime lowerBound = currentTime - PeriodSeconds(start_time); //--- Define the upper bound of the event time range based on the user-defined end time offset datetime upperBound = currentTime + PeriodSeconds(end_time); //--- Log the overall event time range for debugging purposes Print("Event time range: ", TimeToString(lowerBound, TIME_SECONDS), " to ", TimeToString(upperBound, TIME_SECONDS)); //--- Retrieve historical calendar values (news events) within the defined time range MqlCalendarValue values[]; int totalValues = CalendarValueHistory(values, lowerBound, upperBound, NULL, NULL); //--- Log the total number of events found in the specified time range Print("Total events found: ", totalValues); //--- If no events are found, delete any existing countdown and exit the function if(totalValues <= 0) { if(ObjectFind(0, "NewsCountdown") >= 0) ObjectDelete(0, "NewsCountdown"); return; } }
Здесь мы определяем функцию CheckForNewsTrade, которая сканирует новости и совершает сделки на основе выбранных нами критериев. Мы начинаем с регистрации вызова с помощью функции Print, отображающей текущее время сервера, полученное через функцию TimeTradeServer. Затем мы проверяем, отключена ли торговля, сравнивая переменную tradeMode с режимом NO_TRADE или PAUSE_TRADING. Если да, используем функцию ObjectFind, чтобы определить, существует ли объект обратного отсчета NewsCountdown. Если объект найден, он удаляется с использованием ObjectDelete перед выходом из функции.
Затем функция вычисляет общий временной диапазон события, устанавливая lowerBound равным текущему времени за вычетом количества секунд от входного параметра start_time (преобразованного с помощью функции PeriodSeconds) и upperBound к текущему времени плюс секунды из входного параметра end_time. Этот общий временной диапазон затем регистрируется с помощью Print. Наконец, функция вызывает CalendarValueHistory для извлечения всех новостей в пределах определенного временного диапазона; если события не найдены, она очищает все существующие объекты обратного отсчета и выходит, тем самым подготавливая систему к последующему выбору событий-кандидатов и исполнению сделки.
//--- Initialize candidate event variables for trade selection datetime candidateEventTime = 0; string candidateEventName = ""; string candidateTradeSide = ""; int candidateEventID = -1; //--- Loop through all retrieved events to evaluate each candidate for trading for(int i = 0; i < totalValues; i++) { //--- Declare an event structure to hold event details MqlCalendarEvent event; //--- Attempt to populate the event structure by its ID; if it fails, skip to the next event if(!CalendarEventById(values[i].event_id, event)) continue; //----- Apply Filters ----- //--- If currency filtering is enabled, check if the event's currency matches the selected filters if(enableCurrencyFilter) { //--- Declare a country structure to hold country details MqlCalendarCountry country; //--- Populate the country structure based on the event's country ID CalendarCountryById(event.country_id, country); //--- Initialize a flag to determine if there is a matching currency bool currencyMatch = false; //--- Loop through each selected currency filter for(int k = 0; k < ArraySize(curr_filter_selected); k++) { //--- Check if the event's country currency matches the current filter selection if(country.currency == curr_filter_selected[k]) { //--- Set flag to true if a match is found and break out of the loop currencyMatch = true; break; } } //--- If no matching currency is found, log the skip and continue to the next event if(!currencyMatch) { Print("Event ", event.name, " skipped due to currency filter."); continue; } } //--- If importance filtering is enabled, check if the event's impact matches the selected filters if(enableImportanceFilter) { //--- Initialize a flag to determine if the event's impact matches any filter selection bool impactMatch = false; //--- Loop through each selected impact filter option for(int k = 0; k < ArraySize(imp_filter_selected); k++) { //--- Check if the event's importance matches the current filter selection if(event.importance == imp_filter_selected[k]) { //--- Set flag to true if a match is found and break out of the loop impactMatch = true; break; } } //--- If no matching impact is found, log the skip and continue to the next event if(!impactMatch) { Print("Event ", event.name, " skipped due to impact filter."); continue; } } //--- If time filtering is enabled and the event time exceeds the upper bound, skip the event if(enableTimeFilter && values[i].time > upperBound) { Print("Event ", event.name, " skipped due to time filter."); continue; } //--- Check if the event has already triggered a trade by comparing its ID to recorded events bool alreadyTriggered = false; //--- Loop through the list of already triggered news events for(int j = 0; j < ArraySize(triggeredNewsEvents); j++) { //--- If the event ID matches one that has been triggered, mark it and break out of the loop if(triggeredNewsEvents[j] == values[i].event_id) { alreadyTriggered = true; break; } } //--- If the event has already triggered a trade, log the skip and continue to the next event if(alreadyTriggered) { Print("Event ", event.name, " already triggered a trade. Skipping."); continue; }
Здесь мы инициализируем переменные-кандидаты событий с помощью переменной даты и времени (candidateEventTime), двух "строковых" переменных (candidateEventName и candidateTradeSide) и переменной int ("candidateEventID"), установленной на -1. Далее мы перебираем каждое событие, полученное функцией CalendarValueHistory (хранящейся в массиве структур MqlCalendarValue) и используем функцию CalendarEventById для заполнения структуры MqlCalendarEvent данными о событии.
Далее мы применяем наши фильтры: если включена сортировка валют, мы извлекаем соответствующую структуру MqlCalendarCountry с помощью CalendarCountryById и проверяем, соответствует ли ее поле currency (валюта) какой-либо записи в массиве curr_filter_selected; если нет, мы регистрируем сообщение и пропускаем событие. Аналогично, если включена сортировка по важности, мы просматриваем массив imp_filter_selected, чтобы убедиться, что "важность" события соответствует одному из выбранных уровней, регистрируя и пропуская событие, если это не так.
Наконец, мы проверяем, инициировало ли уже событие сделку, сравнивая его идентификатор события с идентификаторами, хранящимися в массиве triggeredNewsEvents. Если да, мы регистрируем его и пропускаем. Этот цикл гарантирует, что только события, соответствующие всем критериям — валюта, важность, временной диапазон и уникальность — рассматриваются как кандидаты на исполнение сделки. Если все прошло успешно и у нас есть события, мы можем приступить к их сортировке по временным рамкам, установленному пользователем.
//--- For TRADE_BEFORE mode, check if the current time is within the valid window (event time minus offset to event time) if(tradeMode == TRADE_BEFORE) { if(currentTime >= (values[i].time - offsetSeconds) && currentTime < values[i].time) { //--- Retrieve the forecast and previous values for the event MqlCalendarValue calValue; //--- If unable to retrieve calendar values, log the error and skip this event if(!CalendarValueById(values[i].id, calValue)) { Print("Error retrieving calendar value for event: ", event.name); continue; } //--- Get the forecast value from the calendar data double forecast = calValue.GetForecastValue(); //--- Get the previous value from the calendar data double previous = calValue.GetPreviousValue(); //--- If either forecast or previous is zero, log the skip and continue to the next event if(forecast == 0.0 || previous == 0.0) { Print("Skipping event ", event.name, " because forecast or previous value is empty."); continue; } //--- If forecast equals previous, log the skip and continue to the next event if(forecast == previous) { Print("Skipping event ", event.name, " because forecast equals previous."); continue; } //--- If this candidate event is earlier than any previously found candidate, record its details if(candidateEventTime == 0 || values[i].time < candidateEventTime) { candidateEventTime = values[i].time; candidateEventName = event.name; candidateEventID = (int)values[i].event_id; candidateTradeSide = (forecast > previous) ? "BUY" : "SELL"; //--- Log the candidate event details including its time and trade side Print("Candidate event: ", event.name, " with event time: ", TimeToString(values[i].time, TIME_SECONDS), " Side: ", candidateTradeSide); } } }
Здесь мы оцениваем потенциальные новостные события при работе в режиме TRADE_BEFORE. Мы проверяем, соответствует ли текущее время, полученное с помощью функции TimeTradeServer допустимому торговому окну, которое простирается от запланированного времени события за вычетом определяемого пользователем смещения (offsetSeconds) до точного времени события, как определено ниже.
//--- Get the current trading server time datetime currentTime = TimeTradeServer(); //--- Calculate the offset in seconds based on trade offset hours, minutes, and seconds int offsetSeconds = tradeOffsetHours * 3600 + tradeOffsetMinutes * 60 + tradeOffsetSeconds;
Если условие выполняется, мы извлекаем прогноз события и предыдущие значения с помощью функции CalendarValueById для заполнения структуры MqlCalendarValue. Если извлечение не удалось, регистрируем сообщение об ошибке и пропускаем событие. Затем мы извлекаем прогнозные и предыдущие значения, используя методы GetForecastValue и GetPreviousValue соответственно. Если какое-либо из значений нулевое или они равны, мы регистрируем сообщение и переходим к следующему событию, чтобы гарантировать обработку только событий со значимыми данными.
Если событие соответствует требованиям и происходит раньше любого ранее определенного кандидата, мы обновляем переменные: candidateEventTime сохраняет время события, candidateEventName содержит имя события, candidateEventID записывает идентификатор события, а candidateTradeSide определяет направление сделки - покупка (если прогноз больше предыдущего значения) или продажа (если прогноз ниже). Наконец, мы регистрируем сведения о выбранном событии-кандидате, гарантируя отслеживание самого раннего допустимого события для исполнения сделки. Затем мы можем выбрать событие для исполнения сделки.
//--- If a candidate event has been selected and the trade mode is TRADE_BEFORE, attempt to execute the trade if(tradeMode == TRADE_BEFORE && candidateEventTime > 0) { //--- Calculate the target time to start trading by subtracting the offset from the candidate event time datetime targetTime = candidateEventTime - offsetSeconds; //--- Log the candidate target time for debugging purposes Print("Candidate target time: ", TimeToString(targetTime, TIME_SECONDS)); //--- Check if the current time falls within the trading window (target time to candidate event time) if(currentTime >= targetTime && currentTime < candidateEventTime) { //--- Loop through events again to get detailed information for the candidate event for(int i = 0; i < totalValues; i++) { //--- Identify the candidate event by matching its time if(values[i].time == candidateEventTime) { //--- Declare an event structure to store event details MqlCalendarEvent event; } } } }
Мы проверяем, выбрано ли событие-кандидат и установлен ли режим торговли TRADE_BEFORE, путем проверки того, что candidateEventTime больше нуля. Затем мы вычисляем targetTime, вычитая заданное пользователем смещение (offsetSeconds) из запланированного времени события-кандидата, и регистрируем это целевое время для отладки с помощью функции Print. Затем мы определяем, попадает ли текущее время в допустимое торговое окно — между targetTime и временем события-кандидата — и если да, то мы проходим по массиву событий, чтобы идентифицировать событие-кандидат, сопоставляя его время, чтобы затем можно было приступить к получению дополнительных сведений и выполнению сделки.
//--- Attempt to retrieve the event details; if it fails, skip to the next event if(!CalendarEventById(values[i].event_id, event)) continue; //--- If the current time is past the event time, log the skip and continue if(currentTime >= values[i].time) { Print("Skipping candidate ", event.name, " because current time is past event time."); continue; } //--- Retrieve detailed calendar values for the candidate event MqlCalendarValue calValue; //--- If retrieval fails, log the error and skip the candidate if(!CalendarValueById(values[i].id, calValue)) { Print("Error retrieving calendar value for candidate event: ", event.name); continue; } //--- Get the forecast value for the candidate event double forecast = calValue.GetForecastValue(); //--- Get the previous value for the candidate event double previous = calValue.GetPreviousValue(); //--- If forecast or previous is zero, or if they are equal, log the skip and continue if(forecast == 0.0 || previous == 0.0 || forecast == previous) { Print("Skipping candidate ", event.name, " due to invalid forecast/previous values."); continue; } //--- Construct a news information string for the candidate event string newsInfo = "Trading on news: " + event.name + " (Time: " + TimeToString(values[i].time, TIME_SECONDS)+")"; //--- Log the news trading information Print(newsInfo); //--- Create a label on the chart to display the news trading information createLabel1("NewsTradeInfo", 355, 22, newsInfo, clrBlue, 11);
Прежде чем входит в сделку, мы пытаемся получить подробную информацию о событии-кандидате с помощью функции CalendarEventById для заполнения структуры MqlCalendarEvent. Если извлечение не удалось, мы немедленно переходим к следующему событию. Затем мы проверяем, прошло ли уже текущее время (полученное с помощью TimeTradeServer) относительно запланированного времени события-кандидата. Если это так, мы регистрируем сообщение и пропускаем обработку этого события.
Затем мы извлекаем подробные значения календаря для события, используя CalendarValueById для заполнения структуры MqlCalendarValue, затем извлекаем значения forecast и previous с помощью методов GetForecastValue и GetPreviousValue соответственно. Если какое-либо из значений равно нулю или оба равны, мы регистрируем причину и пропускаем событие-кандидат. Наконец, мы создаем строку, содержащую ключевую новостную информацию, и регистрируем ее, а также отображаем эту информацию на графике с помощью функции createLabel1. Фрагмент кода функции приведен ниже.
//--- Function to create a label on the chart with specified properties bool createLabel1(string objName, int x, int y, string text, color txtColor, int fontSize) { //--- Attempt to create the label object; if it fails, log the error and return false if(!ObjectCreate(0, objName, OBJ_LABEL, 0, 0, 0)) { //--- Print error message with the label name and the error code Print("Error creating label ", objName, " : ", GetLastError()); //--- Return false to indicate label creation failure return false; } //--- Set the horizontal distance (X coordinate) for the label ObjectSetInteger(0, objName, OBJPROP_XDISTANCE, x); //--- Set the vertical distance (Y coordinate) for the label ObjectSetInteger(0, objName, OBJPROP_YDISTANCE, y); //--- Set the text that will appear on the label ObjectSetString(0, objName, OBJPROP_TEXT, text); //--- Set the color of the label's text ObjectSetInteger(0, objName, OBJPROP_COLOR, txtColor); //--- Set the font size for the label text ObjectSetInteger(0, objName, OBJPROP_FONTSIZE, fontSize); //--- Set the font style to "Arial Bold" for the label text ObjectSetString(0, objName, OBJPROP_FONT, "Arial Bold"); //--- Set the label's anchor corner to the top left of the chart ObjectSetInteger(0, objName, OBJPROP_CORNER, CORNER_LEFT_UPPER); //--- Redraw the chart to reflect the new label ChartRedraw(); //--- Return true indicating that the label was created successfully return true; }
Логика этой функции не нова, и нам не нужно ее подробно объяснять, поскольку мы уже сделали это при создании панели управления. Поэтому мы просто переходим к открытию сделок на основе полученных значений.
//--- Initialize a flag to store the result of the trade execution bool tradeResult = false; //--- If the candidate trade side is BUY, attempt to execute a buy order if(candidateTradeSide == "BUY") { tradeResult = trade.Buy(tradeLotSize, _Symbol, 0, 0, 0, event.name); } //--- Otherwise, if the candidate trade side is SELL, attempt to execute a sell order else if(candidateTradeSide == "SELL") { tradeResult = trade.Sell(tradeLotSize, _Symbol, 0, 0, 0, event.name); } //--- If the trade was executed successfully, update the triggered events and trade flags if(tradeResult) { Print("Trade executed for candidate event: ", event.name, " Side: ", candidateTradeSide); int size = ArraySize(triggeredNewsEvents); ArrayResize(triggeredNewsEvents, size + 1); triggeredNewsEvents[size] = (int)values[i].event_id; tradeExecuted = true; tradedNewsTime = values[i].time; } else { //--- If trade execution failed, log the error message with the error code Print("Trade execution failed for candidate event: ", event.name, " Error: ", GetLastError()); } //--- Break out of the loop after processing the candidate event break;
Сначала мы инициализируем логический флаг tradeResult для сохранения результата нашей попытки входа в рынок. Затем мы проверяем candidateTradeSide. При BUY (покупка) вызываем функцию trade.Buy с указанным tradeLotSize, символом (_Symbol), а также используем имя события в качестве комментария для уникальности и более легкой идентификации; если candidateTradeSide — это SELL, мы аналогичным образом вызываем trade.Sell. Если сделка выполнена успешно (т.е. tradeResult равен true), мы регистрируем сведения о выполнении, обновляем наш массив triggeredNewsEvents, изменяя его размер с помощью функции ArrayResize, добавляем идентификатор события, устанавливаем tradeExecuted на 'true' и записываем запланированное время события в tradedNewsTime. В противном случае мы регистрируем сообщение об ошибке с помощью GetLastError, а затем прерываем цикл, чтобы предотвратить обработку любых дальнейших событий-кандидатов. Вот пример сделки, открытой по диапазону событий.

После открытия сделки нам осталось только инициализировать логику обратного отсчета событий. Сделаем это в следующем разделе.
Создание и управление таймерами обратного отсчета
Для создания и управления таймерами обратного отсчета нам понадобятся некоторые вспомогательные функции для создания кнопки, которая будет удерживать время, а также для обновления метки при необходимости.
//--- Function to create a button on the chart with specified properties bool createButton1(string objName, int x, int y, int width, int height, string text, color txtColor, int fontSize, color bgColor, color borderColor) { //--- Attempt to create the button object; if it fails, log the error and return false if(!ObjectCreate(0, objName, OBJ_BUTTON, 0, 0, 0)) { //--- Print error message with the button name and the error code Print("Error creating button ", objName, " : ", GetLastError()); //--- Return false to indicate button creation failure return false; } //--- Set the horizontal distance (X coordinate) for the button ObjectSetInteger(0, objName, OBJPROP_XDISTANCE, x); //--- Set the vertical distance (Y coordinate) for the button ObjectSetInteger(0, objName, OBJPROP_YDISTANCE, y); //--- Set the width of the button ObjectSetInteger(0, objName, OBJPROP_XSIZE, width); //--- Set the height of the button ObjectSetInteger(0, objName, OBJPROP_YSIZE, height); //--- Set the text that will appear on the button ObjectSetString(0, objName, OBJPROP_TEXT, text); //--- Set the color of the button's text ObjectSetInteger(0, objName, OBJPROP_COLOR, txtColor); //--- Set the font size for the button text ObjectSetInteger(0, objName, OBJPROP_FONTSIZE, fontSize); //--- Set the font style to "Arial Bold" for the button text ObjectSetString(0, objName, OBJPROP_FONT, "Arial Bold"); //--- Set the background color of the button ObjectSetInteger(0, objName, OBJPROP_BGCOLOR, bgColor); //--- Set the border color of the button ObjectSetInteger(0, objName, OBJPROP_BORDER_COLOR, borderColor); //--- Set the button's anchor corner to the top left of the chart ObjectSetInteger(0, objName, OBJPROP_CORNER, CORNER_LEFT_UPPER); //--- Enable the background display for the button ObjectSetInteger(0, objName, OBJPROP_BACK, true); //--- Redraw the chart to reflect the new button ChartRedraw(); //--- Return true indicating that the button was created successfully return true; } //--- Function to update the text of an existing label bool updateLabel1(string objName, string text) { //--- Check if the label exists on the chart; if not, log the error and return false if(ObjectFind(0, objName) < 0) { //--- Print error message indicating that the label was not found Print("updateLabel1: Object ", objName, " not found."); //--- Return false because the label does not exist return false; } //--- Update the label's text property with the new text ObjectSetString(0, objName, OBJPROP_TEXT, text); //--- Redraw the chart to update the label display ChartRedraw(); //--- Return true indicating that the label was updated successfully return true; } //--- Function to update the text of an existing label bool updateLabel1(string objName, string text) { //--- Check if the label exists on the chart; if not, log the error and return false if(ObjectFind(0, objName) < 0) { //--- Print error message indicating that the label was not found Print("updateLabel1: Object ", objName, " not found."); //--- Return false because the label does not exist return false; } //--- Update the label's text property with the new text ObjectSetString(0, objName, OBJPROP_TEXT, text); //--- Redraw the chart to update the label display ChartRedraw(); //--- Return true indicating that the label was updated successfully return true; }
Здесь мы просто вводим вспомогательные функции, которые позволят нам создать кнопку таймера, а также функцию обновления метки. Нам не нужно объяснять функции, поскольку мы уже подробно описали их логику в аналогичных функциях в предыдущих частях серии. Поэтому мы просто приступим к их реализации.
//--- Begin handling the post-trade countdown scenario if(tradeExecuted) { //--- If the current time is before the traded news time, display the countdown until news release if(currentTime < tradedNewsTime) { //--- Calculate the remaining seconds until the traded news time int remainingSeconds = (int)(tradedNewsTime - currentTime); //--- Calculate hours from the remaining seconds int hrs = remainingSeconds / 3600; //--- Calculate minutes from the remaining seconds int mins = (remainingSeconds % 3600) / 60; //--- Calculate seconds remainder int secs = remainingSeconds % 60; //--- Construct the countdown text string string countdownText = "News in: " + IntegerToString(hrs) + "h " + IntegerToString(mins) + "m " + IntegerToString(secs) + "s"; //--- If the countdown object does not exist, create it with a blue background if(ObjectFind(0, "NewsCountdown") < 0) { createButton1("NewsCountdown", 50, 17, 300, 30, countdownText, clrWhite, 12, clrBlue, clrBlack); //--- Log that the post-trade countdown was created Print("Post-trade countdown created: ", countdownText); } else { //--- If the countdown object exists, update its text updateLabel1("NewsCountdown", countdownText); //--- Log that the post-trade countdown was updated Print("Post-trade countdown updated: ", countdownText); } } else { //--- If current time is past the traded news time, calculate elapsed time since trade int elapsed = (int)(currentTime - tradedNewsTime); //--- If less than 15 seconds have elapsed, show a reset countdown if(elapsed < 15) { //--- Calculate the remaining delay for reset int remainingDelay = 15 - elapsed; //--- Construct the reset countdown text string countdownText = "News Released, resetting in: " + IntegerToString(remainingDelay) + "s"; //--- If the countdown object does not exist, create it with a red background if(ObjectFind(0, "NewsCountdown") < 0) { createButton1("NewsCountdown", 50, 17, 300, 30, countdownText, clrWhite, 12, clrRed, clrBlack); //--- Set the background color property explicitly to red ObjectSetInteger(0,"NewsCountdown",OBJPROP_BGCOLOR,clrRed); //--- Log that the post-trade reset countdown was created Print("Post-trade reset countdown created: ", countdownText); } else { //--- If the countdown object exists, update its text and background color updateLabel1("NewsCountdown", countdownText); ObjectSetInteger(0,"NewsCountdown",OBJPROP_BGCOLOR,clrRed); //--- Log that the post-trade reset countdown was updated Print("Post-trade reset countdown updated: ", countdownText); } } else { //--- If 15 seconds have elapsed since traded news time, log the reset action Print("News Released. Resetting trade status after 15 seconds."); //--- If the countdown object exists, delete it from the chart if(ObjectFind(0, "NewsCountdown") >= 0) ObjectDelete(0, "NewsCountdown"); //--- Reset the tradeExecuted flag to allow new trades tradeExecuted = false; } } //--- Exit the function as post-trade processing is complete return; }
Здесь мы подробно рассмотрим сценарий обратного отсчета после совершения сделки. После совершения сделки мы сначала используем функцию TimeTradeServer для получения текущего времени сервера и его сравнения с tradedNewsTime, в котором хранится запланированное время выхода события-кандидата. Если текущее время еще предшествует tradedNewsTime, мы вычисляем оставшиеся секунды и преобразуем их в часы, минуты и секунды, создавая строку обратного отсчета в формате "News in: __h __m __s" (новости через: __ч __м __с) с помощью функции IntegerToString.
Затем мы проверяем наличие объекта NewsCountdown с помощью ObjectFind и либо создаем его (применяя пользовательскую функцию createButton1) с координатами X=50, Y=17, шириной 300, высотой 30 и синим фоном, либо обновляем (с опомщью updateLabel1), если объект уже существует. Однако если текущее время превысило tradedNewsTime, мы вычисляем прошедшее время. Если прошедшее время меньше 15 секунд, мы отображаем сообщение о сбросе в объекте обратного отсчета — "News Released, resetting in: XXs" (вышла новость, сброс через: XX с) — и явно устанавливаем его фоновый цвет на красный с помощью функции ObjectSetInteger.
По завершении 15-секундного периода сброса мы удаляем объект NewsCountdown и сбрасываем флаг tradeExecuted, чтобы разрешить новые сделки, гарантируя, что наша система динамически реагирует на изменения в сроках новостей и поддерживает контролируемое исполнение сделок. Также нам необходимо показать обратный отсчет, если у нас есть совершенная сделка. Сделаем это с помощью следующей логики.
if(currentTime >= targetTime && currentTime < candidateEventTime) { //--- } else { //--- If current time is before the candidate event window, show a pre-trade countdown int remainingSeconds = (int)(candidateEventTime - currentTime); int hrs = remainingSeconds / 3600; int mins = (remainingSeconds % 3600) / 60; int secs = remainingSeconds % 60; //--- Construct the pre-trade countdown text string countdownText = "News in: " + IntegerToString(hrs) + "h " + IntegerToString(mins) + "m " + IntegerToString(secs) + "s"; //--- If the countdown object does not exist, create it with specified dimensions and blue background if(ObjectFind(0, "NewsCountdown") < 0) { createButton1("NewsCountdown", 50, 17, 300, 30, countdownText, clrWhite, 12, clrBlue, clrBlack); //--- Log that the pre-trade countdown was created Print("Pre-trade countdown created: ", countdownText); } else { //--- If the countdown object exists, update its text updateLabel1("NewsCountdown", countdownText); //--- Log that the pre-trade countdown was updated Print("Pre-trade countdown updated: ", countdownText); } }
Если текущее время не попадает в торговое окно события-кандидата, то есть если текущее время не больше или равно targetTime (рассчитываемому как запланированное время события-кандидата за вычетом смещения) и все еще не меньше запланированного времени события-кандидата, мы предполагаем, что текущее время все еще опережает торговое окно, поэтому мы вычисляем оставшееся время до события-кандидата, вычитая текущее время из запланированного времени события-кандидата, а затем преобразуем эту разницу в часы, минуты и секунды.
Используя IntegerToString, мы формируем текстовую строку обратного отсчета в формате "News in: __h __m __s". Затем мы используем функцию ObjectFind для проверки того, существует ли уже объект NewsCountdown. Если нет, создаем его с помощью функции createButton1 с указанными размерами (X=50, Y=17, ширина=300, высота=30) и синим фоном, регистрируя создание предторгового обратного отсчета, в противном случае обновляем его текст с помощью updateLabel1 и регистрируем обновление. Наконец, если после анализа не выбрано ни одного события, мы просто удаляем наши объекты.
//--- If no candidate event is selected, delete any existing countdown and trade info objects if(ObjectFind(0, "NewsCountdown") >= 0) { ObjectDelete(0, "NewsCountdown"); ObjectDelete(0, "NewsTradeInfo"); //--- Log that the pre-trade countdown was deleted Print("Pre-trade countdown deleted."); }
Если ни одно событие-кандидат не выбрано, то есть, если ни одно событие не соответствует критериям для выполнения сделки, мы проверяем наличие объекта NewsCountdown с помощью функции ObjectFind. Если он найден, удаляем оба объекта NewsCountdown и NewsTradeInfo с графика, вызывая функцию ObjectDelete, чтобы избавиться от устаревшей информации об обратном отсчете или торговле.
Однако пользователь может явно завершить программу, а это значит, что в такой ситуации нам все равно придется очищать наш график. Таким образом, мы можем определить функцию, которая легко справится с очисткой.
//--- Function to delete trade-related objects from the chart and redraw the chart void deleteTradeObjects(){ //--- Delete the countdown object from the chart ObjectDelete(0, "NewsCountdown"); //--- Delete the news trade information label from the chart ObjectDelete(0, "NewsTradeInfo"); //--- Redraw the chart to reflect the deletion of objects ChartRedraw(); }
После определения функции мы просто вызываем ее в обработчике событий OnDeinit, в котором мы также уничтожаем существующую панель управления, обеспечивая полную очистку, как выделено желтым цветом ниже.
//+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason){ //--- destroy_Dashboard(); deleteTradeObjects(); }
Осталось только отслеживать обновленную информацию фильтра, когда пользователь нажимает на панель инструментов, чтобы мы могли оставаться в курсе последних данных. Это значит, что нам придется отслеживать события в обработчике событий OnChartEvent. Создадим функцию, которая позволяет легко это реализовать.
//--- Function to log active filter selections in the Experts log void UpdateFilterInfo() { //--- Initialize the filter information string with the prefix "Filters: " string filterInfo = "Filters: "; //--- Check if the currency filter is enabled if(enableCurrencyFilter) { //--- Append the currency filter label to the string filterInfo += "Currency: "; //--- Loop through each selected currency filter option for(int i = 0; i < ArraySize(curr_filter_selected); i++) { //--- Append the current currency filter value filterInfo += curr_filter_selected[i]; //--- If not the last element, add a comma separator if(i < ArraySize(curr_filter_selected) - 1) filterInfo += ","; } //--- Append a semicolon to separate this filter's information filterInfo += "; "; } else { //--- Indicate that the currency filter is turned off filterInfo += "Currency: Off; "; } //--- Check if the importance filter is enabled if(enableImportanceFilter) { //--- Append the impact filter label to the string filterInfo += "Impact: "; //--- Loop through each selected impact filter option for(int i = 0; i < ArraySize(imp_filter_selected); i++) { //--- Append the string representation of the current importance filter value filterInfo += EnumToString(imp_filter_selected[i]); //--- If not the last element, add a comma separator if(i < ArraySize(imp_filter_selected) - 1) filterInfo += ","; } //--- Append a semicolon to separate this filter's information filterInfo += "; "; } else { //--- Indicate that the impact filter is turned off filterInfo += "Impact: Off; "; } //--- Check if the time filter is enabled if(enableTimeFilter) { //--- Append the time filter information with the upper limit filterInfo += "Time: Up to " + EnumToString(end_time); } else { //--- Indicate that the time filter is turned off filterInfo += "Time: Off"; } //--- Print the complete filter information to the Experts log Print("Filter Info: ", filterInfo); }
Создадим пустую функцию UpdateFilterInfo. Сначала мы инициализируем строку с префиксом "Filters:" (фильтры:), а затем проверяем, включен ли фильтр валют. Если да, то добавляем Currency: (валюта:) и проходим по массиву curr_filter_selected с помощью ArraySize, добавляя каждую валюту (разделяя их запятыми) и завершая точкой с запятой. Если фильтр отключен, то просто записываем "Currency: Off;". Затем мы выполняем аналогичный процесс для фильтра важности: если он включен, мы добавляем "Impact: " (важность:) и перебираем imp_filter_selected, преобразуя каждый выбранный уровень воздействия в строку EnumToString перед их добавлением или указываем "Impact: Off; ", если не включен.
Наконец, мы обращаемся к фильтру времени, добавляя "Time: Up to " (время: до) вместе со строковым представлением входного параметра "end_time" (снова с помощью EnumToString) или Time: Off, если фильтр отключен. После объединения всех сегментов мы выводим полную информацию о фильтре в журнале "Эксперты", используя функцию Print. Это дает нам четкую картину действующих фильтров в реальном времени для устранения неполадок и проверки. Затем мы вызываем функции в обработчике OnChartEvent, а также OnTick.
//+------------------------------------------------------------------+ //| OnChartEvent handler function | //+------------------------------------------------------------------+ void OnChartEvent( const int id, // event ID const long& lparam, // long type event parameter const double& dparam, // double type event parameter const string& sparam // string type event parameter ){ if (id == CHARTEVENT_OBJECT_CLICK){ //--- Check if the event is a click on an object UpdateFilterInfo(); CheckForNewsTrade(); } } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick(){ //--- UpdateFilterInfo(); CheckForNewsTrade(); if (isDashboardUpdate){ update_dashboard_values(curr_filter_selected,imp_filter_selected); } }
После запуска программы мы получили следующий результат.

Мы видим, что можем открывать сделки на основе выбранных настроек, а при торговле на новости, мы создаем таймер обратного отсчета и метку для отображения и обновления информации, а затем регистрируем обновления. Осталось только проверить нашу логику. Сделаем это в следующем разделе.
Тестирование торговой логики
Что касается тестирования на истории, мы ждали торговых новостей в реальном времени, и по результатам тестирования получили результат, показанный на видео ниже.
Заключение
Мы успешно интегрировали автоматизированный ввод торговых операций в нашу систему экономического календаря MQL5 с помощью настраиваемых фильтров, точных временных смещений и динамических таймеров обратного отсчета. Наше решение сканирует новостные события, сравнивает прогнозные и предыдущие значения и автоматически исполняет ордера на покупку или продажу на основе четких календарных сигналов.
Однако необходимы дальнейшие усовершенствования для адаптации системы к реальным торговым условиям. Мы призываем к постоянной разработке и тестированию, особенно в части совершенствования управления рисками и точной настройки критериев сортировки, для обеспечения оптимальной производительности. Удачной торговли!
Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/17271
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.
Знакомство с языком MQL5 (Часть 13): Руководство для начинающих по созданию пользовательских индикаторов (II)
Моделирование рынка (Часть 14): Сокеты (VIII)
Моделирование рынка (Часть 15): Сокеты (IX)
Разрабатываем менеджер терминалов (Часть 3): Получаем информацию о счёте и добавляем конфигурацию
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования
Здравствуйте! Спасибо за проделанную работу.
У меня проблемы с отображением таблицы на 2К мониторе.
Здравствуйте! Спасибо за проделанную работу.
У меня проблемы с отображением таблицы на 2К мониторе.
Здравствуйте. Добро пожаловать. Для этого нужно изменить размер шрифтов, чтобы все идеально подходило.