English
preview
От новичка до эксперта: Создание анимированного советника для новостей в MQL5 (I)

От новичка до эксперта: Создание анимированного советника для новостей в MQL5 (I)

MetaTrader 5Примеры |
91 0
Clemence Benjamin
Clemence Benjamin

Содержание:


Введение

Сегодня мы стремимся устранить распространенное ограничение в доступе к экономическим новостям и календарным событиям в терминале MetaTrader 5, в частности, во время анализа активных графиков.

В терминале MetaTrader 5 обе вкладки "Новости" (News) и "Экономический календарь» (Economic Calendar) доступны под панелью инструментов. На этих вкладках представлена важная информация, полученная от авторитетных поставщиков новостей. Однако, весьма важно понимать разницу между ними:

  • Вкладка «Новости»: Отображает заголовки и обновления, которые уже выпущены.
  • «Экономический календарь»: Содержит расписание предстоящих экономических событий, распределенных по дате, времени и важности, что полезно для планирования на будущее.

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

Accessing News and Calendar on MetaTrader 5

Доступ к вкладкам «Новости» и «Календарь» в MetaTrader 5.

На опубликованном ранее скриншоте можно заметить, что MetaTrader 5 предоставляет доступ как к «Новостям», так и к Экономическому календарю в рамках единой интегрированной среды. Однако становится очевидным ключевое ограничение: чтобы просмотреть любую из этих функций, пользователь должен вручную перейти к окну Панель инструментов.

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

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

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

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

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


Концепция

Чтобы воплотить эту идею в жизнь, мы воспользуемся Стандартной библиотекой MQL5, которая предоставляет надежные инструменты для разработки графического интерфейса. В частности, будем использовать класс CCanvas, расположенный в файле MQL5\Include\Canvas\Canvas.mqh. Этот класс позволяет нам создавать прямоугольные прозрачные графические поверхности, идеально подходящие для наложения динамического контента, такого как заголовки новостей и обновления экономических событий, непосредственно на график.

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

Фрагмент определения заголовка из Google search.

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

The news headline concept

Концепция: Извлекать данные экономического календаря с помощью API MQL5 и отображать предстоящие события непосредственно на графике с помощью нашего настраиваемого советника «Заголовки новостей». 

Чтобы упростить этот этап разработки, начнем с того, что сосредоточимся на двух основных компонентах:

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

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

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

На первый взгляд, трейдер, использующий эту систему, сможет:
  • Мгновенно определять важные предстоящие события.
  • Следите, как скоро они произойдут (например, через часы или минуты).
  • Распознавать ожидаемый уровень их воздействия, что позволяет трейдеру применять более осторожную или обоснованную стратегию.

Рассмотрев основные концепции, переходим к более глубокому изучению деталей реализации.


Реализация

Освоение заголовка Canvas

Класс CCanvas на MQL5 - это мощная и универсальная утилита, предназначенная для создания пользовательских графических интерфейсов и визуальных элементов непосредственно в графиках MetaTrader 5, а также управления ими. По своей сути, CCanvas позволяет разработчикам создавать растровые поверхности в памяти, которые отображаются с помощью объектов диаграммы, таких как OBJ_BITMAP и OBJ_BITMAP_LABEL. Это позволяет динамично отображать графический контент, включая линии, фигуры, полигоны и текст, поверх диаграмм, не вмешиваясь в работу собственных элементов диаграммы. Класс обеспечивает низкоуровневый контроль над рендерингом через пиксельный буфер (m_pixels[]), а также такие функции, как CreateBitmapLabel() для инициализации и Update() для обновления выходных данных.

 CCanvas обеспечивает эффективное управление ресурсами: разработчики могут загружать или сохранять растровые изображения, присоединять / отсоединять холсты от диаграмм и манипулировать пикселями напрямую с помощью PixelSet() или PixelGet(), что делает его идеальным для приложений, чувствительных к производительности. Его гибкость распространяется на цветовые форматы (ARGB, XRGB с прозрачностью), рендеринг многоугольников (включая невыпуклые заливки) и интерфейсы многоуровневых диаграмм.

На практике CCanvas открывает двери для сложной разработки пользовательского интерфейса на MetTrader 5. Он обычно используется для создания пользовательских индикаторов (например, сглаженных или затененных наложений), интерактивных инструментов (например, линий тренда с пользовательскими заглавными буквами), информационных панелей визуализации торговли и полнофункциональных графических панелей с кнопками, ползунками или даже бегущей строкой новостей. Хотя привязка к процессору ограничивает возможности рендеринга в условиях сверхвысокого разрешения, точность на уровне пикселей и полная настраиваемость делают его незаменимым для высококлассных графических интерфейсов.

Освоение вкладки Экономический календарь

При работе с экономическим календарем MetaTrader 5, самое первое, что нужно понять, - это то, что каждое событие привязано к определенной стране (или экономическому союзу) с помощью уникального идентификатора страны. В MQL5 это представлено структурой MqlCalendarCountry, которая включает в себя такие поля, как id (код ISO 3166-1), название страны, двухбуквенный код, код валюты и символ, и даже удобное для URL-адреса название страны. Запросив список записей MqlCalendarCountry в календаре один раз, вы получите доступ ко всем атрибутам, необходимым для фильтрации или группировки событий по регионам. Каждое событие календаря затем ссылается на свою страну через поле country_id в структуре MqlCalendarEvent.

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

Фактические запланированные события этих типов — с временными метками, фактическими и прогнозируемыми значениями, а также любыми изменениями — хранятся в отдельной таблице структур MqlCalendarValue. Каждая запись MqlCalendarValue содержит event_id (ссылку на шаблон в MqlCalendarEvent), точное время и период, а также четыре числовых поля (actual_value, forecast_value, prev_value, revised_prev_value), которые могут быть, а могут и не быть еще заполнены. Вспомогательные методы, такие как HasActualValue() и GetActualValue(), упрощают проверку и извлечение реальных значений (автоматически уменьшаемых по сравнению с внутренним представлением “ppm” в календаре).

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

Советник «Заголовки новостей»

Настройка пользовательских элементов управления

Во-первых, мы решаем, какие параметры трейдеры должны иметь возможность настраивать. Мы показываем скорость прокрутки для каждой важной полосы (InpSpeedHigh, InpSpeedMed, InpSpeedLow), скорость передачи новостей (InpNewsSpeed) и интервал между фреймами (InpTimerMs). Мы также даем возможность пользователю выбирать, находится ли тикер вверху или внизу (InpPositionTop), на каком расстоянии он должен находиться от края графика (InpTopOffset) и какие полосы показывать (ShowHigh, ShowMed, ShowLow). Выделив их в аккуратный блок “Пользовательский ввод” (User Inputs), любой желающий может быстро настроить поведение, не вникая в детали реализации.

//+------------------------------------------------------------------+
//| 1) USER INPUTS                                                   |
//+------------------------------------------------------------------+
input int   InpSpeedHigh   = 4;    // px/frame for High-impact lane
input int   InpSpeedMed    = 2;    // px/frame for Medium-impact lane
input int   InpSpeedLow    = 1;    // px/frame for Low-impact lane
input int   InpNewsSpeed   = 5;    // px/frame for news ticker row
input int   InpTimerMs     = 50;   // ms between frames (~20 fps)
input bool  InpPositionTop = true; // true=top, false=bottom
input int   InpTopOffset   = 50;   // px offset from chart edge
input bool  ShowHigh       = true; // toggle High lane
input bool  ShowMed        = true; // toggle Medium lane
input bool  ShowLow        = true; // toggle Low lane

Определение констант компоновки

Затем устанавливаем правила фиксированного интервала, которые определяют визуальный макет: сколько пикселей отделяет метку с оставшимся временем от валютной пары (GapTimeToSym), сколько отступов мы оставляем вокруг нашего встроенного поля важности (GapSymToRect, GapRectToName) и размер этого поля (RectSize). Объединив эти значения, мы можем точно настроить внешний вид в одном месте, а не копаться в коде для рисования.

//+------------------------------------------------------------------+
//| 2) DEVELOPER CONSTANTS                                           |
//+------------------------------------------------------------------+
static const int GapTimeToSym = 10;  // px gap after “[1h]”
static const int GapSymToRect = 5;   // px gap before inline box
static const int RectSize     = 8;   // width & height of inline box
static const int GapRectToName= 10;  // px gap after inline box

Сохранение состояния и буферов отрисовки

Затем объявляем глобальные переменные для хранения размеров диаграммы (canvW), высоты строки (lineH), текста-заполнителя для полосы новостей и временной метки, чтобы избежать повторных запросов к календарю (lastReloadDay). Мы также инстанцируем экземпляры двух объектов CCanvas — один для трех полос событий и один для новостного тикера. Наконец, определяем наш класс CEvent и три динамических массива (highArr, medArr, lowArr) для хранения входящих событий календаря по важности. Текущее смещение прокрутки каждой полосы (offHigh и т.д.) определяет состояние, которое мы поддерживаем во время работы советника.

//+------------------------------------------------------------------+
//| 3) GLOBALS                                                       |
//+------------------------------------------------------------------+
int        lineH      = 16;           // row height in px
int        canvW;                     // chart width
string     placeholder =              // news ticker text
           "News feed coming soon – stay tuned with the calendar";
datetime   lastReloadDay = 0;         // daily reload guard

CCanvas    eventsCanvas, newsCanvas;  // two layers

// Event struct and arrays
class CEvent : public CObject
{
public:
  datetime time;
  string   sym, name;
  int      imp;
  CEvent(datetime t,const string &S,const string &N,int I)
    { time=t; sym=S; name=N; imp=I; }
};
CEvent *highArr[], *medArr[], *lowArr[];
int     offHigh, offMed, offLow, offNews;

Вспомогательные функции по размещению и сортировке

Чтобы сохранить нашу основную логику чистой, мы выделяем две небольшие вспомогательные функции. Функция SetCanvas() помещает объект canvas в верхнюю или нижнюю часть графика в зависимости от пользовательских настроек. SortArr() - это простая пузырьковая сортировка, которая упорядочивает каждый массив важности по времени события, гарантируя, что наши полосы всегда отображают предстоящие события в верной последовательности.

//+------------------------------------------------------------------+
//| Helper: position a canvas label                                  |
//+------------------------------------------------------------------+
void SetCanvas(string name,bool top,int yDist)
{
  ObjectSetInteger(0,name,OBJPROP_CORNER,    top?CORNER_LEFT_UPPER:CORNER_LEFT_LOWER);
  ObjectSetInteger(0,name,OBJPROP_XDISTANCE, 0);
  ObjectSetInteger(0,name,OBJPROP_YDISTANCE, yDist);
}

//+------------------------------------------------------------------+
//| Helper: sort events by time                                      |
//+------------------------------------------------------------------+
void SortArr(CEvent* &arr[])
{
  int n=ArraySize(arr);
  for(int i=0;i<n-1;i++)
    for(int j=i+1;j<n;j++)
      if(arr[i].time > arr[j].time)
      {
        CEvent *tmp=arr[i]; arr[i]=arr[j]; arr[j]=tmp;
      }
}

Получение сегодняшних событий

Функция ReloadEvents() занимает центральное место в том, как мы извлекаем и фильтруем данные. Она запрашивает экономический календарь MetaTrader на предмет событий, запланированных с полуночи сегодняшнего дня до суток спустя. Мы пропускаем любое событие, временная метка которого уже истекла. Каждое допустимое событие упаковывается в объект CEvent, а затем помещается в массив high/medium/low в соответствии с его важностью. В конце мы сортируем каждую полосу так, чтобы самое раннее событие появлялось первым в прокрутке этой полосы.

//+------------------------------------------------------------------+
//| ReloadEvents: load only *future* events for *today*              |
//+------------------------------------------------------------------+
void ReloadEvents()
{
  datetime srv = TimeTradeServer();
  // midnight today
  MqlDateTime dt; TimeToStruct(srv, dt);
  MqlDateTime m0 = {dt.year, dt.mon, dt.day,0,0,0};
  datetime today = StructToTime(m0);
  if(today == lastReloadDay) return;
  lastReloadDay = today;

  // clear previous
  for(int i=0;i<ArraySize(highArr);i++) delete highArr[i];
  for(int i=0;i<ArraySize(medArr); i++) delete medArr[i];
  for(int i=0;i<ArraySize(lowArr); i++) delete lowArr[i];
  ArrayResize(highArr,0); ArrayResize(medArr,0); ArrayResize(lowArr,0);

  // fetch events [today, today+24h)
  MqlCalendarValue vals[];
  int cnt = CalendarValueHistory(vals, today, today+86400);
  for(int i=0;i<cnt;i++)
  {
    if(vals[i].time <= srv) continue; // skip past
    MqlCalendarEvent e;
    if(!CalendarEventById(vals[i].event_id, e)) continue;
    MqlCalendarCountry c;
    if(!CalendarCountryById(e.country_id, c)) continue;
    string sym = "[" + c.currency + "]";
    CEvent *ev = new CEvent(vals[i].time, sym, e.name, e.importance);
    // classify
    if(e.importance==CALENDAR_IMPORTANCE_HIGH)
      { int s=ArraySize(highArr)+1; ArrayResize(highArr,s); highArr[s-1]=ev; }
    else if(e.importance==CALENDAR_IMPORTANCE_MODERATE)
      { int s=ArraySize(medArr)+1; ArrayResize(medArr,s); medArr[s-1]=ev; }
    else
      { int s=ArraySize(lowArr)+1;  ArrayResize(lowArr,s);  lowArr[s-1]=ev; }
  }
  SortArr(highArr); SortArr(medArr); SortArr(lowArr);
}

Более подробно, когда запускается функция ReloadEvents(), она извлекает полный список сегодняшних записей календаря посредством CalendarValueHistory(), но каждая необработанная запись сообщает нам только event_id и country_id. Объединив их в таблице MqlCalendarEvent, где для каждого типа событий указано его название, частота, сектор и, самое главное, его важность, мы можем представить только действительно актуальные для рынка позиции. Структура MqlCalendarCountry гарантирует, что мы помечаем каждый заголовок корректной валютой (например, [USD] для США), исходя из ее кода ISO и символа. Этот двухэтапный поиск (значение → тип события → страна) позволяет нашему советнику быстро обучаться, извлекая только то, что необходимо, и быть точным, поскольку мы никогда не кодируем жестко информацию о стране или событии, а полагаемся на собственную постоянно синхронизируемую базу данных MetaTrader.

Константы важности (CALENDAR_IMPORTANCE_HIGH, ..._MODERATE, ..._LOW) лежат в основе нашей логики создания полос. Выбирая уровни важности для включения (ShowHigh/ShowMed/ShowLow) и окрашивая каждое встроенное поле в красный, оранжевый или белый цвета, мы даем трейдеру мгновенный визуальный сигнал: красным отмечаем сообщения, которые оказывают наибольшее влияние (например, решения ФРС по ставкам, данные о заработной плате в несельскохозяйственном секторе), оранжевым - сообщения, которые оказывают среднее влияние (Индекс потребительских цен, розничные продажи), а белым цветом - наименее важные (незначительные выступления, данные более низкого уровня).

На практике это помогает трейдеру сразу определить, нужно ли ему увеличивать стоп-ордера или даже приостанавливать автоматические стратегии по мере приближения важного события. Без этого фильтра и маркировки цветом, основанной на поле важности в MqlCalendarEvent, прокручиваемый список из десятков записей быстро превратился бы в шум, а не в сигнал.

Рендеринг полосы прокрутки

DrawLane() инкапсулирует логику для одной горизонтальной полосы. Мы выбрали моноширинный шрифт (“Courier New”), чтобы все символы, включая скобки и цифры, были одинаковой ширины, что обеспечивает аккуратное выравнивание. Затем рисуем:

  1. Метка оставшегося времени (часы или минуты).
  2. Символ валюты.
  3. Встроенное поле важности (окрашено в красный/оранжевый/белый цвета).
  4. Название события, за которым следует разделитель, если за ним следуют другие события.

Наконец, уменьшаем смещение полосы на величину скорости полосы, и если вся строка прокрутилась к левому краю, мы возвращаем ее вправо.

//+------------------------------------------------------------------+
//| DrawLane: scroll one lane with inline importance box             |
//+------------------------------------------------------------------+
void DrawLane(CEvent* &arr[], int &offset, int y, int speed)
{
  int n=ArraySize(arr);
  if(n==0) return;

  // monospaced for alignment
  eventsCanvas.FontNameSet("Courier New");
  eventsCanvas.FontSizeSet(-100);

  int x = offset;
  datetime srv = TimeTradeServer();

  for(int i=0;i<n;i++)
  {
    CEvent *e = arr[i];
    // time-left “[1h]” or “[45m]”
    long diff = (long)e.time - (long)srv;
    string tl = (diff>=3600 ? IntegerToString(diff/3600)+"h"
                            : IntegerToString(diff/60)+"m");
    string part = "[" + tl + "]";
    eventsCanvas.TextOut(x,y,part,XRGB(255,255,255),ALIGN_LEFT);
    x += eventsCanvas.TextWidth(part) -20;

    // symbol “[USD]”
    eventsCanvas.TextOut(x,y,e.sym,XRGB(255,255,255),ALIGN_RIGHT);
    x += eventsCanvas.TextWidth(e.sym) + GapSymToRect;

    // inline importance box
    uint col = (e.imp==CALENDAR_IMPORTANCE_HIGH    ? XRGB(255,0,0) :
                e.imp==CALENDAR_IMPORTANCE_MODERATE? XRGB(255,165,0):
                                                     XRGB(255,255,255));
    eventsCanvas.FillRectangle(x, y + (lineH-RectSize)/2,
                               x+RectSize, y + (lineH-RectSize)/2 + RectSize,
                               col);
    x += RectSize + GapRectToName;

    // event name + separator
    eventsCanvas.TextOut(x,y,e.name,XRGB(255,255,255),ALIGN_RIGHT);
    x += eventsCanvas.TextWidth(e.name)+60;
    if(i+1<n)
    {
      eventsCanvas.TextOut(x,y,"|",XRGB(180,180,180),ALIGN_RIGHT);
      x += eventsCanvas.TextWidth("|") + 20;
    }
  }

  // scroll + wrap
  int totalW = x - offset;
  offset -= speed;
  if(offset + totalW < 0) offset = canvW;
}

Организация всех полос и новостного ряда

В DrawAll() размещаем три полосы событий вертикально, затем размещаем заполнитель новостей под ними (или над ними, в зависимости от положения). После отображения событий в eventsCanvas вызываем функцию Update(false), чтобы переместить их в объект графика. Лента новостей использует собственную newsCanvas с более простым отображением только текста, за которым следует Update(true) для синхронного обновления.

//+------------------------------------------------------------------+
//| DrawAll: render lanes + news row                                |
//+------------------------------------------------------------------+
void DrawAll()
{
  // clear events
  eventsCanvas.Erase(ARGB(180,0,0,0));
  int y=0;

  if(ShowHigh)
  {
    DrawLane(highArr, offHigh, y, InpSpeedHigh);
    y += lineH;
  }
  if(ShowMed)
  {
    DrawLane(medArr, offMed, y, InpSpeedMed);
    y += lineH;
  }
  if(ShowLow)
  {
    DrawLane(lowArr, offLow, y, InpSpeedLow);
    y += lineH;
  }
  eventsCanvas.Update(false);

  // news placeholder
  newsCanvas.Erase(ARGB(170,0,0,0));
  newsCanvas.FontNameSet("Tahoma");
  newsCanvas.FontSizeSet(-120);
  int yOff = (lineH - newsCanvas.TextHeight(placeholder)) / 2;
  newsCanvas.TextOut(offNews, yOff, placeholder, XRGB(255,255,255), ALIGN_LEFT);
  offNews -= InpNewsSpeed;
  if(offNews + newsCanvas.TextWidth(placeholder) < -20) offNews = canvW;
  newsCanvas.Update(true);
}

Инициализация, таймер и очистка

Наконец, в OnInit() создаем и настраиваем наши холсты, вызываем ReloadEvents() в первый раз, устанавливаем все смещения на canvW и позиционируем два холста на основе InpPositionTop и InpTopOffset. Затем рисуем первый фрейм и запускаем миллисекундный таймер. 

OnTimer() просто перемещает холсты (чтобы пользователи могли переключать InpPositionTop в режиме реального времени), перезагружает события один раз в день, настраивает размер графика и снова вызывает DrawAll(). OnDeinit() очищает холсты и удаляет все выделенные объекты CEvent.

//+------------------------------------------------------------------+
//| OnInit: setup canvases, initial load & position                 |
//+------------------------------------------------------------------+
int OnInit()
{
  // force reload Today
  lastReloadDay = 0;

  // clear arrays
  ArrayResize(highArr,0);
  ArrayResize(medArr,0);
  ArrayResize(lowArr,0);

  // chart width
  canvW = (int)ChartGetInteger(0,CHART_WIDTH_IN_PIXELS);

  // create events canvas (4 rows tall)
  eventsCanvas.CreateBitmapLabel("EvCanvas",0,0,canvW,4*lineH,COLOR_FORMAT_ARGB_RAW);
  eventsCanvas.TransparentLevelSet(150);

  // create news canvas (1 row tall)
  newsCanvas.CreateBitmapLabel("NwCanvas",0,0,canvW,lineH,COLOR_FORMAT_ARGB_RAW);
  newsCanvas.TransparentLevelSet(0);

  // load data + init offsets
  ReloadEvents();
  offHigh = offMed = offLow = offNews = canvW;

  // initial positioning
  {
    int rows = (ShowHigh?1:0)+(ShowMed?1:0)+(ShowLow?1:0);
    int yOff = InpTopOffset + (InpPositionTop ? 0 : rows*lineH);
    SetCanvas("EvCanvas", InpPositionTop, InpTopOffset);
    SetCanvas("NwCanvas", InpPositionTop, yOff + (InpPositionTop ? rows*lineH : 0));
  }

  // first draw & timer
  DrawAll();
  EventSetMillisecondTimer(InpTimerMs);
  return INIT_SUCCEEDED;
}

//+------------------------------------------------------------------+
//| OnTimer: reposition, daily reload, redraw                       |
//+------------------------------------------------------------------+
void OnTimer()
{
  // reposition every tick
  int rows = (ShowHigh?1:0)+(ShowMed?1:0)+(ShowLow?1:0);
  if(InpPositionTop)
  {
    SetCanvas("EvCanvas", true,  InpTopOffset);
    SetCanvas("NwCanvas", true,  InpTopOffset + rows*lineH);
  }
  else
  {
    SetCanvas("EvCanvas", false, InpTopOffset);
    SetCanvas("NwCanvas", false, InpTopOffset + lineH);
  }

  // reload once per day
  ReloadEvents();

  // adapt width
  int wNew = (int)ChartGetInteger(0,CHART_WIDTH_IN_PIXELS);
  if(wNew != canvW)
  {
    canvW = wNew;
    ObjectSetInteger(0,"EvCanvas",OBJPROP_WIDTH,canvW);
    ObjectSetInteger(0,"NwCanvas",OBJPROP_WIDTH,canvW);
  }

  // redraw
  DrawAll();
}

//+------------------------------------------------------------------+
//| OnDeinit: cleanup                                               |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
  EventKillTimer();
  eventsCanvas.Destroy(); ObjectDelete(0,"EvCanvas");
  newsCanvas.Destroy();   ObjectDelete(0,"NwCanvas");
  for(int i=0;i<ArraySize(highArr);i++) delete highArr[i];
  for(int i=0;i<ArraySize(medArr); i++) delete medArr[i];
  for(int i=0;i<ArraySize(lowArr); i++) delete lowArr[i];
}

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


Тестирование

В терминале MetaTrader 5 перейдите в раздел "Советники" и перетащите советник «Заголовки новостей» на график. После успешного добавления советник по умолчанию отображается в верхней части графика со смещением по вертикали на 50 пикселей. Это смещение предотвращает перекрытие кнопок Depth of Market (биржевой стакан) и Trade Panel (торговая панель), а также названия советника в правом верхнем углу.

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

Testing the News Headline EA.mq5

Тестирование советника «Заголовки новостей»

На изображении выше показано успешное внедрение советника «Заголовки новостей» Он выглядит так, как и было задумано, отображая все предстоящие новостные события с гладкой и плавной анимацией.


Заключение

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

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

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

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

Делитесь своими мыслями или задавайте вопросы в разделе комментариев ниже. Вы также можете найти прикрепленные файлы непосредственно под этой статьей.

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

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

Прикрепленные файлы |
Разработка инструментария для анализа движения цен (Часть 6): Возврат к среднему значению Разработка инструментария для анализа движения цен (Часть 6): Возврат к среднему значению
Хотя некоторые концепции на первый взгляд кажутся простыми, воплотить их в жизнь на практике может быть довольно сложно. В статье ниже мы рассмотрим инновационный подход к автоматизации советника, который анализирует рынок, используя стратегию возврата к среднему значению.
Возвратные стратегии дневной торговли RSI2 Ларри Коннорса Возвратные стратегии дневной торговли RSI2 Ларри Коннорса
Ларри Коннорс — известный трейдер и автор книг, наиболее известный своими работами в области количественной (алгоритмизированной) торговли и таких стратегий, как 2-периодный индекс относительной силы RSI (RSI2), помогающих определять краткосрочные состояния перекупленности и перепроданности рынка. В этой статье объясним сначала актуальность нашего исследования, затем воссоздадим три самые известные стратегии Коннорса на языке MQL5 и применим их к внутридневной торговле на индексе CFD S&P 500.
Особенности написания экспертов Особенности написания экспертов
Написание и тестирование экспертов в торговой системе MetaTrader 4.
Форекс советник на нейросети N-BEATS Network Форекс советник на нейросети N-BEATS Network
Реализация архитектуры N-BEATS для форекс-трейдинга в MetaTrader 5 с квантильным прогнозированием и адаптивным риск-менеджментом. Архитектура адаптирована через билинейную нормализацию и специализированные функции потерь для финансовых данных. Тестирование на данных 2025 года показало неспособность генерировать прибыль, подтверждая разрыв между теоретическими достижениями и практической торговой эффективностью.