
Создаем динамическую мультисимвольную мультипериодную панель индекса относительной силы (RSI) в MQL5
Введение
В статье мы подробно рассмотрим создание динамической мультисимвольной мультипериодной панели RSI (Relative Strength Index, индекса относительной силы) в MetaQuotes Language 5 (MQL5) для MetaTrader 5. Мы рассмотрим функционал и практическое применение пользовательской панели мониторинга RSI, а также шаги, необходимые для ее разработки в MetaQuotes Language 5 (MQL5).
Динамическая панель RSI служит мощным инструментом для трейдеров, предоставляя консолидированное представление значений RSI по нескольким символам и таймфреймам. Это позволяет принимать более обоснованные решения, выявляя состояния перекупленности или перепроданности на рынке. Визуализируя данные RSI в едином интерфейсе, трейдеры могут быстро оценивать рыночные условия и соответствующим образом адаптировать свои стратегии.
Мы рассмотрим следующие ключевые области:
- Инициализация панели: Настройка среды, создание основных кнопок, отображение таймфреймов и символов.
- Обновления в реальном времени: Реализация функциональности для динамического расчета и отображения значений RSI с обновлениями на основе реальных рыночных данных.
- Создание и обновление кнопок: Подробное объяснение функций, используемых для создания и обновления кнопок, гарантирующих удобство и информативность панели управления.
- Настройка и практическое использование: Как настроить панель инструментов в соответствии с индивидуальными потребностями торговли и интегрировать ее в свою торговую стратегию.
К концу этой статьи вы будете иметь полное представление о том, как создать и использовать мультисимвольную, мультипериодную панель RSI на языке MQL5. Это расширит ваш торговый инструментарий, улучшит вашу способность анализировать рыночные тренды и, в конечном итоге, поможет вам принимать более обоснованные торговые решения. Темы, которые мы рассмотрим в этой статье:
- Обзор элементов
- Реализация средствами MQL5
- Заключение
В нашей работе мы будем активно использовать язык MetaQuotes Language 5 (MQL5) в качестве нашей базовой среды разработки (IDE) в MetaEditor и запускать файлы в торговом терминале MetaTrader 5.
Обзор элементов
Мы создадим комплексную панель, которая объединит значения RSI по нескольким торговым символам и таймфреймам для анализа рынка. Наша разработка будет сосредоточена на следующих ключевых элементах:
- Инициализация панели:
Создание главной кнопки: Первым шагом в процессе инициализации будет создание центральной кнопки, указывающей базу и точку отсчета панели управления. Эта кнопка будет служить основным элементом управления и станет центром пользовательского интерфейса. Она будет расположена в верхней части панели, чтобы обеспечить легкий доступ и хорошую видимость.
Кнопки таймфрейма: Далее мы создадим кнопки для переключения таймфреймов. Они будут расположены горизонтально рядом с главной кнопкой и предназначены для отображения значений RSI за каждый соответствующий период. Каждая кнопка таймфрейма будет помечена соответствующей аббревиатурой, что позволит трейдерам быстро идентифицировать и сравнивать данные по разным таймфреймам.
- Кнопки символов:
Динамический список символов: Чтобы обеспечить комплексное представление рынка, мы создадим кнопки для каждого торгового символа, доступного в MetaTrader 5 пользователя в окне "Обзор рынка". Эти кнопки будут созданы динамически и расположены вертикально под главной кнопкой. Текущий активный торговый символ будет выделен особым цветом (например, салатовым или зеленым), чтобы его было легче узнать. Эта функция позволит трейдерам быстро идентифицировать активный символ и отслеживать его значения RSI в режиме реального времени.
Базовая кнопка: Внизу списка символов мы добавим базовую кнопку, которая охватывает ширину всех кнопок таймфреймов вместе взятых. Этой кнопке будет присвоен заголовок панели управления. Ее можно использовать для отображения состояния сигнала текущего символа или в качестве сводки или нижнего колонтитула для списка символов. Она обеспечит четкое разграничение между кнопками символов и отображением значения RSI, гарантируя, что панель управления будет хорошо организована и удобна для навигации.
- Обновления в реальном времени:
Расчет RSI: Чтобы гарантировать, что панель предоставляет точную и актуальную информацию, мы будем рассчитывать значения RSI для каждого символа и таймфрейма в реальном времени, используя встроенные функции MQL5. Функция вычисляет RSI на основе цен закрытия выбранного периода, предоставляя важный индикатор динамики рынка. Значения RSI будут храниться в массиве и обновляться на каждом тике, чтобы отражать последние рыночные данные.
Динамическое отображение: Рассчитанные значения RSI будут динамически отображаться на соответствующих кнопках. Для повышения визуальной ясности мы реализуем схему цветового кодирования на основе предопределенных пороговых значений RSI. Если значение RSI ниже 30, что указывает на состояние перепроданности, цвет фона кнопки изменится на зеленый. Если значение RSI выше 70, что указывает на состояние перекупленности, цвет фона изменится на красный. При значениях RSI от 30 до 70 цвет фона останется белым, что указывает на нейтральное состояние. Это динамическое отображение позволит трейдерам быстро оценивать состояние рынка и принимать обоснованные торговые решения.
Вот что мы хотим получить в итоге.
Чтобы проиллюстрировать весь процесс разработки, мы разделим каждый элемент на подробные шаги, предоставив фрагменты кода и пояснения. К концу статьи у вас будет полностью функциональная панель RSI, которую вы сможете настроить и интегрировать в свою торговую стратегию.
Реализация средствами MQL5
Панель индикаторов будет основана на советнике. В терминале MetaTrader 5 выберите "Сервис" > "Редактор MetaQuotes Language" или просто нажмите F4. Кроме того, вы можете щелкнуть иконку IDE (интегрированная среда разработки) на панели инструментов. Откроется среда разработки на MQL5, которая позволяет писать торговых роботов, технические индикаторы, скрипты и библиотеки функций.
На панели инструментов выберите "Файл" - "Новый файл" или нажмите CTRL + N, чтобы создать новый документ. Также вы можете нажать на иконку "Создать" в панели инструментов. Откроется окно Мастера MQL.
В открывшемся Мастере выберите Советник (шаблон) и нажмите Далее.
В общих свойствах укажите имя файла вашего советника. Чтобы указать или создать папку, если она не существует, используйте обратную косую черту перед именем советника. По умолчанию указана папка Experts\. Это значит, что наш советник будет создан в папке Experts. Остальные разделы довольно просты, но вы можете перейти по ссылке в нижней части Мастера, чтобы узнать детали.
После указания имени файла советника нажмите "Далее" > "Далее" > "Готово". Мы готовы к созданию панели.
Для начала нужно создать функцию для создаваемых кнопок. Это будет очень полезно, поскольку позволит нам повторно использовать одну и ту же функцию при создании аналогичных объектов, а не повторять весь процесс. Это также сэкономит нам много времени и места, сделав процесс быстрым и простым, а фрагменты кода — короткими.
Для создания кнопок мы создадим функцию, которая принимает 11 аргументов или параметров.
//+------------------------------------------------------------------+ //| Function to create a button | //+------------------------------------------------------------------+ bool createButton(string objName, string text, int xD, int yD, int xS, int yS, color clrTxt, color clrBg, int fontSize = 12, color clrBorder = clrNONE, string font = "Arial Rounded MT Bold" ) { ... }
Сигнатура функции говорит сама за себя. Это логическая функция createButton. Она возвращает два логических флага, true или false, в случае успеха или неудачи соответственно. Чтобы легче понять ее параметры, давайте опишем и объясним их по отдельности ниже.
- objName (string) - имя объекта кнопки. Каждая кнопка должна иметь уникальное имя, чтобы отличать ее от других объектов на графике. Это имя используется для ссылки на кнопку при обновлениях и изменениях.
- text (string) - текст на кнопке. Это может быть RSI, BUY или любой текст, соответствующий назначению кнопки.
- xD (int) - горизонтальное расстояние кнопки от указанного угла графика. Единица измерения — пиксели.
- yD (int) - вертикальное расстояние кнопки от указанного угла графика.
- xS (int) - ширина кнопки в пикселях. Определяет, насколько широко кнопка будет отображаться на графике.
- yS (int) - высота кнопки в пикселях. Определяет высоту кнопки на графике.
- clrTxt (color) - цвет текста, отображаемого на кнопке. Цвет можно указать с помощью предопределенных цветовых констант в MQL5, таких как clrBlack, clrWhite, clrRed и т.д.
- clrBg (color) - цвет фона кнопки. Он также указывается с использованием предопределенных цветовых констант и определяет цвет заливки кнопки.
- fontSize (int) - размер шрифта текста кнопки. По умолчанию 12. Определяет размер текста, отображаемого на кнопке.
- clrBorder (color) - цвет границы кнопки (опционально). По умолчанию clrNONE (не применяется). Если указать цвет границы, он может улучшить вид кнопки.
- font (string) - шрифт текста кнопки (опционально). По умолчанию Arial Rounded MT Bold. Шрифт определяет стиль текста, отображаемого на кнопке.
Возможно, вы заметили, что некоторые аргументы уже инициализированы тем или иным значение в сигнатуре функции. Значение инициализации представляет собой значение по умолчанию, которое будет присвоено этому параметру в случае, если он будет проигнорирован во время вызова функции. Например, если значение цвета границы не указано во время вызова функции, то к границе нашей прямоугольной метки цвет применен не будет.
Процедуры создания объектов определяются внутри тела функции, заключенного в фигурные скобки ({}).
// Attempt to create the button if (!ObjectCreate(0, objName, OBJ_BUTTON, 0, 0, 0)) { Print(__FUNCTION__, ": failed to create Btn: ERR Code: ", GetLastError()); // Print error message if button creation fails return (false); // Return false if creation fails }
Начнем с использования оператора if, чтобы проверить, не создан ли объект. Используется функция ObjectCreate, логическое значение которой принимает 6 аргументов. Эта функция создает объект с указанным именем, типом и начальными координатами в указанном подокне графика. Сначала мы указываем окно графика, 0 означает, что объект будет создан в главном окне. Затем указываем имя объекта. Это уникальное имя, которое будет присвоено конкретному объекту. Тип объекта, который мы хотим создать — OBJ_BUTTON, что означает объект для создания и проектирования панели пользовательских индикаторов. Затем мы переходим к предоставлению подокна, 0 - текущее подокно. Наконец, мы указываем значения времени и цены как ноль (0), поскольку мы будем прикреплять их не к графику, а к координатам окна графика. Для настройки отображения используются пиксели.
Если не удается объект, функция ObjectCreate в конечном итоге возвращает false. Очевидно, что нет смысла продолжать, возвращаемся с ошибкой. В этом случае мы сообщаем об ошибке, указывая ее в журнале вместе с кодом и возвращая false. Возможно, была предыдущая ошибка, и поэтому, чтобы получить текущую ошибку, нам нужно очистить предыдущую. Это делается с помощью функции ResetLastError, которая является встроенной функцией MQL5, расположенной непосредственно перед нашей логикой создания объекта.
ResetLastError(); // Reset the last error code
Цель — установить значение функции GetLastError, которая возвращает код ошибки последней операции, равным нулю. Вызывая ее, мы гарантируем, что все предыдущие коды ошибок будут очищены перед продолжением операций. Этот шаг важен, поскольку он позволяет нам обрабатывать новые ошибки без риска нарваться на предыдущие.
Достижение этого этапа означает, что мы создали объект, и, таким образом, мы можем продолжить обновление свойств объекта. Встроенная функция ObjectSet... устанавливает значение соответствующего свойства объекта. Свойство объекта должно иметь тип datetime, integer, color, boolean или symbol.
// Set button properties ObjectSetInteger(0, objName, OBJPROP_XDISTANCE, xD); // Set X distance ObjectSetInteger(0, objName, OBJPROP_YDISTANCE, yD); // Set Y distance ObjectSetInteger(0, objName, OBJPROP_XSIZE, xS); // Set X size ObjectSetInteger(0, objName, OBJPROP_YSIZE, yS); // Set Y size ObjectSetInteger(0, objName, OBJPROP_CORNER, CORNER_RIGHT_UPPER); // Set corner position ObjectSetString(0, objName, OBJPROP_TEXT, text); // Set button text ObjectSetString(0, objName, OBJPROP_FONT, font); // Set font type ObjectSetInteger(0, objName, OBJPROP_FONTSIZE, fontSize); // Set font size ObjectSetInteger(0, objName, OBJPROP_COLOR, clrTxt); // Set text color ObjectSetInteger(0, objName, OBJPROP_BGCOLOR, clrBg); // Set background color ObjectSetInteger(0, objName, OBJPROP_BORDER_COLOR, clrBorder); // Set border color ObjectSetInteger(0, objName, OBJPROP_BACK, false); // Set background property ObjectSetInteger(0, objName, OBJPROP_STATE, false); // Set button state ObjectSetInteger(0, objName, OBJPROP_SELECTABLE, false); // Set if the button is selectable ObjectSetInteger(0, objName, OBJPROP_SELECTED, false); // Set if the button is selected
Сосредоточимся на логике первого свойства.
ObjectSetInteger(0, objName, OBJPROP_XDISTANCE, xD); // Set X distance
Здесь мы используем встроенную функцию ObjectSetInteger и передаем параметры. Параметры описаны ниже.
- Chart id - идентификатор графика. 0 означает текущий график (chart ID). Редактируем свойства объекта в этом графике.
- Name - имя объекта. objName - уникальное имя, присвоенное объекту метки прямоугольника.
- Property id - идентификатор свойства объекта. Значение равно одному из значений перечисления ENUM_OBJECT_PROPERTY_INTEGER. OBJPROP_XDISTANCE указывает, что мы изменяем свойство расстояния X.
- Property value - значение свойства. Значение, присвоенное xD, определяет, насколько далеко вправо (или влево, если значение отрицательное) будет располагаться верхний левый угол нашей прямоугольной метки по горизонтали от левого края графика.
Остальные свойства используют тот же формат. OBJPROP_YDISTANCE - свойство расстояния Y метки прямоугольника. Значение yD определяет, насколько далеко верхний левый угол прямоугольной метки будет расположен по вертикали от верхнего края графика. Другими словами, параметр управляет вертикальным расположением метки в области графика. Задается расстояние Y от угла. OBJPROP_XSIZE и OBJPROP_YSIZE - ширина и высота прямоугольника соответственно.
Чтобы расположить наш объект, мы используем свойство OBJPROP_CORNER, чтобы определить угол, в котором должен располагаться наш объект в окне графика.
ObjectSetInteger(0, objName, OBJPROP_CORNER, CORNER_RIGHT_UPPER); // Set corner position
Имеется 4 возможных типа свойства:
- CORNER_LEFT_UPPER - центр координат в левом верхнем углу графика.
- CORNER_LEFT_LOWER - центр координат в левом нижнем углу графика.
- CORNER_RIGHT_LOWER - центр координат в правом нижнем углу графика.
- CORNER_RIGHT_UPPER - центр координат в правом верхнем углу графика.
На рисунке это выглядит так:
Остальные свойства просты. Они снабжены комментариями для более легкого понимания. Перерисуем график, чтобы изменения вступали в силу автоматически, без необходимости ждать изменений котировок или событий графика.
ChartRedraw(0); // Redraw the chart to reflect the new button
Наконец мы возвращаем true, означая, что создание и обновление свойств объекта прошло успешно.
return (true); // Return true if creation is successful
Ниже приведен полный код функции, отвечающей за создание объекта кнопки в окне графика.
//+------------------------------------------------------------------+ //| Function to create a button | //+------------------------------------------------------------------+ bool createButton(string objName, string text, int xD, int yD, int xS, int yS, color clrTxt, color clrBg, int fontSize = 12, color clrBorder = clrNONE, string font = "Arial Rounded MT Bold" ) { ResetLastError(); // Reset the last error code // Attempt to create the button if (!ObjectCreate(0, objName, OBJ_BUTTON, 0, 0, 0)) { Print(__FUNCTION__, ": failed to create Btn: ERR Code: ", GetLastError()); // Print error message if button creation fails return (false); // Return false if creation fails } // Set button properties ObjectSetInteger(0, objName, OBJPROP_XDISTANCE, xD); // Set X distance ObjectSetInteger(0, objName, OBJPROP_YDISTANCE, yD); // Set Y distance ObjectSetInteger(0, objName, OBJPROP_XSIZE, xS); // Set X size ObjectSetInteger(0, objName, OBJPROP_YSIZE, yS); // Set Y size ObjectSetInteger(0, objName, OBJPROP_CORNER, CORNER_RIGHT_UPPER); // Set corner position ObjectSetString(0, objName, OBJPROP_TEXT, text); // Set button text ObjectSetString(0, objName, OBJPROP_FONT, font); // Set font type ObjectSetInteger(0, objName, OBJPROP_FONTSIZE, fontSize); // Set font size ObjectSetInteger(0, objName, OBJPROP_COLOR, clrTxt); // Set text color ObjectSetInteger(0, objName, OBJPROP_BGCOLOR, clrBg); // Set background color ObjectSetInteger(0, objName, OBJPROP_BORDER_COLOR, clrBorder); // Set border color ObjectSetInteger(0, objName, OBJPROP_BACK, false); // Set background property ObjectSetInteger(0, objName, OBJPROP_STATE, false); // Set button state ObjectSetInteger(0, objName, OBJPROP_SELECTABLE, false); // Set if the button is selectable ObjectSetInteger(0, objName, OBJPROP_SELECTED, false); // Set if the button is selected ChartRedraw(0); // Redraw the chart to reflect the new button return (true); // Return true if creation is successful }
Теперь, когда у нас есть функция создания кнопки, используем ее для создания панели индикаторов. Нам понадобятся имена объектов. Определим макросы, чтобы легко управлять взаимодействием имен объектов.
// Define button identifiers and properties #define BTN1 "BTN1"
Мы используем ключевое слово #define для определения макроса BTN1 со значением BTN1 для простого сохранения имени главной кнопки, вместо того, чтобы повторно вводить имя при каждом создании раздела кнопки, что значительно экономит наше время и снижает вероятность неправильного указания имени. По сути, макросы используются для подстановки текста во время компиляции.
Аналогичным образом мы определяем другие макросы, которые собираемся использовать.
#define Desc "Desc " #define Symb "Symb " #define Base "Base " #define RSI "RSI " #define XS1 90 #define XS2 100 #define YS1 25 #define clrW clrWhite #define clrB clrBlack #define clrW_Gray C'230,230,230'
Здесь мы используем макрос Desc в качестве префикса для названий кнопок, описывающих различные временные рамки, что позволяет нам генерировать уникальные названия для этих кнопок описания путем добавления индексов и дополнительных строк, таких как Desc0, Desc1 и т. д. Аналогично макрос Symb используется в качестве префикса для названий кнопок, представляющих различные торговые символы, помогая нам создавать уникальные идентификаторы, такие как Symb0, Symb1 и т. д. Макрос Base служит префиксом для имени базовой кнопки, обеспечивая четкое и единообразное соглашение об именовании компонентов нашей панели. Для обработки кнопок, связанных с RSI, мы используем макрос RSI, создающий уникальные идентификаторы для кнопок, отображающих значения RSI для разных символов и таймфреймов.
Что касается размеров, XS1 задает ширину определенных кнопок, а XS2 и YS1 указывают ширину и высоту для других кнопок соответственно, стандартизируя размер наших элементов графического интерфейса. Определяем цветовые макросы clrW и clrB, чтобы иметь возможность удобно ссылаться на белый и черный цвета в нашем коде. MQL5 имеет предопределенный формат переменных color. Это то, что мы назначаем и на что ссылаемся, когда используем clrWhite для белого цвета; Web-цвета.
Формат color, раздел 1:
Формат color, раздел 2:
Кроме того, мы определяем пользовательский серый цвет как clrW_Gray C'230,230,230' для использования в качестве цвета фона или границы, обеспечивая единый визуальный стиль для всей панели управления. Чтобы получить больше контроля над цветами, мы представляем последний макрос в литеральном виде. Он принимает формат "C'000,000,000'", где три нуля могут быть любым числом от 0 до 255. Принятый формат - RGB (Red, Green, Blue). Три значения представляют красный, зеленый и синий компоненты соответственно по шкале от 0 до 255. Таким образом, 230,230,230 соответствует почти белому оттенку.
Нам нужно будет определить конкретные таймфреймы или периоды символов, которые мы будем использовать на нашей панели. Поэтому нам необходимо будет их хранить. Самый простой способ сделать это — хранить их в массивах, где к ним можно будет легко получить доступ.
// Define the timeframes to be used ENUM_TIMEFRAMES periods[] = {PERIOD_M1, PERIOD_M5, PERIOD_H1, PERIOD_H4, PERIOD_D1};
Определяем статический массив типа ENUM_TIMEFRAMES для указания таймфреймов, которые будут использоваться в нашей панели управления. Назовем массив periods и включим следующие конкретные таймфреймы: PERIOD_M1 (1 minute), PERIOD_M5 (5 minutes), PERIOD_H1 (1 hour), PERIOD_H4 (4 hours) and PERIOD_D1 (1 day). Перечисляя эти временные рамки в массиве periods, мы гарантируем, что на нашей панели будут отображаться значения RSI для каждого из этих отдельных периодов, обеспечивая комплексное представление о рыночных трендах на нескольких таймфреймах. Такая настройка позволит нам проходить по массиву и применять наши вычисления и создания кнопок единообразно для каждого указанного периода в дальнейшем. Переменная типа данных, используемая для определения массива, представляет собой перечисление, состоящее из всех доступных таймфреймов в MetaTrader 5. Вы можете использовать любой из них, если он явно указан. Вот список всех таймфреймов, которые вы можете использовать.
Наконец, в глобальной переменной нам нужно будет создать хэндл индикатора, который будет содержать наш индикатор, и массив, в котором мы будем хранить данные индикатора для различных используемых таймфреймов и символов. Это достигается с помощью следующего фрагмента кода.
// Global variables int handleName_Id; // Variable to store the handle ID for the RSI indicator double rsi_Data_Val[]; // Array to store the RSI values
Объявим целочисленный тип данных handleName_Id для хранения идентификатора хэндла индикатора RSI. Когда мы создаем индикатор RSI для определенного символа и таймфрейма, он возвращает уникальный идентификатор хэндла. Затем идентификатор будет сохранен в хэндле индикатора, что позволит нам ссылаться на этот конкретный индикатор RSI в последующих операциях, например, для извлечения его значений для дальнейшего анализа. Здесь в дело вступает второй массив переменных. Определяем двойной динамический массив rsi_Data_Val для хранения значений RSI, полученных от индикатора. Когда мы извлекаем данные RSI, значения будут скопированы в этот массив. Сделав этот массив глобальной переменной, мы гарантируем, что данные RSI будут доступны во всей программе, что позволит нам использовать значения для обновлений в реальном времени и отображать их на кнопках панели.
Сначала наша панель будет создана в разделе инициализации советника, поэтому давайте посмотрим, что делает обработчик событий инициализации.
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { ... return(INIT_SUCCEEDED); // Return initialization success }
Функция OnInit представляет собой обработчик событий, который вызывается для экземпляра инициализации советника при выполнении необходимых инициализаций. Она предназначена для выполнения всех необходимых задач первоначальной настройки, гарантирующих корректную работу советника. Сюда входит создание элементов пользовательского интерфейса, инициализация переменных и настройка необходимых условий для правильной работы программы. В нашем случае мы будем использовать ее для инициализации элементов панели.
Вызываем функцию для создания кнопки путем ввода ее имени и указания ее параметров.
// Create the main button for the pair with specific properties createButton(BTN1, "PAIR", 600, 50, XS1, YS1, clrW, clrGray, 15, clrGray);
Здесь имя нашей кнопки — BTN1 по определению макроса. Второй параметр используется для указания формулировки или текста, который будет отображаться в непосредственной близости от кнопок. Наше расстояние по оси X, шкале времени и даты, от правого верхнего угла окна графика составляет 600 пикселей, а расстояние по оси Y, шкале цен, составляет 50 пикселей. Ширина берется из уже предопределенного макроса для более удобного обращения к последующим кнопкам. Ширина — XS1, что составляет 90 пикселей, а высота — YSI, что составляет 25 пикселей соответственно. Мы выбираем цвет текста clrW, то есть белый цвет, с серым фоном, размером шрифта 15 и серой границей кнопки. Чтобы получить приблизительный диапазон пикселей, можно уменьшить масштаб диаграммы до 0, а количество полос между двумя координатами перекрестия будет равно количеству пикселей на горизонтальной шкале. Ниже показано, что мы имеем в виду.
Другой параметр был пропущен, что означает, что автоматически будет применено значение по умолчанию, то есть имя шрифта будет Arial Rounded MT Bold. Вот что мы получаем после компиляции.
Даже если бы у нас были все параметры со значениями по умолчанию, как показано ниже, результаты всегда были бы одинаковыми.
// Create the main button for the pair with specific properties createButton(BTN1, "PAIR", 600, 50, XS1, YS1, clrW, clrGray, 15, clrGray, "Arial Rounded MT Bold");
Далее мы хотим создать кнопки для каждого предопределенного таймфрейма, чтобы отображать соответствующую информацию RSI. Мы можем сделать это статически, вызвав функцию создания кнопок для каждого элемента, который мы хотим создать, но это сделает наш код очень длинным. Таким образом, мы будем использовать динамический формат, который поможет нам создавать кнопки в контролируемых итерациях.
// Loop to create buttons for each timeframe with the corresponding RSI label for(int i = 0; i < ArraySize(periods); i++) { createButton(Desc + IntegerToString(i), truncPrds(periods[i]) + " RSI 14", (600 - XS1) + i * -XS2, 50, XS2, YS1, clrW, clrGray, 13, clrGray); }
Инициируем цикл for, который проходит по массиву periods, содержащему указанные таймфреймы, которые мы определили и заполнили ранее. Используем функцию ArraySize, чтобы гарантировать, что цикл охватит все элементы массива. Функция проста, принимает только один аргумент и просто возвращает количество элементов выбранного массива, в нашем случае periods. Внутри цикла мы вызываем функцию createButton, чтобы создать кнопку для каждого таймфрейма. Имя кнопки формируется путем объединения макроса Desc с индексом i, преобразованным в строку с помощью функции IntegerToString, гарантирующей, что каждая кнопка имеет уникальное имя, например Desc 0, Desc 1 и так далее. Функция преобразует значение целочисленного типа в строку указанной длины и возвращает полученную строку. Она принимает 3 входных параметра, но последние 2 являются необязательными. Первый параметр — число для преобразования, в нашем случае индекс i. Применим пользовательскую функцию truncPrds для генерации метки кнопки. Функция обрезает строковое представление таймфрейма до более удобного формата (например, M1, M5) и добавляет RSI 14, чтобы указать, что эта кнопка будет отображать значение RSI с периодом 14. Фрагмент кода функции выглядит так:
// Function to truncate the ENUM_TIMEFRAMES string for display purposes string truncPrds(ENUM_TIMEFRAMES period) { // Extract the timeframe abbreviation from the full ENUM string string prd = StringSubstr(EnumToString(period), 7); return prd; // Return the truncated string }
Функция принимает период типа ENUM_TIMEFRAMES в качестве параметра и преобразует значение ENUM в строку с помощью функции EnumToString. Обычно это приводит к строке, включающей префикс, который нам не нужен для отображения. Чтобы его удалить, используем функцию StringSubstr для извлечения подстроки, начиная с седьмого символа. Это обрежет строку до более короткой и удобной для чтения формы, подходящей для отображения в пользовательском интерфейсе. Наконец, мы возвращаем усеченную строку, предоставляя четкое и краткое представление временного интервала, которое можно использовать для маркировки кнопок на нашей панели. Чтобы понять, зачем нам нужна эта функция, приведем иллюстрацию.
Примененная логика:
Print("BEFORE: ",EnumToString(periods[i])); Print("AFTER: ",truncPrds(periods[i]));
Выражения print.
Теперь вы можете ясно видеть, что неусеченные периоды длиннее усеченных и содержат ненужные 7 символов PERIOD_, включая символ подчеркивания, который мы в конечном итоге удалим.
Координата X каждой кнопки рассчитывается динамически, начиная с начального значения 600 - XS1 и корректируя его путем вычитания XS2 (ширины кнопки), умноженной на индекс i. Такое расположение гарантирует, что каждая кнопка будет располагаться слева от предыдущей, создавая горизонтальное выравнивание. Координата Y зафиксирована на расстоянии 50 пикселей от верхней части графика, что обеспечивает единообразное вертикальное положение всех кнопок таймфрейма. Затем размеры кнопки задаются с использованием значений, определенных макросами XS2 для ширины (100 пикселей) и YS1 для высоты (25 пикселей). Кроме того, мы устанавливаем цвет текста каждой кнопки на clrWhite, что соответствует белому цвету, цвет фона — на серый цвет, размер шрифта — на 13 и, наконец, цвет границы — на серый. После компиляции мы получаем следующее.
Разумеется, вы можете установить любой цвет. Например, чтобы получить синий фон и черную рамку, вы можете в код следующие изменения.
createButton(Desc + IntegerToString(i), truncPrds(periods[i]) + " RSI 14", (600 - XS1) + i * -XS2, 50, XS2, YS1, clrW, clrDodgerBlue, 13, clrBlack);
Здесь мы изменили цвет фона на ярко-синий, а цвет границы — на черный. Вот что мы получаем после компиляции:
Кнопки стали более визуально привлекательными. Однако в целях соблюдения единообразия статьи будем использовать более спокойные цвета по умолчанию. Далее в статье мы будем использовать более яркие цвета при создании и определении подходящих сигналов.
Далее нам необходимо создать еще одну динамическую вертикальную серию кнопок с символами. Нам не нужно определять конкретные символы в массиве и использовать их для визуализации. Мы можем автоматически получить доступ к символам, которые предоставляет брокер. Для этого мы используем цикл for для перебора всех символов, предоставленных брокером, и при необходимости выбираем необходимые.
// Loop to create buttons for each symbol for(int i = 0; i < SymbolsTotal(true); i++) { ... }
Для получения символов, предоставляемых брокером, используем встроенную MQL5-функцию SymbolsTotal. Функция возвращает количество доступных (выбранных в "Обзоре рынка" или всех) символов. Для этого требуется всего один входной логический параметр, который, если ему присвоено значение true, вернет количество символов, выбранных в "Обзоре рынка". При false будет возвращено общее количество всех символов. Для наглядности выведем значения, когда входной параметр функции имеет значение false.
// Loop to create buttons for each symbol for(int i = 0; i < SymbolsTotal(false); i++) { Print("Index ",i,": Symbol = ",SymbolName(i,false)); ... }
В цикле for устанавливаем флаг выбранного значения на false, чтобы получить доступ ко всему списку символов. В выражении print мы использовали функцию SymbolName для получения имени символа в списке по позиции. Второй параметр функции задает режим запроса на основе критериев выбора символа в "Обзоре рынка". При true символ берется из списка символов, выбранных в "Обзоре рынка". При false символ берется из общего списка. Вот что мы получаем после компиляции.
Продолжение.
Как видим, выбраны все символы. В этом случае выбраны и отображены 396 символов. Теперь представьте, что вы хотите отобразить все символы на графике. Слишком много, не правда ли? Они не поместятся на одном графике, шрифт будет настолько мелким, что вы не сможете разглядеть символы. График просто засорится данными. К тому же, вам наверняка нужны не все символы. На этом этапе вы решите оставить только несколько наиболее приоритетных и исключить остальные. Предполагается, что выбор нужных пар будет осуществляться в "Обзоре рынка". Здесь вы размещаете необходимые вам символы, чтобы быстро просматривать котировки цен и отслеживать их динамику. Именно из "Обзора рынка" мы будем выбирать отображаемые символы. Для этого установим значение обеих функций равным true.
// Loop to create buttons for each symbol for(int i = 0; i < SymbolsTotal(true); i++) { Print("Index ",i,": Symbol = ",SymbolName(i,true)); ... }
Вот что мы получаем после компиляции:
Обратите внимание, что символы в "Обзоре рынка" (их 12) — те же символы, что указаны в панели "Инструменты" в том же хронологическом порядке. Мы выделили их красным и черным цветами соответственно для удобства различения и ссылок. Для создания динамических вертикальных кнопок используем следующую логику.
createButton(Symb + IntegerToString(i), SymbolName(i, true), 600, (50 + YS1) + i * YS1, XS1, YS1, clrW, clrGray, 11, clrGray);
Здесь имя кнопки формируется путем объединения макроса Symb с индексом i, преобразованным в строку с помощью функции IntegerToString, гарантирующей, что каждая кнопка имеет уникальный идентификатор, например Symb 0, Symb 1 и так далее. Устанавливаем метку кнопки на имя торгового символа с индексом i, полученным с помощью функции SymbolName, которая извлекает имя символа. Параметр true обеспечивает наличие имени в списке "Обзора рынка". Устанавливаем X-координату кнопки на 600 пикселей, выравнивая все кнопки символов по вертикали. Координата Y динамически рассчитывается как (50 + YS1) + i, умноженный на YS1, что последовательно смещает каждую кнопку вниз путем добавления высоты кнопки (YS1, которая составляет 25 пикселей), умноженной на индекс i, к начальному смещению в 50 пикселей плюс YS1. Размеры кнопки задаем с помощью значений XS1 (90 пикселей по ширине) и YS1 (25 пикселей по высоте). Устанавливаем цвет текста clrW (белый), цвет фона - серый, размер шрифта - 11, цвет границы - серый. После компиляции получаем следующее:
Это список всех символов, которые есть в "Обзоре рынка". Если вы добавите или удалите символы, они будут созданы автоматически в соответствии с динамической логикой их создания. Давайте удалим последние три пары, и посмотрим, так ли это.
Как видим, это делается автоматически. Теперь добавим удаленные символы обратно и продолжим разрабатывать логику, которая поможет идентифицировать и приоритизировать текущий выбранный символ, а также отличать его от остальных кнопок символов.
if (SymbolName(i, true) == _Symbol) { createButton(Symb + IntegerToString(i), "*" + SymbolName(i, true), 600, (50 + YS1) + i * YS1, XS1, YS1, clrB, clrLimeGreen, 11, clrW); } else { createButton(Symb + IntegerToString(i), SymbolName(i, true), 600, (50 + YS1) + i * YS1, XS1, YS1, clrW, clrGray, 11, clrGray); }
Здесь вместо создания кнопок с похожими текстурами мы создаем кнопки и различаем внешний вид кнопки текущего активного символа. Начнем с проверки того, совпадает ли имя символа с индексом i с текущим активным символом, извлеченным из предопределенной переменной _Symbol. Если есть совпадение, создаем кнопку с особым внешним видом, чтобы выделить активный символ. Для активного символа устанавливаем цвет текста clrB (черный), цвет фона - салатовый, размер шрифта - 11, а цвет границы - clrW (белый). Эта четкая цветовая схема выделит активный символ для легкой идентификации. Если символ не соответствует активному, то функция по умолчанию берет на себя управление и создает кнопку со стандартной схемой внешнего вида, гарантируя, что неактивные символы будут отображаться согласованно и визуально отлично от активного. Вот что мы получаем после компиляции.
После создания всех необходимых кнопок символов давайте создадим нижний колонтитул, обозначающий конец матрицы визуализации символов, и добавим в него некоторую сводную информацию. Используем следующий фрагмент кода.
// Create the base button for the RSI Dashboard at the end if (i == SymbolsTotal(true) - 1) { createButton(Base + IntegerToString(i), "RSI DashBoard", 600, (50 + YS1) + (i * YS1) + YS1, XS1 + XS2 * ArraySize(periods), YS1, clrW, clrGray, 11, clrGray); }
Чтобы создать базовую кнопку для панели RSI и убедиться, что она расположена в конце списка символов, мы начинаем с проверки того, равен ли текущий индекс i общему количеству символов минус один, что означает, что это последний символ в списке. Если это условие выполняется, то переходим к созданию базовой кнопки. Используем функцию createButton для определения внешнего вида и положения базовой кнопки. Чтобы создать имя кнопки, объединяем строку Base с индексом i, образуя уникальный идентификатор, и устанавливаем ее метку на RSI Dashboard. Это произвольное значение. Вы можете изменить его на другое по своему усмотрению. Затем мы размещаем кнопку в точке с координатой X 600 пикселей и координатой Y, рассчитанной как (50 + YS1) + (i * YS1) + YS1, гарантируя, что она появится чуть ниже последней кнопки символа. Определяем ширину кнопки как XS1 + XS2, умноженную на функцию ArraySize, которая возвращает общее количество элементов в массиве periods, чтобы охватить ширину всех кнопок таймфреймов вместе взятых, при этом мы определяем высоту как YS1 (25 пикселей). Цветовая схема кнопки имеет стандартный вид. Как правило, эта базовая кнопка будет действовать как понятная метка для всей панели RSI, обеспечивая визуальную привязку в нижней части списка символов. Вот основные результаты.
Наконец, теперь нам нужно создать кнопки, отображающие значения RSI для каждой комбинации символа и таймфрейма. Используем следующий фрагмент кода.
// Loop to create buttons for RSI values for each symbol and timeframe for (int j = 0; j < ArraySize(periods); j++) { createButton(RSI + IntegerToString(j) + " " + SymbolName(i, true), "-/-", (600 - XS1) + (j * -XS2), (50 + YS1) + (i * YS1), XS2 - 1, YS1 - 1, clrB, clrW, 12, clrW); } }
Начинаем цикл с целочисленного счетчика j, который представляет собой индекс таймфрейма. Для каждого таймфрейма вызываем функцию createButton, чтобы настроить кнопку, которая будет отображать значение RSI для текущего символа и таймфрейма. Генерируем имя кнопки путем объединения строки RSI с индексом j и именем символа, обеспечивая уникальный идентификатор для каждой кнопки. Устанавливаем метку для кнопки на -/-, которую позже обновим фактическим значением RSI. Сейчас нам просто нужно убедиться, что мы можем создать сетку символов и периодов. Размещаем кнопку в точке с координатой X (600 - XS1) + (j * - XS2), что позволяет расположить кнопки горизонтально на расстоянии XS2 (100 пикселей) с учетом ширины кнопки. Аналогично устанавливаем координату Y на (50 + YS1) + (i * YS1). Таким образом кнопки будут размещены вертикально на основе индекса символа. Размеры кнопки: XS2 - 1 по ширине и YS1 - 1 по высоте. Вычитание 1 гарантирует, что мы оставим границу в 1 пиксель, чтобы возникла иллюзия сетки кнопок. Затем устанавливаем цветовую схему для кнопок с цветом текста clrB (черный), цветом фона clrW (белый), размером шрифта 12 и цветом границы clrW (белый). Эта настройка организует кнопки RSI в виде сетки, каждая из которых связана с определенным символом и таймфреймом, обеспечивая четкое и структурированное представление значений RSI за разные периоды. Вот что мы получаем после компиляции:
Теперь, когда мы можем создать общую сетку панели мониторинга, нам осталось только получить значения индикаторов и обновить кнопки. Прежде чем мы дойдем до этого момента, мы можем предварительно просмотреть созданные объекты, щелкнув правой кнопкой мыши в любом месте графика и выбрав опцию "Список объектов" во всплывающем окне. Также вы можете нажать Ctrl + B. Во всплывающем окне нажмите "Все", и вам будет доступен список созданных нами элементов.
Теперь мы уверены, что создаем объекты на схеме в хронологическом порядке с соответствующими им уникальными именами. Это гарантирует, что ничто не будет пропущено. Например, мы можем видеть, что в первый символ обозначен как Symb 0, что отличает его от других символов. Если бы мы не объединили имена символов с соответствующими объектами, все кнопки имели бы одинаковое имя символа, что привело бы к ошибке, поскольку после создания имени оно закрепляется за объектом, и никакая другая кнопка не получит то же самое имя. Таким образом, технически будет создана только одна кнопка, а остальные будут проигнорированы. Довольно хитро придумано! Продолжим создавать значения инициализации.
// Loop to initialize RSI values and update buttons for(int i = 0; i < SymbolsTotal(true); i++) { for (int j = 0; j < ArraySize(periods); j++) { // Get the handle ID for the RSI indicator for the specific symbol and timeframe handleName_Id = iRSI(SymbolName(i, true), periods[j], 14, PRICE_CLOSE); ArraySetAsSeries(rsi_Data_Val, true); // Set the array to be used as a time series CopyBuffer(handleName_Id, 0, 0, 1, rsi_Data_Val); // Copy the RSI values into the array ... }
Здесь мы используем два цикла для перебора каждого торгового символа и таймфрейма, чтобы инициализировать значения RSI и обновить соответствующие кнопки. Мы начинаем с внешнего цикла с индексом i, который выбирает торговый символ в "Обзоре рынка", а внутри этого цикла у нас есть внутренний цикл for, который проходит по каждому определенному таймфрейму для каждого выбранного символа. Это означает, что, например, выбрав AUDUSD, мы перебираем все заданные таймфреймы, то есть M1, M5, H1, H4 и D1. Давайте посмотрим на следующий код, чтобы понять логику.
// Loop to initialize RSI values and update buttons for(int i = 0; i < SymbolsTotal(true); i++) { Print("SELECTED SYMBOL = ",SymbolName(i, true)); for (int j = 0; j < ArraySize(periods); j++) { Print("PERIOD = ",EnumToString(periods[j])); ... }
Вот что у нас есть.
Далее для каждой комбинации символа и таймфрейма мы получаем хэндл индикатора RSI, используя функцию iRSI с параметрами: символ, таймфрейм, период RSI равный 14 и цену закрытия в качестве типа цены. Хэндл handleName_Id — это просто целое число, которое определяется и хранится где-то в памяти нашего компьютера и позволяет нам взаимодействовать с данными индикатора RSI. По умолчанию целое число начинается с индекса 10, и если создается какой-либо другой индикатор, оно увеличивается на единицу, то есть 10, 11, 12, 13 и так далее. Для наглядности давайте его распечатаем.
Print("PERIOD = ",EnumToString(periods[j]),": HANDLE ID = ",handleName_Id);
Вот что мы получаем после компиляции:
Как видим, идентификатор хэндла начинается с 10, и поскольку у нас есть 12 символов и 5 периодов для каждого символа, мы ожидаем в общей сложности (12 * 5 = 60) 60 хэндлов индикатора. Но поскольку мы начинаем индексацию с 10, нам нужно будет включить в результат первые 9 значений, чтобы получить окончательный идентификатор хэндла. Математически это будет 60 + 9, что дает (60 + 9 = 69) 69. Давайте проверим правильность этого утверждения с помощью визуального представления.
Всё правильно. Затем установим массив rsi_Data_Val для использования в качестве временного ряда, вызвав функцию ArraySetAsSeries и установив флаг на true для подтверждения действия. Такая конфигурация гарантирует, что самые последние значения RSI будут находиться в начале массива. Затем копируем значения RSI в массив, используя функцию CopyBuffer, где 0 указывает индекс буфера, 0 — начальная позиция, 1 - количество точек данных для извлечения, а rsi_Data_Val - целевой массив, в котором мы сохраняем извлеченные данные для дальнейшего анализа. Распечатаем данные в журнале и посмотрим, что получится. Используем функцию ArrayPrint для отображения простого динамического массива.
ArrayPrint(rsi_Data_Val);
Вот что мы получаем:
Прекрасно! Мы получили правильные данные. Вы можете сопоставить данные в окне индикатора и в окне данных с теми, которые мы извлекаем. Можно двигаться дальше. Для каждой новой логики, добавляемой на панель управления, рекомендуется скомпилировать и запустить тест, чтобы подтвердить, что вы получаете ожидаемые результаты. Теперь нам просто нужно использовать значения индикаторов для обновления панели, и всё будет готово.
// Update the button with the RSI value and colors update_Button(RSI + IntegerToString(j) + " " + SymbolName(i, true), DoubleToString(rsi_Data_Val[0], 2), clrBlack, clrW_Gray, clrWhite);
Здесь мы используем пользовательскую функцию update_Button для обновления кнопок панели управления соответствующими данными индикатора. Логика функции следующая.
//+------------------------------------------------------------------+ //| Function to update a button | //+------------------------------------------------------------------+ bool update_Button(string objName, string text, color clrTxt = clrBlack, color clrBG = clrWhite, color clrBorder = clrWhite ) { int found = ObjectFind(0, objName); // Find the button by name // Check if the button exists if (found < 0) { ResetLastError(); // Reset the last error code Print("UNABLE TO FIND THE BTN: ERR Code: ", GetLastError()); // Print error message if button is not found return (false); // Return false if button is not found } else { // Update button properties ObjectSetString(0, objName, OBJPROP_TEXT, text); // Set button text ObjectSetInteger(0, objName, OBJPROP_COLOR, clrTxt); // Set text color ObjectSetInteger(0, objName, OBJPROP_BGCOLOR, clrBG); // Set background color ObjectSetInteger(0, objName, OBJPROP_BORDER_COLOR, clrBorder); // Set border color ChartRedraw(0); // Redraw the chart to reflect the updated button return (true); // Return true if update is successful } }
Эта функция полностью идентична той, которую мы использовали для создания кнопки. Разница в том, что вместо создания кнопки мы находим кнопку с помощью функции ObjectFind. В случае успеха функция возвращает номер подокна (0 означает главное окно графика), в котором найден объект. Если объект не найден, функция возвращает отрицательное число. Таким образом, возвращаемое значение меньше 0 означает отсутствие объекта, и мы сообщаем об ошибке, указывая ее код, а перед этим сбрасываем предыдущую возможную ошибку и возвращаем false для завершения функции. Если тест пройден, находим объект и приступаем к обновлению его свойств, то есть текста, его цветовых схем, фона и границы. Вот что мы получаем после компиляции:
Отлично! Теперь у нас есть данные индикатора в сетке. Посмотрите, насколько правильно отображаются данные с помощью всего одной строки кода. Также убедитесь, что данные, которые у нас были ранее, а именно 55,27 для часового таймфрейма текущего символа, заполнены правильно. Для большей полноты давайте определим условия входа на рынок на основе уровней перекупленности и перепроданности и изменим цвета для более удобного использования. Итак, теперь вместо использования фиксированных цветов будем использовать динамические.
// Declare variables for button colors color text_clr, bg_clr, border_clr; // Determine button colors based on RSI value if (rsi_Data_Val[0] < 30) { text_clr = clrWhite; bg_clr = clrGreen; border_clr = clrGreen; } else if (rsi_Data_Val[0] > 70) { text_clr = clrWhite; bg_clr = clrRed; border_clr = clrRed; } else { text_clr = clrBlack; bg_clr = clrW_Gray; border_clr = clrWhite; } // Update the button with the RSI value and colors update_Button(RSI + IntegerToString(j) + " " + SymbolName(i, true), DoubleToString(rsi_Data_Val[0], 2), text_clr, bg_clr, border_clr);
Сначала объявим три переменные для цветов кнопок: text_clr - для цвета текста, bg_clr - для цвета фона и border_clr - для цвета границы. Далее определим соответствующие цвета на основе значения RSI, хранящегося в массиве данных RSI. Если значение RSI ниже 30, что обычно указывает на перепроданность актива, мы устанавливаем белый цвет текста, зеленый цвет фона и зеленый цвет границы, что позволяет выделить кнопку зеленым цветом, указывая на потенциальную возможность покупки.
Аналогично, если значение RSI выше 70, что говорит о перекупленности актива, мы устанавливаем цвет текста на белый, цвет фона на красный и цвет границы на красный, указывая на потенциальную возможность продажи с помощью красной кнопки. Для значений RSI от 30 до 70 сохраняется цветовая схема по умолчанию, отражающая стандартный, или нейтральный статус. Наконец, мы обновляем внешний вид кнопки, вызывая функцию update_Button и передавая соответствующие параметры кнопки. Это гарантирует, что каждая кнопка на панели управления точно отражает текущее состояние RSI и визуально передает рыночные условия. Результаты представлены ниже:
Замечательно! Мы создали динамичную и адаптивную панель индикатора, которая отображает текущие преобладающие рыночные условия на графике с возможностью более удобного выделения.
Полный исходный код, отвечающий за инициализацию панели индикатора, выглядит следующим образом:
//+------------------------------------------------------------------+ //| ADVANCED IND DASHBOARD.mq5 | //| Copyright 2024, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2024, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" // Define button identifiers and properties #define BTN1 "BTN1" #define Desc "Desc " #define Symb "Symb " #define Base "Base " #define RSI "RSI " #define XS1 90 #define XS2 100 #define YS1 25 #define clrW clrWhite #define clrB clrBlack #define clrW_Gray C'230,230,230' // Define the timeframes to be used ENUM_TIMEFRAMES periods[] = {PERIOD_M1, PERIOD_M5, PERIOD_H1, PERIOD_H4, PERIOD_D1}; // Function to truncate the ENUM_TIMEFRAMES string for display purposes string truncPrds(ENUM_TIMEFRAMES period) { // Extract the timeframe abbreviation from the full ENUM string string prd = StringSubstr(EnumToString(period), 7); return prd; // Return the truncated string } // Global variables int handleName_Id; // Variable to store the handle ID for the RSI indicator double rsi_Data_Val[]; // Array to store the RSI values //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { // Create the main button for the pair with specific properties createButton(BTN1, "PAIR", 600, 50, XS1, YS1, clrW, clrGray, 15, clrGray); // Loop to create buttons for each timeframe with the corresponding RSI label for(int i = 0; i < ArraySize(periods); i++) { //Print("BEFORE: ",EnumToString(periods[i])); //Print("AFTER: ",truncPrds(periods[i])); createButton(Desc + IntegerToString(i), truncPrds(periods[i]) + " RSI 14", (600 - XS1) + i * -XS2, 50, XS2, YS1, clrW, clrGray, 13, clrGray); } // Loop to create buttons for each symbol for(int i = 0; i < SymbolsTotal(true); i++) { //Print("Index ",i,": Symbol = ",SymbolName(i,true)); // Check if the symbol is the current symbol being traded if (SymbolName(i, true) == _Symbol) { createButton(Symb + IntegerToString(i), "*" + SymbolName(i, true), 600, (50 + YS1) + i * YS1, XS1, YS1, clrB, clrLimeGreen, 11, clrW); } else { createButton(Symb + IntegerToString(i), SymbolName(i, true), 600, (50 + YS1) + i * YS1, XS1, YS1, clrW, clrGray, 11, clrGray); } // Create the base button for the RSI Dashboard at the end if (i == SymbolsTotal(true) - 1) { createButton(Base + IntegerToString(i), "RSI DashBoard", 600, (50 + YS1) + (i * YS1) + YS1, XS1 + XS2 * ArraySize(periods), YS1, clrW, clrGray, 11, clrGray); } // Loop to create buttons for RSI values for each symbol and timeframe for (int j = 0; j < ArraySize(periods); j++) { createButton(RSI + IntegerToString(j) + " " + SymbolName(i, true), "-/-", (600 - XS1) + (j * -XS2), (50 + YS1) + (i * YS1), XS2 - 1, YS1 - 1, clrB, clrW, 12, clrW); } } // Loop to initialize RSI values and update buttons for(int i = 0; i < SymbolsTotal(true); i++) { //Print("SELECTED SYMBOL = ",SymbolName(i, true)); for (int j = 0; j < ArraySize(periods); j++) { //Print("PERIOD = ",EnumToString(periods[j])); // Get the handle ID for the RSI indicator for the specific symbol and timeframe handleName_Id = iRSI(SymbolName(i, true), periods[j], 14, PRICE_CLOSE); //Print("PERIOD = ",EnumToString(periods[j]),": HANDLE ID = ",handleName_Id); ArraySetAsSeries(rsi_Data_Val, true); // Set the array to be used as a time series CopyBuffer(handleName_Id, 0, 0, 1, rsi_Data_Val); // Copy the RSI values into the array //ArrayPrint(rsi_Data_Val); // Declare variables for button colors color text_clr, bg_clr, border_clr; // Determine button colors based on RSI value if (rsi_Data_Val[0] < 30) { text_clr = clrWhite; bg_clr = clrGreen; border_clr = clrGreen; } else if (rsi_Data_Val[0] > 70) { text_clr = clrWhite; bg_clr = clrRed; border_clr = clrRed; } else { text_clr = clrBlack; bg_clr = clrW_Gray; border_clr = clrWhite; } // Update the button with the RSI value and colors update_Button(RSI + IntegerToString(j) + " " + SymbolName(i, true), DoubleToString(rsi_Data_Val[0], 2), text_clr, bg_clr, border_clr); } } return(INIT_SUCCEEDED); // Return initialization success } //+------------------------------------------------------------------+ //| Function to create a button | //+------------------------------------------------------------------+ bool createButton(string objName, string text, int xD, int yD, int xS, int yS, color clrTxt, color clrBg, int fontSize = 12, color clrBorder = clrNONE, string font = "Arial Rounded MT Bold" ) { ResetLastError(); // Reset the last error code // Attempt to create the button if (!ObjectCreate(0, objName, OBJ_BUTTON, 0, 0, 0)) { Print(__FUNCTION__, ": failed to create Btn: ERR Code: ", GetLastError()); // Print error message if button creation fails return (false); // Return false if creation fails } // Set button properties ObjectSetInteger(0, objName, OBJPROP_XDISTANCE, xD); // Set X distance ObjectSetInteger(0, objName, OBJPROP_YDISTANCE, yD); // Set Y distance ObjectSetInteger(0, objName, OBJPROP_XSIZE, xS); // Set X size ObjectSetInteger(0, objName, OBJPROP_YSIZE, yS); // Set Y size ObjectSetInteger(0, objName, OBJPROP_CORNER, CORNER_RIGHT_UPPER); // Set corner position ObjectSetString(0, objName, OBJPROP_TEXT, text); // Set button text ObjectSetString(0, objName, OBJPROP_FONT, font); // Set font type ObjectSetInteger(0, objName, OBJPROP_FONTSIZE, fontSize); // Set font size ObjectSetInteger(0, objName, OBJPROP_COLOR, clrTxt); // Set text color ObjectSetInteger(0, objName, OBJPROP_BGCOLOR, clrBg); // Set background color ObjectSetInteger(0, objName, OBJPROP_BORDER_COLOR, clrBorder); // Set border color ObjectSetInteger(0, objName, OBJPROP_BACK, false); // Set background property ObjectSetInteger(0, objName, OBJPROP_STATE, false); // Set button state ObjectSetInteger(0, objName, OBJPROP_SELECTABLE, false); // Set if the button is selectable ObjectSetInteger(0, objName, OBJPROP_SELECTED, false); // Set if the button is selected ChartRedraw(0); // Redraw the chart to reflect the new button return (true); // Return true if creation is successful } //+------------------------------------------------------------------+ //| Function to update a button | //+------------------------------------------------------------------+ bool update_Button(string objName, string text, color clrTxt = clrBlack, color clrBG = clrWhite, color clrBorder = clrWhite ) { int found = ObjectFind(0, objName); // Find the button by name // Check if the button exists if (found < 0) { ResetLastError(); // Reset the last error code Print("UNABLE TO FIND THE BTN: ERR Code: ", GetLastError()); // Print error message if button is not found return (false); // Return false if button is not found } else { // Update button properties ObjectSetString(0, objName, OBJPROP_TEXT, text); // Set button text ObjectSetInteger(0, objName, OBJPROP_COLOR, clrTxt); // Set text color ObjectSetInteger(0, objName, OBJPROP_BGCOLOR, clrBG); // Set background color ObjectSetInteger(0, objName, OBJPROP_BORDER_COLOR, clrBorder); // Set border color ChartRedraw(0); // Redraw the chart to reflect the updated button return (true); // Return true if update is successful } }
Даже если мы создадим панель со всеми элементами, нам придется обновлять ее на каждом этапе, чтобы данные на панели отражали самые последние данные. Это достигается с помощью обработчика событий OnTick.
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { ... }
Это функция обработки события void, которая вызывается всякий раз, когда происходят изменения в котировках цен. Чтобы обновить значения индикатора, нам потребуется запустить здесь некоторую часть кода инициализации, чтобы получить последние значения.
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { // Loop to update RSI values and buttons on each tick for(int i = 0; i < SymbolsTotal(true); i++) { for (int j = 0; j < ArraySize(periods); j++) { // Get the handle ID for the RSI indicator for the specific symbol and timeframe handleName_Id = iRSI(SymbolName(i, true), periods[j], 14, PRICE_CLOSE); ArraySetAsSeries(rsi_Data_Val, true); // Set the array to be used as a time series CopyBuffer(handleName_Id, 0, 0, 1, rsi_Data_Val); // Copy the RSI values into the array // Declare variables for button colors color text_clr, bg_clr, border_clr; // Determine button colors based on RSI value if (rsi_Data_Val[0] < 30) { text_clr = clrWhite; bg_clr = clrGreen; border_clr = clrGreen; } else if (rsi_Data_Val[0] > 70) { text_clr = clrWhite; bg_clr = clrRed; border_clr = clrRed; } else { text_clr = clrBlack; bg_clr = clrW_Gray; border_clr = clrWhite; } // Update the button with the RSI value and colors update_Button(RSI + IntegerToString(j) + " " + SymbolName(i, true), DoubleToString(rsi_Data_Val[0], 2), text_clr, bg_clr, border_clr); } } }
Здесь мы просто копируем и вставляем фрагмент кода в раздел инициализации, который содержит два цикла for - внутренний и внешний, - и обновляем значения индикатора. Это гарантирует, что на каждом тике мы обновляем значения в соответствии с последними полученными данными. Таким образом, панель управления становится очень динамичной, живой и привлекательной в использовании. Результаты показаны на GIF-изображении ниже:
Наконец, нам нужно избавиться от созданных нами объектов, то есть от панели индикатора, после того как советник будет удален с графика. Это гарантирует, что панель будет удалена, а график останется чистым. Таким образом, функция имеет решающее значение для поддержания чистой и эффективной торговой среды. Это достигается с помощью функции обработчика событий OnDeinit, которая вызывается всякий раз, когда советник удаляется с графика.
//+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { // remove all dashboard objects ObjectsDeleteAll(0,BTN1); ObjectsDeleteAll(0,Desc); ObjectsDeleteAll(0,Symb); ObjectsDeleteAll(0,Base); ObjectsDeleteAll(0,RSI); ChartRedraw(0); }
Здесь мы вызываем функцию ObjectDeleteAll для удаления всех объектов с указанным префиксом имени. Функция удаляет все объекты указанного типа, используя префиксы в именах объектов. Логика здесь довольно проста. Мы вызываем функцию для каждого из определенных префиксов: BTN1, Desc, Symb, Base и RSI, поскольку это гарантирует удаление всех объектов, связанных с этими префиксами, что фактически удалит все кнопки и графические элементы с графика. Наконец мы вызываем функцию ChartRedraw для обновления графика и фиксации удаления этих объектов, гарантируя, что график обновлен и свободен от любых элементов, созданных программой. Посмотрим на результат:
Мы создали полнофункциональную панель индикатора на языке MQL5. Полный исходный код для создания панели выглядит так:
//+------------------------------------------------------------------+ //| ADVANCED IND DASHBOARD.mq5 | //| Copyright 2024, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2024, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" // Define button identifiers and properties #define BTN1 "BTN1" #define Desc "Desc " #define Symb "Symb " #define Base "Base " #define RSI "RSI " #define XS1 90 #define XS2 100 #define YS1 25 #define clrW clrWhite #define clrB clrBlack #define clrW_Gray C'230,230,230' // Define the timeframes to be used ENUM_TIMEFRAMES periods[] = {PERIOD_M1, PERIOD_M5, PERIOD_H1, PERIOD_H4, PERIOD_D1}; // Function to truncate the ENUM_TIMEFRAMES string for display purposes string truncPrds(ENUM_TIMEFRAMES period) { // Extract the timeframe abbreviation from the full ENUM string string prd = StringSubstr(EnumToString(period), 7); return prd; // Return the truncated string } // Global variables int handleName_Id; // Variable to store the handle ID for the RSI indicator double rsi_Data_Val[]; // Array to store the RSI values //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { // Create the main button for the pair with specific properties createButton(BTN1, "PAIR", 600, 50, XS1, YS1, clrW, clrGray, 15, clrGray); // Loop to create buttons for each timeframe with the corresponding RSI label for(int i = 0; i < ArraySize(periods); i++) { //Print("BEFORE: ",EnumToString(periods[i])); //Print("AFTER: ",truncPrds(periods[i])); createButton(Desc + IntegerToString(i), truncPrds(periods[i]) + " RSI 14", (600 - XS1) + i * -XS2, 50, XS2, YS1, clrW, clrGray, 13, clrGray); } // Loop to create buttons for each symbol for(int i = 0; i < SymbolsTotal(true); i++) { //Print("Index ",i,": Symbol = ",SymbolName(i,true)); // Check if the symbol is the current symbol being traded if (SymbolName(i, true) == _Symbol) { createButton(Symb + IntegerToString(i), "*" + SymbolName(i, true), 600, (50 + YS1) + i * YS1, XS1, YS1, clrB, clrLimeGreen, 11, clrW); } else { createButton(Symb + IntegerToString(i), SymbolName(i, true), 600, (50 + YS1) + i * YS1, XS1, YS1, clrW, clrGray, 11, clrGray); } // Create the base button for the RSI Dashboard at the end if (i == SymbolsTotal(true) - 1) { createButton(Base + IntegerToString(i), "RSI DashBoard", 600, (50 + YS1) + (i * YS1) + YS1, XS1 + XS2 * ArraySize(periods), YS1, clrW, clrGray, 11, clrGray); } // Loop to create buttons for RSI values for each symbol and timeframe for (int j = 0; j < ArraySize(periods); j++) { createButton(RSI + IntegerToString(j) + " " + SymbolName(i, true), "-/-", (600 - XS1) + (j * -XS2), (50 + YS1) + (i * YS1), XS2 - 1, YS1 - 1, clrB, clrW, 12, clrW); } } // Loop to initialize RSI values and update buttons for(int i = 0; i < SymbolsTotal(true); i++) { //Print("SELECTED SYMBOL = ",SymbolName(i, true)); for (int j = 0; j < ArraySize(periods); j++) { //Print("PERIOD = ",EnumToString(periods[j])); // Get the handle ID for the RSI indicator for the specific symbol and timeframe handleName_Id = iRSI(SymbolName(i, true), periods[j], 14, PRICE_CLOSE); //Print("PERIOD = ",EnumToString(periods[j]),": HANDLE ID = ",handleName_Id); ArraySetAsSeries(rsi_Data_Val, true); // Set the array to be used as a time series CopyBuffer(handleName_Id, 0, 0, 1, rsi_Data_Val); // Copy the RSI values into the array //ArrayPrint(rsi_Data_Val); // Declare variables for button colors color text_clr, bg_clr, border_clr; // Determine button colors based on RSI value if (rsi_Data_Val[0] < 30) { text_clr = clrWhite; bg_clr = clrGreen; border_clr = clrGreen; } else if (rsi_Data_Val[0] > 70) { text_clr = clrWhite; bg_clr = clrRed; border_clr = clrRed; } else { text_clr = clrBlack; bg_clr = clrW_Gray; border_clr = clrWhite; } // Update the button with the RSI value and colors update_Button(RSI + IntegerToString(j) + " " + SymbolName(i, true), DoubleToString(rsi_Data_Val[0], 2), text_clr, bg_clr, border_clr); } } return(INIT_SUCCEEDED); // Return initialization success } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { // remove all dashboard objects ObjectsDeleteAll(0,BTN1); ObjectsDeleteAll(0,Desc); ObjectsDeleteAll(0,Symb); ObjectsDeleteAll(0,Base); ObjectsDeleteAll(0,RSI); ChartRedraw(0); } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { // Loop to update RSI values and buttons on each tick for(int i = 0; i < SymbolsTotal(true); i++) { for (int j = 0; j < ArraySize(periods); j++) { // Get the handle ID for the RSI indicator for the specific symbol and timeframe handleName_Id = iRSI(SymbolName(i, true), periods[j], 14, PRICE_CLOSE); ArraySetAsSeries(rsi_Data_Val, true); // Set the array to be used as a time series CopyBuffer(handleName_Id, 0, 0, 1, rsi_Data_Val); // Copy the RSI values into the array // Declare variables for button colors color text_clr, bg_clr, border_clr; // Determine button colors based on RSI value if (rsi_Data_Val[0] < 30) { text_clr = clrWhite; bg_clr = clrGreen; border_clr = clrGreen; } else if (rsi_Data_Val[0] > 70) { text_clr = clrWhite; bg_clr = clrRed; border_clr = clrRed; } else { text_clr = clrBlack; bg_clr = clrW_Gray; border_clr = clrWhite; } // Update the button with the RSI value and colors update_Button(RSI + IntegerToString(j) + " " + SymbolName(i, true), DoubleToString(rsi_Data_Val[0], 2), text_clr, bg_clr, border_clr); } } } //+------------------------------------------------------------------+ //| Function to create a button | //+------------------------------------------------------------------+ bool createButton(string objName, string text, int xD, int yD, int xS, int yS, color clrTxt, color clrBg, int fontSize = 12, color clrBorder = clrNONE, string font = "Arial Rounded MT Bold" ) { ResetLastError(); // Reset the last error code // Attempt to create the button if (!ObjectCreate(0, objName, OBJ_BUTTON, 0, 0, 0)) { Print(__FUNCTION__, ": failed to create Btn: ERR Code: ", GetLastError()); // Print error message if button creation fails return (false); // Return false if creation fails } // Set button properties ObjectSetInteger(0, objName, OBJPROP_XDISTANCE, xD); // Set X distance ObjectSetInteger(0, objName, OBJPROP_YDISTANCE, yD); // Set Y distance ObjectSetInteger(0, objName, OBJPROP_XSIZE, xS); // Set X size ObjectSetInteger(0, objName, OBJPROP_YSIZE, yS); // Set Y size ObjectSetInteger(0, objName, OBJPROP_CORNER, CORNER_RIGHT_UPPER); // Set corner position ObjectSetString(0, objName, OBJPROP_TEXT, text); // Set button text ObjectSetString(0, objName, OBJPROP_FONT, font); // Set font type ObjectSetInteger(0, objName, OBJPROP_FONTSIZE, fontSize); // Set font size ObjectSetInteger(0, objName, OBJPROP_COLOR, clrTxt); // Set text color ObjectSetInteger(0, objName, OBJPROP_BGCOLOR, clrBg); // Set background color ObjectSetInteger(0, objName, OBJPROP_BORDER_COLOR, clrBorder); // Set border color ObjectSetInteger(0, objName, OBJPROP_BACK, false); // Set background property ObjectSetInteger(0, objName, OBJPROP_STATE, false); // Set button state ObjectSetInteger(0, objName, OBJPROP_SELECTABLE, false); // Set if the button is selectable ObjectSetInteger(0, objName, OBJPROP_SELECTED, false); // Set if the button is selected ChartRedraw(0); // Redraw the chart to reflect the new button return (true); // Return true if creation is successful } //+------------------------------------------------------------------+ //| Function to update a button | //+------------------------------------------------------------------+ bool update_Button(string objName, string text, color clrTxt = clrBlack, color clrBG = clrWhite, color clrBorder = clrWhite ) { int found = ObjectFind(0, objName); // Find the button by name // Check if the button exists if (found < 0) { ResetLastError(); // Reset the last error code Print("UNABLE TO FIND THE BTN: ERR Code: ", GetLastError()); // Print error message if button is not found return (false); // Return false if button is not found } else { // Update button properties ObjectSetString(0, objName, OBJPROP_TEXT, text); // Set button text ObjectSetInteger(0, objName, OBJPROP_COLOR, clrTxt); // Set text color ObjectSetInteger(0, objName, OBJPROP_BGCOLOR, clrBG); // Set background color ObjectSetInteger(0, objName, OBJPROP_BORDER_COLOR, clrBorder); // Set border color ChartRedraw(0); // Redraw the chart to reflect the updated button return (true); // Return true if update is successful } }
Заключение
Мультисимвольная мультипериодная панель индикатора RSI на языке MetaQuotes Language 5 (MQL5) является полезным инструментом анализа рынка. Панель отображает значения RSI в реальном времени на разных символах и таймфреймах, помогая трейдерам быстрее принимать обоснованные решения.Создание панели управления включало в себя настройку компонентов, создание кнопок и их обновление на основе данных в реальном времени с использованием функций MQL5. Конечный продукт функционален и удобен в использовании.
Статья продемонстрировала, как можно использовать MQL5 для создания практичных торговых инструментов. Трейдеры могут копировать или модифицировать созданную панель в соответствии со своими потребностями и использовать ее как в полуавтоматических, так и в ручных торговых стратегиях в меняющейся рыночной среде.
Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/15356





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