English 中文 Deutsch 日本語
preview
Торговые инструменты MQL5 (Часть 4):  Улучшаем панель мультитаймфреймового сканера — динамическое позиционирование и сворачивание/разворачивание

Торговые инструменты MQL5 (Часть 4): Улучшаем панель мультитаймфреймового сканера — динамическое позиционирование и сворачивание/разворачивание

MetaTrader 5Трейдинг |
260 2
Allan Munene Mutiiria
Allan Munene Mutiiria

Введение

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

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

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


Понимание архитектуры динамического позиционирования и переключения

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

POSITIONING & DRAG STATES ARCHITECTURE


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

Чтобы реализовать усовершенствования на MQL5, мы определим дополнительный объект toggle button, который будем использовать позже для переключения между развернутым и свернутым состояниями, поскольку наведение курсора мыши и перетаскивание будут выполняться на объекте header, который у нас уже есть в реализации.

// Define identifiers and properties for UI elements
#define MAIN_PANEL              "PANEL_MAIN"                     //--- Main panel rectangle identifier

//--- THE REST OF THE EXISTING OBJECTS

#define TOGGLE_BUTTON           "BUTTON_TOGGLE"                  //--- Toggle (minimize/maximize) button identifier

//--- THE REST OF THE EXISTING OBJECTS

#define COLOR_DARK_GRAY         C'105,105,105'                   //--- Dark gray color for indicator backgrounds

Начинаем усовершенствование нашей панели сканера по нескольким таймфреймам с обновления определений элементов пользовательского интерфейса (UI), включив в него новый идентификатор для кнопки переключения, что соответствует нашей цели по добавлению функциональности сворачивания/разворачивания. Сохраняем существующие определения, чтобы сохранить основную структуру панели. Ключевым изменением является добавление идентификатора "TOGGLE_BUTTON", определенного как "BUTTON_TOGGLE", который позволит создать кнопку для переключения панели между свернутым и развернутым состояниями.

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

bool panel_minimized = false;                                    //--- Flag to control minimized state
int panel_x = 632, panel_y = 40;                                 //--- Panel position coordinates
bool panel_dragging = false;                                     //--- Flag to track if panel is being dragged
int panel_drag_x = 0, panel_drag_y = 0;                          //--- Mouse coordinates when drag starts
int panel_start_x = 0, panel_start_y = 0;                        //--- Panel coordinates when drag starts
int prev_mouse_state = 0;                                        //--- Variable to track previous mouse state
bool header_hovered = false;                                     //--- Header hover state
bool toggle_hovered = false;                                     //--- Toggle button hover state
bool close_hovered = false;                                      //--- Close button hover state
int last_mouse_x = 0, last_mouse_y = 0;                          //--- Track last mouse position for optimization
bool prev_header_hovered = false;                                //--- Track previous header hover state
bool prev_toggle_hovered = false;                                //--- Track previous toggle hover state
bool prev_close_hovered = false;                                 //--- Track previous close button hover state

Чтобы помочь отслеживать состояния панели, мы добавили несколько глобальных переменных. Вводим "panel_minimized" для отслеживания свернутого состояния, "panel_x" и "panel_y" для определения положения панели и "panel_dragging", "panel_drag_x", "panel_drag_y", "panel_start_x" и "panel_start_y" для перетаскивания. Также добавляем "prev_mouse_state", "header_hovered", "toggle_hovered", "close_hovered", "last_mouse_x", "last_mouse_y", "prev_header_hovered", "prev_toggle_hovered" и "prev_close_hovered" для управления событиями мыши и состояниями наведения курсора. Это позволит создать интерактивную панель с возможностью перетаскивания и поддержкой переключения.

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

//+------------------------------------------------------------------+
//| Create full dashboard UI                                         |
//+------------------------------------------------------------------+
void create_full_dashboard() {
   create_rectangle(MAIN_PANEL, panel_x, panel_y, 617, 374, C'30,30,30', BORDER_FLAT); //--- Create main panel background
   create_rectangle(HEADER_PANEL, panel_x, panel_y, 617, 27, C'60,60,60', BORDER_FLAT); //--- Create header panel background
   create_label(HEADER_PANEL_ICON, CharToString(91), panel_x - 12, panel_y + 14, 18, clrAqua, "Wingdings"); //--- Create header icon
   create_label(HEADER_PANEL_TEXT, "TimeframeScanner", panel_x - 105, panel_y + 12, 13, COLOR_WHITE); //--- Create header title
   create_label(CLOSE_BUTTON, CharToString('r'), panel_x - 600, panel_y + 14, 18, clrYellow, "Webdings"); //--- Create close button
   create_label(TOGGLE_BUTTON, CharToString('r'), panel_x - 570, panel_y + 14, 18, clrYellow, "Wingdings"); //--- Create minimize button (-)

   // Create header rectangle and label
   create_rectangle(SYMBOL_RECTANGLE, panel_x - 2, panel_y + 35, WIDTH_TIMEFRAME, HEIGHT_RECTANGLE, clrGray); //--- Create symbol rectangle
   create_label(SYMBOL_TEXT, _Symbol, panel_x - 47, panel_y + 45, 11, COLOR_WHITE); //--- Create symbol label
   
   // Create summary and indicator headers (rectangles and labels)
   string header_names[] = {"BUY", "SELL", "RSI", "STOCH", "CCI", "ADX", "AO"}; //--- Define header titles
   for(int header_index = 0; header_index < ArraySize(header_names); header_index++) { //--- Loop through headers
      int x_offset = panel_x - WIDTH_TIMEFRAME - (header_index < 2 ? header_index * WIDTH_SIGNAL : 2 * WIDTH_SIGNAL + (header_index - 2) * WIDTH_INDICATOR) + (1 + header_index); //--- Calculate x position
      int width = (header_index < 2 ? WIDTH_SIGNAL : WIDTH_INDICATOR); //--- Set width based on header type
      create_rectangle(HEADER_RECTANGLE + IntegerToString(header_index), x_offset, panel_y + 35, width, HEIGHT_RECTANGLE, clrGray); //--- Create header rectangle
      create_label(HEADER_TEXT + IntegerToString(header_index), header_names[header_index], x_offset - width/2, panel_y + 45, 11, COLOR_WHITE); //--- Create header label
   }
   
   // Create timeframe rectangles and labels, and summary/indicator cells
   for(int timeframe_index = 0; timeframe_index < ArraySize(timeframes_array); timeframe_index++) { //--- Loop through timeframes
      // Highlight current timeframe
      color timeframe_background = (timeframes_array[timeframe_index] == _Period) ? clrLimeGreen : clrGray; //--- Set background color for current timeframe
      color timeframe_text_color = (timeframes_array[timeframe_index] == _Period) ? COLOR_BLACK : COLOR_WHITE; //--- Set text color for current timeframe
      
      create_rectangle(TIMEFRAME_RECTANGLE + IntegerToString(timeframe_index), panel_x - 2, (panel_y + 35 + HEIGHT_RECTANGLE) + timeframe_index * HEIGHT_RECTANGLE - (1 + timeframe_index), WIDTH_TIMEFRAME, HEIGHT_RECTANGLE, timeframe_background); //--- Create timeframe rectangle
      create_label(TIMEFRAME_TEXT + IntegerToString(timeframe_index), truncate_timeframe_name(timeframe_index), panel_x - 47, (panel_y + 45 + HEIGHT_RECTANGLE) + timeframe_index * HEIGHT_RECTANGLE - (1 + timeframe_index), 11, timeframe_text_color); //--- Create timeframe label
                  
      // Create summary and indicator cells
      for(int header_index = 0; header_index < ArraySize(header_names); header_index++) { //--- Loop through headers for cells
         string cell_rectangle_name, cell_text_name;              //--- Declare cell name and label variables
         color cell_background = (header_index < 2) ? COLOR_LIGHT_GRAY : COLOR_BLACK; //--- Set cell background color
         switch(header_index) {                                   //--- Select cell type
            case 0: cell_rectangle_name = BUY_RECTANGLE + IntegerToString(timeframe_index); cell_text_name = BUY_TEXT + IntegerToString(timeframe_index); break; //--- Buy cell
            case 1: cell_rectangle_name = SELL_RECTANGLE + IntegerToString(timeframe_index); cell_text_name = SELL_TEXT + IntegerToString(timeframe_index); break; //--- Sell cell
            case 2: cell_rectangle_name = RSI_RECTANGLE + IntegerToString(timeframe_index); cell_text_name = RSI_TEXT + IntegerToString(timeframe_index); break; //--- RSI cell
            case 3: cell_rectangle_name = STOCH_RECTANGLE + IntegerToString(timeframe_index); cell_text_name = STOCH_TEXT + IntegerToString(timeframe_index); break; //--- Stochastic cell
            case 4: cell_rectangle_name = CCI_RECTANGLE + IntegerToString(timeframe_index); cell_text_name = CCI_TEXT + IntegerToString(timeframe_index); break; //--- CCI cell
            case 5: cell_rectangle_name = ADX_RECTANGLE + IntegerToString(timeframe_index); cell_text_name = ADX_TEXT + IntegerToString(timeframe_index); break; //--- ADX cell
            case 6: cell_rectangle_name = AO_RECTANGLE + IntegerToString(timeframe_index); cell_text_name = AO_TEXT + IntegerToString(timeframe_index); break; //--- AO cell
         }
         int x_offset = panel_x - WIDTH_TIMEFRAME - (header_index < 2 ? header_index * WIDTH_SIGNAL : 2 * WIDTH_SIGNAL + (header_index - 2) * WIDTH_INDICATOR) + (1 + header_index); //--- Calculate x position
         int width = (header_index < 2 ? WIDTH_SIGNAL : WIDTH_INDICATOR); //--- Set width based on cell type
         create_rectangle(cell_rectangle_name, x_offset, (panel_y + 35 + HEIGHT_RECTANGLE) + timeframe_index * HEIGHT_RECTANGLE - (1 + timeframe_index), width, HEIGHT_RECTANGLE, cell_background); //--- Create cell rectangle
         create_label(cell_text_name, "-/-", x_offset - width/2, (panel_y + 45 + HEIGHT_RECTANGLE) + timeframe_index * HEIGHT_RECTANGLE - (1 + timeframe_index), 10, COLOR_WHITE); //--- Create cell label
      }
   }
   ChartRedraw(0);
}

Реализуем состояния панели, создав функцию "create_full_dashboard", переместив в нее статическую логику создания и обновив ее для поддержки функций динамического позиционирования и переключения. Основное изменение заключается в интеграции переменных "panel_x" и "panel_y" в позиционирование всех элементов панели, что позволяет размещать панель в любом месте графика. Мы создаем главную панель с помощью "create_rectangle", используя "MAIN_PANEL", "panel_x" и "panel_y" для позиции, сохраняя размер 617x374. Аналогично, мы размещаем панель заголовка, иконку и заголовок, используя "create_rectangle" и "create_label" для "HEADER_PANEL", "HEADER_PANEL_ICON" и "HEADER_PANEL_TEXT", корректируя их координаты по осям x и y относительно "panel_x" и "panel_y".

Добавляем новую "TOGGLE_BUTTON" с помощью "create_label", помещая ее в "panel_x - 570" и "panel_y + 14", отображая символ сворачивания ('r' в Webdings). Прямоугольник и метка инструмента ("SYMBOL_RECTANGLE" и "SYMBOL_TEXT"), а также прямоугольники и метки заголовка ("HEADER_RECTANGLE" и "HEADER_TEXT") используют "panel_x" и "panel_y" для смещения по осям x и y, гарантируя, что они перемещаются вместе с панелью. Для каждого таймфрейма в "timeframes_array" создаем прямоугольники и метки таймфреймов ("TIMEFRAME_RECTANGLE" и "TIMEFRAME_TEXT") и ячейки индикаторов ("RSI_RECTANGLE", "STOCH_RECTANGLE" и т.д.) с позициями, рассчитанными относительно "panel_x" и "panel_y", сохраняя расположение, но делая его доступным для перемещения. Вызываем функцию ChartRedraw для обновления дисплея. В отличие от фиксированных координат предыдущей версии (например, 632, 40), в данной функции используются динамические координаты, позволяющие перетаскивать панель, и добавляется кнопка переключения для сворачивания / разворачивания. У нас получится нечто похожее на приведенный ниже образец.

MAXIMIZED STATE

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

//+------------------------------------------------------------------+
//| Create minimized dashboard UI                                    |
//+------------------------------------------------------------------+
void create_minimized_dashboard() {
   create_rectangle(HEADER_PANEL, panel_x, panel_y, 617, 27, C'60,60,60', BORDER_FLAT); //--- Create header panel background
   create_label(HEADER_PANEL_ICON, CharToString(91), panel_x - 12, panel_y + 14, 18, clrAqua, "Wingdings"); //--- Create header icon
   create_label(HEADER_PANEL_TEXT, "TimeframeScanner", panel_x - 105, panel_y + 12, 13, COLOR_WHITE); //--- Create header title
   create_label(CLOSE_BUTTON, CharToString('r'), panel_x - 600, panel_y + 14, 18, clrYellow, "Webdings"); //--- Create close button
   create_label(TOGGLE_BUTTON, CharToString('o'), panel_x - 570, panel_y + 14, 18, clrYellow, "Wingdings"); //--- Create maximize button (+)
   ChartRedraw(0);
}

Чтобы поддерживать переключение на компактное представление, определяем функцию "create_minimized_dashboard". Создаем панель заголовка с помощью "create_rectangle" для "HEADER_PANEL" в "panel_x" и "panel_y", добавляем "HEADER_PANEL_ICON" и "HEADER_PANEL_TEXT" с помощью "create_label" для пиктограммы и заголовка, включаем "CLOSE_BUTTON" и добавляем "TOGGLE_BUTTON" с символом разворачивания ('o' в Webdings) в "panel_x - 570" и "panel_y + 14". Вызываем "ChartRedraw", чтобы обновить отображение, включив подвижное, свернутое состояние панели. Как вы могли заметить, мы выбрали шрифт Wingdings как для кнопок "развернуть", так и "свернуть" из соображений единообразия. Вы можете выбрать любой из понравившихся вам вариантов. В нашем случае значками являются 'r' и 'o' соответственно. Вот их общий вид.

WINGDINGS FONT CHARACTERS

При запуске состояния свернутой панели, вы получаете следующий результат.

MINIMIZED STATE

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

//+------------------------------------------------------------------+
//| 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 title
   ObjectDelete(0, CLOSE_BUTTON);                                //--- Delete close button
   ObjectDelete(0, TOGGLE_BUTTON);                               //--- Delete toggle button

   ObjectsDeleteAll(0, SYMBOL_RECTANGLE);                        //--- Delete all symbol rectangles
   ObjectsDeleteAll(0, SYMBOL_TEXT);                             //--- Delete all symbol labels
   ObjectsDeleteAll(0, TIMEFRAME_RECTANGLE);                     //--- Delete all timeframe rectangles
   ObjectsDeleteAll(0, TIMEFRAME_TEXT);                          //--- Delete all timeframe labels
   ObjectsDeleteAll(0, HEADER_RECTANGLE);                        //--- Delete all header rectangles
   ObjectsDeleteAll(0, HEADER_TEXT);                             //--- Delete all header labels
   ObjectsDeleteAll(0, RSI_RECTANGLE);                           //--- Delete all RSI rectangles
   ObjectsDeleteAll(0, RSI_TEXT);                                //--- Delete all RSI labels
   ObjectsDeleteAll(0, STOCH_RECTANGLE);                         //--- Delete all Stochastic rectangles
   ObjectsDeleteAll(0, STOCH_TEXT);                              //--- Delete all Stochastic labels
   ObjectsDeleteAll(0, CCI_RECTANGLE);                           //--- Delete all CCI rectangles
   ObjectsDeleteAll(0, CCI_TEXT);                                //--- Delete all CCI labels
   ObjectsDeleteAll(0, ADX_RECTANGLE);                           //--- Delete all ADX rectangles
   ObjectsDeleteAll(0, ADX_TEXT);                                //--- Delete all ADX labels
   ObjectsDeleteAll(0, AO_RECTANGLE);                            //--- Delete all AO rectangles
   ObjectsDeleteAll(0, AO_TEXT);                                 //--- Delete all AO labels
   ObjectsDeleteAll(0, BUY_RECTANGLE);                           //--- Delete all buy rectangles
   ObjectsDeleteAll(0, BUY_TEXT);                                //--- Delete all buy labels
   ObjectsDeleteAll(0, SELL_RECTANGLE);                          //--- Delete all sell rectangles
   ObjectsDeleteAll(0, SELL_TEXT);                               //--- Delete all sell labels
}

Чтобы иметь больше контроля над тем, когда удалять наши объекты, создаем и обновляем функцию "delete_all_objects", включив в нее удаление новой "TOGGLE_BUTTON", поддерживающей улучшение функциональности переключения. Добавляем функцию ObjectDelete для "TOGGLE_BUTTON" в список удаляемых объектов, гарантируя надлежащее удаление кнопки-переключателя при закрытии или переключении панели. Сохраняем возможность удаления всех других объектов, таких как "MAIN_PANEL", "HEADER_PANEL", "HEADER_PANEL_ICON", "HEADER_PANEL_TEXT", "CLOSE_BUTTON", а также всех прямоугольников и меток инструментов, таймфреймов, заголовков, индикаторов и сигналов, используя функцию ObjectsDeleteAll.  Это изменение гарантирует, что наша подвижная и сворачиваемая панель очистит все компоненты, включая новую кнопку переключения, сохраняя аккуратный график при скрытии или повторной инициализации панели.

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

//+------------------------------------------------------------------+
//| Update panel object positions                                    |
//+------------------------------------------------------------------+
void update_panel_positions() {
   // Update header and buttons
   ObjectSetInteger(0, HEADER_PANEL, OBJPROP_XDISTANCE, panel_x); //--- Set header panel x position
   ObjectSetInteger(0, HEADER_PANEL, OBJPROP_YDISTANCE, panel_y); //--- Set header panel y position
   ObjectSetInteger(0, HEADER_PANEL_ICON, OBJPROP_XDISTANCE, panel_x - 12); //--- Set header icon x position
   ObjectSetInteger(0, HEADER_PANEL_ICON, OBJPROP_YDISTANCE, panel_y + 14); //--- Set header icon y position
   ObjectSetInteger(0, HEADER_PANEL_TEXT, OBJPROP_XDISTANCE, panel_x - 105); //--- Set header title x position
   ObjectSetInteger(0, HEADER_PANEL_TEXT, OBJPROP_YDISTANCE, panel_y + 12); //--- Set header title y position
   ObjectSetInteger(0, CLOSE_BUTTON, OBJPROP_XDISTANCE, panel_x - 600); //--- Set close button x position
   ObjectSetInteger(0, CLOSE_BUTTON, OBJPROP_YDISTANCE, panel_y + 14); //--- Set close button y position
   ObjectSetInteger(0, TOGGLE_BUTTON, OBJPROP_XDISTANCE, panel_x - 570); //--- Set toggle button x position
   ObjectSetInteger(0, TOGGLE_BUTTON, OBJPROP_YDISTANCE, panel_y + 14); //--- Set toggle button y position

   if (!panel_minimized) {
      // Update main panel
      ObjectSetInteger(0, MAIN_PANEL, OBJPROP_XDISTANCE, panel_x); //--- Set main panel x position
      ObjectSetInteger(0, MAIN_PANEL, OBJPROP_YDISTANCE, panel_y); //--- Set main panel y position

      // Update symbol rectangle and label
      ObjectSetInteger(0, SYMBOL_RECTANGLE, OBJPROP_XDISTANCE, panel_x - 2); //--- Set symbol rectangle x position
      ObjectSetInteger(0, SYMBOL_RECTANGLE, OBJPROP_YDISTANCE, panel_y + 35); //--- Set symbol rectangle y position
      ObjectSetInteger(0, SYMBOL_TEXT, OBJPROP_XDISTANCE, panel_x - 47); //--- Set symbol text x position
      ObjectSetInteger(0, SYMBOL_TEXT, OBJPROP_YDISTANCE, panel_y + 45); //--- Set symbol text y position

      // Update header rectangles and labels
      string header_names[] = {"BUY", "SELL", "RSI", "STOCH", "CCI", "ADX", "AO"};
      for(int header_index = 0; header_index < ArraySize(header_names); header_index++) { //--- Loop through headers
         int x_offset = panel_x - WIDTH_TIMEFRAME - (header_index < 2 ? header_index * WIDTH_SIGNAL : 2 * WIDTH_SIGNAL + (header_index - 2) * WIDTH_INDICATOR) + (1 + header_index); //--- Calculate x position
         ObjectSetInteger(0, HEADER_RECTANGLE + IntegerToString(header_index), OBJPROP_XDISTANCE, x_offset); //--- Set header rectangle x position
         ObjectSetInteger(0, HEADER_RECTANGLE + IntegerToString(header_index), OBJPROP_YDISTANCE, panel_y + 35); //--- Set header rectangle y position
         ObjectSetInteger(0, HEADER_TEXT + IntegerToString(header_index), OBJPROP_XDISTANCE, x_offset - (header_index < 2 ? WIDTH_SIGNAL/2 : WIDTH_INDICATOR/2)); //--- Set header text x position
         ObjectSetInteger(0, HEADER_TEXT + IntegerToString(header_index), OBJPROP_YDISTANCE, panel_y + 45); //--- Set header text y position
      }

      // Update timeframe rectangles, labels, and cells
      for(int timeframe_index = 0; timeframe_index < ArraySize(timeframes_array); timeframe_index++) { //--- Loop through timeframes
         int y_offset = (panel_y + 35 + HEIGHT_RECTANGLE) + timeframe_index * HEIGHT_RECTANGLE - (1 + timeframe_index); //--- Calculate y position
         ObjectSetInteger(0, TIMEFRAME_RECTANGLE + IntegerToString(timeframe_index), OBJPROP_XDISTANCE, panel_x - 2); //--- Set timeframe rectangle x position
         ObjectSetInteger(0, TIMEFRAME_RECTANGLE + IntegerToString(timeframe_index), OBJPROP_YDISTANCE, y_offset); //--- Set timeframe rectangle y position
         ObjectSetInteger(0, TIMEFRAME_TEXT + IntegerToString(timeframe_index), OBJPROP_XDISTANCE, panel_x - 47); //--- Set timeframe text x position
         ObjectSetInteger(0, TIMEFRAME_TEXT + IntegerToString(timeframe_index), OBJPROP_YDISTANCE, y_offset + 10); //--- Set timeframe text y position

         for(int header_index = 0; header_index < ArraySize(header_names); header_index++) { //--- Loop through cells
            string cell_rectangle_name, cell_text_name;
            switch(header_index) { //--- Select cell type
               case 0: cell_rectangle_name = BUY_RECTANGLE + IntegerToString(timeframe_index); cell_text_name = BUY_TEXT + IntegerToString(timeframe_index); break; //--- Buy cell
               case 1: cell_rectangle_name = SELL_RECTANGLE + IntegerToString(timeframe_index); cell_text_name = SELL_TEXT + IntegerToString(timeframe_index); break; //--- Sell cell
               case 2: cell_rectangle_name = RSI_RECTANGLE + IntegerToString(timeframe_index); cell_text_name = RSI_TEXT + IntegerToString(timeframe_index); break; //--- RSI cell
               case 3: cell_rectangle_name = STOCH_RECTANGLE + IntegerToString(timeframe_index); cell_text_name = STOCH_TEXT + IntegerToString(timeframe_index); break; //--- Stochastic cell
               case 4: cell_rectangle_name = CCI_RECTANGLE + IntegerToString(timeframe_index); cell_text_name = CCI_TEXT + IntegerToString(timeframe_index); break; //--- CCI cell
               case 5: cell_rectangle_name = ADX_RECTANGLE + IntegerToString(timeframe_index); cell_text_name = ADX_TEXT + IntegerToString(timeframe_index); break; //--- ADX cell
               case 6: cell_rectangle_name = AO_RECTANGLE + IntegerToString(timeframe_index); cell_text_name = AO_TEXT + IntegerToString(timeframe_index); break; //--- AO cell
            }
            int x_offset = panel_x - WIDTH_TIMEFRAME - (header_index < 2 ? header_index * WIDTH_SIGNAL : 2 * WIDTH_SIGNAL + (header_index - 2) * WIDTH_INDICATOR) + (1 + header_index); //--- Calculate x position
            int width = (header_index < 2 ? WIDTH_SIGNAL : WIDTH_INDICATOR); //--- Set cell width
            ObjectSetInteger(0, cell_rectangle_name, OBJPROP_XDISTANCE, x_offset); //--- Set cell rectangle x position
            ObjectSetInteger(0, cell_rectangle_name, OBJPROP_YDISTANCE, y_offset); //--- Set cell rectangle y position
            ObjectSetInteger(0, cell_text_name, OBJPROP_XDISTANCE, x_offset - width/2); //--- Set cell text x position
            ObjectSetInteger(0, cell_text_name, OBJPROP_YDISTANCE, y_offset + 10); //--- Set cell text y position
         }
      }
   }
   ChartRedraw(0);                                               //--- Redraw chart
}

Для поддержки динамического позиционирования панели вводим новую функцию "update_panel_positions". Функция регулирует положение всех элементов панели на основе текущих координат "panel_x" и "panel_y", позволяя перетаскивать панель по графику. Обновляем панель заголовка, значок, заголовок, кнопку закрытия и кнопку переключения ("HEADER_PANEL", "HEADER_PANEL_ICON", "HEADER_PANEL_TEXT", "CLOSE_BUTTON", "TOGGLE_BUTTON"), используя функцию ObjectSetInteger с помощью OBJPROP_XDISTANCE и "OBJPROP_YDISTANCE", устанавливая их положения относительно "panel_x" и "panel_y".

Если значение "panel_minimized" равно false, меняем положение главной панели ("MAIN_PANEL"), прямоугольника инструмента и метки ("SYMBOL_RECTANGLE", "SYMBOL_TEXT"), прямоугольников заголовков и меток ("HEADER_RECTANGLE", "HEADER_TEXT"), а также прямоугольников таймфреймов, меток и ячеек индикатора ("TIMEFRAME_RECTANGLE", "TIMEFRAME_TEXT", "BUY_RECTANGLE", "RSI_RECTANGLE" и т.д.), используя вычисленные смещения из "panel_x" и "panel_y". Вызываем функцию ChartRedraw для обновления дисплея. Эта функция гарантирует, что все элементы будут перемещаться вместе при перетаскивании информационной панели, что является критически важной функцией для улучшения нашей подвижной информационной панели. Затем мы можем попробовать протестировать информационную панель. Для этого вызовем функцию для создания полной панели в обработчике событий OnInit и вызовем функцию уничтожения в обработчике событий OnDeinit. 

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()                                                     //--- Initialize EA
{
   create_full_dashboard();                                      //--- Create full dashboard
   ArraySetAsSeries(rsi_values, true);                           //--- Set RSI array as timeseries
   ArraySetAsSeries(stochastic_values, true);                    //--- Set Stochastic array as timeseries
   ArraySetAsSeries(cci_values, true);                           //--- Set CCI array as timeseries
   ArraySetAsSeries(adx_values, true);                           //--- Set ADX array as timeseries
   ArraySetAsSeries(ao_values, true);                            //--- Set AO array as timeseries
    
   ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true);             //--- Enable mouse move events
   return(INIT_SUCCEEDED);                                       //--- Return initialization success
}

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

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

INIT AND DEINIT GIF

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

//+------------------------------------------------------------------+
//| 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);
   
   // Header panel bounds
   int header_x = (int)ObjectGetInteger(0, HEADER_PANEL, OBJPROP_XDISTANCE);
   int header_y = (int)ObjectGetInteger(0, HEADER_PANEL, OBJPROP_YDISTANCE);
   int header_width = (int)ObjectGetInteger(0, HEADER_PANEL, OBJPROP_XSIZE);
   int header_height = (int)ObjectGetInteger(0, HEADER_PANEL, OBJPROP_YSIZE);
   int header_left = chart_width - header_x;
   int header_right = header_left + header_width;
   bool in_header = (mouse_x >= header_left && mouse_x <= header_right && 
                     mouse_y >= header_y && mouse_y <= header_y + header_height);

   // Close button bounds
   int close_x = (int)ObjectGetInteger(0, CLOSE_BUTTON, OBJPROP_XDISTANCE);
   int close_y = (int)ObjectGetInteger(0, CLOSE_BUTTON, OBJPROP_YDISTANCE);
   int close_width = 20;
   int close_height = 20;
   int close_left = chart_width - close_x;
   int close_right = close_left + close_width;
   bool in_close = (mouse_x >= close_left && mouse_x <= close_right && 
                    mouse_y >= close_y && mouse_y <= close_y + close_height);

   // Toggle button bounds
   int toggle_x = (int)ObjectGetInteger(0, TOGGLE_BUTTON, OBJPROP_XDISTANCE);
   int toggle_y = (int)ObjectGetInteger(0, TOGGLE_BUTTON, OBJPROP_YDISTANCE);
   int toggle_width = 20;
   int toggle_height = 20;
   int toggle_left = chart_width - toggle_x;
   int toggle_right = toggle_left + toggle_width;
   bool in_toggle = (mouse_x >= toggle_left && mouse_x <= toggle_right && 
                     mouse_y >= toggle_y && mouse_y <= toggle_y + toggle_height);

   return in_header || in_close || in_toggle;
}

Во-первых, вводим новую функцию "is_cursor_in_header_or_buttons" для поддержки функций динамического позиционирования и переключения. Функция проверяет, находится ли курсор мыши на панели заголовка, кнопке закрытия или кнопке переключения, позволяя выполнять интерактивное перетаскивание и нажатия кнопок. Начинаем с получения ширины графика с помощью ChartGetInteger с параметром CHART_WIDTH_IN_PIXELS. Для панели заголовка получаем "header_x", "header_y", "header_width" и "header_height", используя ObjectGetInteger с параметрами "OBJPROP_XDISTANCE", "OBJPROP_YDISTANCE", "OBJPROP_XSIZE" и OBJPROP_YSIZE для "HEADER_PANEL", вычисляя "header_left" и "header_right" относительно ширины графика. Проверяем, попадают ли "mouse_x" и "mouse_y" в эти границы, устанавливая для "in_header" значение true, если это так.

Для кнопки закрытия выбираем "close_x" и "close_y" для "CLOSE_BUTTON", определяем область размером 20x20 пикселей и вычисляем "close_left" и "close_right", устанавливая значение "in_close" равным true, если курсор находится в пределах этой области. Аналогично, для кнопки переключения получаем "toggle_x" и "toggle_y" для "TOGGLE_BUTTON", определяем область размером 20x20 и устанавливаем для "in_toggle" значение true, если курсор находится внутри. Возвращаем значение true, если курсор находится в любой из этих областей ("in_header", "in_close" или "in_toggle"). Эта функция будет иметь решающее значение для обнаружения взаимодействия мыши с перетаскиваемым заголовком и кнопками информационной панели, что позволит использовать наши улучшенные подвижные и интерактивные функции. Затем можем обновить состояния наведения курсора, визуализируя цвета для облегчения распознавания.

//+------------------------------------------------------------------+
//| Update button hover states                                       |
//+------------------------------------------------------------------+
void update_button_hover_states(int mouse_x, int mouse_y) {
   int chart_width = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS);
   
   // Close button hover
   int close_x = (int)ObjectGetInteger(0, CLOSE_BUTTON, OBJPROP_XDISTANCE);
   int close_y = (int)ObjectGetInteger(0, CLOSE_BUTTON, OBJPROP_YDISTANCE);
   int close_width = 20;
   int close_height = 20;
   int close_left = chart_width - close_x;
   int close_right = close_left + close_width;
   bool is_close_hovered = (mouse_x >= close_left && mouse_x <= close_right && 
                            mouse_y >= close_y && mouse_y <= close_y + close_height);

   if (is_close_hovered != prev_close_hovered) {
      ObjectSetInteger(0, CLOSE_BUTTON, OBJPROP_COLOR, is_close_hovered ? clrWhite : clrYellow);
      ObjectSetInteger(0, CLOSE_BUTTON, OBJPROP_BGCOLOR, is_close_hovered ? clrDodgerBlue : clrNONE);
      prev_close_hovered = is_close_hovered;
      ChartRedraw(0);
   }

   // Toggle button hover
   int toggle_x = (int)ObjectGetInteger(0, TOGGLE_BUTTON, OBJPROP_XDISTANCE);
   int toggle_y = (int)ObjectGetInteger(0, TOGGLE_BUTTON, OBJPROP_YDISTANCE);
   int toggle_width = 20;
   int toggle_height = 20;
   int toggle_left = chart_width - toggle_x;
   int toggle_right = toggle_left + toggle_width;
   bool is_toggle_hovered = (mouse_x >= toggle_left && mouse_x <= toggle_right && 
                             mouse_y >= toggle_y && mouse_y <= toggle_y + toggle_height);

   if (is_toggle_hovered != prev_toggle_hovered) {
      ObjectSetInteger(0, TOGGLE_BUTTON, OBJPROP_COLOR, is_toggle_hovered ? clrWhite : clrYellow);
      ObjectSetInteger(0, TOGGLE_BUTTON, OBJPROP_BGCOLOR, is_toggle_hovered ? clrDodgerBlue : clrNONE);
      prev_toggle_hovered = is_toggle_hovered;
      ChartRedraw(0);
   }
}

//+------------------------------------------------------------------+
//| Update header hover state                                        |
//+------------------------------------------------------------------+
void update_header_hover_state(int mouse_x, int mouse_y) {
   int chart_width = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS);
   int header_x = (int)ObjectGetInteger(0, HEADER_PANEL, OBJPROP_XDISTANCE);
   int header_y = (int)ObjectGetInteger(0, HEADER_PANEL, OBJPROP_YDISTANCE);
   int header_width = (int)ObjectGetInteger(0, HEADER_PANEL, OBJPROP_XSIZE);
   int header_height = (int)ObjectGetInteger(0, HEADER_PANEL, OBJPROP_YSIZE);
   int header_left = chart_width - header_x;
   int header_right = header_left + header_width;

   // Exclude button areas from header hover
   int close_x = (int)ObjectGetInteger(0, CLOSE_BUTTON, OBJPROP_XDISTANCE);
   int close_y = (int)ObjectGetInteger(0, CLOSE_BUTTON, OBJPROP_YDISTANCE);
   int close_width = 20;
   int close_height = 20;
   int close_left = chart_width - close_x;
   int close_right = close_left + close_width;

   int toggle_x = (int)ObjectGetInteger(0, TOGGLE_BUTTON, OBJPROP_XDISTANCE);
   int toggle_y = (int)ObjectGetInteger(0, TOGGLE_BUTTON, OBJPROP_YDISTANCE);
   int toggle_width = 20;
   int toggle_height = 20;
   int toggle_left = chart_width - toggle_x;
   int toggle_right = toggle_left + toggle_width;

   bool is_header_hovered = (mouse_x >= header_left && mouse_x <= header_right && 
                             mouse_y >= header_y && mouse_y <= header_y + header_height &&
                             !(mouse_x >= close_left && mouse_x <= close_right && 
                               mouse_y >= close_y && mouse_y <= close_y + close_height) &&
                             !(mouse_x >= toggle_left && mouse_x <= toggle_right && 
                               mouse_y >= toggle_y && mouse_y <= toggle_y + toggle_height));

   if (is_header_hovered != prev_header_hovered && !panel_dragging) {
      ObjectSetInteger(0, HEADER_PANEL, OBJPROP_BGCOLOR, is_header_hovered ? clrRed : C'60,60,60');
      prev_header_hovered = is_header_hovered;
      ChartRedraw(0);
   }

   update_button_hover_states(mouse_x, mouse_y);
}

Здесь мы представляем две новые функции, "update_button_hover_states" и "update_header_hover_state", которые добавляют визуальную обратную связь для взаимодействия с пользователем, улучшая удобство использования панели. Начинаем с функции "update_button_hover_states", которая использует "mouse_x" и "mouse_y" для определения наведения курсора мыши на кнопки закрытия и переключения. Для "CLOSE_BUTTON" получаем "close_x" и "close_y", используя ObjectGetInteger с "OBJPROP_XDISTANCE" и "OBJPROP_YDISTANCE", вычисляем область размером 20x20 пикселей относительно ширины графика из ChartGetInteger с параметром "CHART_WIDTH_IN_PIXELS" и устанавливаем "is_close_hovered", если курсор находится в пределах этой области.

Если "is_close_hovered" отличается от "prev_close_hovered", обновляем "CLOSE_BUTTON" с помощью "ObjectSetInteger", устанавливая для "OBJPROP_COLOR" значение "clrWhite", а для OBJPROP_BGCOLOR значение "clrDodgerBlue" при наведении курсора мыши, или "clrYellow" и "clrNONE", если нет, обновляем "prev_close_hovered" и вызываем функцию ChartRedraw.  Аналогично, для "TOGGLE_BUTTON" выбираем "toggle_x" и "toggle_y", проверяем область 20x20 и обновляем ее цвета и "prev_toggle_hovered", если меняется "is_toggle_hovered", обеспечивая адаптивную обратную связь кнопки.

Далее создаем функцию "update_header_hover_state", также используя "mouse_x" и "mouse_y". Получаем "header_x", "header_y", "header_width" и "header_height" для "HEADER_PANEL", используя "ObjectGetInteger", вычисляем границы заголовка и исключаем области "CLOSE_BUTTON" и "TOGGLE_BUTTON" (20x20 пикселей каждая), чтобы избежать перекрытия. Если "is_header_hovered" отличается от "prev_header_hovered", а "panel_dragging" имеет значение false, обновляем "OBJPROP_BGCOLOR" для "HEADER_PANEL" до значения "clrRed" при наведении курсора мыши или "C'60,60,60'", если нет, обновляем "prev_header_hovered" и вызываем "ChartRedraw". Затем вызываем "update_button_hover_states", чтобы убедиться, что состояния кнопок обновлены. Эти функции будут давать визуальные подсказки для перетаскивания и взаимодействия с кнопками, повышая интерактивность панели. Затем можем использовать функции в обработчике событий OnChartEvent для полной реализации. Вот применяемая нами логика.

//+------------------------------------------------------------------+
//| Expert chart event handler                                       |
//+------------------------------------------------------------------+
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 object click event
      if (string_param == CLOSE_BUTTON) {                        //--- Check if close button clicked
         Print("Closing the panel now");                         //--- Log panel closure
         PlaySound("alert.wav");                                 //--- Play alert sound
         panel_is_visible = false;                               //--- Hide panel
         delete_all_objects();                                   //--- Delete all objects
         ChartRedraw(0);                                         //--- Redraw chart
      } else if (string_param == TOGGLE_BUTTON) {                //--- Toggle button clicked
         delete_all_objects();                                   //--- Delete current UI
         panel_minimized = !panel_minimized;                     //--- Toggle minimized state
         if (panel_minimized) {
            Print("Minimizing the panel");                       //--- Log minimization
            create_minimized_dashboard();                        //--- Create minimized UI
         } else {
            Print("Maximizing the panel");                       //--- Log maximization
            create_full_dashboard();                             //--- Create full UI
         }
         // Reset hover states after toggle
         prev_header_hovered = false;
         prev_close_hovered = false;
         prev_toggle_hovered = false;
         ObjectSetInteger(0, HEADER_PANEL, OBJPROP_BGCOLOR, C'60,60,60');
         ObjectSetInteger(0, CLOSE_BUTTON, OBJPROP_COLOR, clrYellow);
         ObjectSetInteger(0, CLOSE_BUTTON, OBJPROP_BGCOLOR, clrNONE);
         ObjectSetInteger(0, TOGGLE_BUTTON, OBJPROP_COLOR, clrYellow);
         ObjectSetInteger(0, TOGGLE_BUTTON, OBJPROP_BGCOLOR, clrNONE);
         ChartRedraw(0);
      }
   }
   else if (event_id == CHARTEVENT_MOUSE_MOVE && panel_is_visible) { //--- Handle mouse move events
      int mouse_x = (int)long_param;                             //--- Get mouse x-coordinate
      int mouse_y = (int)double_param;                           //--- Get mouse y-coordinate
      int mouse_state = (int)string_param;                       //--- Get mouse state

      if (mouse_x == last_mouse_x && mouse_y == last_mouse_y && !panel_dragging) { //--- Skip redundant updates
         return;
      }
      last_mouse_x = mouse_x;                                    //--- Update last mouse x position
      last_mouse_y = mouse_y;                                    //--- Update last mouse y position

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

      int chart_width = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS);
      int header_x = (int)ObjectGetInteger(0, HEADER_PANEL, OBJPROP_XDISTANCE);
      int header_y = (int)ObjectGetInteger(0, HEADER_PANEL, OBJPROP_YDISTANCE);
      int header_width = (int)ObjectGetInteger(0, HEADER_PANEL, OBJPROP_XSIZE);
      int header_height = (int)ObjectGetInteger(0, HEADER_PANEL, OBJPROP_YSIZE);
      int header_left = chart_width - header_x;
      int header_right = header_left + header_width;

      int close_x = (int)ObjectGetInteger(0, CLOSE_BUTTON, OBJPROP_XDISTANCE);
      int close_width = 20;
      int close_left = chart_width - close_x;
      int close_right = close_left + close_width;

      int toggle_x = (int)ObjectGetInteger(0, TOGGLE_BUTTON, OBJPROP_XDISTANCE);
      int toggle_width = 20;
      int toggle_left = chart_width - toggle_x;
      int toggle_right = toggle_left + toggle_width;

      if (prev_mouse_state == 0 && mouse_state == 1) {           //--- Detect mouse button down
         if (mouse_x >= header_left && mouse_x <= header_right && 
             mouse_y >= header_y && mouse_y <= header_y + header_height &&
             !(mouse_x >= close_left && mouse_x <= close_right) &&
             !(mouse_x >= toggle_left && mouse_x <= toggle_right)) { //--- Exclude button areas
            panel_dragging = true;                              //--- Start dragging
            panel_drag_x = mouse_x;                             //--- Store mouse x-coordinate
            panel_drag_y = mouse_y;                             //--- Store mouse y-coordinate
            panel_start_x = header_x;                           //--- Store panel x-coordinate
            panel_start_y = header_y;                           //--- Store panel y-coordinate
            ObjectSetInteger(0, HEADER_PANEL, OBJPROP_BGCOLOR, clrMediumBlue); //--- Set header to blue on drag start
            ChartSetInteger(0, CHART_MOUSE_SCROLL, false);      //--- Disable chart scrolling
         }
      }

      if (panel_dragging && mouse_state == 1) {                  //--- Handle dragging
         int dx = mouse_x - panel_drag_x;                        //--- Calculate x displacement
         int dy = mouse_y - panel_drag_y;                        //--- Calculate y displacement
         panel_x = panel_start_x - dx;                           //--- Update panel x-position (inverted for CORNER_RIGHT_UPPER)
         panel_y = panel_start_y + dy;                           //--- Update panel y-position

         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
         panel_x = MathMax(617, MathMin(chart_width, panel_x));  //--- Keep panel within right edge
         panel_y = MathMax(0, MathMin(chart_height - (panel_minimized ? 27 : 374), panel_y)); //--- Adjust height based on state

         update_panel_positions();                               //--- Update all panel object positions
         ChartRedraw(0);                                         //--- Redraw chart during dragging
      }

      if (mouse_state == 0 && prev_mouse_state == 1) {           //--- Detect mouse button release
         if (panel_dragging) {
            panel_dragging = false;                              //--- Stop dragging
            update_header_hover_state(mouse_x, mouse_y);          //--- Update hover state immediately after drag
            ChartSetInteger(0, CHART_MOUSE_SCROLL, true);        //--- Re-enable chart scrolling
            ChartRedraw(0);                                      //--- Redraw chart
         }
      }

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

Здесь мы улучшаем нашу программу, обновляя обработчик событий OnChartEvent для поддержки функций динамического позиционирования и переключения, что значительно улучшает предыдущую версию. Сохраняем обработку CHARTEVENT_OBJECT_CLICK для "CLOSE_BUTTON", которая регистрирует закрытие с помощью Print, воспроизводит звук с помощью PlaySound, устанавливает для "panel_is_visible" значение false, вызывает функцию "delete_all_objects" и перерисовывает график.

Новым дополнением является обработка нажатий на "TOGGLE_BUTTON", где мы вызываем функцию "delete_all_objects", переключаем "panel_minimized" и создаем либо свернутую информационную панель с помощью "create_minimized_dashboard" (регистрируем "Сворачивание панели"), либо полную информационную панель с помощью "create_full_dashboard" (регистрируем "Разворачивание панели"). Устанавливаем значения состояния при наведении курсора ("prev_header_hovered", "prev_close_hovered", "prev_toggle_hovered") в false, восстанавливаем цвета по умолчанию для "HEADER_PANEL", "CLOSE_BUTTON" и "TOGGLE_BUTTON" с помощью функции ObjectSetInteger и перерисовываем график.

Для динамического позиционирования добавляем обработку CHARTEVENT_MOUSE_MOVE, когда значение "panel_is_visible" равно true. Получаем "mouse_x", "mouse_y" и "mouse_state" из параметров события, пропускаем избыточные обновления, если координаты совпадают с "last_mouse_x" и "last_mouse_y", а значение "panel_dragging" равно false, и обновляем эти координаты. Вызываем "update_header_hover_state" для управления эффектами наведения курсора. Если "prev_mouse_state" равно 0, а "mouse_state" равно 1, проверяем, находится ли курсор над "HEADER_PANEL" (исключая области "CLOSE_BUTTON" и "TOGGLE_BUTTON"), используя "ObjectGetInteger" и ChartGetInteger, затем устанавливаем для "panel_dragging" значение true, сохраняем координаты в "panel_drag_x", "panel_drag_y", "panel_start_x" и "panel_start_y", устанавливаем цвет "HEADER_PANEL" на "clrMediumBlue" и отключаем прокрутку графика с помощью "ChartSetInteger".

Пока значения "panel_dragging" и "mouse_state" равны 1, мы вычисляем смещение, обновляем "panel_x" и "panel_y" в пределах графика, вызываем "update_panel_positions" и перерисовываем. Отпустив мышь, мы прекращаем перетаскивание, обновляем состояние при наведении курсора, снова включаем прокрутку и перерисовываем. Обновляем "prev_mouse_state". Эти изменения теперь позволят перетаскивать и переключать объекты, в отличие от предыдущей статической обработки только щелчком мыши. После компиляции получаем следующий результат.

DRAG, HOVER AND VISIBILITY STATES

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

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()                                                     //--- Handle tick events
{
   if (panel_is_visible && !panel_minimized && !is_cursor_in_header_or_buttons(last_mouse_x, last_mouse_y)) { //--- Update indicators only if panel is visible, not minimized, and cursor is not in header/buttons
      updateIndicators();                                        //--- Update indicators
   }
}

Обновляем обработчик событий OnTick, чтобы оптимизировать обновление индикаторов. В отличие от предыдущей версии, в которой функция "updateIndicators" вызывалась исключительно на основе "panel_is_visible", теперь мы добавляем условия для обновления индикаторов только тогда, когда "panel_is_visible" имеет значение true, "panel_minimized" - значение false, а курсор не находится над заголовком или кнопками, что проверяется с помощью "is_cursor_in_header_or_buttons" посредством "last_mouse_x" и "last_mouse_y". Это изменение гарантирует, что обновление индикатора приостанавливается, когда информационная панель свернута или пользователь взаимодействует с заголовком, кнопкой закрытия или кнопкой переключения, что сокращает ненужную обработку и повышает эффективность во время действий по перетаскиванию или переключению. После компиляции получаем следующий результат.

IMPROVED PERFORMANCE GIF

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


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

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

DASHBOARD BACKTEST


Заключение

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

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

Прикрепленные файлы |
Последние комментарии | Перейти к обсуждению на форуме трейдеров (2)
linfo2
linfo2 | 17 июл. 2025 в 17:38
Спасибо, Аллан, это очень здорово, хорошо документировано и охватывает функции, о которых я не знал.
Allan Munene Mutiiria
Allan Munene Mutiiria | 17 июл. 2025 в 18:58
linfo2 #:
Спасибо, Аллан, это довольно круто, хорошо документировано и охватывает функции, которые я не знал, очень ценю.
@linfo2 очень приятно. Спасибо.
Возможности Мастера MQL5, которые вам нужно знать (Часть 66): Использование паттернов FrAMA и индекса силы с ядром скалярного произведения Возможности Мастера MQL5, которые вам нужно знать (Часть 66): Использование паттернов FrAMA и индекса силы с ядром скалярного произведения
Индикатор FrAMA и осциллятор индекса силы (Force Index) — инструменты анализа тренда и объема, которые можно использовать в паре при разработке советника. В продолжение нашей предыдущей статьи, в которой мы представили эту пару, рассмотрим применимость к ней машинного обучения. Мы используем сверточную нейронную сеть (convolution neural network), которая применяет ядро скалярного произведения (dot-product kernel) для построения прогнозов на основе входных данных этих индикаторов. Это делается в пользовательском файле класса сигналов (signal class), который взаимодействует с Мастером MQL5 для создания советника.
Алгоритм извлечения торговых правил из паттернов в MQL5 Алгоритм извлечения торговых правил из паттернов в MQL5
Статья показывает, как формализовать интуитивно замеченные ценовые паттерны и превратить их в статистически проверенные торговые сигналы. Советник кодирует последовательности баров в бинарные строки U/D и для каждого паттерна вычисляет пять независимых метрик: поддержку, уверенность, лифт, хи-квадрат и байесовскую вероятность. Позиция открывается только тогда, когда текущий паттерн совпадает с историческим правилом и все фильтры пройдены — динамический лот масштабируется по силе сигнала, стоп и тейк рассчитываются через дневной ATR.
Нейросети в трейдинге: Адаптивная факторная токенизация (Основные компоненты) Нейросети в трейдинге: Адаптивная факторная токенизация (Основные компоненты)
Продолжаем перенос современных подходов, предложенных авторами фреймворка MTmixATT, на задачи финансовых временных рядов. Представлены практические реализации модулей Multi-Mix Attention и разреженного выбора эксперта, позволяющие структурировать признаки и формировать динамически адаптивных экспертов на основе текущих рыночных данных. Особое внимание уделено оригинальности подхода и его потенциалу для адаптивного структурного анализа рынка.
Разработка пробойной торговой системы на основе волатильности Разработка пробойной торговой системы на основе волатильности
Пробойная система на основе волатильности определяет рыночные диапазоны, а затем совершает сделки, когда цена пробивает эти уровни вверх или вниз, с учетом таких показателей волатильности, как ATR. Такой подход помогает выявлять сильные направленные движения.