English Deutsch 日本語
preview
Торговые инструменты на языке MQL5 (Часть 6): Динамическая голографическая панель с импульсной анимацией и элементами управления

Торговые инструменты на языке MQL5 (Часть 6): Динамическая голографическая панель с импульсной анимацией и элементами управления

MetaTrader 5Трейдинг |
70 0
Allan Munene Mutiiria
Allan Munene Mutiiria

Введение

В своей предыдущей статье (Часть 5) мы создали бегущую тикерную строку на MetaQuotes Language 5 (MQL5) для мониторинга инструментов в режиме реального времени с прокруткой цен, спредов и изменений, чтобы эффективно информировать трейдеров. В Части 6 мы разрабатываем динамическую голографическую панель, отображающую индикаторы на нескольких инструментах и таймфреймах, такие как Индекс относительной силы (RSI) и волатильность (на основе Среднего истинного диапазона (ATR)), с анимацией импульсов, сортировкой и интерактивными элементами управления, создавая привлекательный инструмент анализа. В статье рассмотрим следующие темы:

  1. Понимание архитектуры голографической панели
  2. Реализация средствами MQL5
  3. Тестирование на истории
  4. Заключение

В итоге у вас будет настраиваемая голографическая панель, готовая для вашей торговой настройки. Давайте начнём!


Понимание архитектуры голографической панели

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

Мы достигнем этого, используя массивы для управления данными, хэндлы для таких индикаторов, как ATR и RSI, и функции для эффектов сортировки и импульсов, а также кнопки для переключения видимости и анализа. Мы планируем централизовать обновления в цикле, который будет динамически обновлять пользовательский интерфейс (UI), обеспечивая адаптивность и реагирование панели в целях стратегической торговли. Прежде чем приступим к реализации, просмотрите визуализацию ниже для понимания того, чего мы хотим достичь.

COMPLETE ARCHITECTURE


Реализация средствами 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 и возвращаем результат успеха. При запуске программы получаем следующий результат.

INITIAL DASHBOARD

На изображении видно, что мы успешно инициализировали программу. Мы можем позаботиться о деинициализации программы, где нужно будет удалить созданные объекты и освободить хэндлы индикаторов.

//+------------------------------------------------------------------+
//| 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". Это обеспечивает полную очистку объектов и индикаторов. Вот иллюстрация.

REMOVAL GIF

Теперь, когда с созданными объектами все в порядке, мы можем перейти к обновлениям. Мы намерены выполнить обновления в обработчике 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" на каждой итерации и, наконец, перерисовываем. При компилировании получаем следующий результат.

PULSIVE UPDATES IN ONTICK

Как видно из визуализации, обновления вступают в силу на каждом тике. Теперь мы можем перейти к «оживлению» созданных нами кнопок. Сделаем это посредством обработчика 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. Это позволяет управлять отображением данных на панели и их организацией. После компиляции получаем следующий результат.

RESPONSIVE BUTTON CLICKS

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


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

Мы провели тестирование, а ниже представлена скомпилированная визуализация в едином формате растрового изображения Graphics Interchange Format (GIF).

BACKTESTING


Заключение

В заключение мы создали Динамическую голографическую панель в MQL5, которая отслеживает символы и таймфреймы с помощью RSI, оповещений о волатильности и сортировки, с анимацией импульсов и интерактивными кнопками для полного погружения в торговый процесс. Мы подробно описали архитектуру и реализацию, используя компоненты классов, такие как "CObjectManager", и функции, такие как "HolographicPulse", для обеспечения визуально привлекательной информации в режиме реального времени. Можно настроить эту панель в соответствии со своими торговыми потребностями, улучшив анализ с помощью голографических эффектов и элементов управления.

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

Прикрепленные файлы |
Знакомство с языком MQL5 (Часть 33): Освоение API и функции WebRequest в языке MQL5 (VII) Знакомство с языком MQL5 (Часть 33): Освоение API и функции WebRequest в языке MQL5 (VII)
В этой статье показано, как интегрировать API Google Generative AI в MetaTrader 5 с помощью языка MQL5. Вы научитесь структурировать API-запросы, обрабатывать ответы сервера, извлекать контент, сгенерированный ИИ, управлять лимитами API и сохранять результаты в текстовый файл для удобного доступа.
Машинное обучение и Data Science (Часть 41): YOLOv8v для поиска паттернов на рынках Forex и акций Машинное обучение и Data Science (Часть 41): YOLOv8v для поиска паттернов на рынках Forex и акций
Выявление графических закономерностей на финансовых рынках представляет собой сложную задачу, поскольку требует анализа данных на графике, что трудно осуществить в MQL5 из-за ограничений, связанных с обработкой изображений. В этой статье мы рассмотрим достойную модель на Python, которая позволит с минимальными усилиями обнаруживать паттерны на графике.
Архитектура системы машинного обучения в MetaTrader 5 (Часть 1): Утечка данных и исправление меток времени Архитектура системы машинного обучения в MetaTrader 5 (Часть 1): Утечка данных и исправление меток времени
Прежде чем мы сможем даже начать использовать машинное обучение в нашей торговле на MetaTrader 5, крайне важно разобраться с одной из самых недооцененных ловушек — утечкой данных. Эта статья раскрывает, как утечка данных, в частности ловушка с временными метками в MetaTrader 5, может исказить производительность нашей модели и привести к ненадежным торговым сигналам. Углубляясь в механику этой проблемы и предлагая стратегии ее предотвращения, мы прокладываем путь к созданию надежных моделей машинного обучения, которые будут давать достоверные прогнозы в условиях реальной торговли.
Нейросети в трейдинге: Адаптивная факторная токенизация (Окончание) Нейросети в трейдинге: Адаптивная факторная токенизация (Окончание)
Статья завершает перенос и интеграцию ключевых компонентов фреймворка MTmixAtt в архитектуру торговой модели для анализа рыночных данных. Продемонстрировано, как адаптивная токенизация и блоки MTmixAttBlock позволяют эффективно выявлять локальные и глобальные паттерны, учитывать сценарии поведения цены.