Торговые инструменты на языке MQL5 (Часть 6): Динамическая голографическая панель с импульсной анимацией и элементами управления
Введение
В своей предыдущей статье (Часть 5) мы создали бегущую тикерную строку на MetaQuotes Language 5 (MQL5) для мониторинга инструментов в режиме реального времени с прокруткой цен, спредов и изменений, чтобы эффективно информировать трейдеров. В Части 6 мы разрабатываем динамическую голографическую панель, отображающую индикаторы на нескольких инструментах и таймфреймах, такие как Индекс относительной силы (RSI) и волатильность (на основе Среднего истинного диапазона (ATR)), с анимацией импульсов, сортировкой и интерактивными элементами управления, создавая привлекательный инструмент анализа. В статье рассмотрим следующие темы:
- Понимание архитектуры голографической панели
- Реализация средствами MQL5
- Тестирование на истории
- Заключение
В итоге у вас будет настраиваемая голографическая панель, готовая для вашей торговой настройки. Давайте начнём!
Понимание архитектуры голографической панели
Создаваемая нами голографическая панель — это визуальный инструмент, отслеживающий несколько инструментов и таймфреймов, отображая такие индикаторы, как RSI и волатильность, а также функции сортировки и оповещений, чтобы помочь нам быстро выявлять возможности. Такая архитектура важна, поскольку она сочетает данные в реальном времени с интерактивными элементами управления и анимацией, что делает анализ более увлекательным и эффективным в перегруженной графической среде.
Мы достигнем этого, используя массивы для управления данными, хэндлы для таких индикаторов, как ATR и RSI, и функции для эффектов сортировки и импульсов, а также кнопки для переключения видимости и анализа. Мы планируем централизовать обновления в цикле, который будет динамически обновлять пользовательский интерфейс (UI), обеспечивая адаптивность и реагирование панели в целях стратегической торговли. Прежде чем приступим к реализации, просмотрите визуализацию ниже для понимания того, чего мы хотим достичь.

Реализация средствами MQL5
Чтобы создать программу на MQL5, нам нужно будет определить метаданные программы, а затем определить некоторые входные данные, которые позволят легко изменять работу программы, не вмешиваясь непосредственно в код.
//+------------------------------------------------------------------+ //| Holographic Dashboard EA.mq5 | //| Copyright 2025, Allan Munene Mutiiria. | //| https://t.me/Forex_Algo_Trader | //+------------------------------------------------------------------+ #property copyright "Copyright 2025, Allan Munene Mutiiria." #property link "https://t.me/Forex_Algo_Trader" #property version "1.00" #include <Arrays\ArrayString.mqh> //--- Include ArrayString library for string array operations #include <Files\FileTxt.mqh> //--- Include FileTxt library for text file operations // Input Parameters input int BaseFontSize = 9; // Base Font Size input string FontType = "Calibri"; // Font Type (Professional) input int X_Offset = 30; // X Offset input int Y_Offset = 30; // Y Offset input color PanelColor = clrDarkSlateGray; // Panel Background Color input color TitleColor = clrWhite; // Title/Symbol Color input color DataColor = clrLightGray; // Bid/Neutral Color input color ActiveColor = clrLime; // Active Timeframe/Symbol Color input color UpColor = clrDeepSkyBlue; // Uptrend Color input color DownColor = clrCrimson; // Downtrend Color input color LineColor = clrSilver; // Grid Line Color input bool EnableAnimations = true; // Enable Pulse Animations input int PanelWidth = 730; // Panel Width (px) input int ATR_Period = 14; // ATR Period for Volatility input int RSI_Period = 14; // RSI Period input double Vol_Alert_Threshold = 2.0; // Volatility Alert Threshold (%) input color GlowColor = clrDodgerBlue; // Glow Color for holographic effect input int AnimationSpeed = 30; // Animation delay in ms for pulse input int PulseCycles = 3; // Number of pulse cycles for animations
Начинаем с включения библиотек для ведения лога массивов строк и текстовых файлов, а также настройки входных данных для настройки пользовательского интерфейса и индикаторов. Включаем "<Arrays\ArrayString.mqh>" для обработки списков инструментов и "<Files\FileTxt.mqh>" для вывода ошибок в файл. Входные данные позволят настроить базовый размер шрифта на 9, выбрать профессиональный шрифт, такой как Calibri, установить смещения для позиционирования x и y на 30 пикселей каждое и выбрать такие цвета, как темно-серый для фона панели, белый для заголовков, светло-серый для данных, желтый для активных элементов, глубокий небесно-голубой - для восходящих трендов, малиновый - для нисходящих и серебристый - для линий сетки.
Включаем импульсную анимацию по умолчанию, определяем ширину панели в 730 пикселей, устанавливаем периоды ATR и RSI равными 14 для расчета волатильности и импульса, устанавливаем порог в 2,0% для оповещений о волатильности, выбираем Dodger Blue для голографического свечения и настраиваем скорость анимации на 30 мс с 3 импульсными циклами. Эти настройки позволят легко адаптировать панель к визуальным и функциональным предпочтениям. Затем нужно определить несколько глобальных переменных, которые мы будем использовать на протяжении всей программы.
// Global Variables double prices_PrevArray[]; //--- Array for previous prices double volatility_Array[]; //--- Array for volatility values double bid_array[]; //--- Array for bid prices long spread_array[]; //--- Array for spreads double change_array[]; //--- Array for percentage changes double vol_array[]; //--- Array for volumes double rsi_array[]; //--- Array for RSI values int indices[]; //--- Array for sorted indices ENUM_TIMEFRAMES periods[] = {PERIOD_M1, PERIOD_M5, PERIOD_H1, PERIOD_H2, PERIOD_H4, PERIOD_D1, PERIOD_W1}; //--- Array of timeframes string logFileName = "Holographic_Dashboard_Log.txt"; //--- Log file name int sortMode = 0; //--- Current sort mode string sortNames[] = {"Name ASC", "Vol DESC", "Change ABS DESC", "RSI DESC"}; //--- Sort mode names int atr_handles_sym[]; //--- ATR handles for symbols int rsi_handles_sym[]; //--- RSI handles for symbols int atr_handles_tf[]; //--- ATR handles for timeframes int rsi_handles_tf[]; //--- RSI handles for timeframes int totalSymbols; //--- Total number of symbols bool dashboardVisible = true; //--- Dashboard visibility flag
Здесь мы определяем глобальные переменные для управления данными и индикаторами в нашей программе, поддерживая мониторинг, сортировку и анимацию в режиме реального времени. Создаем массивы типа "prices_PrevArray" для предыдущих цен для расчета изменений, "volatility_Array" для значений волатильности, "bid_array" для текущих предложений цены, "spread_array" для длинных спредов, "change_array" для процентных изменений, "vol_array" для объемов, "rsi_array" для значений RSI и "indices" для сортировки индексов. Задаем "periods" как массив таймфреймов от PERIOD_M1 до PERIOD_W1, "logFileName" как "Holographic_Dashboard_Log.txt" для вывода ошибок, "sortMode" как 0 для начальной сортировки, "sortNames" как строки для параметров сортировки, таких как "Name ASC" или "Vol DESC", и массивы для хэндлов ATR и RSI ("atr_handles_sym", "rsi_handles_sym" для инструментов, "atr_handles_tf", "rsi_handles_tf" для таймфреймов).
Целочисленное значение "totalSymbols" отслеживает количество инструментов, а значение "dashboardVisible" равно true, что управляет состоянием панели. Для более эффективного управления объектами создадим класс.
// Object Manager Class class CObjectManager : public CArrayString { public: void AddObject(string name) { //--- Add object name to manager if (!Add(name)) { //--- Check if add failed LogError(__FUNCTION__ + ": Failed to add object name: " + name); //--- Log error } } void DeleteAllObjects() { //--- Delete all managed objects for (int i = Total() - 1; i >= 0; i--) { //--- Iterate through objects string name = At(i); //--- Get object name if (ObjectFind(0, name) >= 0) { //--- Check if object exists if (!ObjectDelete(0, name)) { //--- Delete object LogError(__FUNCTION__ + ": Failed to delete object: " + name + ", Error: " + IntegerToString(GetLastError())); //--- Log deletion failure } } Delete(i); //--- Remove from array } ChartRedraw(0); //--- Redraw chart } };
Для эффективного управления объектами панели создаём класс "CObjectManager", расширяющий класс CArrayString. В методе "AddObject" добавляем объект "name" в массив с помощью "Add", выводя ошибки через "LogError" в случае неудачи. Используем метод "DeleteAllObjects", который проходит в цикле в обратном порядке по массиву с помощью "Total", получает каждое "name" с помощью параметра "At", проверяет наличие объекта с помощью функции ObjectFind, удаляет объект с помощью ObjectDelete и выводит ошибки в лог в случае неудачи, удаляет его из массива с помощью параметра "Delete" и перерисовывает график с помощью ChartRedraw. С помощью расширения класса мы можем создать несколько вспомогательных функций, которые будем вызывать на протяжении всей программы для повторного использования.
CObjectManager objManager; //--- Object manager instance //+------------------------------------------------------------------+ //| Utility Functions | //+------------------------------------------------------------------+ void LogError(string message) { CFileTxt file; //--- Create file object if (file.Open(logFileName, FILE_WRITE | FILE_TXT | FILE_COMMON, true) >= 0) { //--- Open log file file.WriteString(message + "\n"); //--- Write message file.Close(); //--- Close file } Print(message); //--- Print message } string Ask(string symbol) { double value; //--- Variable for ask price if (SymbolInfoDouble(symbol, SYMBOL_ASK, value)) { //--- Get ask price return DoubleToString(value, (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS)); //--- Return formatted ask } LogError(__FUNCTION__ + ": Failed to get ask price for " + symbol + ", Error: " + IntegerToString(GetLastError())); //--- Log error return "N/A"; //--- Return N/A on failure } string Bid(string symbol) { double value; //--- Variable for bid price if (SymbolInfoDouble(symbol, SYMBOL_BID, value)) { //--- Get bid price return DoubleToString(value, (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS)); //--- Return formatted bid } LogError(__FUNCTION__ + ": Failed to get bid price for " + symbol + ", Error: " + IntegerToString(GetLastError())); //--- Log error return "N/A"; //--- Return N/A on failure } string Spread(string symbol) { long value; //--- Variable for spread if (SymbolInfoInteger(symbol, SYMBOL_SPREAD, value)) { //--- Get spread return IntegerToString(value); //--- Return spread as string } LogError(__FUNCTION__ + ": Failed to get spread for " + symbol + ", Error: " + IntegerToString(GetLastError())); //--- Log error return "N/A"; //--- Return N/A on failure } string PercentChange(double current, double previous) { if (previous == 0) return "0.00%"; //--- Handle zero previous value return StringFormat("%.2f%%", ((current - previous) / previous) * 100); //--- Calculate and format percentage } string TruncPeriod(ENUM_TIMEFRAMES period) { return StringSubstr(EnumToString(period), 7); //--- Truncate timeframe string }
Чтобы управлять объектами и данными, мы инстанцируем экземпляр "objManager" как "CObjectManager" для отслеживания элементов пользовательского интерфейса. Создаем функцию "LogError" для вывода ошибок, открываем "LogFileName" с помощью "CFileTxt", используя "FILE_WRITE | FILE_TXT | FILE_COMMON", записываем "message" с помощью "WriteString", закрываем файл и выводим его. Функция "Ask" извлекает цену ask для "symbol" с помощью SymbolInfoDouble, форматирует ее с помощью DoubleToString, используя "SymbolInfoInteger" для цифр, выводит ошибки с помощью "LogError" в случае неудачи и возвращает "N/A" после неудачи. Аналогично, функция "Bid" извлекает цену bid, форматирует ее и обрабатывает ошибки.
Функция "Spread" получает спред с помощью "SymbolInfoInteger", возвращает его в виде строки с помощью IntegerToString или "N/A" в случае сбоя. Функция "PercentChange" вычисляет процентное изменение между "текущей" и "предыдущей" ценами с использованием StringFormat, возвращая значение "0,00%", если "предыдущая" равна нулю. Функция "TruncPeriod" обрезает строки ENUM_TIMEFRAMES с помощью StringSubstr для краткого отображения таймфреймов, гарантируя, что мы получим чистые выходные данные. Теперь мы можем создать функцию для голографических импульсов.
//+------------------------------------------------------------------+ //| Holographic Animation Function | //+------------------------------------------------------------------+ void HolographicPulse(string objName, color mainClr, color glowClr) { if (!EnableAnimations) return; //--- Exit if animations disabled int cycles = PulseCycles; //--- Set pulse cycles int delay = AnimationSpeed; //--- Set animation delay for (int i = 0; i < cycles; i++) { //--- Iterate through cycles ObjectSetInteger(0, objName, OBJPROP_COLOR, glowClr); //--- Set glow color ChartRedraw(0); //--- Redraw chart Sleep(delay); //--- Delay ObjectSetInteger(0, objName, OBJPROP_COLOR, mainClr); //--- Set main color ChartRedraw(0); //--- Redraw chart Sleep(delay / 2); //--- Shorter delay } }
Здесь мы реализуем функцию "HolographicPulse" для создания эффекта импульсной анимации элементов панели. Если параметр "EnableAnimations" имеет значение false, мы завершаем работу досрочно, чтобы пропустить анимации. Мы устанавливаем значение "cycles" равным "PulseCycles", а значение параметра "delay" равным "AnimationSpeed", затем выполняем цикл по "cycles" с помощью цикла for. В каждой итерации устанавливаем цвет объекта на "glowClr" с помощью ObjectSetInteger для OBJPROP_COLOR, перерисовываем график с помощью ChartRedraw, делаем паузу с помощью "Sleep" для "delay", переключаемся обратно на "mainClr", снова перерисовываем и делаем паузу на "delay / 2" для более короткого эффекта. Это позволит добавить голографический импульс для визуального выделения активных или предупреждающих элементов. Вооружившись этими функциями, мы можем перейти к созданию основной панели инициализации. Для этого нам понадобятся вспомогательные функции, чтобы программа оставалась модульной.
//+------------------------------------------------------------------+ //| Create Text Label Function | //+------------------------------------------------------------------+ bool createText(string objName, string text, int x, int y, color clrTxt, int fontsize, string font, bool animate = false, double opacity = 1.0) { ResetLastError(); //--- Reset error code if (ObjectFind(0, objName) < 0) { //--- Check if object exists if (!ObjectCreate(0, objName, OBJ_LABEL, 0, 0, 0)) { //--- Create label LogError(__FUNCTION__ + ": Failed to create label: " + objName + ", Error: " + IntegerToString(GetLastError())); //--- Log error return false; //--- Return failure } objManager.AddObject(objName); //--- Add to manager } ObjectSetInteger(0, objName, OBJPROP_XDISTANCE, x); //--- Set x-coordinate ObjectSetInteger(0, objName, OBJPROP_YDISTANCE, y); //--- Set y-coordinate ObjectSetInteger(0, objName, OBJPROP_CORNER, CORNER_LEFT_UPPER); //--- Set corner ObjectSetString(0, objName, OBJPROP_TEXT, text); //--- Set text ObjectSetInteger(0, objName, OBJPROP_COLOR, clrTxt); //--- Set color ObjectSetInteger(0, objName, OBJPROP_FONTSIZE, fontsize); //--- Set font size ObjectSetString(0, objName, OBJPROP_FONT, font); //--- Set font ObjectSetInteger(0, objName, OBJPROP_BACK, false); //--- Set foreground ObjectSetInteger(0, objName, OBJPROP_SELECTABLE, false); //--- Disable selection ObjectSetInteger(0, objName, OBJPROP_ZORDER, StringFind(objName, "Glow") >= 0 ? -1 : 0); //--- Set z-order if (animate && EnableAnimations) { //--- Check for animation ObjectSetInteger(0, objName, OBJPROP_COLOR, DataColor); //--- Set temporary color ChartRedraw(0); //--- Redraw chart Sleep(50); //--- Delay ObjectSetInteger(0, objName, OBJPROP_COLOR, clrTxt); //--- Set final color } ChartRedraw(0); //--- Redraw chart return true; //--- Return success } //+------------------------------------------------------------------+ //| Create Button Function | //+------------------------------------------------------------------+ bool createButton(string objName, string text, int x, int y, int width, int height, color textColor, color bgColor, color borderColor, bool animate = false) { ResetLastError(); //--- Reset error code if (ObjectFind(0, objName) < 0) { //--- Check if object exists if (!ObjectCreate(0, objName, OBJ_BUTTON, 0, 0, 0)) { //--- Create button LogError(__FUNCTION__ + ": Failed to create button: " + objName + ", Error: " + IntegerToString(GetLastError())); //--- Log error return false; //--- Return failure } objManager.AddObject(objName); //--- Add to manager } ObjectSetInteger(0, objName, OBJPROP_XDISTANCE, x); //--- Set x-coordinate ObjectSetInteger(0, objName, OBJPROP_YDISTANCE, y); //--- Set y-coordinate ObjectSetInteger(0, objName, OBJPROP_XSIZE, width); //--- Set width ObjectSetInteger(0, objName, OBJPROP_YSIZE, height); //--- Set height ObjectSetString(0, objName, OBJPROP_TEXT, text); //--- Set text ObjectSetInteger(0, objName, OBJPROP_COLOR, textColor); //--- Set text color ObjectSetInteger(0, objName, OBJPROP_BGCOLOR, bgColor); //--- Set background color ObjectSetInteger(0, objName, OBJPROP_BORDER_COLOR, borderColor); //--- Set border color ObjectSetInteger(0, objName, OBJPROP_FONTSIZE, BaseFontSize + (StringFind(objName, "SwitchTFBtn") >= 0 ? 3 : 0)); //--- Set font size ObjectSetString(0, objName, OBJPROP_FONT, FontType); //--- Set font ObjectSetInteger(0, objName, OBJPROP_ZORDER, 1); //--- Set z-order ObjectSetInteger(0, objName, OBJPROP_STATE, false); //--- Reset state if (animate && EnableAnimations) { //--- Check for animation ObjectSetInteger(0, objName, OBJPROP_BGCOLOR, clrLightGray); //--- Set temporary background ChartRedraw(0); //--- Redraw chart Sleep(50); //--- Delay ObjectSetInteger(0, objName, OBJPROP_BGCOLOR, bgColor); //--- Set final background } ChartRedraw(0); //--- Redraw chart return true; //--- Return success } //+------------------------------------------------------------------+ //| Create Panel Function | //+------------------------------------------------------------------+ bool createPanel(string objName, int x, int y, int width, int height, color clr, double opacity = 1.0) { ResetLastError(); //--- Reset error code if (ObjectFind(0, objName) < 0) { //--- Check if object exists if (!ObjectCreate(0, objName, OBJ_RECTANGLE_LABEL, 0, 0, 0)) { //--- Create panel LogError(__FUNCTION__ + ": Failed to create panel: " + objName + ", Error: " + IntegerToString(GetLastError())); //--- Log error return false; //--- Return failure } objManager.AddObject(objName); //--- Add to manager } ObjectSetInteger(0, objName, OBJPROP_XDISTANCE, x); //--- Set x-coordinate ObjectSetInteger(0, objName, OBJPROP_YDISTANCE, y); //--- Set y-coordinate ObjectSetInteger(0, objName, OBJPROP_XSIZE, width); //--- Set width ObjectSetInteger(0, objName, OBJPROP_YSIZE, height); //--- Set height ObjectSetInteger(0, objName, OBJPROP_BGCOLOR, clr); //--- Set background color ObjectSetInteger(0, objName, OBJPROP_BORDER_TYPE, BORDER_FLAT); //--- Set border type ObjectSetInteger(0, objName, OBJPROP_ZORDER, -1); //--- Set z-order ChartRedraw(0); //--- Redraw chart return true; //--- Return success } //+------------------------------------------------------------------+ //| Create Line Function | //+------------------------------------------------------------------+ bool createLine(string objName, int x1, int y1, int x2, int y2, color clrLine, double opacity = 1.0) { ResetLastError(); //--- Reset error code if (ObjectFind(0, objName) < 0) { //--- Check if object exists if (!ObjectCreate(0, objName, OBJ_RECTANGLE_LABEL, 0, 0, 0)) { //--- Create line as rectangle LogError(__FUNCTION__ + ": Failed to create line: " + objName + ", Error: " + IntegerToString(GetLastError())); //--- Log error return false; //--- Return failure } objManager.AddObject(objName); //--- Add to manager } ObjectSetInteger(0, objName, OBJPROP_XDISTANCE, x1); //--- Set x1 ObjectSetInteger(0, objName, OBJPROP_YDISTANCE, y1); //--- Set y1 ObjectSetInteger(0, objName, OBJPROP_XSIZE, x2 - x1); //--- Set width ObjectSetInteger(0, objName, OBJPROP_YSIZE, StringFind(objName, "Glow") >= 0 ? 3 : 1); //--- Set height ObjectSetInteger(0, objName, OBJPROP_BGCOLOR, clrLine); //--- Set color ObjectSetInteger(0, objName, OBJPROP_ZORDER, StringFind(objName, "Glow") >= 0 ? -1 : 0); //--- Set z-order ChartRedraw(0); //--- Redraw chart return true; //--- Return success }
Здесь определяем функцию "createText" для создания текстовой метки. Начинаем с вызова ResetLastError, чтобы устранить любую предыдущую ошибку. Если объект не существует (проверяется с помощью "ObjectFind(0, objName) < 0"), создаем его с помощью ObjectCreate с типом OBJ_LABEL. В случае неудачи выводим сообщение об ошибке и возвращаем значение false. Добавляем его в "objManager" через "AddObject". Задаем свойства: OBJPROP_XDISTANCE для "x", "OBJPROP_YDISTANCE" для "y" и то же самое для остальных. Если "animate" и "EnableAnimations" имеют значения true, временно устанавливаем для "OBJPROP_COLOR" значение "DataColor", перерисовываем, задерживаем с помощью "Sleep(50)", затем устанавливаем цвет текста. Наконец, перерисовываем и возвращаем значение true.
Далее аналогично определяем "createButton": сбрасываем ошибку, проверяем существование, создаем с помощью OBJ_BUTTON, если необходимо, выводим сообщение об ошибке и добавляем в менеджер. Затем устанавливаем свойства объекта и, если анимация включена, временно устанавливаем для OBJPROP_BGCOLOR значение "clrLightGray", перерисовываем, значение sleep равно 50 мс, затем устанавливаем значение "bgColor". Перерисовываем и возвращаем значение true. Для "createPanel" используем аналогичный подход.
Наконец, "createLine" использует аналогичный паттерн: сбросить, проверить, создать как OBJ_RECTANGLE_LABEL (имитируя строку), вывести сообщение об ошибке и добавить в менеджер. Установим для "OBJPROP_XDISTANCE" значение "x1", для "OBJPROP_YDISTANCE" - "y1", для "OBJPROP_XSIZE" - "x2-x1", для "OBJPROP_YSIZE" - 3, если "Glow" в имени равно 1, для "OBJPROP_BGCOLOR" - "clrLine", для OBJPROP_ZORDER значение -1, если "Glow" равно 0. Перерисовываем и возвращаем значение true. Теперь мы используем эти функции для создания основной функции, которая позволит создать основную панель следующим образом.
//+------------------------------------------------------------------+ //| Dashboard Creation Function with Holographic Effects | //+------------------------------------------------------------------+ void InitDashboard() { // Get chart dimensions long chartWidth, chartHeight; //--- Variables for chart dimensions if (!ChartGetInteger(0, CHART_WIDTH_IN_PIXELS, 0, chartWidth) || !ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS, 0, chartHeight)) { //--- Get chart size LogError(__FUNCTION__ + ": Failed to get chart dimensions, Error: " + IntegerToString(GetLastError())); //--- Log error return; //--- Exit on failure } int fontSize = (int)(BaseFontSize * (chartWidth / 800.0)); //--- Calculate font size int cellWidth = PanelWidth / 8; //--- Calculate cell width int cellHeight = 18; //--- Set cell height int panelHeight = 70 + (ArraySize(periods) + 1) * cellHeight + 40 + (totalSymbols + 1) * cellHeight + 50; //--- Calculate panel height // Create Dark Panel createPanel("DashboardPanel", X_Offset, Y_Offset, PanelWidth, panelHeight, PanelColor); //--- Create dashboard panel // Create Header with Glow createText("Header", "HOLOGRAPHIC DASHBOARD", X_Offset + 10, Y_Offset + 10, TitleColor, fontSize + 4, FontType); //--- Create header text createText("HeaderGlow", "HOLOGRAPHIC DASHBOARD", X_Offset + 11, Y_Offset + 11, GlowColor, fontSize + 4, FontType, true); //--- Create header glow createText("SubHeader", StringFormat("%s | TF: %s", _Symbol, TruncPeriod(_Period)), X_Offset + 10, Y_Offset + 30, DataColor, fontSize, FontType); //--- Create subheader // Timeframe Grid int y = Y_Offset + 50; //--- Set y-coordinate for timeframe grid createText("TF_Label", "Timeframe", X_Offset + 10, y, TitleColor, fontSize, FontType); //--- Create timeframe label createText("Trend_Label", "Trend", X_Offset + 10 + cellWidth, y, TitleColor, fontSize, FontType); //--- Create trend label createText("Vol_Label", "Vol", X_Offset + 10 + cellWidth * 2, y, TitleColor, fontSize, FontType); //--- Create vol label createText("RSI_Label", "RSI", X_Offset + 10 + cellWidth * 3, y, TitleColor, fontSize, FontType); //--- Create RSI label createLine("TF_Separator", X_Offset + 5, y + cellHeight + 2, X_Offset + PanelWidth - 5, y + cellHeight + 2, LineColor, 0.6); //--- Create separator line createLine("TF_Separator_Glow", X_Offset + 4, y + cellHeight + 1, X_Offset + PanelWidth - 4, y + cellHeight + 3, GlowColor, 0.3); //--- Create glow separator if (EnableAnimations) HolographicPulse("TF_Separator", LineColor, GlowColor); //--- Animate separator if enabled y += cellHeight + 5; //--- Update y-coordinate for (int i = 0; i < ArraySize(periods); i++) { //--- Iterate through timeframes color periodColor = (periods[i] == _Period) ? ActiveColor : DataColor; //--- Set period color createText("Period_" + IntegerToString(i), TruncPeriod(periods[i]), X_Offset + 10, y, periodColor, fontSize, FontType); //--- Create period text createText("Trend_" + IntegerToString(i), "-", X_Offset + 10 + cellWidth, y, DataColor, fontSize, FontType); //--- Create trend text createText("Vol_" + IntegerToString(i), "0.00%", X_Offset + 10 + cellWidth * 2, y, DataColor, fontSize, FontType); //--- Create vol text createText("RSI_" + IntegerToString(i), "0.0", X_Offset + 10 + cellWidth * 3, y, DataColor, fontSize, FontType); //--- Create RSI text y += cellHeight; //--- Update y-coordinate } // Symbol Grid y += 30; //--- Update y-coordinate for symbol grid createText("Symbol_Label", "Symbol", X_Offset + 10, y, TitleColor, fontSize, FontType); //--- Create symbol label createText("Bid_Label", "Bid", X_Offset + 10 + cellWidth, y, TitleColor, fontSize, FontType); //--- Create bid label createText("Spread_Label", "Spread", X_Offset + 10 + cellWidth * 2, y, TitleColor, fontSize, FontType); //--- Create spread label createText("Change_Label", "% Change", X_Offset + 10 + cellWidth * 3, y, TitleColor, fontSize, FontType); //--- Create change label createText("Vol_Label_Symbol", "Vol", X_Offset + 10 + cellWidth * 4, y, TitleColor, fontSize, FontType); //--- Create vol label createText("RSI_Label_Symbol", "RSI", X_Offset + 10 + cellWidth * 5, y, TitleColor, fontSize, FontType); //--- Create RSI label createText("UpArrow_Label", CharToString(236), X_Offset + 10 + cellWidth * 6, y, TitleColor, fontSize, "Wingdings"); //--- Create up arrow label createText("DownArrow_Label", CharToString(238), X_Offset + 10 + cellWidth * 7, y, TitleColor, fontSize, "Wingdings"); //--- Create down arrow label createLine("Symbol_Separator", X_Offset + 5, y + cellHeight + 2, X_Offset + PanelWidth - 5, y + cellHeight + 2, LineColor, 0.6); //--- Create separator line createLine("Symbol_Separator_Glow", X_Offset + 4, y + cellHeight + 1, X_Offset + PanelWidth - 4, y + cellHeight + 3, GlowColor, 0.3); //--- Create glow separator if (EnableAnimations) HolographicPulse("Symbol_Separator", LineColor, GlowColor); //--- Animate separator if enabled y += cellHeight + 5; //--- Update y-coordinate for (int i = 0; i < totalSymbols; i++) { //--- Iterate through symbols string symbol = SymbolName(i, true); //--- Get symbol name string displaySymbol = (symbol == _Symbol) ? "*" + symbol : symbol; //--- Format display symbol color symbolColor = (symbol == _Symbol) ? ActiveColor : DataColor; //--- Set symbol color createText("Symbol_" + IntegerToString(i), displaySymbol, X_Offset + 10, y, symbolColor, fontSize, FontType); //--- Create symbol text createText("Bid_" + IntegerToString(i), Bid(symbol), X_Offset + 10 + cellWidth, y, DataColor, fontSize, FontType); //--- Create bid text createText("Spread_" + IntegerToString(i), Spread(symbol), X_Offset + 10 + cellWidth * 2, y, DataColor, fontSize, FontType); //--- Create spread text createText("Change_" + IntegerToString(i), "0.00%", X_Offset + 10 + cellWidth * 3, y, DataColor, fontSize, FontType); //--- Create change text createText("Vol_" + IntegerToString(i), "0.00%", X_Offset + 10 + cellWidth * 4, y, DataColor, fontSize, FontType); //--- Create vol text createText("RSI_" + IntegerToString(i), "0.0", X_Offset + 10 + cellWidth * 5, y, DataColor, fontSize, FontType); //--- Create RSI text createText("ArrowUp_" + IntegerToString(i), CharToString(236), X_Offset + 10 + cellWidth * 6, y, UpColor, fontSize, "Wingdings"); //--- Create up arrow createText("ArrowDown_" + IntegerToString(i), CharToString(238), X_Offset + 10 + cellWidth * 7, y, DownColor, fontSize, "Wingdings"); //--- Create down arrow y += cellHeight; //--- Update y-coordinate } // Interactive Buttons with Pulse Animation createButton("ToggleBtn", "TOGGLE DASHBOARD", X_Offset + 10, y + 20, 150, 25, TitleColor, PanelColor, UpColor); //--- Create toggle button createButton("SwitchTFBtn", "NEXT TF", X_Offset + 170, y + 20, 120, 25, UpColor, PanelColor, UpColor); //--- Create switch TF button createButton("SortBtn", "SORT: " + sortNames[sortMode], X_Offset + 300, y + 20, 150, 25, TitleColor, PanelColor, UpColor); //--- Create sort button ChartRedraw(0); //--- Redraw chart }
Здесь мы инициализируем панель, сначала извлекая размеры графика с помощью переменных "chartWidth" и "chartHeight". Достигаем этого путем вызова функции ChartGetInteger дважды: один раз с помощью CHART_WIDTH_IN_PIXELS, чтобы получить ширину, и один раз с помощью "CHART_HEIGHT_IN_PIXELS", чтобы получить высоту. Затем мы вычисляем "fontSize", масштабируя "BaseFontSize" на основе ширины графика относительно 800 пикселей, преобразуя результат в целое число. Затем определяем "cellWidth", разделив "PanelWidth" на 8, и устанавливаем "cellHeight" на фиксированное значение 18. Параметр "panelHeight" вычисляется путем сложения 70, произведения ("ArraySize(periods)" + 1) и "cellHeight", 40, произведения ("totalSymbols" + 1) и "cellHeight", и 50 — это учитывает общую компоновку, включая таймфреймы и символы.
Далее создаём тёмный фон панели, вызывая функцию "createPanel" с именем "DashboardPanel", располагая её по координатам "X_Offset" и "Y_Offset", задавая размеры "PanelWidth" и "panelHeight" и окрашивая цветом "PanelColor". Для заголовка создаём основной текстовый заголовок "Header" со строкой "HOLOGRAPHIC DASHBOARD", используя функцию "createText" в координатах "X_Offset" + 10" и "Y_Offset" + 10", с использованием стиля "TitleColor", размера шрифта "fontSize" + 4" и типа шрифта "FontType". Чтобы добавить эффект свечения, создаём ещё одну текстовую метку "HeaderGlow" с той же строкой, но со смещением на 1 пиксель по осям x и y, используя "GlowColor", тот же размер шрифта, "FontType" и установив флаг opacity в значение true.
Затем добавляем метку подзаголовка "SubHeader", отформатированную с использованием текущего символа _Symbol и усеченный период из "TruncPeriod(_Period)" с помощью "StringFormat", расположенный в точках "X_Offset" + 10" и "Y_Offset" + 30, и окрашенный с помощью "DataColor", "fontSize" и "FontType".
Переходя к разделу сетки таймфреймов, устанавливаем значение "y" равным "Y_Offset" + 50". Создаём метки для "Timeframe", "Trend", "Vol" и "RSI", используя функцию "createText" для каждой из них, располагая их горизонтально со смещением, основанным на "cellWidth", при этом для всех меток используются "TitleColor", "fontSize" и "FontType"". Ниже рисуем разделительную линию "TF_Separator" с помощью функции "createLine" от "X_Offset + 5" до "X_Offset + PanelWidth - 5"" на высоте "y + cellHeight + 2"", окрашенную в цвет "LineColor" с прозрачностью 0,6". Для эффекта свечения добавляем "TF_Separator_Glow" в виде еще одной строки, слегка смещенной и более широкой, с параметром "GlowColor" и прозрачностью 0,3". Если параметр "EnableAnimations" имеет значение true, применяем анимацию с помощью параметра "HolographicPulse" с параметрами "LineColor" и "GlowColor". Аналогичную логику используем для всех остальных объектов-меток.
Наконец, создаём интерактивные кнопки: "ToggleBtn"" как "TOGGLE DASHBOARD" с координатами "X_Offset" + 10", "y + 20", размером 150x25, с параметрами "TitleColor", "PanelColor", "UpColor"; "SwitchTFBtn"" как "NEXT TF" с координатами "X_Offset" + 170", тем же значением y, размером 120x25, с параметрами "UpColor", "PanelColor", "UpColor"; и "SortBtn"" как "SORT: " + sortNames[sortMode]" с координатами "X_Offset" + 300", тем же значением y, размером 150x25, с параметрами "TitleColor", "PanelColor", "UpColor". В заключение перерисовываем график с помощью функции "ChartRedraw(0)". С помощью этой функции мы можем вызвать её в обработчике события инициализации, и основная работа будет выполнена автоматически.
//+------------------------------------------------------------------+ //| Expert Initialization Function | //+------------------------------------------------------------------+ int OnInit() { // Clear existing objects if (ObjectsDeleteAll(0, -1, -1) < 0) { //--- Delete all objects LogError(__FUNCTION__ + ": Failed to delete objects, Error: " + IntegerToString(GetLastError())); //--- Log error } objManager.DeleteAllObjects(); //--- Delete managed objects // Initialize arrays totalSymbols = SymbolsTotal(true); //--- Get total symbols if (totalSymbols == 0) { //--- Check for symbols LogError(__FUNCTION__ + ": No symbols available"); //--- Log error return INIT_FAILED; //--- Return failure } ArrayResize(prices_PrevArray, totalSymbols); //--- Resize previous prices array ArrayResize(volatility_Array, totalSymbols); //--- Resize volatility array ArrayResize(bid_array, totalSymbols); //--- Resize bid array ArrayResize(spread_array, totalSymbols); //--- Resize spread array ArrayResize(change_array, totalSymbols); //--- Resize change array ArrayResize(vol_array, totalSymbols); //--- Resize vol array ArrayResize(rsi_array, totalSymbols); //--- Resize RSI array ArrayResize(indices, totalSymbols); //--- Resize indices array ArrayResize(atr_handles_sym, totalSymbols); //--- Resize ATR symbol handles ArrayResize(rsi_handles_sym, totalSymbols); //--- Resize RSI symbol handles ArrayResize(atr_handles_tf, ArraySize(periods)); //--- Resize ATR timeframe handles ArrayResize(rsi_handles_tf, ArraySize(periods)); //--- Resize RSI timeframe handles ArrayInitialize(prices_PrevArray, 0); //--- Initialize previous prices ArrayInitialize(volatility_Array, 0); //--- Initialize volatility // Create indicator handles for timeframes for (int i = 0; i < ArraySize(periods); i++) { //--- Iterate through timeframes atr_handles_tf[i] = iATR(_Symbol, periods[i], ATR_Period); //--- Create ATR handle if (atr_handles_tf[i] == INVALID_HANDLE) { //--- Check for invalid handle LogError(__FUNCTION__ + ": Failed to create ATR handle for TF " + EnumToString(periods[i])); //--- Log error return INIT_FAILED; //--- Return failure } rsi_handles_tf[i] = iRSI(_Symbol, periods[i], RSI_Period, PRICE_CLOSE); //--- Create RSI handle if (rsi_handles_tf[i] == INVALID_HANDLE) { //--- Check for invalid handle LogError(__FUNCTION__ + ": Failed to create RSI handle for TF " + EnumToString(periods[i])); //--- Log error return INIT_FAILED; //--- Return failure } } // Create indicator handles for symbols on H1 for (int i = 0; i < totalSymbols; i++) { //--- Iterate through symbols string symbol = SymbolName(i, true); //--- Get symbol name atr_handles_sym[i] = iATR(symbol, PERIOD_H1, ATR_Period); //--- Create ATR handle if (atr_handles_sym[i] == INVALID_HANDLE) { //--- Check for invalid handle LogError(__FUNCTION__ + ": Failed to create ATR handle for symbol " + symbol); //--- Log error return INIT_FAILED; //--- Return failure } rsi_handles_sym[i] = iRSI(symbol, PERIOD_H1, RSI_Period, PRICE_CLOSE); //--- Create RSI handle if (rsi_handles_sym[i] == INVALID_HANDLE) { //--- Check for invalid handle LogError(__FUNCTION__ + ": Failed to create RSI handle for symbol " + symbol); //--- Log error return INIT_FAILED; //--- Return failure } } InitDashboard(); //--- Initialize dashboard dashboardVisible = true; //--- Set dashboard visible return INIT_SUCCEEDED; //--- Return success }
В функции OnInit очищаем существующие объекты с помощью ObjectsDeleteAll для всех графиков и типов, выводя сообщения об ошибке с помощью "LogError" в случае неудачи, и вызываем "objManager.DeleteAllObjects" для удаления управляемых элементов. Получаем значение "totalSymbols" из SymbolsTotal со значением true для отслеживаемых рынком символов, возвращаем INIT_FAILED, если оно равно нулю, и выводим в лог сообщение об ошибке "LogError". Изменяем размер массивов, таких как "prices_PrevArray", "volatility_Array", "bid_array", "spread_array", "change_array", "vol_array", "rsi_array", "indices", "atr_handles_sym", "rsi_handles_sym", "atr_handles_tf" и "rsi_handles_tf", чтобы они соответствовали "totalSymbols" или "ArraySize(periods)" с помощью функции ArrayResize, и инициализируем "prices_PrevArray" и "volatility_Array" нулями с помощью функции ArrayInitialize.
Для таймфреймов перебираем "periods" и создаем "atr_handles_tf[i]" с iATR на "_Symbol", "periods[i]" и "ATR_Period", а также "rsi_handles_tf[i]" с iRSI на _Symbol, "periods[i]", "RSI_Period" и "PRICE_CLOSE", регистрируя и возвращая INIT_FAILED, если "INVALID_HANDLE". Аналогично для символов перебираем "totalSymbols", получаем "symbol" с "SymbolName" и значением true, создаем "atr_handles_sym[i]" с помощью "iATR" на "symbol", "PERIOD_H1" и "ATR_Period", и "rsi_handles_sym[i]" с помощью iRSIн а "symbol", "PERIOD_H1", "RSI_Period" и "PRICE_CLOSE", регистрируя и возвращая "INIT_FAILED", если значение недействительно. Вызываем "InitDashboard" для создания пользовательского интерфейса, устанавливаем для "dashboardVisible" значение true и возвращаем результат успеха. При запуске программы получаем следующий результат.

На изображении видно, что мы успешно инициализировали программу. Мы можем позаботиться о деинициализации программы, где нужно будет удалить созданные объекты и освободить хэндлы индикаторов.
//+------------------------------------------------------------------+ //| Expert Deinitialization Function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { if (ObjectsDeleteAll(0, -1, -1) < 0) { //--- Delete all objects LogError(__FUNCTION__ + ": Failed to delete objects, Error: " + IntegerToString(GetLastError())); //--- Log error } objManager.DeleteAllObjects(); //--- Delete managed objects // Release indicator handles for (int i = 0; i < ArraySize(atr_handles_tf); i++) { //--- Iterate through timeframe ATR handles if (atr_handles_tf[i] != INVALID_HANDLE) IndicatorRelease(atr_handles_tf[i]); //--- Release handle if (rsi_handles_tf[i] != INVALID_HANDLE) IndicatorRelease(rsi_handles_tf[i]); //--- Release handle } for (int i = 0; i < ArraySize(atr_handles_sym); i++) { //--- Iterate through symbol ATR handles if (atr_handles_sym[i] != INVALID_HANDLE) IndicatorRelease(atr_handles_sym[i]); //--- Release handle if (rsi_handles_sym[i] != INVALID_HANDLE) IndicatorRelease(rsi_handles_sym[i]); //--- Release handle } }
В обработчике OnDeinit очищаем ресурсы при удалении советника. Удаляем все объекты графика с помощью ObjectsDeleteAll, используя значение -1 для всех графиков и типов, выводя сообщения об ошибках с помощью "LogError", если результат отрицательный. Для удаления управляемых элементов вызываем "objManager.DeleteAllObjects". Для хэндлов таймфреймов перебираем "atr_handles_tf" и "rsi_handles_tf" с помощью ArraySize, освобождая действительные хэндлы с помощью IndicatorRelease, если значение не "INVALID_HANDLE". Аналогично, для хэндлов символов в "atr_handles_sym" и "rsi_handles_sym". Это обеспечивает полную очистку объектов и индикаторов. Вот иллюстрация.

Теперь, когда с созданными объектами все в порядке, мы можем перейти к обновлениям. Мы намерены выполнить обновления в обработчике OnTick, чтобы упростить работу, но их можно выполнить и в обработчике OnTimer. Давайте начнем с раздела о таймфреймах.
//+------------------------------------------------------------------+ //| Expert Tick Function with Holographic Updates | //+------------------------------------------------------------------+ void OnTick() { if (!dashboardVisible) return; //--- Exit if dashboard hidden long chartWidth; //--- Variable for chart width ChartGetInteger(0, CHART_WIDTH_IN_PIXELS, 0, chartWidth); //--- Get chart width int fontSize = (int)(BaseFontSize * (chartWidth / 800.0)); //--- Calculate font size int cellWidth = PanelWidth / 8; //--- Calculate cell width int cellHeight = 18; //--- Set cell height int y = Y_Offset + 75; //--- Set y-coordinate for timeframe data // Update Timeframe Data with Pulse for (int i = 0; i < ArraySize(periods); i++) { //--- Iterate through timeframes double open = iOpen(_Symbol, periods[i], 0); //--- Get open price double close = iClose(_Symbol, periods[i], 0); //--- Get close price double atr_buf[1]; //--- Buffer for ATR if (CopyBuffer(atr_handles_tf[i], 0, 0, 1, atr_buf) != 1) { //--- Copy ATR data LogError(__FUNCTION__ + ": Failed to copy ATR buffer for TF " + EnumToString(periods[i])); //--- Log error continue; //--- Skip on failure } double vol = (close > 0) ? (atr_buf[0] / close) * 100 : 0.0; //--- Calculate volatility double rsi_buf[1]; //--- Buffer for RSI if (CopyBuffer(rsi_handles_tf[i], 0, 0, 1, rsi_buf) != 1) { //--- Copy RSI data LogError(__FUNCTION__ + ": Failed to copy RSI buffer for TF " + EnumToString(periods[i])); //--- Log error continue; //--- Skip on failure } double rsi = rsi_buf[0]; //--- Get RSI value color clr = DataColor; //--- Set default color string trend = "-"; //--- Set default trend if (rsi > 50) { clr = UpColor; trend = "↑"; } //--- Set up trend else if (rsi < 50) { clr = DownColor; trend = "↓"; } //--- Set down trend createText("Trend_" + IntegerToString(i), trend, X_Offset + 10 + cellWidth, y, clr, fontSize, FontType, EnableAnimations); //--- Update trend text createText("Vol_" + IntegerToString(i), StringFormat("%.2f%%", vol), X_Offset + 10 + cellWidth * 2, y, vol > Vol_Alert_Threshold ? UpColor : DataColor, fontSize, FontType, vol > Vol_Alert_Threshold && EnableAnimations); //--- Update vol text color rsi_clr = (rsi > 70 ? DownColor : (rsi < 30 ? UpColor : DataColor)); //--- Set RSI color createText("RSI_" + IntegerToString(i), StringFormat("%.1f", rsi), X_Offset + 10 + cellWidth * 3, y, rsi_clr, fontSize, FontType, (rsi > 70 || rsi < 30) && EnableAnimations); //--- Update RSI text HolographicPulse("Period_" + IntegerToString(i), (periods[i] == _Period) ? ActiveColor : DataColor, GlowColor); //--- Pulse period text y += cellHeight; //--- Update y-coordinate } // Update Symbol Data with Advanced Glow y += 50; //--- Update y-coordinate for symbol data for (int i = 0; i < totalSymbols; i++) { //--- Iterate through symbols string symbol = SymbolName(i, true); //--- Get symbol name double bidPrice; //--- Variable for bid price if (!SymbolInfoDouble(symbol, SYMBOL_BID, bidPrice)) { //--- Get bid price LogError(__FUNCTION__ + ": Failed to get bid for " + symbol + ", Error: " + IntegerToString(GetLastError())); //--- Log error continue; //--- Skip on failure } long spread; //--- Variable for spread if (!SymbolInfoInteger(symbol, SYMBOL_SPREAD, spread)) { //--- Get spread LogError(__FUNCTION__ + ": Failed to get spread for " + symbol + ", Error: " + IntegerToString(GetLastError())); //--- Log error continue; //--- Skip on failure } double change = (prices_PrevArray[i] == 0 ? 0 : (bidPrice - prices_PrevArray[i]) / prices_PrevArray[i] * 100); //--- Calculate change double close = iClose(symbol, PERIOD_H1, 0); //--- Get close price double atr_buf[1]; //--- Buffer for ATR if (CopyBuffer(atr_handles_sym[i], 0, 0, 1, atr_buf) != 1) { //--- Copy ATR data LogError(__FUNCTION__ + ": Failed to copy ATR buffer for symbol " + symbol); //--- Log error continue; //--- Skip on failure } double vol = (close > 0) ? (atr_buf[0] / close) * 100 : 0.0; //--- Calculate volatility double rsi_buf[1]; //--- Buffer for RSI if (CopyBuffer(rsi_handles_sym[i], 0, 0, 1, rsi_buf) != 1) { //--- Copy RSI data LogError(__FUNCTION__ + ": Failed to copy RSI buffer for symbol " + symbol); //--- Log error continue; //--- Skip on failure } double rsi = rsi_buf[0]; //--- Get RSI value bid_array[i] = bidPrice; //--- Store bid spread_array[i] = spread; //--- Store spread change_array[i] = change; //--- Store change vol_array[i] = vol; //--- Store vol rsi_array[i] = rsi; //--- Store RSI volatility_Array[i] = vol; //--- Store volatility prices_PrevArray[i] = bidPrice; //--- Update previous price } }
С помощью функции OnTick обрабатываем обновления на каждом тике, обеспечивая обновление данных по таймфреймам и символам в режиме реального времени. Если значение "dashboardVisible" равно false, чтобы пропустить ненужную обработку - выходим. Получаем значение "chartWidth" с помощью функции ChartGetInteger, используя CHART_WIDTH_IN_PIXELS, вычисляем "fontSize", масштабированный с помощью "chartWidth / 800.0", "cellWidth" как "PanelWidth / 8", и "cellHeight" как 18. Устанавливаем значение "y" равным "Y_Offset + 75" для сетки таймфреймов и перебираем "periods" с помощью ArraySize. Для каждого таймфрейма получаем значение "open" с помощью iOpen и значение "close" с помощью "iClose" при нулевом сдвиге, копируем "atr_buf" с помощью CopyBuffer из "atr_handles_tf[i]" и вычисляем "vol" как процентное соотношение ATR к "close", если оно положительное.
Копируем "rsi_buf" из "rsi_handles_tf[i]" и получаем "rsi", устанавливая "clr" и "trend" на основе RSI > 50 для роста ("↑" в "UpColor") или < 50 для снижения ("↓" в "DownColor"). Мы могли бы использовать стрелки из шрифтов, но эти, написанные вручную, плавно вписываются в текст и повышают привлекательность голографии. Обновляем тексты с помощью функции "createText" для тренда, vol (цвет "UpColor" если > "Vol_Alert_Threshold" с анимацией) и RSI (цвет в зависимости от перекупленности/перепроданности с анимацией), а также вызываем функцию "HolographicPulse" для текста периода с параметром "ActiveColor", если он соответствует значению _Period. Увеличиваем значение "y" на "cellHeight".
Обновляем значение "y" на 50 для сетки символов и перебираем циклом значение "totalSymbols". Для каждого символа из SymbolName со значением true получаем "bidPrice" с помощью "SymbolInfoDouble", используя "SYMBOL_BID", и "spread" с помощью SymbolInfoInteger, используя SYMBOL_SPREAD, выводя сообщение об ошибке и пропуская. Вычисляем "change" в процентах по "prices_PrevArray[i]", получаем "close" с помощью iClose на "PERIOD_H1" при сдвиге 0, копируем "atr_buf" из "atr_handles_sym[i]" для вычисления "vol" и "rsi_buf" из "rsi_handles_sym[i]" для получения "rsi". Храним значения в "bid_array", "spread_array", "change_array", "vol_array", "rsi_array" и "volatility_array" и обновляем "prices_PrevArray[i]" до значения "bidPrice". Теперь перейдём к разделу символов, где нам нужно будет отсортировать и отобразить их с эффектами.
// Sort indices for (int i = 0; i < totalSymbols; i++) indices[i] = i; //--- Initialize indices bool swapped = true; //--- Swap flag while (swapped) { //--- Loop until no swaps swapped = false; //--- Reset flag for (int j = 0; j < totalSymbols - 1; j++) { //--- Iterate through indices bool do_swap = false; //--- Swap decision int a = indices[j], b = indices[j + 1]; //--- Get indices if (sortMode == 0) { //--- Sort by name ASC string na = SymbolName(a, true), nb = SymbolName(b, true); //--- Get names if (na > nb) do_swap = true; //--- Swap if needed } else if (sortMode == 1) { //--- Sort by vol DESC if (vol_array[a] < vol_array[b]) do_swap = true; //--- Swap if needed } else if (sortMode == 2) { //--- Sort by change ABS DESC if (MathAbs(change_array[a]) < MathAbs(change_array[b])) do_swap = true; //--- Swap if needed } else if (sortMode == 3) { //--- Sort by RSI DESC if (rsi_array[a] < rsi_array[b]) do_swap = true; //--- Swap if needed } if (do_swap) { //--- Perform swap int temp = indices[j]; //--- Temporary store indices[j] = indices[j + 1]; //--- Swap indices[j + 1] = temp; //--- Complete swap swapped = true; //--- Set flag } } } // Display sorted symbols with pulse on high vol for (int j = 0; j < totalSymbols; j++) { //--- Iterate through sorted indices int i = indices[j]; //--- Get index string symbol = SymbolName(i, true); //--- Get symbol double bidPrice = bid_array[i]; //--- Get bid long spread = spread_array[i]; //--- Get spread double change = change_array[i]; //--- Get change double vol = vol_array[i]; //--- Get vol double rsi = rsi_array[i]; //--- Get RSI color clr_s = (symbol == _Symbol) ? ActiveColor : DataColor; //--- Set symbol color color clr_p = DataColor, clr_sp = DataColor, clr_ch = DataColor, clr_vol = DataColor, clr_rsi = DataColor; //--- Set default colors color clr_a1 = DataColor, clr_a2 = DataColor; //--- Set arrow colors // Price Change if (change > 0) { //--- Check positive change clr_p = UpColor; clr_ch = UpColor; clr_a1 = UpColor; clr_a2 = DataColor; //--- Set up colors } else if (change < 0) { //--- Check negative change clr_p = DownColor; clr_ch = DownColor; clr_a1 = DataColor; clr_a2 = DownColor; //--- Set down colors } // Volatility Alert if (vol > Vol_Alert_Threshold) { //--- Check high volatility clr_vol = UpColor; //--- Set vol color clr_s = (symbol == _Symbol) ? ActiveColor : UpColor; //--- Set symbol color } // RSI Color clr_rsi = (rsi > 70 ? DownColor : (rsi < 30 ? UpColor : DataColor)); //--- Set RSI color // Update Texts string displaySymbol = (symbol == _Symbol) ? "*" + symbol : symbol; //--- Format display symbol createText("Symbol_" + IntegerToString(j), displaySymbol, X_Offset + 10, y, clr_s, fontSize, FontType, vol > Vol_Alert_Threshold && EnableAnimations); //--- Update symbol text createText("Bid_" + IntegerToString(j), Bid(symbol), X_Offset + 10 + cellWidth, y, clr_p, fontSize, FontType, EnableAnimations); //--- Update bid text createText("Spread_" + IntegerToString(j), Spread(symbol), X_Offset + 10 + cellWidth * 2, y, clr_sp, fontSize, FontType); //--- Update spread text createText("Change_" + IntegerToString(j), StringFormat("%.2f%%", change), X_Offset + 10 + cellWidth * 3, y, clr_ch, fontSize, FontType); //--- Update change text createText("Vol_" + IntegerToString(j), StringFormat("%.2f%%", vol), X_Offset + 10 + cellWidth * 4, y, clr_vol, fontSize, FontType, vol > Vol_Alert_Threshold && EnableAnimations); //--- Update vol text createText("RSI_" + IntegerToString(j), StringFormat("%.1f", rsi), X_Offset + 10 + cellWidth * 5, y, clr_rsi, fontSize, FontType, (rsi > 70 || rsi < 30) && EnableAnimations); //--- Update RSI text createText("ArrowUp_" + IntegerToString(j), CharToString(236), X_Offset + 10 + cellWidth * 6, y, clr_a1, fontSize, "Wingdings"); //--- Update up arrow createText("ArrowDown_" + IntegerToString(j), CharToString(238), X_Offset + 10 + cellWidth * 7, y, clr_a2, fontSize, "Wingdings"); //--- Update down arrow // Pulse on high volatility if (vol > Vol_Alert_Threshold) { //--- Check high volatility HolographicPulse("Symbol_" + IntegerToString(j), clr_s, GlowColor); //--- Pulse symbol text } y += cellHeight; //--- Update y-coordinate } ChartRedraw(0); //--- Redraw chart }
Здесь мы сортируем индексы, сначала инициализируя массив "indexes" от 0 до "totalSymbols" - 1 в цикле. Мы используем метод пузырьковой сортировки с первоначальным установлением флага "swapped" в значение true, вводя цикл while до тех пор, пока больше не произойдет перестановок. Внутри сбрасываем значение "swapped" на false, затем выполняем цикл от 0 до "totalSymbols" - 2, устанавливая для "do_swap" значение false и получая "a" и "b" как "indices[j]" и "indices[j+1]". В зависимости от параметра "sortMode": для 0 (name ASC) имена получаются через "SymbolName(a, true)" и "SymbolName(b, true)", происходит обмен местами, если "na > nb"; для 1 (vol DESC) происходит обмен местами, если "vol_array[a] < vol_array[b]"; для 2 (изменение ABS DESC) происходит обмен местами, если "MathAbs(change_array[a]) < MathAbs(change_array[b])"; для 3 (RSI DESC), происходит обмен местами, если "rsi_array[a] < rsi_array[b]". Если используется параметр "do_swap", мы меняем местами "indices[j]" и "indices[j+1]", используя переменную "temp", и устанавливаем значение параметра "swapped" в true.
Далее отображаем отсортированные символы, проходя циклом по "totalSymbols", получая "i" как "indices[j]", затем извлекая "symbol" через "SymbolName(i, true)", "bidPrice" из "bid_array[i]", "spread" из "spread_array[i]", "change" из "change_array[i]", "vol" из "vol_array[i]" и "rsi" из "rsi_array[i]". Устанавливаем значение "clr_s" для "ActiveColor" если совпадает со значением "_Symbol", в противном случае - "_DataColor"; остальные цвета по умолчанию устанавливаются в значение "DataColor". Для изменения цены: если "change > 0", установим "clr_p", "clr_ch", "clr_a1" в значение "UpColor", а "clr_a2" в значение "DataColor"; если < 0, установим в значение "DownColor" с помощью "clr_a1" как "DataColor". Для оповещения о волатильности: если "vol > Vol_Alert_Threshold", установим "clr_vol" в значение "UpColor" и обновим "clr_s", если это не текущий символ. Для RSI: установим значение "clr_rsi" равным "DownColor", если >70, "UpColor", если <30, иначе "DataColor".
Форматируем "displaySymbol" с помощью "*", если совпадает с "_Symbol". Обновим тексты с помощью "createText": symbol ("Symbol_j") с помощью "displaySymbol", "clr_s", анимируем, если высокий уровень vol и включен; bid ("Bid_j") с помощью "Bid(symbol)", "clr_p", анимируем, если включен; spread ("Spread_j") с помощью "Spread(symbol)", "clr_sp"; change ("Change_j") отформатирован "%.2f%%" с помощью "StringFormat", "clr_ch"; vol ("Vol_j") отформатирован "%.2f%%", "clr_vol", анимируем, если высокий уровень vol и включен; RSI ("RSI_j") отформатирован "%.1f", "clr_rsi", анимируем при наличии перекупленности/перепроданности и включим; стрелка вверх ("ArrowUp_j") с помощью "CharToString(236)", "clr_a1", "Wingdings"; стрелка вниз ("ArrowDown_j") с помощью "CharToString(238)", "clr_a2", "Wingdings". При высоком уровне vol применим "HolographicPulse" к тексту символа с параметрами "clr_s" и "GlowColor". Увеличиваем значение "y" на "cellHeight" на каждой итерации и, наконец, перерисовываем. При компилировании получаем следующий результат.

Как видно из визуализации, обновления вступают в силу на каждом тике. Теперь мы можем перейти к «оживлению» созданных нами кнопок. Сделаем это посредством обработчика OnChartEvent.
//+------------------------------------------------------------------+ //| Chart Event Handler | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { if (id == CHARTEVENT_OBJECT_CLICK) { //--- Handle click event if (sparam == "ToggleBtn") { //--- Check toggle button dashboardVisible = !dashboardVisible; //--- Toggle visibility objManager.DeleteAllObjects(); //--- Delete objects if (dashboardVisible) { //--- Check if visible InitDashboard(); //--- Reinitialize dashboard } else { createButton("ToggleBtn", "TOGGLE DASHBOARD", X_Offset + 10, Y_Offset + 10, 150, 25, TitleColor, PanelColor, UpColor); //--- Create toggle button } } else if (sparam == "SwitchTFBtn") { //--- Check switch TF button int currentIdx = -1; //--- Initialize current index for (int i = 0; i < ArraySize(periods); i++) { //--- Find current timeframe if (periods[i] == _Period) { //--- Match found currentIdx = i; //--- Set index break; //--- Exit loop } } int nextIdx = (currentIdx + 1) % ArraySize(periods); //--- Calculate next index if (!ChartSetSymbolPeriod(0, _Symbol, periods[nextIdx])) { //--- Switch timeframe LogError(__FUNCTION__ + ": Failed to switch timeframe, Error: " + IntegerToString(GetLastError())); //--- Log error } createButton("SwitchTFBtn", "NEXT TF", X_Offset + 170, (int)ObjectGetInteger(0, "SwitchTFBtn", OBJPROP_YDISTANCE), 120, 25, UpColor, PanelColor, UpColor, EnableAnimations); //--- Update button } else if (sparam == "SortBtn") { //--- Check sort button sortMode = (sortMode + 1) % 4; //--- Cycle sort mode createButton("SortBtn", "SORT: " + sortNames[sortMode], X_Offset + 300, (int)ObjectGetInteger(0, "SortBtn", OBJPROP_YDISTANCE), 150, 25, TitleColor, PanelColor, UpColor, EnableAnimations); //--- Update button } ObjectSetInteger(0, sparam, OBJPROP_STATE, false); //--- Reset button state ChartRedraw(0); //--- Redraw chart } }
Реализуем обработчик OnChartEvent для обработки интерактивных событий, реагируя на нажатия кнопок для переключения режимов видимости, изменения таймфреймов и циклического изменения режимов сортировки. Для CHARTEVENT_OBJECT_CLICK проверяем "sparam" на соответствие "ToggleBtn", переключая "dashboardVisible", удаляя объекты с помощью "objManager.DeleteAllObjects" и повторную инициализацию с помощью "InitDashboard", если она видна, или создание новой "ToggleBtn" с помощью "createButton", если скрыта. Если параметр "sparam" равен "SwitchTFBtn", находим индекс текущего таймфрейма в "periods" с помощью цикла, вычисляем "nextIdx" как "(currentIdx + 1) % ArraySize(periods)", переключаем график с помощью "ChartSetSymbolPeriod", используя "periods[nextIdx]", выводим сообщения об ошибках с помощью "LogError" и обновляем кнопку с помощью "createButton", включая анимацию, если "EnableAnimations".
Для "SortBtn" циклически повторяем "sortMode" с помощью "(sortMode + 1) % 4" и обновляем текст кнопки на "SORT: " + "sortNames[sortMode]", используя функцию "createButton" с анимацией. Сбрасываем состояние кнопки с помощью ObjectSetInteger, устанавливая OBJPROP_STATE в значение false, и перерисовываем график с помощью функции ChartRedraw. Это позволяет управлять отображением данных на панели и их организацией. После компиляции получаем следующий результат.

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

Заключение
В заключение мы создали Динамическую голографическую панель в MQL5, которая отслеживает символы и таймфреймы с помощью RSI, оповещений о волатильности и сортировки, с анимацией импульсов и интерактивными кнопками для полного погружения в торговый процесс. Мы подробно описали архитектуру и реализацию, используя компоненты классов, такие как "CObjectManager", и функции, такие как "HolographicPulse", для обеспечения визуально привлекательной информации в режиме реального времени. Можно настроить эту панель в соответствии со своими торговыми потребностями, улучшив анализ с помощью голографических эффектов и элементов управления.
Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/18880
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.
Знакомство с языком MQL5 (Часть 33): Освоение API и функции WebRequest в языке MQL5 (VII)
Машинное обучение и Data Science (Часть 41): YOLOv8v для поиска паттернов на рынках Forex и акций
Архитектура системы машинного обучения в MetaTrader 5 (Часть 1): Утечка данных и исправление меток времени
Нейросети в трейдинге: Адаптивная факторная токенизация (Окончание)
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования