English
preview
Торговые инструменты MQL5 (Часть 12): Улучшение интерактивности панели корреляционной матрицы

Торговые инструменты MQL5 (Часть 12): Улучшение интерактивности панели корреляционной матрицы

MetaTrader 5Трейдинг |
184 4
Allan Munene Mutiiria
Allan Munene Mutiiria

Введение

В своей предыдущей статье (Часть 11) мы создали панель корреляционной матрицы в (MQL5), рассчитывающей взаимосвязи между активами с использованием методов Пирсона (Pearson), Спирмена (Spearman) и Кенделла (Kendall) за настраиваемый таймфрейм и количество баров. В Части 12 мы расширим интерактивность панели корреляционной матрицы. Это улучшение добавляет такие возможности, как перетаскивание панели и её сворачивание с помощью мыши, эффекты при наведении курсора мыши на кнопку для визуальной обратной связи, сортировка символов по степени корреляции в режимах возрастания / убывания, переключение между представлениями корреляции и p-значения, переключение светлой / темной темы с динамическим обновлением цвета и всплывающие подсказки для ячеек для получения подробной информации. В статье рассмотрим следующие темы:

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

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


Понимание улучшений интерактивной корреляционной матрицы

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

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

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

ENHANCEMENTS FRAMEWORK


Реализация средствами MQL5

Для улучшения программы в MQL5 нам потребуется определить несколько новых входных параметров для управления механизмом многопанельного управления, а также несколько дополнительных глобальных переменных.

input color ColorLightThemeBg           = clrWhite;                                                         // Light theme background
input color ColorLightThemeText         = clrBlack;                                                         // Light theme text

enum ViewMode
{
   VIEW_CORR, // Show correlations
   VIEW_PVAL  // Show p-values
};

#define SORT_BUTTON             "BUTTON_SORT"                    // Define sort button identifier
#define THEME_BUTTON            "BUTTON_THEME"                   // Define theme toggle button identifier
#define COLOR_LIGHT_GRAY        C'230,230,230'                   // Define light gray for neutral cells
#define COLOR_DARK_GRAY         C'105,105,105'                   // Define dark gray for headers
#define BUTTON_HOVER_SIZE       24                               // Define hover size for buttons (centered around anchor)
#define NUM_LEGEND_ITEMS        15                               // Define increased to cover max possible (e.g., 11 in heatmap)

bool panel_is_visible = true;                                    //--- Set panel visibility flag
bool panel_minimized = false;                                    //--- Set minimized state flag
bool panel_dragging = false;                                     //--- Set dragging flag
int panel_drag_x = 0, panel_drag_y = 0;                          //--- Initialize drag start mouse coordinates
int panel_start_x = 0, panel_start_y = 0;                        //--- Initialize drag start panel coordinates
int prev_mouse_state = 0;                                        //--- Initialize previous mouse state
bool prev_header_hovered = false;                                //--- Set previous header hover state
bool prev_toggle_hovered = false;                                //--- Set previous toggle hover state
bool prev_close_hovered = false;                                 //--- Set previous close hover state
bool prev_heatmap_hovered = false;                               //--- Set previous heatmap hover state
bool prev_pval_hovered = false;                                  //--- Set previous pval hover state
bool prev_sort_hovered = false;                                  //--- Set previous sort hover state
bool prev_theme_hovered = false;                                 //--- Set previous theme hover state
bool prev_tf_hovered[NUM_TF];                                    //--- Declare previous TF hover states array
int last_mouse_x = 0, last_mouse_y = 0;                          //--- Initialize last mouse position
bool is_light_theme = false;                                     //--- Set theme flag (dark by default)
int sort_mode = 0;                                               //--- Initialize sort mode (0=original)

string original_symbols[MAX_SYMBOLS];                            //--- Declare array to store original order

ViewMode global_view_mode = VIEW_CORR;

Начинаем реализацию с добавления входных параметров для настройки светлой темы, включая настройку "ColorLightThemeBg" в значение белого цвета для фона, а настройку "ColorLightThemeText" в значение черного цвета для текста. Затем определяем перечисление "ViewMode" с параметрами "VIEW_CORR" для отображения корреляций и "VIEW_PVAL" для показа p-значений, что позволяет переключаться между отображениями. Далее вводим определения для новых элементов пользовательского интерфейса, таких как идентификаторы "SORT_BUTTON" и "THEME_BUTTON", а также такие цвета, как "COLOR_LIGHT_GRAY" в светло-сером цвете для нейтральных ячеек в светлом режиме, "COLOR_DARK_GRAY" в темно-сером цвете для заголовков, "BUTTON_HOVER_SIZE" в двадцать четыре пикселя для определения области кнопки при наведении курсора мыши, а "NUM_LEGEND_ITEMS" расширен до пятнадцати, чтобы разместить более мелкие легенды в таких режимах, как тепловая карта.

Мы объявляем глобальные переменные для управления состояниями интерактивности: "panel_is_visible" инициализируется значением true для видимости панели, "panel_minimized" — значением false для полного отображения, "panel_dragging" — значением false с координатами, такими как "panel_drag_x" и "panel_start_x", равными нулю, для обработки перетаскиваний, "prev_mouse_state" — значением ноль для отслеживания состояний кнопок мыши, логические флаги, такие как "prev_header_hovered", установленные в значение false для отслеживания наведения курсора на такие элементы, как заголовок, переключатель, закрытие, тепловая карта, pval, сортировка и кнопки тем оформления, массив "prev_tf_hovered" для наведения курсора на ячейки таймфреймов, "last_mouse_x" и "last_mouse_y" равными нулю для последней позиции курсора, "is_light_theme" — значением false для начала работы в темном режиме, и "sort_mode" равным нулю для исходного порядка символов. Кроме того, добавляем строковый массив "original_symbols" с максимальным размером символа, чтобы сохранить исходную последовательность символов для сброса сортировки, и устанавливаем "global_view_mode" в значение "VIEW_CORR" в качестве отображения по умолчанию.

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

//+------------------------------------------------------------------+
//| Parse symbols list into array                                    |
//+------------------------------------------------------------------+
void parse_symbols() {
   string temp = SymbolsList;                                   //--- Copy input symbols list
   num_symbols = 0;                                             //--- Reset symbol count
   while (StringFind(temp, ",") >= 0 && num_symbols < MAX_SYMBOLS) { //--- Loop through comma-separated symbols
      int pos = StringFind(temp, ",");                          //--- Find comma position
      string sym = StringSubstr(temp, 0, pos);                  //--- Extract symbol
      if (SymbolSelect(sym, true)) {                            //--- Select symbol if available
         symbols_array[num_symbols] = sym;                      //--- Store valid symbol
         original_symbols[num_symbols] = sym;                   //--- Store in original order
         num_symbols++;                                         //--- Increment count
      } else {                                                  //--- Handle unavailable symbol
         Print("Warning: Symbol ", sym, " not available.");     //--- Print warning
      }
      temp = StringSubstr(temp, pos + 1);                       //--- Update remaining string
   }
   if (StringLen(temp) > 0 && num_symbols < MAX_SYMBOLS) {      //--- Handle last symbol
      if (SymbolSelect(temp, true)) {                           //--- Select last symbol if available
         symbols_array[num_symbols] = temp;                     //--- Store last valid symbol
         original_symbols[num_symbols] = temp;                  //--- Store in original order
         num_symbols++;                                         //--- Increment count
      } else {                                                  //--- Handle unavailable last symbol
         Print("Warning: Symbol ", temp, " not available.");    //--- Print warning
      }
   }
   if (num_symbols < 2) {                                       //--- Check minimum symbols
      Print("Error: At least 2 valid symbols required. Found: ", num_symbols); //--- Print error
      ExpertRemove();                                           //--- Remove expert
   }
}

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

//+------------------------------------------------------------------+
//| Sort symbols by average correlation strength                     |
//+------------------------------------------------------------------+
void sort_symbols_by_strength() {
   if (sort_mode == 0) {                                        //--- Check original mode
      ArrayCopy(symbols_array, original_symbols);               //--- Restore original symbols
      update_correlations();                                    //--- Recompute correlations
      return;                                                   //--- Exit function
   }

   double avg_corr[MAX_SYMBOLS];                                //--- Declare average correlation array
   ArrayInitialize(avg_corr, 0.0);                              //--- Initialize averages
   for (int i = 0; i < num_symbols; i++) {                      //--- Loop symbols
      for (int j = 0; j < num_symbols; j++) {                   //--- Loop pairs
         if (i != j) avg_corr[i] += MathAbs(correlation_matrix[i][j]); //--- Accumulate absolute correlations
      }
      avg_corr[i] /= (num_symbols - 1);                         //--- Compute average
   }

   int indices[MAX_SYMBOLS];                                    //--- Declare indices array
   for (int i = 0; i < num_symbols; i++) indices[i] = i;        //--- Initialize indices
   for (int i = 0; i < num_symbols - 1; i++) {                  //--- Loop outer for sorting
      for (int j = i + 1; j < num_symbols; j++) {               //--- Loop inner for comparison
         bool swap = (sort_mode == 1) ? (avg_corr[indices[i]] < avg_corr[indices[j]]) : (avg_corr[indices[i]] > avg_corr[indices[j]]); //--- Determine swap
         if (swap) {                                            //--- Check if swap needed
            int temp = indices[i];                              //--- Store temporary
            indices[i] = indices[j];                            //--- Swap indices
            indices[j] = temp;                                  //--- Complete swap
         }
      }
   }

   string new_symbols[MAX_SYMBOLS];                             //--- Declare new symbols array
   double new_corr[MAX_SYMBOLS][MAX_SYMBOLS];                   //--- Declare new correlation matrix
   double new_pval[MAX_SYMBOLS][MAX_SYMBOLS];                   //--- Declare new p-value matrix
   for (int i = 0; i < num_symbols; i++) {                      //--- Loop to reorder
      int old_i = indices[i];                                   //--- Get old index
      new_symbols[i] = symbols_array[old_i];                    //--- Set new symbol
      for (int j = 0; j < num_symbols; j++) {                   //--- Loop columns
         int old_j = indices[j];                                //--- Get old column index
         new_corr[i][j] = correlation_matrix[old_i][old_j];     //--- Copy correlation
         new_pval[i][j] = pvalue_matrix[old_i][old_j];          //--- Copy p-value
      }
   }
   ArrayCopy(symbols_array, new_symbols);                       //--- Update symbols
   ArrayCopy(correlation_matrix, new_corr);                     //--- Update correlations
   ArrayCopy(pvalue_matrix, new_pval);                          //--- Update p-values
}

//+------------------------------------------------------------------+
//| Cycle sort mode                                                  |
//+------------------------------------------------------------------+
void cycle_sort_mode() {
   sort_mode = (sort_mode + 1) % 3;                             //--- Cycle mode
   string direction;                                            //--- Declare direction string
   if (sort_mode == 0) direction = "original";                  //--- Set original
   else if (sort_mode == 1) direction = "descending";           //--- Set descending
   else direction = "ascending";                                //--- Set ascending
   Print("Sort mode cycled to ", direction);                    //--- Print new mode
   sort_symbols_by_strength();                                  //--- Sort symbols
}

Сначала мы реализуем функцию "sort_symbols_by_strength", чтобы переупорядочить символы на основе их средней абсолютной силы корреляции в соответствии с текущим режимом сортировки. Если параметр "sort_mode" равен нулю для исходного порядка, мы копируем массив "original_symbols" обратно в "symbols_array" с помощью ArrayCopy и пересчитываем матрицы с помощью параметра "update_correlations" перед выходом. В противном случае мы объявляем и инициализируем массив типа double "avg_corr" с максимальным размером символов нулями с помощью функции ArrayInitialize, затем циклом перебираем символы и пары, накапливая абсолютные значения из "correlation_matrix", исключая "self-pairs", и делим на "num_symbols" минус один, чтобы получить средние значения. Мы создаём массив "indices", инициализируемый последовательно, и выполняем на нём пузырьковую сортировку: во вложенных циклах определяем, требуется ли перестановка на основе sort_mode: при значении 1 — сортировка по убыванию, при значении 2 — по возрастанию, и меняем индексы местами, если значение true.

Затем мы объявляем новые массивы для символов, корреляций и p-значений и выполняем цикл для изменения порядка: для каждой новой позиции получаем старый индекс, устанавливаем символ и соответствующим образом копируем строки и столбцы матрицы из старых позиций в новые. Наконец, обновляем глобальные массивы с помощью "ArrayCopy" для символов, "correlation_matrix" и "pvalue_matrix". Далее мы создаём функцию "cycle_sort_mode", которая увеличивает значение переменной "sort_mode" по модулю три, чтобы циклически переключаться между исходным, нисходящим и восходящим порядком сортировки. Мы задаем строку направления на основе нового режима и выводим ее на экран, затем вызываем функцию "sort_symbols_by_strength" для применения сортировки. Далее нам необходимо обеспечить согласованность светлой и темной тем, улучшив визуальную целостность при переключении тем оформления в зависимости от таймфреймов. Для этого нам потребуется обновить подсветку таким образом, чтобы неактивный фон зависел от примененной темы оформления следующим образом.

//+------------------------------------------------------------------+
//| Update TF highlights                                             |
//+------------------------------------------------------------------+
void update_tf_highlights() {
   color inactive_bg = is_light_theme ? clrSilver : C'60,60,60'; //--- Set inactive background
   for (int i = 0; i < num_tf_visible; i++) {                   //--- Loop visible TFs
      string rect_name = TF_CELL_RECT + IntegerToString(i);     //--- Get rectangle name
      color bg = (i == current_tf_index) ? ColorStrongPositiveBg : inactive_bg; //--- Set background
      ObjectSetInteger(0, rect_name, OBJPROP_BGCOLOR, bg);      //--- Update background
   }
   ChartRedraw(0);                                              //--- Redraw chart
}

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

//+------------------------------------------------------------------+
//| Recreate legend objects based on current mode                    |
//+------------------------------------------------------------------+
void recreate_legend() {
   
   //--- Existing logic
   
   color neutral_bg = is_light_theme ? COLOR_LIGHT_GRAY : ColorNeutralBg; //--- Set neutral background
   color text_color = is_light_theme ? ColorLightThemeText : COLOR_WHITE; //--- Set text color

   for (int i = 0; i < num_legend_visible; i++) {                         //--- Loop to create
      int x_offset = x_start + i * (WIDTH_LEGEND_CELL + LEGEND_SPACING);  //--- Compute offset
      string rect_name = LEGEND_CELL_RECTANGLE + IntegerToString(i);      //--- Get rectangle name
      string text_name = LEGEND_CELL_TEXT + IntegerToString(i);           //--- Get text name
      create_rectangle(rect_name, x_offset, legend_y + 2, WIDTH_LEGEND_CELL, HEIGHT_LEGEND, neutral_bg); //--- Create rectangle
      create_label(text_name, "0%", x_offset + WIDTH_LEGEND_CELL / 2, legend_y + 2 + HEIGHT_LEGEND / 2 - 1, 10, text_color, "Arial"); //--- Create label
   }
}

//+------------------------------------------------------------------+
//| Update legend colors and texts based on mode                     |
//+------------------------------------------------------------------+
void update_legend() {
   color default_txt = is_light_theme ? ColorLightThemeText : ColorTextStrong; //--- Set default text color
   
   //--- Rest of the logic
   
}

//+------------------------------------------------------------------+
//| Update borders for main panel and legend                         |
//+------------------------------------------------------------------+
void update_borders() {
   color border_col = is_light_theme ? COLOR_BLACK : COLOR_WHITE; //--- Set border color
   
   //--- Rest of the logic
   
}

Мы модифицируем функцию "recreate_legend", чтобы она поддерживала смену тем оформления, устанавливая нейтральный цвет фона в зависимости от условия: если "is_light_theme" равно true, мы используем "COLOR_LIGHT_GRAY"; в противном случае — "ColorNeutralBg". Аналогичным образом, в светлом режиме мы устанавливаем цвет текста на "ColorLightThemeText", а в темном — на белый. В цикле создания мы применяем эти цвета при вызове функции "create_rectangle" для каждого элемента легенды с вычисленным нейтральным фоном и функции "create_label" с обновленным цветом текста, обеспечивая соответствие визуальных элементов легенды текущей теме оформления.

Далее мы обновляем функцию "update_legend", чтобы установить цвет текста по умолчанию на основе темы оформления: "ColorLightThemeText" используется для светлой темы, а "ColorTextStrong" — для темной, и затем применяется в цикле для манипуляций с цветом текста в различных случаях корреляции. Затем мы реализуем функцию "update_borders" для обновления цветов рамки ключевых панелей. Цвет рамки определяется как черный в светлой теме или белый в темной с помощью функции "is_light_theme", и применяется к заголовкам, основной панели и панели легенды путем установки параметра OBJPROP_COLOR с помощью ObjectSetInteger. Теперь нам нужны новые функции для управления темами оформления, сворачиванием, перетаскиванием и наведением курсора на компоненты панели. Начнём с обработки нажатий на переключатели.

//+------------------------------------------------------------------+
//| Toggle theme (dark/light)                                        |
//+------------------------------------------------------------------+
void toggle_theme() {
   is_light_theme = !is_light_theme;                                      //--- Switch theme
   color main_bg = is_light_theme ? ColorLightThemeBg : C'30,30,30';      //--- Set main background
   color header_bg = is_light_theme ? clrSilver : C'60,60,60';            //--- Set header background
   color text_color = is_light_theme ? ColorLightThemeText : COLOR_WHITE; //--- Set text color
   color neutral_bg = is_light_theme ? COLOR_LIGHT_GRAY : ColorNeutralBg; //--- Set neutral background
   color diagonal_bg = is_light_theme ? clrGray : ColorDiagonalBg;        //--- Set diagonal background
   color theme_icon_color = is_light_theme ? clrBlack : clrWhite;         //--- Set theme icon color
   color button_text = is_light_theme ? clrNavy : clrGold;                //--- Set button text color
   color close_text = is_light_theme ? clrBlack : clrWhite;               //--- Set close text color
   color header_icon_color = is_light_theme ? clrDodgerBlue : clrAqua;    //--- Set header icon color

   ObjectSetInteger(0, MAIN_PANEL, OBJPROP_BGCOLOR, main_bg);             //--- Update main background
   ObjectSetInteger(0, HEADER_PANEL, OBJPROP_BGCOLOR, header_bg);         //--- Update header background
   ObjectSetInteger(0, LEGEND_PANEL, OBJPROP_BGCOLOR, main_bg);           //--- Update legend background
   ObjectSetInteger(0, HEADER_PANEL_TEXT, OBJPROP_COLOR, text_color);     //--- Update header text color
   ObjectSetInteger(0, HEADER_PANEL_ICON, OBJPROP_COLOR, header_icon_color); //--- Update header icon color
   ObjectSetInteger(0, CLOSE_BUTTON, OBJPROP_COLOR, close_text);          //--- Update close color
   ObjectSetInteger(0, TOGGLE_BUTTON, OBJPROP_COLOR, button_text);        //--- Update toggle color
   ObjectSetInteger(0, HEATMAP_BUTTON, OBJPROP_COLOR, button_text);       //--- Update heatmap color
   ObjectSetInteger(0, PVAL_BUTTON, OBJPROP_COLOR, button_text);          //--- Update pval color
   ObjectSetInteger(0, SORT_BUTTON, OBJPROP_COLOR, button_text);          //--- Update sort color
   ObjectSetInteger(0, THEME_BUTTON, OBJPROP_COLOR, theme_icon_color);    //--- Update theme color

   for (int i = 0; i < num_symbols; i++) {                                //--- Loop symbols
      ObjectSetInteger(0, SYMBOL_ROW_RECTANGLE + IntegerToString(i), OBJPROP_BGCOLOR, header_bg); //--- Update row background
      ObjectSetInteger(0, SYMBOL_ROW_TEXT + IntegerToString(i), OBJPROP_COLOR, text_color);       //--- Update row text color
      ObjectSetInteger(0, SYMBOL_COL_RECTANGLE + IntegerToString(i), OBJPROP_BGCOLOR, header_bg); //--- Update column background
      ObjectSetInteger(0, SYMBOL_COL_TEXT + IntegerToString(i), OBJPROP_COLOR, text_color);       //--- Update column text color
      for (int j = 0; j < num_symbols; j++) {                             //--- Loop cells
         string cell_name = CELL_RECTANGLE + IntegerToString(i) + "_" + IntegerToString(j); //--- Get cell name
         color bg = (i == j) ? diagonal_bg : neutral_bg;                  //--- Set base background
         ObjectSetInteger(0, cell_name, OBJPROP_BGCOLOR, bg);             //--- Update cell background
      }
   }
   update_dashboard();                                                    //--- Reapply colors
   update_borders();                                                      //--- Update borders
   ChartRedraw(0);                                                        //--- Redraw chart
}

Мы реализуем функцию "toggle_theme" для переключения между светлой и темной темами оформления панели. Эта функция инвертирует логическое значение "is_light_theme" и устанавливает соответствующие локальные цвета. В качестве основного фона используется либо тема "ColorLightThemeBg", либо темно-серый цвет. Заголовок меняется на серебристый или средне-серый (medium gray). Текст становится "ColorLightThemeText" или белым. Нейтральный цвет — светло-серый или "ColorNeutralBg". Цвет диагонали — серый или "ColorDiagonalBg". Значок темы оформления становится чёрным или белым.

Текст на кнопке может быть темно-синим (navy) или золотым. Текст при закрытии отображается черным или белым цветом. Значок заголовка переключается на яркий оттенок лазурного цвета (Dodger Blue) или аква (aqua). Мы используем ObjectSetInteger для обновления свойств объектов. Фон главной панели, заголовка и легенды получает новые цвета главной панели или заголовка. Текст заголовка и значок изменяются на обновленные цвета текста и значка. Кнопки закрытия, переключения, тепловой карты, "pval", сортировки и темы оформления приобретают соответствующие новые цвета.

В цикле работы с символами мы обновляем прямоугольники строк и столбцов до нового фона заголовка и устанавливаем для их текста новый цвет текста. Внутри цикла для каждой ячейки мы формируем имя, определяем базовый фон — диагональный, если она находится по диагонали, нейтральный в противном случае — и устанавливаем его. В итоге мы вызываем функцию "update_dashboard" для повторного применения цветов ячеек, "update_borders" для рамок панели и ChartRedraw для обновления отображения. В режиме сворачивания мы просто создаём компоненты заголовка, как описано ниже.

//+------------------------------------------------------------------+
//| Create minimized dashboard UI                                    |
//+------------------------------------------------------------------+
void create_minimized_dashboard() {
   color header_bg = is_light_theme ? clrSilver : C'60,60,60';            //--- Set header background
   color text_color = is_light_theme ? ColorLightThemeText : COLOR_WHITE; //--- Set text color
   color button_text = is_light_theme ? clrNavy : clrGold;                //--- Set button text color
   color close_text = is_light_theme ? clrBlack : clrWhite;               //--- Set close text color
   color header_icon_color = is_light_theme ? clrDodgerBlue : clrAqua;    //--- Set header icon color
   int panel_width = WIDTH_SYMBOL + num_symbols * (WIDTH_CELL - 1) + 4;   //--- Compute panel width
   create_rectangle(HEADER_PANEL, panel_x, panel_y, panel_width, HEIGHT_HEADER, header_bg);                                  //--- Create header panel
   create_label(HEADER_PANEL_ICON, CharToString(181), panel_x + 12, panel_y + 14, 18, header_icon_color, "Wingdings");       //--- Create header icon
   create_label(HEADER_PANEL_TEXT, "Correlation Matrix", panel_x + 90, panel_y + 12, 13, text_color);                        //--- Create header text
   create_label(CLOSE_BUTTON, CharToString('r'), panel_x + (panel_width - 17), panel_y + 14, 18, close_text, "Webdings");    //--- Create close button
   create_label(TOGGLE_BUTTON, CharToString('o'), panel_x + (panel_width - 47), panel_y + 14, 18, button_text, "Wingdings"); //--- Create toggle button
   update_borders();                                                      //--- Update borders
   ChartRedraw(0);                                                        //--- Redraw chart
}

Здесь мы реализуем функцию "create_minimized_dashboard" для создания компактной версии пользовательского интерфейса при сворачивании панели, отображая только заголовок для экономии места. Она задает локальные цвета в зависимости от темы оформления: фон заголовка — серебристый в светлом режиме или средне-серый (medium gray) в темном, цвет текста — "ColorLightThemeText" или белый, текст кнопок — темно-синий (navy) или золотой, текст закрытия — черный или белый, а значок заголовка — яркий оттенок лазурного цвета (dodger blue) или аква (aqua).

Мы вычисляем ширину панели на основе констант, скорректированных с учетом количества символов, затем вызываем функцию "create_rectangle" для "HEADER_PANEL" с текущей позицией и высотой заголовка, используя фон, соответствующий теме оформления. Добавляем метки для значка заголовка с символом 181 из Wingdings, заголовка как «Корреляционная матрица», кнопки закрытия с буквой 'r' из Webdings и кнопки переключения с буквой 'o' из Wingdings, все они расположены относительно панели и используют цвета темы оформления.

Наконец, вызываем функцию "update_borders" для обновления рамок и ChartRedraw для обновления отображения. Для обработки нажатий при закрытии и переключении таймфреймов мы реализуем следующие функции.

//+------------------------------------------------------------------+
//| Delete all dashboard objects                                     |
//+------------------------------------------------------------------+
void delete_all_objects() {
   ObjectDelete(0, MAIN_PANEL);                                 //--- Delete main panel
   ObjectDelete(0, HEADER_PANEL);                               //--- Delete header panel
   ObjectDelete(0, HEADER_PANEL_ICON);                          //--- Delete header icon
   ObjectDelete(0, HEADER_PANEL_TEXT);                          //--- Delete header text
   ObjectDelete(0, CLOSE_BUTTON);                               //--- Delete close button
   ObjectDelete(0, TOGGLE_BUTTON);                              //--- Delete toggle button
   ObjectDelete(0, HEATMAP_BUTTON);                             //--- Delete heatmap button
   ObjectDelete(0, PVAL_BUTTON);                                //--- Delete pval button
   ObjectDelete(0, SORT_BUTTON);                                //--- Delete sort button
   ObjectDelete(0, THEME_BUTTON);                               //--- Delete theme button
   for (int i = 0; i < NUM_TF; i++) {                           //--- Loop TFs
      ObjectDelete(0, TF_CELL_RECT + IntegerToString(i));       //--- Delete TF rectangle
      ObjectDelete(0, TF_CELL_TEXT + IntegerToString(i));       //--- Delete TF text
   }
   ObjectDelete(0, "SYMBOL_ROW_HEADER");                        //--- Delete row header rectangle
   ObjectDelete(0, "SYMBOL_ROW_HEADER_TEXT");                   //--- Delete row header text
   for (int i = 0; i < num_symbols; i++) {                      //--- Loop symbols
      ObjectDelete(0, SYMBOL_ROW_RECTANGLE + IntegerToString(i)); //--- Delete row rectangle
      ObjectDelete(0, SYMBOL_ROW_TEXT + IntegerToString(i));    //--- Delete row text
      ObjectDelete(0, SYMBOL_COL_RECTANGLE + IntegerToString(i)); //--- Delete column rectangle
      ObjectDelete(0, SYMBOL_COL_TEXT + IntegerToString(i));    //--- Delete column text
      for (int j = 0; j < num_symbols; j++) {                   //--- Loop cells
         ObjectDelete(0, CELL_RECTANGLE + IntegerToString(i) + "_" + IntegerToString(j)); //--- Delete cell rectangle
         ObjectDelete(0, CELL_TEXT + IntegerToString(i) + "_" + IntegerToString(j)); //--- Delete cell text
      }
   }
   ObjectDelete(0, LEGEND_PANEL);                               //--- Delete legend panel
   for (int i = 0; i < NUM_LEGEND_ITEMS; i++) {                 //--- Loop legend items
      ObjectDelete(0, LEGEND_CELL_RECTANGLE + IntegerToString(i)); //--- Delete legend rectangle
      ObjectDelete(0, LEGEND_CELL_TEXT + IntegerToString(i));   //--- Delete legend text
   }
}

//+------------------------------------------------------------------+
//| Switch to specific timeframe                                     |
//+------------------------------------------------------------------+
void switch_timeframe(int index) {
   if (index < 0 || index >= num_tf_visible) return;            //--- Check valid index
   global_correlation_tf = tf_list[index];                      //--- Set new timeframe
   current_tf_index = index;                                    //--- Update index
   Print("Switched timeframe to ", tf_strings[index]);          //--- Print switch
   update_tf_highlights();                                      //--- Update highlights
   update_dashboard();                                          //--- Update dashboard
}

Для кликов закрытия мы реализуем функцию "delete_all_objects" для очистки всех связанных с панелью графических объектов при закрытии или сбросе настроек. В ней используется ObjectDelete с нулевым подокном для удаления главной панели, панели заголовка, значка заголовка и текста, кнопок закрытия, переключения, тепловой карты, pval, сортировки и темы. Мы в цикле перебираем количество таймфреймов, чтобы удалить каждый прямоугольник ячеек таймфреймов и текст, используя префиксы и индексные строки. Затем удаляем прямоугольник заголовка строки символов и текст, а в цикле по символам удаляем прямоугольники строк и столбцов, а также тексты. Внутри этих ячеек мы перебираем их в цикле, чтобы удалить прямоугольник и текст каждой ячейки с корреляцией, содержащие конкатенированные имена. Наконец, мы удаляем панель условных обозначений и перебираем элементы условных обозначений, чтобы удалить их прямоугольники и тексты.

Далее мы создаем функцию "switch_timeframe", чтобы изменить таймфрейм анализа на основе заданного индекса. Она проверяет, действителен ли индекс в пределах от нуля до "num_tf_visible" минус единица, и завершает работу раньше, если это не так. Мы устанавливаем для "global_correlation_tf" значение по этому индексу в "tf_list", обновляем "current_tf_index", выводим сообщение о переключении с соответствующей строкой из "tf_strings", вызываем "update_tf_highlights" для обновления визуальных элементов и "update_dashboard" для повторного вычисления и отображения новых данных. Далее нужно обновить всю панель, чтобы она была чувствительна к теме оформления, и добавить новые кнопки, которые нам нужны для установки темы оформления и сортировки.

//+------------------------------------------------------------------+
//| Create full dashboard UI                                         |
//+------------------------------------------------------------------+
void create_full_dashboard() { 
   color main_bg = is_light_theme ? ColorLightThemeBg : C'30,30,30';      //--- Set main background
   color header_bg = is_light_theme ? clrSilver : C'60,60,60';            //--- Set header background
   color text_color = is_light_theme ? ColorLightThemeText : COLOR_WHITE; //--- Set text color
   color neutral_bg = is_light_theme ? COLOR_LIGHT_GRAY : ColorNeutralBg; //--- Set neutral background
   color button_text = is_light_theme ? clrNavy : clrGold;                //--- Set button text color
   color theme_icon_color = is_light_theme ? clrBlack : clrWhite;         //--- Set theme icon color
   color close_text = is_light_theme ? clrBlack : clrWhite;               //--- Set close text color
   color header_icon_color = is_light_theme ? clrDodgerBlue : clrAqua;    //--- Set header icon color
   int panel_width = WIDTH_SYMBOL + num_symbols * (WIDTH_CELL - 1) + 4;   //--- Compute panel width
   int panel_height = HEIGHT_HEADER + HEIGHT_TF_CELL + GAP_HEIGHT + HEIGHT_RECTANGLE * (num_symbols + 1) - num_symbols + 2; //--- Compute panel height
   create_rectangle(MAIN_PANEL, panel_x, panel_y, panel_width, panel_height, main_bg);                //--- Create main panel
   create_rectangle(HEADER_PANEL, panel_x, panel_y, panel_width, HEIGHT_HEADER, header_bg);           //--- Create header panel
   create_label(HEADER_PANEL_ICON, CharToString(181), panel_x + 12, panel_y + 14, 18, header_icon_color, "Wingdings"); //--- Create header icon
   create_label(HEADER_PANEL_TEXT, "Correlation Matrix", panel_x + 90, panel_y + 12, 13, text_color); //--- Create header text
   create_label(CLOSE_BUTTON, CharToString('r'), panel_x + (panel_width - 17), panel_y + 14, 18, close_text, "Webdings"); //--- Create close button
   ObjectSetString(0, CLOSE_BUTTON, OBJPROP_TOOLTIP, "Close Panel");       //--- Set close tooltip
   create_label(TOGGLE_BUTTON, CharToString('r'), panel_x + (panel_width - 47), panel_y + 14, 18, button_text, "Wingdings"); //--- Create toggle button
   ObjectSetString(0, TOGGLE_BUTTON, OBJPROP_TOOLTIP, "Toggle Minimize/Maximize");                    //--- Set toggle tooltip
   string heatmap_icon = CharToString(global_display_mode == MODE_STANDARD ? (uchar)82 : (uchar)110); //--- Set heatmap icon
   create_label(HEATMAP_BUTTON, heatmap_icon, panel_x + (panel_width - 77), panel_y + 14, 18, button_text, "Wingdings"); //--- Create heatmap button
   ObjectSetString(0, HEATMAP_BUTTON, OBJPROP_TOOLTIP, "Toggle Heatmap/Standard Mode");               //--- Set heatmap tooltip
   create_label(PVAL_BUTTON, CharToString('X'), panel_x + (panel_width - 107), panel_y + 14, 18, button_text, "Wingdings"); //--- Create pval button
   ObjectSetString(0, PVAL_BUTTON, OBJPROP_TOOLTIP, "Toggle Correlation/P-Value View");               //--- Set pval tooltip
   string sort_icon;                                                      //--- Declare sort icon
   if (sort_mode == 0) sort_icon = CharToString('N');                     //--- Set neutral for original
   else if (sort_mode == 1) sort_icon = CharToString('K');                //--- Set descending
   else sort_icon = CharToString('J');                                    //--- Set ascending
   create_label(SORT_BUTTON, sort_icon, panel_x + (panel_width - 137), panel_y + 14, 18, button_text, "Wingdings 3");             //--- Create sort button
   ObjectSetString(0, SORT_BUTTON, OBJPROP_TOOLTIP, "Sort Symbols by Strength (Cycle Original/Desc/Asc)");                        //--- Set sort tooltip
   create_label(THEME_BUTTON, CharToString('['), panel_x + (panel_width - 167), panel_y + 14, 18, theme_icon_color, "Wingdings"); //--- Create theme button
   ObjectSetString(0, THEME_BUTTON, OBJPROP_TOOLTIP, "Toggle Dark/Light Theme");                                                  //--- Set theme tooltip

   int tf_y = panel_y + HEIGHT_HEADER;                                    //--- Compute TF y position
   int tf_x_start = panel_x + 2;                                          //--- Set TF start x
   for (int i = 0; i < num_tf_visible; i++) {                             //--- Loop visible TFs
      int x_offset = tf_x_start + i * WIDTH_TF_CELL;                      //--- Compute offset
      string rect_name = TF_CELL_RECT + IntegerToString(i);               //--- Get rectangle name
      string text_name = TF_CELL_TEXT + IntegerToString(i);               //--- Get text name
      color bg = (i == current_tf_index) ? ColorStrongPositiveBg : header_bg; //--- Set background
      create_rectangle(rect_name, x_offset, tf_y, WIDTH_TF_CELL, HEIGHT_TF_CELL, bg); //--- Create TF rectangle
      create_label(text_name, tf_strings[i], x_offset + (WIDTH_TF_CELL / 2), tf_y + (HEIGHT_TF_CELL / 2), 10, text_color, "Arial Bold"); //--- Create TF text
   }

   int matrix_y = tf_y + HEIGHT_TF_CELL + GAP_HEIGHT;                     //--- Compute matrix y
   create_rectangle("SYMBOL_ROW_HEADER", panel_x + 2, matrix_y, WIDTH_SYMBOL, HEIGHT_RECTANGLE, header_bg); //--- Create row header rectangle
   create_label("SYMBOL_ROW_HEADER_TEXT", "Symbols", panel_x + (WIDTH_SYMBOL / 2 + 2), matrix_y + (HEIGHT_RECTANGLE / 2), 10, text_color, "Arial Bold"); //--- Create row header text
   for (int i = 0; i < num_symbols; i++) {                                //--- Loop row symbols
      int y_offset = matrix_y + HEIGHT_RECTANGLE * (i + 1) - (1 + i);     //--- Compute y offset
      create_rectangle(SYMBOL_ROW_RECTANGLE + IntegerToString(i), panel_x + 2, y_offset, WIDTH_SYMBOL, HEIGHT_RECTANGLE, header_bg); //--- Create row rectangle
      create_label(SYMBOL_ROW_TEXT + IntegerToString(i), symbols_array[i], panel_x + (WIDTH_SYMBOL / 2 + 2), y_offset + (HEIGHT_RECTANGLE / 2 - 1), 10, text_color, "Arial Bold"); //--- Create row text
   }

   for (int j = 0; j < num_symbols; j++) {                               //--- Loop column symbols
      int x_offset = panel_x + WIDTH_SYMBOL + j * WIDTH_CELL - j + 1;    //--- Compute x offset
      create_rectangle(SYMBOL_COL_RECTANGLE + IntegerToString(j), x_offset, matrix_y, WIDTH_CELL, HEIGHT_RECTANGLE, header_bg); //--- Create column rectangle
      create_label(SYMBOL_COL_TEXT + IntegerToString(j), symbols_array[j], x_offset + (WIDTH_CELL / 2), matrix_y + (HEIGHT_RECTANGLE / 2), 10, text_color, "Arial Bold"); //--- Create column text
   }

   for (int i = 0; i < num_symbols; i++) {                               //--- Loop rows for cells
      int y_offset = matrix_y + HEIGHT_RECTANGLE * (i + 1) - (1 + i);    //--- Compute y offset
      for (int j = 0; j < num_symbols; j++) {                            //--- Loop columns for cells
         string cell_name = CELL_RECTANGLE + IntegerToString(i) + "_" + IntegerToString(j); //--- Get cell name
         string text_name = CELL_TEXT + IntegerToString(i) + "_" + IntegerToString(j); //--- Get text name
         int x_offset = panel_x + WIDTH_SYMBOL + j * WIDTH_CELL - j + 1; //--- Compute x offset
         create_rectangle(cell_name, x_offset, y_offset, WIDTH_CELL, HEIGHT_RECTANGLE, neutral_bg); //--- Create cell rectangle
         create_label(text_name, "0.00", x_offset + (WIDTH_CELL / 2), y_offset + (HEIGHT_RECTANGLE / 2 - 1), 10, text_color, "Arial"); //--- Create cell text
      }
   }

   int legend_y = panel_y + panel_height + GAP_MAIN_LEGEND;              //--- Compute legend y
   create_rectangle(LEGEND_PANEL, panel_x, legend_y, panel_width, HEIGHT_LEGEND_PANEL, main_bg); //--- Create legend panel
   recreate_legend();                                                    //--- Recreate legend
   ChartRedraw(0);                                                       //--- Redraw chart
}

//+------------------------------------------------------------------+
//| Update dashboard cells with correlation values and colors        |
//+------------------------------------------------------------------+
void update_dashboard() {
   update_correlations();                                                //--- Update correlations
   double strong_pos = StrongPositiveThresholdPct / 100.0;               //--- Set positive threshold
   double strong_neg = StrongNegativeThresholdPct / 100.0;               //--- Set negative threshold
   color text_base = is_light_theme ? ColorLightThemeText : ColorTextStrong; //--- Set base text color
   for (int i = 0; i < num_symbols; i++) {                               //--- Loop rows
      for (int j = 0; j < num_symbols; j++) {                            //--- Loop columns
         double corr = correlation_matrix[i][j];                         //--- Get correlation
         double pval = pvalue_matrix[i][j];                              //--- Get p-value
         string text = "";                                               //--- Initialize text
         if (global_view_mode == VIEW_CORR) {                            //--- Handle correlation view
            double corr_pct = corr * 100.0;                              //--- Compute percentage
            text = DoubleToString(corr_pct, 1) + "%" + get_significance_stars(pval); //--- Format correlation text
         } else {                                                        //--- Handle p-value view
            text = DoubleToString(pval, 4) + get_significance_stars(pval); //--- Format p-value text
         }
         color bg_color = is_light_theme ? clrLightGray : ColorNeutralBg; //--- Initialize background
         color txt_color = is_light_theme ? clrBlack : ColorTextZero;    //--- Initialize text color

         if (i == j) {                                                   //--- Handle diagonal
            bg_color = is_light_theme ? clrGray : ColorDiagonalBg;       //--- Set diagonal background
            txt_color = text_base;                                       //--- Set text color
         } else {                                                        //--- Handle off-diagonal
            if (global_display_mode == MODE_STANDARD) {                  //--- Handle standard mode
               if (corr >= strong_pos) {                                 //--- Check strong positive
                  bg_color = ColorStrongPositiveBg;                      //--- Set positive background
                  txt_color = text_base;                                 //--- Set text color
               } else if (corr <= strong_neg) {                          //--- Check strong negative
                  bg_color = ColorStrongNegativeBg;                      //--- Set negative background
                  txt_color = text_base;                                 //--- Set text color
               } else {                                                  //--- Handle mild
                  bg_color = is_light_theme ? clrLightGray : ColorNeutralBg; //--- Set neutral background
                  if (corr > 0.0) {                                      //--- Check positive mild
                     txt_color = is_light_theme ? clrBlue : ColorTextPositive; //--- Set positive text
                  } else if (corr < 0.0) {                               //--- Check negative mild
                     txt_color = is_light_theme ? clrRed : ColorTextNegative; //--- Set negative text
                  } else {                                               //--- Handle zero
                     txt_color = text_base;                              //--- Set base text
                  }
               }
            } else {                                                     //--- Handle heatmap mode
               txt_color = text_base;                                    //--- Set text color
               bg_color = interpolate_heatmap_color(corr);               //--- Set interpolated background
            }
         }
         string cell_name = CELL_RECTANGLE + IntegerToString(i) + "_" + IntegerToString(j); //--- Get cell name
         string text_name = CELL_TEXT + IntegerToString(i) + "_" + IntegerToString(j);      //--- Get text name
         ObjectSetInteger(0, cell_name, OBJPROP_BGCOLOR, bg_color);      //--- Update background
         ObjectSetString(0, text_name, OBJPROP_TEXT, text);              //--- Update text
         ObjectSetInteger(0, text_name, OBJPROP_COLOR, txt_color);       //--- Update text color

         string sym1 = symbols_array[i];                                 //--- Get first symbol
         string sym2 = symbols_array[j];                                 //--- Get second symbol
         string tf_str = EnumToString(global_correlation_tf);            //--- Get timeframe string
         string method_str = EnumToString(CorrMethod);                   //--- Get method string
         string tooltip = StringFormat("Symbols: %s vs %s\nTimeframe: %s\nMethod: %s\nCorrelation: %.4f\nP-value: %.4f\nBars: %d", sym1, sym2, tf_str, method_str, corr, pval, CorrelationBars); //--- Format tooltip
         ObjectSetString(0, text_name, OBJPROP_TOOLTIP, tooltip);        //--- Set text tooltip
         ObjectSetString(0, cell_name, OBJPROP_TOOLTIP, tooltip);        //--- Set cell tooltip
      }
   }
   update_legend();                                                      //--- Update legend
   ChartRedraw(0);                                                       //--- Redraw chart
}

Во-первых, мы изменяем функцию "create_full_dashboard", чтобы включить настройки цвета с учетом темы оформления и дополнительные элементы интерактивности. Мы условно устанавливаем локальные цвета для "is_light_theme": основной фон - "ColorLightThemeBg" или темно-серый, заголовок - серебристый или средне-серый (medium gray), текст - "ColorLightThemeText" или белый, нейтральный - "COLOR_LIGHT_GRAY" или "ColorNeutralBg", текст кнопки - темно-синий (navy) или золотой, значок темы оформления - черный или белый, закрыть текст - черным или белым, а значок в заголовке - яркий оттенок лазурного цвета (dodger blue) или аква (aqua).

Мы рассчитываем размеры панелей, как и раньше, и создаем главную панель и панель заголовков с этими фонами в соответствии с темой оформления. Мы дополняем метки для значка заголовка символом 181 из Wingdings, заголовком в качестве корреляционной матрицы, кнопку закрытия буквой 'r' из Webdings, настраивая всплывающую подсказку для закрытия панели с помощью ObjectSetString подсказкой OBJPROP_TOOLTIP, кнопку переключения буквой 'r' из Wingdings и всплывающую подсказку для переключения сворачивания / разворачивания, кнопку тепловой карты динамическим значком на основе режима отображения и всплывающую подсказку для переключения тепловая карта / стандарт, кнопку pval - знаком 'X' из Wingdings и всплывающую подсказку для корреляции переключения / отображения p-значения, кнопку сортировки значком, зависящим от "sort_mode" ('N' для исходного, 'K' для убывающего, 'J' — для возрастающего) из Wingdings 3 и всплывающую подсказку для сортировки по режимам циклического изменения силы, а также кнопку темы оформления - значком '[' из Wingdings и всплывающей подсказкой для переключения темной / светлой темы - все с использованием цветов темы оформления.

Для строки таймфрейма мы вычисляем позиции и проходим циклом, чтобы создать прямоугольники с фоном, зависящим от текущего индекса и цвета заголовка, а также метки со строками таймфрейма, выделенные шрифтом Arial Bold. Мы создаем прямоугольник заголовка строки символов и текст как Symbols с цветами темы оформления. Для символов строк перебираем в цикле, чтобы создать прямоугольники с фоном заголовка и метками с названиями символов. Аналогично, для символов столбцов создаём прямоугольники и метки. Для ячеек мы используем вложенные циклы для создания прямоугольников с нейтральным фоном и начальными текстовыми метками размером 0,00 шрифтом Arial. Затем создаём панель легенды с основным фоном, вызываем функцию "recreate_legend" и перерисовываем её. Затем обновляем функцию "update_dashboard", чтобы она поддерживала режимы просмотра и темы оформления. Используется и аналогичная логика. При вызове этих функций для тестирования получаем следующий результат для темной и светлой тем оформления.

LIGHT AND DARK THEMES

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

//+------------------------------------------------------------------+
//| Check if cursor is inside header or buttons                      |
//+------------------------------------------------------------------+
bool is_cursor_in_header_or_buttons(int mouse_x, int mouse_y) {
   int chart_width = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS);         //--- Get chart width
   int header_x = (int)ObjectGetInteger(0, HEADER_PANEL, OBJPROP_XDISTANCE); //--- Get header x
   int header_y = (int)ObjectGetInteger(0, HEADER_PANEL, OBJPROP_YDISTANCE); //--- Get header y
   int header_width = (int)ObjectGetInteger(0, HEADER_PANEL, OBJPROP_XSIZE); //--- Get header width
   int header_height = (int)ObjectGetInteger(0, HEADER_PANEL, OBJPROP_YSIZE);//--- Get header height
   int header_left = header_x;                                               //--- Set left edge
   int header_right = header_left + header_width;                            //--- Set right edge
   bool in_header = (mouse_x >= header_left && mouse_x <= header_right && mouse_y >= header_y && mouse_y <= header_y + header_height); //--- Check in header

   int half_size = BUTTON_HOVER_SIZE / 2;                                    //--- Compute half hover size
   int close_x = (int)ObjectGetInteger(0, CLOSE_BUTTON, OBJPROP_XDISTANCE);  //--- Get close x
   int close_y = (int)ObjectGetInteger(0, CLOSE_BUTTON, OBJPROP_YDISTANCE);  //--- Get close y
   bool in_close = (mouse_x >= close_x - half_size && mouse_x <= close_x + half_size && mouse_y >= close_y - half_size && mouse_y <= close_y + half_size); //--- Check in close

   int toggle_x = (int)ObjectGetInteger(0, TOGGLE_BUTTON, OBJPROP_XDISTANCE); //--- Get toggle x
   int toggle_y = (int)ObjectGetInteger(0, TOGGLE_BUTTON, OBJPROP_YDISTANCE); //--- Get toggle y
   bool in_toggle = (mouse_x >= toggle_x - half_size && mouse_x <= toggle_x + half_size && mouse_y >= toggle_y - half_size && mouse_y <= toggle_y + half_size); //--- Check in toggle

   int heatmap_x = (int)ObjectGetInteger(0, HEATMAP_BUTTON, OBJPROP_XDISTANCE); //--- Get heatmap x
   int heatmap_y = (int)ObjectGetInteger(0, HEATMAP_BUTTON, OBJPROP_YDISTANCE); //--- Get heatmap y
   bool in_heatmap = (mouse_x >= heatmap_x - half_size && mouse_x <= heatmap_x + half_size && mouse_y >= heatmap_y - half_size && mouse_y <= heatmap_y + half_size); //--- Check in heatmap

   int pval_x = (int)ObjectGetInteger(0, PVAL_BUTTON, OBJPROP_XDISTANCE);     //--- Get pval x
   int pval_y = (int)ObjectGetInteger(0, PVAL_BUTTON, OBJPROP_YDISTANCE);     //--- Get pval y
   bool in_pval = (mouse_x >= pval_x - half_size && mouse_x <= pval_x + half_size && mouse_y >= pval_y - half_size && mouse_y <= pval_y + half_size); //--- Check in pval

   int sort_x = (int)ObjectGetInteger(0, SORT_BUTTON, OBJPROP_XDISTANCE);     //--- Get sort x
   int sort_y = (int)ObjectGetInteger(0, SORT_BUTTON, OBJPROP_YDISTANCE);     //--- Get sort y
   bool in_sort = (mouse_x >= sort_x - half_size && mouse_x <= sort_x + half_size && mouse_y >= sort_y - half_size && mouse_y <= sort_y + half_size); //--- Check in sort

   int theme_x = (int)ObjectGetInteger(0, THEME_BUTTON, OBJPROP_XDISTANCE);   //--- Get theme x
   int theme_y = (int)ObjectGetInteger(0, THEME_BUTTON, OBJPROP_YDISTANCE);   //--- Get theme y
   bool in_theme = (mouse_x >= theme_x - half_size && mouse_x <= theme_x + half_size && mouse_y >= theme_y - half_size && mouse_y <= theme_y + half_size); //--- Check in theme

   bool in_tf = false;                                                        //--- Initialize TF check
   for (int i = 0; i < num_tf_visible; i++) {                                 //--- Loop TFs
      string rect_name = TF_CELL_RECT + IntegerToString(i);                   //--- Get TF name
      int tf_x = (int)ObjectGetInteger(0, rect_name, OBJPROP_XDISTANCE);      //--- Get TF x
      int tf_y = (int)ObjectGetInteger(0, rect_name, OBJPROP_YDISTANCE);      //--- Get TF y
      if (mouse_x >= tf_x && mouse_x <= tf_x + WIDTH_TF_CELL && mouse_y >= tf_y && mouse_y <= tf_y + HEIGHT_TF_CELL) { //--- Check in TF
         in_tf = true;                                                        //--- Set in TF
         break;                                                               //--- Exit loop
      }
   }

   return in_header || in_close || in_toggle || in_heatmap || in_pval || in_sort || in_theme || in_tf; //--- Return if in any area
}

//+------------------------------------------------------------------+
//| Update button hover states                                       |
//+------------------------------------------------------------------+
void update_button_hover_states(int mouse_x, int mouse_y) {
   int half_size = BUTTON_HOVER_SIZE / 2;                       //--- Compute half hover size
   color hover_bg = clrDodgerBlue;                              //--- Set hover background
   color button_normal = is_light_theme ? clrNavy : clrGold;    //--- Set normal button color
   color theme_normal = is_light_theme ? clrBlack : clrWhite;   //--- Set normal theme color
   color close_normal = is_light_theme ? clrBlack : clrWhite;   //--- Set normal close color
   color hover_text = is_light_theme ? clrWhite : clrYellow;    //--- Set hover text color
   color theme_hover = is_light_theme ? clrWhite : clrYellow;   //--- Set hover theme color
   color close_hover = clrRed;                                  //--- Set hover close color

   int close_x = (int)ObjectGetInteger(0, CLOSE_BUTTON, OBJPROP_XDISTANCE); //--- Get close x
   int close_y = (int)ObjectGetInteger(0, CLOSE_BUTTON, OBJPROP_YDISTANCE); //--- Get close y
   bool is_close_hovered = (mouse_x >= close_x - half_size && mouse_x <= close_x + half_size && mouse_y >= close_y - half_size && mouse_y <= close_y + half_size); //--- Check close hover
   if (is_close_hovered != prev_close_hovered) {                //--- Check state change
      ObjectSetInteger(0, CLOSE_BUTTON, OBJPROP_COLOR, is_close_hovered ? close_hover : close_normal); //--- Update close color
      ObjectSetInteger(0, CLOSE_BUTTON, OBJPROP_BGCOLOR, is_close_hovered ? hover_bg : clrNONE); //--- Update close background
      prev_close_hovered = is_close_hovered;                    //--- Update previous state
      ChartRedraw(0);                                           //--- Redraw chart
   }

   int toggle_x = (int)ObjectGetInteger(0, TOGGLE_BUTTON, OBJPROP_XDISTANCE); //--- Get toggle x
   int toggle_y = (int)ObjectGetInteger(0, TOGGLE_BUTTON, OBJPROP_YDISTANCE); //--- Get toggle y
   bool is_toggle_hovered = (mouse_x >= toggle_x - half_size && mouse_x <= toggle_x + half_size && mouse_y >= toggle_y - half_size && mouse_y <= toggle_y + half_size); //--- Check toggle hover
   if (is_toggle_hovered != prev_toggle_hovered) {              //--- Check state change
      ObjectSetInteger(0, TOGGLE_BUTTON, OBJPROP_COLOR, is_toggle_hovered ? hover_text : button_normal); //--- Update toggle color
      ObjectSetInteger(0, TOGGLE_BUTTON, OBJPROP_BGCOLOR, is_toggle_hovered ? hover_bg : clrNONE); //--- Update toggle background
      prev_toggle_hovered = is_toggle_hovered;                  //--- Update previous state
      ChartRedraw(0);                                           //--- Redraw chart
   }

   int heatmap_x = (int)ObjectGetInteger(0, HEATMAP_BUTTON, OBJPROP_XDISTANCE); //--- Get heatmap x
   int heatmap_y = (int)ObjectGetInteger(0, HEATMAP_BUTTON, OBJPROP_YDISTANCE); //--- Get heatmap y
   bool is_heatmap_hovered = (mouse_x >= heatmap_x - half_size && mouse_x <= heatmap_x + half_size && mouse_y >= heatmap_y - half_size && mouse_y <= heatmap_y + half_size); //--- Check heatmap hover
   if (is_heatmap_hovered != prev_heatmap_hovered) {            //--- Check state change
      ObjectSetInteger(0, HEATMAP_BUTTON, OBJPROP_COLOR, is_heatmap_hovered ? hover_text : button_normal); //--- Update heatmap color
      ObjectSetInteger(0, HEATMAP_BUTTON, OBJPROP_BGCOLOR, is_heatmap_hovered ? hover_bg : clrNONE); //--- Update heatmap background
      prev_heatmap_hovered = is_heatmap_hovered;                //--- Update previous state
      ChartRedraw(0);                                           //--- Redraw chart
   }

   int pval_x = (int)ObjectGetInteger(0, PVAL_BUTTON, OBJPROP_XDISTANCE); //--- Get pval x
   int pval_y = (int)ObjectGetInteger(0, PVAL_BUTTON, OBJPROP_YDISTANCE); //--- Get pval y
   bool is_pval_hovered = (mouse_x >= pval_x - half_size && mouse_x <= pval_x + half_size && mouse_y >= pval_y - half_size && mouse_y <= pval_y + half_size); //--- Check pval hover
   if (is_pval_hovered != prev_pval_hovered) {                  //--- Check state change
      ObjectSetInteger(0, PVAL_BUTTON, OBJPROP_COLOR, is_pval_hovered ? hover_text : button_normal); //--- Update pval color
      ObjectSetInteger(0, PVAL_BUTTON, OBJPROP_BGCOLOR, is_pval_hovered ? hover_bg : clrNONE); //--- Update pval background
      prev_pval_hovered = is_pval_hovered;                     //--- Update previous state
      ChartRedraw(0);                                          //--- Redraw chart
   }

   int sort_x = (int)ObjectGetInteger(0, SORT_BUTTON, OBJPROP_XDISTANCE); //--- Get sort x
   int sort_y = (int)ObjectGetInteger(0, SORT_BUTTON, OBJPROP_YDISTANCE); //--- Get sort y
   bool is_sort_hovered = (mouse_x >= sort_x - half_size && mouse_x <= sort_x + half_size && mouse_y >= sort_y - half_size && mouse_y <= sort_y + half_size); //--- Check sort hover
   if (is_sort_hovered != prev_sort_hovered) {                            //--- Check state change
      ObjectSetInteger(0, SORT_BUTTON, OBJPROP_COLOR, is_sort_hovered ? hover_text : button_normal); //--- Update sort color
      ObjectSetInteger(0, SORT_BUTTON, OBJPROP_BGCOLOR, is_sort_hovered ? hover_bg : clrNONE); //--- Update sort background
      prev_sort_hovered = is_sort_hovered;                                //--- Update previous state
      ChartRedraw(0);                                                     //--- Redraw chart
   }

   int theme_x = (int)ObjectGetInteger(0, THEME_BUTTON, OBJPROP_XDISTANCE); //--- Get theme x
   int theme_y = (int)ObjectGetInteger(0, THEME_BUTTON, OBJPROP_YDISTANCE); //--- Get theme y
   bool is_theme_hovered = (mouse_x >= theme_x - half_size && mouse_x <= theme_x + half_size && mouse_y >= theme_y - half_size && mouse_y <= theme_y + half_size); //--- Check theme hover
   if (is_theme_hovered != prev_theme_hovered) {                          //--- Check state change
      ObjectSetInteger(0, THEME_BUTTON, OBJPROP_COLOR, is_theme_hovered ? theme_hover : theme_normal); //--- Update theme color
      ObjectSetInteger(0, THEME_BUTTON, OBJPROP_BGCOLOR, is_theme_hovered ? hover_bg : clrNONE); //--- Update theme background
      prev_theme_hovered = is_theme_hovered;                              //--- Update previous state
      ChartRedraw(0);                                                     //--- Redraw chart
   }

   for (int i = 0; i < num_tf_visible; i++) {                             //--- Loop TFs
      string rect_name = TF_CELL_RECT + IntegerToString(i);               //--- Get TF name
      int tf_x = (int)ObjectGetInteger(0, rect_name, OBJPROP_XDISTANCE);  //--- Get TF x
      int tf_y = (int)ObjectGetInteger(0, rect_name, OBJPROP_YDISTANCE);  //--- Get TF y
      bool is_hovered = (mouse_x >= tf_x && mouse_x <= tf_x + WIDTH_TF_CELL && mouse_y >= tf_y && mouse_y <= tf_y + HEIGHT_TF_CELL); //--- Check hover
      if (is_hovered != prev_tf_hovered[i]) {                             //--- Check state change
         color bg = is_hovered ? clrDodgerBlue : (i == current_tf_index ? ColorStrongPositiveBg : (is_light_theme ? clrSilver : C'60,60,60')); //--- Set background
         ObjectSetInteger(0, rect_name, OBJPROP_BGCOLOR, bg);             //--- Update background
         prev_tf_hovered[i] = is_hovered;                                 //--- Update previous state
         ChartRedraw(0);                                                  //--- Redraw chart
      }
   }
}

//+------------------------------------------------------------------+
//| Update header hover state                                        |
//+------------------------------------------------------------------+
void update_header_hover_state(int mouse_x, int mouse_y) {
   int header_x = (int)ObjectGetInteger(0, HEADER_PANEL, OBJPROP_XDISTANCE);  //--- Get header x
   int header_y = (int)ObjectGetInteger(0, HEADER_PANEL, OBJPROP_YDISTANCE);  //--- Get header y
   int header_width = (int)ObjectGetInteger(0, HEADER_PANEL, OBJPROP_XSIZE);  //--- Get header width
   int header_height = (int)ObjectGetInteger(0, HEADER_PANEL, OBJPROP_YSIZE); //--- Get header height
   int header_left = header_x;                                                //--- Set left edge
   int header_right = header_left + header_width;                             //--- Set right edge

   int half_size = BUTTON_HOVER_SIZE / 2;                                   //--- Compute half hover size
   int close_x = (int)ObjectGetInteger(0, CLOSE_BUTTON, OBJPROP_XDISTANCE); //--- Get close x
   int close_y = (int)ObjectGetInteger(0, CLOSE_BUTTON, OBJPROP_YDISTANCE); //--- Get close y
   bool in_close_area = (mouse_x >= close_x - half_size && mouse_x <= close_x + half_size && mouse_y >= close_y - half_size && mouse_y <= close_y + half_size); //--- Check close area

   int toggle_x = (int)ObjectGetInteger(0, TOGGLE_BUTTON, OBJPROP_XDISTANCE); //--- Get toggle x
   int toggle_y = (int)ObjectGetInteger(0, TOGGLE_BUTTON, OBJPROP_YDISTANCE); //--- Get toggle y
   bool in_toggle_area = (mouse_x >= toggle_x - half_size && mouse_x <= toggle_x + half_size && mouse_y >= toggle_y - half_size && mouse_y <= toggle_y + half_size); //--- Check toggle area

   int heatmap_x = (int)ObjectGetInteger(0, HEATMAP_BUTTON, OBJPROP_XDISTANCE); //--- Get heatmap x
   int heatmap_y = (int)ObjectGetInteger(0, HEATMAP_BUTTON, OBJPROP_YDISTANCE); //--- Get heatmap y
   bool in_heatmap_area = (mouse_x >= heatmap_x - half_size && mouse_x <= heatmap_x + half_size && mouse_y >= heatmap_y - half_size && mouse_y <= heatmap_y + half_size); //--- Check heatmap area

   int pval_x = (int)ObjectGetInteger(0, PVAL_BUTTON, OBJPROP_XDISTANCE);     //--- Get pval x
   int pval_y = (int)ObjectGetInteger(0, PVAL_BUTTON, OBJPROP_YDISTANCE);     //--- Get pval y
   bool in_pval_area = (mouse_x >= pval_x - half_size && mouse_x <= pval_x + half_size && mouse_y >= pval_y - half_size && mouse_y <= pval_y + half_size); //--- Check pval area

   int sort_x = (int)ObjectGetInteger(0, SORT_BUTTON, OBJPROP_XDISTANCE);     //--- Get sort x
   int sort_y = (int)ObjectGetInteger(0, SORT_BUTTON, OBJPROP_YDISTANCE);     //--- Get sort y
   bool in_sort_area = (mouse_x >= sort_x - half_size && mouse_x <= sort_x + half_size && mouse_y >= sort_y - half_size && mouse_y <= sort_y + half_size); //--- Check sort area

   int theme_x = (int)ObjectGetInteger(0, THEME_BUTTON, OBJPROP_XDISTANCE);   //--- Get theme x
   int theme_y = (int)ObjectGetInteger(0, THEME_BUTTON, OBJPROP_YDISTANCE);   //--- Get theme y
   bool in_theme_area = (mouse_x >= theme_x - half_size && mouse_x <= theme_x + half_size && mouse_y >= theme_y - half_size && mouse_y <= theme_y + half_size); //--- Check theme area

   bool is_header_hovered = (mouse_x >= header_left && mouse_x <= header_right && mouse_y >= header_y && mouse_y <= header_y + header_height &&
                             !in_close_area && !in_toggle_area && !in_heatmap_area && !in_pval_area && !in_sort_area && !in_theme_area); //--- Check header hover

   color header_bg = is_light_theme ? clrSilver : C'60,60,60';                //--- Set header background
   if (is_header_hovered != prev_header_hovered && !panel_dragging) {         //--- Check state change
      ObjectSetInteger(0, HEADER_PANEL, OBJPROP_BGCOLOR, is_header_hovered ? clrRed : header_bg); //--- Update header color
      prev_header_hovered = is_header_hovered;                                //--- Update previous state
      ChartRedraw(0);                                                         //--- Redraw chart
   }

   update_button_hover_states(mouse_x, mouse_y);                              //--- Update button hovers
}

Здесь мы реализуем функцию "is_cursor_in_header_or_buttons", которая определяет, находится ли курсор мыши над панелью заголовка или какими-либо интерактивными элементами, такими как кнопки или ячеек таймфреймов, возвращая логическое значение. Она извлекает значение ширины графика с помощью ChartGetInteger, используя CHART_WIDTH_IN_PIXELS, и свойств заголовка, таких как x, y, ширина и высота, с помощью ObjectGetInteger с помощью "OBJPROP_XDISTANCE", "OBJPROP_YDISTANCE", "OBJPROP_XSIZE" и "OBJPROP_YSIZE". Рассчитываем левый и правый края заголовка и проверяем, находятся ли координаты мыши в пределах границ заголовка.

Для кнопок мы вычисляем половину "BUTTON_HOVER_SIZE", аналогично получаем положение каждой кнопки по осям x и y и проверяем, находится ли мышь внутри областей наведения на них для закрытия, переключения, тепловой карты, pval, сортировки и темы. Для ячеек таймфреймов мы инициализируем флаг значением false и перебираем циклом "num_tf_visible", получая позицию каждого прямоугольника с именем из префикса и индекса "TF_CELL_RECT", проверяя, находится ли курсор мыши в пределах его ширины и высоты, устанавливая флаг в значение true и прерывая цикл, если это так. Мы возвращаем значение true, если находится в заголовке, любой кнопке или области таймфрейма.

Далее создаём функцию "update_button_hover_states", которая обновляет визуальную обратную связь для наведения курсора на кнопку и временной интервал на основе положения мыши. Вычисляем половину размера при наведении курсора, устанавливаем фон при наведении на яркий оттенок лазурного цвета (dodger blue), а также обычные цвета для кнопок, темы оформления и закрытия, с соответствующим оформлением. Для каждой кнопки мы получаем позиции, проверяем состояние при наведении курсора, как описано выше, и если состояние отличается от "prev_close_hovered" или аналогичного, обновляем цвет объекта на "hover" или "normal", а фон на "hover" или "none" с помощью ObjectSetInteger,  используя "OBJPROP_COLOR" и OBJPROP_BGCOLOR, устанавливаем предыдущее состояние и перерисовываем. Для временных интервалов мы перебираем видимые ячейки, формируем имена, получаем позиции, проверяем, находится ли точка при наведении курсора в пределах заданных границ, и если она изменяется по сравнению с массивом "prev_tf_hovered", устанавливаем фон в яркий оттенок лазурного цвета (dodger blue) при наведении курсора или в ярко-положительный (strong positive), если выбрано, в противном случае — в обычный цвет темы оформления, обновляем данные с помощью "ObjectSetInteger", устанавливаем предыдущее состояние и перерисовываем.

Затем определяем функцию "update_header_hover_state" для управления визуальными эффектами при наведении курсора на заголовок, за исключением областей кнопок. Мы получаем позиции и размеры заголовков, как и раньше, вычисляем половину размера при наведении курсора и проверяем отдельные области кнопок на предмет закрытия, переключения, тепловой карты, pval, сортировки и темы. Мы определяем, наведен ли курсор на заголовок, но не в какой-либо области кнопок, и если состояние отличается от "prev_header_hovered" и не выполняется перетаскивание, обновляем фон заголовка на красный при наведении курсора или на обычную тему оформления с помощью "ObjectSetInteger" и "OBJPROP_BGCOLOR", устанавливаем предыдущее состояние и выполняем перерисовку. Наконец, мы вызываем функцию "update_button_hover_states" для одновременной обработки наведения курсора на кнопку. Теперь мы будем использовать эти функции в обработчике событий взаимодействия с графиком. Однако для обработки перетаскивания при движении мыши нам потребуется еще одна вспомогательная функция, которая будет динамически обновлять положение элементов панели вместе с движением мыши.

//+------------------------------------------------------------------+
//| Update panel object positions                                    |
//+------------------------------------------------------------------+
void update_panel_positions() {
   ObjectSetInteger(0, HEADER_PANEL, OBJPROP_XDISTANCE, panel_x);           //--- Update header x
   ObjectSetInteger(0, HEADER_PANEL, OBJPROP_YDISTANCE, panel_y);           //--- Update header y
   int panel_width = WIDTH_SYMBOL + num_symbols * (WIDTH_CELL - 1) + 4;     //--- Compute width
   ObjectSetInteger(0, HEADER_PANEL, OBJPROP_XSIZE, panel_width); //--- Update header size
   ObjectSetInteger(0, HEADER_PANEL_ICON, OBJPROP_XDISTANCE, panel_x + 12); //--- Update icon x
   ObjectSetInteger(0, HEADER_PANEL_ICON, OBJPROP_YDISTANCE, panel_y + 14); //--- Update icon y
   ObjectSetInteger(0, HEADER_PANEL_TEXT, OBJPROP_XDISTANCE, panel_x + 105); //--- Update text x
   ObjectSetInteger(0, HEADER_PANEL_TEXT, OBJPROP_YDISTANCE, panel_y + 12); //--- Update text y
   ObjectSetInteger(0, CLOSE_BUTTON, OBJPROP_XDISTANCE, panel_x + (panel_width - 17)); //--- Update close x
   ObjectSetInteger(0, CLOSE_BUTTON, OBJPROP_YDISTANCE, panel_y + 14);      //--- Update close y
   ObjectSetInteger(0, TOGGLE_BUTTON, OBJPROP_XDISTANCE, panel_x + (panel_width - 47)); //--- Update toggle x
   ObjectSetInteger(0, TOGGLE_BUTTON, OBJPROP_YDISTANCE, panel_y + 14);     //--- Update toggle y
   ObjectSetInteger(0, HEATMAP_BUTTON, OBJPROP_XDISTANCE, panel_x + (panel_width - 77)); //--- Update heatmap x
   ObjectSetInteger(0, HEATMAP_BUTTON, OBJPROP_YDISTANCE, panel_y + 14);    //--- Update heatmap y
   ObjectSetInteger(0, PVAL_BUTTON, OBJPROP_XDISTANCE, panel_x + (panel_width - 107)); //--- Update pval x
   ObjectSetInteger(0, PVAL_BUTTON, OBJPROP_YDISTANCE, panel_y + 14);       //--- Update pval y
   ObjectSetInteger(0, SORT_BUTTON, OBJPROP_XDISTANCE, panel_x + (panel_width - 137)); //--- Update sort x
   ObjectSetInteger(0, SORT_BUTTON, OBJPROP_YDISTANCE, panel_y + 14);       //--- Update sort y
   ObjectSetInteger(0, THEME_BUTTON, OBJPROP_XDISTANCE, panel_x + (panel_width - 167)); //--- Update theme x
   ObjectSetInteger(0, THEME_BUTTON, OBJPROP_YDISTANCE, panel_y + 14);      //--- Update theme y

   if (!panel_minimized) {                                                  //--- Check if maximized
      int panel_height = HEIGHT_HEADER + HEIGHT_TF_CELL + GAP_HEIGHT + HEIGHT_RECTANGLE * (num_symbols + 1) - num_symbols + 2; //--- Compute height
      
      ObjectSetInteger(0, MAIN_PANEL, OBJPROP_XDISTANCE, panel_x);          //--- Update main x
      ObjectSetInteger(0, MAIN_PANEL, OBJPROP_YDISTANCE, panel_y);          //--- Update main y
      ObjectSetInteger(0, MAIN_PANEL, OBJPROP_XSIZE, panel_width);          //--- Update main width
      ObjectSetInteger(0, MAIN_PANEL, OBJPROP_YSIZE, panel_height);         //--- Update main height

      int tf_y = panel_y + HEIGHT_HEADER;                                  //--- Compute TF y
      int tf_x_start = panel_x + 2;                                        //--- Set TF start x
      for (int i = 0; i < num_tf_visible; i++) {                           //--- Loop TFs
         int x_offset = tf_x_start + i * WIDTH_TF_CELL;                    //--- Compute offset
         string rect_name = TF_CELL_RECT + IntegerToString(i);             //--- Get rectangle name
         string text_name = TF_CELL_TEXT + IntegerToString(i);             //--- Get text name
         ObjectSetInteger(0, rect_name, OBJPROP_XDISTANCE, x_offset);      //--- Update TF rect x
         ObjectSetInteger(0, rect_name, OBJPROP_YDISTANCE, tf_y);          //--- Update TF rect y
         ObjectSetInteger(0, text_name, OBJPROP_XDISTANCE, x_offset + (WIDTH_TF_CELL / 2)); //--- Update TF text x
         ObjectSetInteger(0, text_name, OBJPROP_YDISTANCE, tf_y + (HEIGHT_TF_CELL / 2));    //--- Update TF text y
      }

      int matrix_y = tf_y + HEIGHT_TF_CELL + GAP_HEIGHT;                   //--- Compute matrix y
      ObjectSetInteger(0, "SYMBOL_ROW_HEADER", OBJPROP_XDISTANCE, panel_x + 2); //--- Update row header x
      ObjectSetInteger(0, "SYMBOL_ROW_HEADER", OBJPROP_YDISTANCE, matrix_y); //--- Update row header y
      ObjectSetInteger(0, "SYMBOL_ROW_HEADER_TEXT", OBJPROP_XDISTANCE, panel_x + (WIDTH_SYMBOL / 2 + 2)); //--- Update row header text x
      ObjectSetInteger(0, "SYMBOL_ROW_HEADER_TEXT", OBJPROP_YDISTANCE, matrix_y + (HEIGHT_RECTANGLE / 2)); //--- Update row header text y

      for (int i = 0; i < num_symbols; i++) {                              //--- Loop rows
         int y_offset = matrix_y + HEIGHT_RECTANGLE * (i + 1) - (1 + i);   //--- Compute y offset
         ObjectSetInteger(0, SYMBOL_ROW_RECTANGLE + IntegerToString(i), OBJPROP_XDISTANCE, panel_x + 2); //--- Update row rect x
         ObjectSetInteger(0, SYMBOL_ROW_RECTANGLE + IntegerToString(i), OBJPROP_YDISTANCE, y_offset); //--- Update row rect y
         ObjectSetInteger(0, SYMBOL_ROW_TEXT + IntegerToString(i), OBJPROP_XDISTANCE, panel_x + (WIDTH_SYMBOL / 2 + 2)); //--- Update row text x
         ObjectSetInteger(0, SYMBOL_ROW_TEXT + IntegerToString(i), OBJPROP_YDISTANCE, y_offset + (HEIGHT_RECTANGLE / 2 - 1)); //--- Update row text y

         int x_offset_col = panel_x + WIDTH_SYMBOL + i * WIDTH_CELL - i + 1; //--- Compute column x
         ObjectSetInteger(0, SYMBOL_COL_RECTANGLE + IntegerToString(i), OBJPROP_XDISTANCE, x_offset_col); //--- Update column rect x
         ObjectSetInteger(0, SYMBOL_COL_RECTANGLE + IntegerToString(i), OBJPROP_YDISTANCE, matrix_y); //--- Update column rect y
         ObjectSetInteger(0, SYMBOL_COL_TEXT + IntegerToString(i), OBJPROP_XDISTANCE, x_offset_col + (WIDTH_CELL / 2)); //--- Update column text x
         ObjectSetInteger(0, SYMBOL_COL_TEXT + IntegerToString(i), OBJPROP_YDISTANCE, matrix_y + (HEIGHT_RECTANGLE / 2)); //--- Update column text y

         for (int j = 0; j < num_symbols; j++) {                            //--- Loop columns
            string cell_name = CELL_RECTANGLE + IntegerToString(i) + "_" + IntegerToString(j); //--- Get cell name
            string text_name = CELL_TEXT + IntegerToString(i) + "_" + IntegerToString(j);      //--- Get text name
            int x_offset = panel_x + WIDTH_SYMBOL + j * WIDTH_CELL - j + 1; //--- Compute cell x
            ObjectSetInteger(0, cell_name, OBJPROP_XDISTANCE, x_offset);    //--- Update cell rect x
            ObjectSetInteger(0, cell_name, OBJPROP_YDISTANCE, y_offset);    //--- Update cell rect y
            ObjectSetInteger(0, text_name, OBJPROP_XDISTANCE, x_offset + (WIDTH_CELL / 2)); //--- Update cell text x
            ObjectSetInteger(0, text_name, OBJPROP_YDISTANCE, y_offset + (HEIGHT_RECTANGLE / 2 - 1)); //--- Update cell text y
         }
      }

      int legend_y = panel_y + panel_height + GAP_MAIN_LEGEND;               //--- Compute legend y
      ObjectSetInteger(0, LEGEND_PANEL, OBJPROP_XDISTANCE, panel_x);         //--- Update legend x
      ObjectSetInteger(0, LEGEND_PANEL, OBJPROP_YDISTANCE, legend_y);        //--- Update legend y
      ObjectSetInteger(0, LEGEND_PANEL, OBJPROP_XSIZE, panel_width);         //--- Update legend width
      ObjectSetInteger(0, LEGEND_PANEL, OBJPROP_YSIZE, HEIGHT_LEGEND_PANEL); //--- Update legend height

      int total_legend_width = num_legend_visible * WIDTH_LEGEND_CELL + (num_legend_visible - 1) * LEGEND_SPACING; //--- Compute legend width
      int x_start = panel_x + (panel_width - total_legend_width) / 2;        //--- Compute start x
      for (int i = 0; i < num_legend_visible; i++) {                         //--- Loop legends
         int x_offset = x_start + i * (WIDTH_LEGEND_CELL + LEGEND_SPACING);  //--- Compute offset
         string rect_name = LEGEND_CELL_RECTANGLE + IntegerToString(i);      //--- Get rectangle name
         string text_name = LEGEND_CELL_TEXT + IntegerToString(i);           //--- Get text name
         ObjectSetInteger(0, rect_name, OBJPROP_XDISTANCE, x_offset);        //--- Update legend rect x
         ObjectSetInteger(0, rect_name, OBJPROP_YDISTANCE, legend_y + 2);    //--- Update legend rect y
         ObjectSetInteger(0, text_name, OBJPROP_XDISTANCE, x_offset + WIDTH_LEGEND_CELL / 2); //--- Update legend text x
         ObjectSetInteger(0, text_name, OBJPROP_YDISTANCE, legend_y + 2 + HEIGHT_LEGEND / 2 - 1); //--- Update legend text y
      }
   }
   ChartRedraw(0);                                                           //--- Redraw chart
}

Здесь мы реализуем функцию "update_panel_positions" для корректировки положения всех объектов панели при перетаскивании или изменении размера панели, обеспечивая выравнивание всех элементов по текущим координатам "panel_x" и "panel_y". Мы обновляем значения x и y для панели заголовка и пересчитываем ее ширину для соответствия расположению символов и ячеек, соответствующим образом устанавливая значение параметра "OBJPROP_XSIZE". Затем перемещаем значок заголовка, текст и кнопки, такие как «Закрыть», «Переключить», «Тепловая карта», «pval», «Сортировка» и «Тема», вычисляя их относительное смещение по оси X от края панели и устанавливая OBJPROP_XDISTANCE и "OBJPROP_YDISTANCE" с помощью "ObjectSetInteger".

Если панель не свернута, вычисляем её полную высоту на основе констант и строк, а затем аналогичным образом обновляем положение, размер и габариты главной панели. Для строки с таймфреймами рассчитываем y и начальное x, затем в цикле перебираем видимые таймфреймы, чтобы обновить значения x и y для каждого прямоугольника и текстовой метки. Мы задаем значение матрицы по оси y, обновляем положение прямоугольника заголовка строки символов и текста. Для символов строк мы используем цикл для вычисления смещений по оси y и обновления координат по осям x и y прямоугольников и текста. Для символов столбцов мы поступаем аналогично со смещениями по оси x и фиксированными значениями по оси y.

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

//--- Add this as the last line in ontick

ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true);            //--- Enable mouse events

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

//+------------------------------------------------------------------+
//| Handle chart event                                               |
//+------------------------------------------------------------------+
void OnChartEvent(const int event_id, const long& long_param, const double& double_param, const string& string_param) {
   if (event_id == CHARTEVENT_OBJECT_CLICK) {                   //--- Handle click event
      if (string_param == CLOSE_BUTTON) {                       //--- Check close click
         Print("Closing the panel now");                        //--- Print closing
         PlaySound("alert.wav");                                //--- Play sound
         panel_is_visible = false;                              //--- Hide panel
         delete_all_objects();                                  //--- Delete objects
         ChartRedraw(0);                                        //--- Redraw chart
      } else if (string_param == TOGGLE_BUTTON) {               //--- Check toggle click
         delete_all_objects();                                  //--- Delete objects
         panel_minimized = !panel_minimized;                    //--- Toggle minimized
         if (panel_minimized) {                                 //--- Handle minimize
            Print("Minimizing the panel");                      //--- Print minimizing
            create_minimized_dashboard();                       //--- Create minimized
            update_borders();                                   //--- Update borders
         } else {                                               //--- Handle maximize
            Print("Maximizing the panel");                      //--- Print maximizing
            create_full_dashboard();                            //--- Create full
            update_borders();                                   //--- Update borders
            update_tf_highlights();                             //--- Update highlights
            update_dashboard();                                 //--- Update dashboard
         }
         prev_header_hovered = false;                           //--- Reset header hover
         prev_close_hovered = false;                            //--- Reset close hover
         prev_toggle_hovered = false;                           //--- Reset toggle hover
         prev_heatmap_hovered = false;                          //--- Reset heatmap hover
         prev_pval_hovered = false;                             //--- Reset pval hover
         prev_sort_hovered = false;                             //--- Reset sort hover
         prev_theme_hovered = false;                            //--- Reset theme hover
         ArrayInitialize(prev_tf_hovered, false);               //--- Reset TF hovers
         color header_bg = is_light_theme ? clrSilver : C'60,60,60';               //--- Set header background
         ObjectSetInteger(0, HEADER_PANEL, OBJPROP_BGCOLOR, header_bg);            //--- Update header color
         color button_text = is_light_theme ? clrNavy : clrGold;                   //--- Set button color
         color theme_icon_color = is_light_theme ? clrBlack : clrWhite;            //--- Set theme color
         color close_text = is_light_theme ? clrBlack : clrWhite;                  //--- Set close color
         color header_icon_color = is_light_theme ? clrDodgerBlue : clrAqua;       //--- Set icon color
         ObjectSetInteger(0, HEADER_PANEL_ICON, OBJPROP_COLOR, header_icon_color); //--- Update icon color
         ObjectSetInteger(0, CLOSE_BUTTON, OBJPROP_COLOR, close_text);             //--- Update close color
         ObjectSetInteger(0, CLOSE_BUTTON, OBJPROP_BGCOLOR, clrNONE);              //--- Reset close background
         ObjectSetInteger(0, TOGGLE_BUTTON, OBJPROP_COLOR, button_text);           //--- Update toggle color
         ObjectSetInteger(0, TOGGLE_BUTTON, OBJPROP_BGCOLOR, clrNONE);             //--- Reset toggle background
         if (!panel_minimized) {                                                   //--- Check if maximized
            ObjectSetInteger(0, HEATMAP_BUTTON, OBJPROP_COLOR, button_text);       //--- Update heatmap color
            ObjectSetInteger(0, HEATMAP_BUTTON, OBJPROP_BGCOLOR, clrNONE);         //--- Reset heatmap background
            ObjectSetInteger(0, PVAL_BUTTON, OBJPROP_COLOR, button_text);          //--- Update pval color
            ObjectSetInteger(0, PVAL_BUTTON, OBJPROP_BGCOLOR, clrNONE);            //--- Reset pval background
            ObjectSetInteger(0, SORT_BUTTON, OBJPROP_COLOR, button_text);          //--- Update sort color
            ObjectSetInteger(0, SORT_BUTTON, OBJPROP_BGCOLOR, clrNONE);            //--- Reset sort background
            ObjectSetInteger(0, THEME_BUTTON, OBJPROP_COLOR, theme_icon_color);    //--- Update theme color
            ObjectSetInteger(0, THEME_BUTTON, OBJPROP_BGCOLOR, clrNONE);           //--- Reset theme background
         }
         ChartRedraw(0);                                        //--- Redraw chart
      } else if (string_param == HEATMAP_BUTTON) {              //--- Check heatmap click
         global_display_mode = (global_display_mode == MODE_STANDARD ? MODE_HEATMAP : MODE_STANDARD); //--- Toggle mode
         string new_icon = CharToString(global_display_mode == MODE_STANDARD ? (uchar)82 : (uchar)110); //--- Set new icon
         ObjectSetString(0, HEATMAP_BUTTON, OBJPROP_TEXT, new_icon); //--- Update icon
         Print("Switching to ", (global_display_mode == MODE_HEATMAP ? "Heatmap" : "Standard"), " mode"); //--- Print switch
         recreate_legend();                                     //--- Recreate legend
         update_dashboard();                                    //--- Update dashboard
         ChartRedraw(0);                                        //--- Redraw chart
      } else if (string_param == PVAL_BUTTON) {                 //--- Check pval click
         global_view_mode = (global_view_mode == VIEW_CORR ? VIEW_PVAL : VIEW_CORR); //--- Toggle view
         Print("Switching to ", (global_view_mode == VIEW_PVAL ? "P-Value" : "Correlation"), " view"); //--- Print switch
         update_dashboard();                                    //--- Update dashboard
         ChartRedraw(0);                                        //--- Redraw chart
      } else if (string_param == SORT_BUTTON) {                 //--- Check sort click
         cycle_sort_mode();                                     //--- Cycle mode
         string new_sort_icon;                                  //--- Declare new icon
         if (sort_mode == 0) new_sort_icon = CharToString('N'); //--- Set neutral
         else if (sort_mode == 1) new_sort_icon = CharToString('K'); //--- Set descending
         else new_sort_icon = CharToString('J');                //--- Set ascending
         ObjectSetString(0, SORT_BUTTON, OBJPROP_TEXT, new_sort_icon); //--- Update icon
         delete_all_objects();                                  //--- Delete objects
         create_full_dashboard();                               //--- Create full
         update_borders();                                      //--- Update borders
         update_dashboard();                                    //--- Update dashboard
         ChartRedraw(0);                                        //--- Redraw chart
      } else if (string_param == THEME_BUTTON) {                //--- Check theme click
         toggle_theme();                                        //--- Toggle theme
         ChartRedraw(0);                                        //--- Redraw chart
      } else {                                                  //--- Handle other clicks
         for (int i = 0; i < num_tf_visible; i++) {             //--- Loop TFs
            if (string_param == TF_CELL_TEXT + IntegerToString(i)) { //--- Check TF click
               switch_timeframe(i);                             //--- Switch timeframe
               ChartRedraw(0);                                  //--- Redraw chart
               break;                                           //--- Exit loop
            }
         }
      }
   } else if (event_id == CHARTEVENT_MOUSE_MOVE && panel_is_visible) { //--- Handle mouse move
      int mouse_x = (int)long_param;                            //--- Get mouse x
      int mouse_y = (int)double_param;                          //--- Get mouse y
      int mouse_state = (int)string_param;                      //--- Get mouse state

      if (mouse_x == last_mouse_x && mouse_y == last_mouse_y && !panel_dragging) return; //--- Skip if unchanged
      last_mouse_x = mouse_x;                                   //--- Update last x
      last_mouse_y = mouse_y;                                   //--- Update last y

      update_header_hover_state(mouse_x, mouse_y);              //--- Update header hover

      int header_x = (int)ObjectGetInteger(0, HEADER_PANEL, OBJPROP_XDISTANCE);  //--- Get header x
      int header_y = (int)ObjectGetInteger(0, HEADER_PANEL, OBJPROP_YDISTANCE);  //--- Get header y
      int header_width = (int)ObjectGetInteger(0, HEADER_PANEL, OBJPROP_XSIZE);  //--- Get header width
      int header_height = (int)ObjectGetInteger(0, HEADER_PANEL, OBJPROP_YSIZE); //--- Get header height
      int header_left = header_x;                               //--- Set left edge
      int header_right = header_left + header_width;            //--- Set right edge

      int half_size = BUTTON_HOVER_SIZE / 2;                    //--- Compute half size
      int close_x = (int)ObjectGetInteger(0, CLOSE_BUTTON, OBJPROP_XDISTANCE); //--- Get close x
      int close_y = (int)ObjectGetInteger(0, CLOSE_BUTTON, OBJPROP_YDISTANCE); //--- Get close y
      bool in_close_area = (mouse_x >= close_x - half_size && mouse_x <= close_x + half_size && mouse_y >= close_y - half_size && mouse_y <= close_y + half_size); //--- Check close area

      int toggle_x = (int)ObjectGetInteger(0, TOGGLE_BUTTON, OBJPROP_XDISTANCE); //--- Get toggle x
      int toggle_y = (int)ObjectGetInteger(0, TOGGLE_BUTTON, OBJPROP_YDISTANCE); //--- Get toggle y
      bool in_toggle_area = (mouse_x >= toggle_x - half_size && mouse_x <= toggle_x + half_size && mouse_y >= toggle_y - half_size && mouse_y <= toggle_y + half_size); //--- Check toggle area

      int heatmap_x = (int)ObjectGetInteger(0, HEATMAP_BUTTON, OBJPROP_XDISTANCE); //--- Get heatmap x
      int heatmap_y = (int)ObjectGetInteger(0, HEATMAP_BUTTON, OBJPROP_YDISTANCE); //--- Get heatmap y
      bool in_heatmap_area = (mouse_x >= heatmap_x - half_size && mouse_x <= heatmap_x + half_size && mouse_y >= heatmap_y - half_size && mouse_y <= heatmap_y + half_size); //--- Check heatmap area

      int pval_x = (int)ObjectGetInteger(0, PVAL_BUTTON, OBJPROP_XDISTANCE); //--- Get pval x
      int pval_y = (int)ObjectGetInteger(0, PVAL_BUTTON, OBJPROP_YDISTANCE); //--- Get pval y
      bool in_pval_area = (mouse_x >= pval_x - half_size && mouse_x <= pval_x + half_size && mouse_y >= pval_y - half_size && mouse_y <= pval_y + half_size); //--- Check pval area

      int sort_x = (int)ObjectGetInteger(0, SORT_BUTTON, OBJPROP_XDISTANCE); //--- Get sort x
      int sort_y = (int)ObjectGetInteger(0, SORT_BUTTON, OBJPROP_YDISTANCE); //--- Get sort y
      bool in_sort_area = (mouse_x >= sort_x - half_size && mouse_x <= sort_x + half_size && mouse_y >= sort_y - half_size && mouse_y <= sort_y + half_size); //--- Check sort area

      int theme_x = (int)ObjectGetInteger(0, THEME_BUTTON, OBJPROP_XDISTANCE); //--- Get theme x
      int theme_y = (int)ObjectGetInteger(0, THEME_BUTTON, OBJPROP_YDISTANCE); //--- Get theme y
      bool in_theme_area = (mouse_x >= theme_x - half_size && mouse_x <= theme_x + half_size && mouse_y >= theme_y - half_size && mouse_y <= theme_y + half_size); //--- Check theme area

      if (prev_mouse_state == 0 && mouse_state == 1) {          //--- Check drag start
         if (mouse_x >= header_left && mouse_x <= header_right && mouse_y >= header_y && mouse_y <= header_y + header_height &&
             !in_close_area && !in_toggle_area && !in_heatmap_area && !in_pval_area && !in_sort_area && !in_theme_area) { //--- Check draggable area
            panel_dragging = true;                              //--- Start dragging
            panel_drag_x = mouse_x;                             //--- Set drag x
            panel_drag_y = mouse_y;                             //--- Set drag y
            panel_start_x = header_x;                           //--- Set start x
            panel_start_y = header_y;                           //--- Set start y
            ObjectSetInteger(0, HEADER_PANEL, OBJPROP_BGCOLOR, clrMediumBlue); //--- Set drag color
            ChartSetInteger(0, CHART_MOUSE_SCROLL, false);      //--- Disable scroll
         }
      }

      if (panel_dragging && mouse_state == 1) {                 //--- Handle dragging
         int dx = mouse_x - panel_drag_x;                       //--- Compute x delta
         int dy = mouse_y - panel_drag_y;                       //--- Compute y delta
         panel_x = panel_start_x + dx;                          //--- Update panel x
         panel_y = panel_start_y + dy;                          //--- Update panel y

         int chart_width = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS);    //--- Get chart width
         int chart_height = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS);  //--- Get chart height
         int panel_width = WIDTH_SYMBOL + num_symbols * (WIDTH_CELL - 1) + 4; //--- Compute panel width
         int panel_height = HEIGHT_HEADER + HEIGHT_TF_CELL + GAP_HEIGHT + HEIGHT_RECTANGLE * (num_symbols + 1) - num_symbols + 2; //--- Compute height
         int total_height = panel_height + GAP_MAIN_LEGEND + HEIGHT_LEGEND_PANEL; //--- Compute total height
         
         panel_x = MathMax(0, MathMin(chart_width - panel_width, panel_x)); //--- Clamp x
         panel_y = MathMax(0, MathMin(chart_height - (panel_minimized ? HEIGHT_HEADER : total_height), panel_y)); //--- Clamp y

         update_panel_positions();                              //--- Update positions
         ChartRedraw(0);                                        //--- Redraw chart
      }

      if (mouse_state == 0 && prev_mouse_state == 1) {          //--- Check drag end
         if (panel_dragging) {                                  //--- Check was dragging
            panel_dragging = false;                             //--- Stop dragging
            update_header_hover_state(mouse_x, mouse_y);        //--- Update hover
            ChartSetInteger(0, CHART_MOUSE_SCROLL, true);       //--- Enable scroll
            ChartRedraw(0);                                     //--- Redraw chart
         }
      }

      prev_mouse_state = mouse_state;                           //--- Update previous state
   }
}

В обработчике OnChartEvent для CHARTEVENT_OBJECT_CLICK мы проверяем параметр "string_param" на соответствие именам кнопок: если "CLOSE_BUTTON", выводим сообщение, воспроизводим звуковое оповещение с помощью PlaySound, устанавливаем "panel_is_visible" в значение false, вызываем "delete_all_objects" и перерисовываем панель с помощью "ChartRedraw"; если "TOGGLE_BUTTON", мы удаляем объекты, переключаем "panel_minimized", и если панель сворачивается, выводим сообщение, создаем свернутую панель, обновляем границы; в противном случае, если панель разворачивается, выводим сообщение, создаем полную панель, обновляем границы, подсветку и саму панель. Мы сбрасываем все предыдущие состояния при наведении курсора на false, включая массив с ArrayInitialize для таймфреймов, устанавливаем фон заголовка в соответствии с темой оформления и применяем его к панели заголовка, обновляем цвета для значка, кнопки закрытия, кнопки переключения и, если она не свернута, для кнопок тепловой карты, pval, сортировки и темы, сбрасывая их фон на none, а затем перерисовываем.

Для CHARTEVENT_MOUSE_MOVE, если "panel_is_visible" равно true, мы приводим "long_param" к значению x мыши, "double_param" к значению y, а "string_param" - к значению state. Если координаты совпадают с "last_mouse_x" и "last_mouse_y" и перетаскивание не происходит, мы преждевременно завершаем работу; в противном случае обновляем последние позиции, вызываем функцию "update_header_hover_state" с указанием x и y. Мы получаем свойства заголовка для определения границ и позиций кнопок, вычисляем половину размера при наведении курсора, проверяем области на предмет закрытия, переключения, тепловой карты, pval, сортировки и темы. Если предыдущее состояние было равно нулю, а текущее равно единице, и мышь находится в области заголовка, но не в области кнопок, начнем перетаскивание, установив значение "panel_dragging" true, сохраним координаты перетаскивания и начала, установим фон заголовка на сине-голубой (medium blue) и отключим прокрутку графика мышью.

Если перетаскивание и state равны единице, вычисляем дельты, обновляем "panel_x" и "panel_y" из start плюс дельты, получаем размеры графика с помощью ChartGetInteger для ширины и высоты в пикселях, вычисляем размеры панели, общую высоту, включая легенду, и зафиксируем "panel_x" и "panel_y" с помощью MathMax и MathMin, чтобы оставаться в пределах графика за вычетом размера панели или заголовка, если она свернута, затем вызываем "update_panel_positions" и перерисовываем. Если state равно нулю, а предыдущее было единицей, и было перетаскивание, остановим "panel_dragging", вызовем "update_header_hover_state", включим прокрутку и перерисуем. Наконец, обновим "prev_mouse_state" до текущего состояния. После этого нам нужно постоянно обновлять панель, поскольку нам не нужно вызывать обновления, если панель закрыта или свернута. В этом нет никакой пользы. Итак, в обработчике тиковых событий мы используем следующую логику.

//+------------------------------------------------------------------+
//| Handle tick event                                                |
//+------------------------------------------------------------------+
void OnTick() {
   if (panel_is_visible && !panel_minimized) {                  //--- Check if update needed
      update_dashboard();                                       //--- Update on tick
   }
}

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

//+------------------------------------------------------------------+
//| Deinitialize expert                                              |
//+------------------------------------------------------------------+
void OnDeinit(const int reason) {
   delete_all_objects();                                        //--- Delete objects
   ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, false);           //--- Disable mouse events
   ChartRedraw(0);                                              //--- Redraw chart
}

В обработчике OnDeinit добавляем логику для очистки ресурсов при удалении или деинициализации программы. Он вызывает "delete_all_objects", чтобы удалить все графические элементы из графика, отключает события перемещения мыши на графике с помощью ChartSetInteger, используя для параметра CHART_EVENT_MOUSE_MOVE значение false, и перерисовывает график с помощью ChartRedraw, чтобы отразить изменения. После компиляции получаем следующий результат.

COMPLETE TEST GIF

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


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

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

CORRELATION MATRIX BACKTEST GIF


Заключение

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

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

Прикрепленные файлы |
Последние комментарии | Перейти к обсуждению на форуме трейдеров (4)
Criistian Moore'
Criistian Moore' | 25 янв. 2026 в 03:01
Вы не знаете, почему символы кнопок не отображаются, а только маленькие прямоугольники?
Allan Munene Mutiiria
Allan Munene Mutiiria | 25 янв. 2026 в 14:44
Criistian Moore' #:
есть ли у вас идеи, почему символы кнопок не отображаются, они показывают только маленькие прямоугольники
Шрифты еще не доступны на вашем компьютере
Vitaly Muzichenko
Vitaly Muzichenko | 25 мар. 2026 в 13:21
Criistian Moore' #:
Вы не знаете, почему символы кнопок не отображаются, а только маленькие прямоугольники?
Обновите терминал до последней версии, должно работать
Юрий Елагин
Юрий Елагин | 27 мар. 2026 в 22:41
Решил написать только потому, что очень похожую таблицу я сгенерировал с помощью ИИ Claude, в моей версии при наведении курсора на пару инструментов и нажатии enter в дополнительном окне появлялся график корреляции за последние N-свечей которые пользователь мог задать сам. Но некоторые нюансы таблицы в вашей версии мне понравились больше. Если вы добавите прорисовку графика для возможности анализа корреляции пар на истории то у вас получится пушка.
Создание и тестирование совета из 15 моделей в MetaTrader 5 Создание и тестирование совета из 15 моделей в MetaTrader 5
Статья описывает переход от дебатов четырёх голосов к Council of 15: десять аналитиков, четыре независимых риск-менеджера и Председатель с жёстким регламентом голосования. Разобраны роли участников, трёхфазная архитектура и параллельное исполнение полного цикла за 10–15 секунд. Показаны журнал работы, правила риск-гейта и обратная совместимость, чтобы вы быстро подключили систему к советнику.
Архитектура системы машинного обучения в MetaTrader 5 (Часть 3): Метод разметки сканированием тренда Архитектура системы машинного обучения в MetaTrader 5 (Часть 3): Метод разметки сканированием тренда
Мы создали надежный конвейер разработки признаков на основе тиковых баров, чтобы исключить утечку данных, и решили критическую проблему разметки с помощью метода тройных барьеров и мета-разметки. В этой части рассматривается продвинутая техника разметки — сканирование тренда — для адаптивных горизонтов. После изложения теории будет показан пример использования меток сканирования тренда в сочетании с мета-разметкой для улучшения классической стратегии на основе пересечения скользящих средних.
Роевой оптимизатор с иерархией суброев — Flock by Leader Роевой оптимизатор с иерархией суброев — Flock by Leader
Мы строим и реализуем в MQL5 алгоритм Flock by Leader: суброи формируются по метрике ARF, лидер определяется по лучшему личному рекорду, а не по положению центроида. Приводим формулы обновления для ролей роя и механизм separation. Класс C_AO_FBL совместим с тестовым стендом и проверен на функциях Hilly, Forest и Megacity в размерностях 10–1000 координат, что упрощает воспроизведение и сравнение.
Возможности Мастера MQL5, которые вам нужно знать (Часть 75): Использование Awesome Oscillator и конвертов Возможности Мастера MQL5, которые вам нужно знать (Часть 75): Использование Awesome Oscillator и конвертов
Инструмент Awesome Oscillator от Билла Уильямса и канал конвертов (Envelopes Channel) — это сочетание, которое можно использовать взаимодополняющим образом в составе советника MQL5. Мы используем Awesome Oscillator за его способность выявлять тренды, а канал конвертов используется для определения уровней поддержки/сопротивления. Как обычно, мы используем Мастер MQL5 для построения паттернов и тестирования потенциала, который может иметь эта пара индикаторов.