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

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

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

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

Заключение
В заключение отметим, что мы улучшили панель корреляционной матрицы в MQL5, добавив в нее интерактивные признаки, включая перетаскивание панели и сворачивание с помощью событий мыши, эффекты при наведении курсора мыши на кнопку для визуальной обратной связи, сортировку символов по степени корреляции в режимах возрастания / убывания, переключение между представлениями корреляции и p-значения, переключение светлой / темной темы с динамическим обновлением цветов и всплывающие подсказки для ячеек для получения подробной информации. Теперь система поддерживает управляемые событиями реакции для удобства использования, включая обнаружение при наведении курсора, фиксацию при перетаскивании, чтобы оставаться в пределах графика, и эффективные обновления для поддержания эффективности. С помощью этой интерактивной панели корреляционной матрицы вы сможете более динамично анализировать взаимозависимости активов и будете готовы к дальнейшей оптимизации вашего торгового процесса. Удачной торговли!
Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/20962
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.
Создание и тестирование совета из 15 моделей в MetaTrader 5
Архитектура системы машинного обучения в MetaTrader 5 (Часть 3): Метод разметки сканированием тренда
Роевой оптимизатор с иерархией суброев — Flock by Leader
Возможности Мастера MQL5, которые вам нужно знать (Часть 75): Использование Awesome Oscillator и конвертов
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования
есть ли у вас идеи, почему символы кнопок не отображаются, они показывают только маленькие прямоугольники
Вы не знаете, почему символы кнопок не отображаются, а только маленькие прямоугольники?