English Deutsch 日本語
preview
Торговые инструменты на языке MQL5 (Часть 8): Улучшенная информационная панель с возможностью перетаскивания и сворачивания

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

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

Введение

В своей предыдущей статье (Часть 7) мы разработали информационную панель на MetaQuotes Language 5 (MQL5) для мониторинга позиций на нескольких символах и показателей счета, таких как "Balance", "Equity" и "Free Margin", с возможностью сортировки столбцов и экспорта данных в формате CSV (Comma Separated Values).  В Части 8 мы улучшаем эту панель, добавив функции перетаскивания и сворачивания, интерактивные кнопки для закрытия, переключения и экспорта, а также эффекты наведения курсора для более динамичного взаимодействия с пользователем. Это усовершенствование сохраняет отслеживание позиций в режиме реального времени и эффект свечения заголовка. В статье рассмотрим следующие темы:

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

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


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

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

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

IMPLEMENTATION PLAN


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

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

//--- existing components

#define HEADER_PANEL_TEXT   "HEADER_PANEL_TEXT"//--- Header title label
#define CLOSE_BUTTON        "BUTTON_CLOSE"     //--- Close button identifier
#define EXPORT_BUTTON       "BUTTON_EXPORT"    //--- Export button identifier
#define TOGGLE_BUTTON       "BUTTON_TOGGLE"    //--- Toggle (minimize/maximize) button

//--- the rest of the components

Начинаем с добавления новых директив defines для поддержки расширенных функций информационной панели, вводя идентификаторы для интерактивных элементов пользовательского интерфейса (UI). Определяем "HEADER_PANEL_TEXT" как "HEADER_PANEL_TEXT" для метки заголовка панели, обеспечивая понятный визуальный заголовок. "CLOSE_BUTTON" определяется как "BUTTON_CLOSE", что создает идентификатор для кнопки закрытия панели, позволяя нам удалить ее с графика. "EXPORT_BUTTON" определяется как "BUTTON_EXPORT" и настраивает кнопку для запуска экспорта в формате CSV, повышая доступность данных. "TOGGLE_BUTTON" определяется как "BUTTON_TOGGLE", позволяя кнопке сворачивать или разворачивать панель, улучшая управление пространством экрана.

Эти определения обеспечивают упорядоченное присвоение имен новым интерактивным компонентам, поддерживая обновления, касающиеся возможности перетаскивания и сворачивания. Следующее, что мы изменим, - это цвет оттенков заголовка, заменив их четко определенными константами MQL5.

// Dashboard settings
struct DashboardSettings {                     //--- Structure for dashboard settings
   int      panel_x;                           //--- X-coordinate of panel
   int      panel_y;                           //--- Y-coordinate of panel
   int      row_height;                        //--- Height of each row
   int      font_size;                         //--- Font size for labels
   string   font;                              //--- Font type for labels
   color    bg_color;                          //--- Background color of main panel
   color    border_color;                      //--- Border color of panels
   color    header_color;                      //--- Default color for header text
   color    text_color;                        //--- Default color for text
   color    section_bg_color;                  //--- Background color for header/footer panels
   int      zorder_panel;                      //--- Z-order for main panel
   int      zorder_subpanel;                   //--- Z-order for sub-panels
   int      zorder_labels;                     //--- Z-order for labels
   int      label_y_offset;                    //--- Y-offset for label positioning
   int      label_x_offset;                    //--- X-offset for label positioning
   int      header_x_distances[9];             //--- X-distances for header labels (9 columns)
   color    header_shades[12];                 //--- Array of header color shades for glow effect
} settings = {                                 //--- Initialize settings with default values
   20,                                         //--- panel_x
   20,                                         //--- panel_y
   24,                                         //--- row_height
   11,                                         //--- font_size
   "Calibri Bold",                             //--- font
   C'240,240,240',                             //--- bg_color (light gray)
   clrBlack,                                   //--- border_color
   C'0,50,70',                                 //--- header_color (dark teal)
   clrBlack,                                   //--- text_color
   C'200,220,230',                             //--- section_bg_color (light blue-gray)
   100,                                        //--- zorder_panel
   101,                                        //--- zorder_subpanel
   102,                                        //--- zorder_labels
   3,                                          //--- label_y_offset
   25,                                         //--- label_x_offset
   {10, 120, 170, 220, 280, 330, 400, 470, 530}, //--- header_x_distances
   {clrBlack, clrRed, clrBlue, clrGreen, clrMagenta, clrDarkOrchid,
    clrDeepPink, clrSkyBlue, clrDodgerBlue, clrDarkViolet, clrOrange, clrCrimson} //--- header_shades
};

//--- the previous one was as below
/*
   //---
   {C'0,0,0', C'255,0,0', C'0,255,0', C'0,0,255', C'255,255,0', C'0,255,255', 
    C'255,0,255', C'255,255,255', C'255,0,255', C'0,255,255', C'255,255,0', C'0,0,255'}
*/

Здесь мы улучшаем структуру "DashboardSettings", обновляя массив "header_shades", чтобы улучшить эффект свечения заголовка и сделать его более визуально привлекательным. Ранее "header_shades" использовал сочетание основных цветов RGB (например, чисто черный, красный, зеленый, синий, желтый, голубой, пурпурный, белый) для цикла свечения. Теперь мы определяем "header_shades" с помощью тщательно подобранного набора из 12 цветов: "clrBlack", "clrRed", "clrBlue", "clrGreen", "clrMagenta", "clrDarkOrchid", "clrDeepPink", "clrSkyBlue", "clrDodgerBlue", "clrDarkViolet", "clrOrange" и "clrCrimson".

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

//--- added global variables


int          prev_num_symbols = 0;             //--- Previous number of active symbols
bool         panel_is_visible = true;          //--- Flag to control panel visibility
bool         panel_minimized = false;          //--- Flag to control minimized state
bool         panel_dragging = false;           //--- Flag to track if panel is being dragged
int          panel_drag_x = 0;                 //--- Mouse x-coordinate when drag starts
int          panel_drag_y = 0;                 //--- Mouse y-coordinate when drag starts
int          panel_start_x = 0;                //--- Panel x-coordinate when drag starts
int          panel_start_y = 0;                //--- Panel y-coordinate when drag starts
int          prev_mouse_state = 0;             //--- 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
bool         export_hovered = false;           //--- Export button hover state
int          last_mouse_x = 0;                 //--- Last mouse x position
int          last_mouse_y = 0;                 //--- Last mouse y position
bool         prev_header_hovered = false;      //--- Previous header hover state
bool         prev_toggle_hovered = false;      //--- Previous toggle hover state
bool         prev_close_hovered = false;       //--- Previous close button hover state
bool         prev_export_hovered = false;      //--- Previous export button hover state

Наконец, мы вводим дополнительные глобальные переменные для поддержки расширенной интерактивности и функций перетаскивания. Определяем "prev_num_symbols" как 0, чтобы отслеживать предыдущее количество активных символов для динамического изменения размера, "panel_is_visible" как true для управления видимостью панели, а "panel_minimized" как false для управления свернутым состоянием. Чтобы включить перетаскивание, устанавливаем значение параметра "panel_dragging" равным false для отслеживания состояния перетаскивания, значения параметров "panel_drag_x" и "panel_drag_y" равными 0 для координат мыши в начале перетаскивания, а также значения параметров "panel_start_x" и "panel_start_y" равными 0 для координат панели в начале перетаскивания.

Указываем "prev_mouse_state" как 0 для отслеживания состояний щелчка мыши, а для эффектов при наведении курсора мыши определяем "header_hovered", "toggle_hovered", "close_hovered" и "export_hovered" как false для отслеживания состояний при наведении курсора мыши на заголовок и кнопки, а "last_mouse_x" и "last_mouse_y" как 0 для сохранения последнего положения мыши, а "prev_header_hovered", "prev_toggle_hovered", "prev_close_hovered" и "prev_export_hovered" как значение false для обнаружения изменений состояния при наведении курсора.

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

//+------------------------------------------------------------------+
//| Creating label object                                            |
//+------------------------------------------------------------------+
bool createLABEL(string objName, string txt, int xD, int yD, color clrTxt, int fontSize, string font, int anchor, string tooltip = "", bool selectable = false) {
   if(!ObjectCreate(0, objName, OBJ_LABEL, 0, 0, 0)) { //--- Creating label object
      Print(__FUNCTION__, ": Failed to create label '", objName, "'. Error code = ", GetLastError()); //--- Logging creation failure
      return(false);                                   //--- Returning failure
   }
   ObjectSetInteger(0, objName, OBJPROP_XDISTANCE, xD);                  //--- Setting x-coordinate
   ObjectSetInteger(0, objName, OBJPROP_YDISTANCE, yD);                  //--- Setting y-coordinate
   ObjectSetInteger(0, objName, OBJPROP_CORNER, CORNER_LEFT_UPPER);      //--- Setting corner alignment
   ObjectSetString(0, objName, OBJPROP_TEXT, txt);                       //--- Setting text content
   ObjectSetInteger(0, objName, OBJPROP_FONTSIZE, fontSize);             //--- Setting font size
   ObjectSetString(0, objName, OBJPROP_FONT, font);                      //--- Setting font type
   ObjectSetInteger(0, objName, OBJPROP_COLOR, clrTxt);                  //--- Setting text color
   ObjectSetInteger(0, objName, OBJPROP_BACK, false);                    //--- Setting to foreground
   ObjectSetInteger(0, objName, OBJPROP_STATE, selectable);              //--- Setting selectable state
   ObjectSetInteger(0, objName, OBJPROP_SELECTABLE, selectable);         //--- Setting selectability
   ObjectSetInteger(0, objName, OBJPROP_SELECTED, false);                //--- Setting not selected
   ObjectSetInteger(0, objName, OBJPROP_ANCHOR, anchor);                 //--- Setting anchor point
   ObjectSetInteger(0, objName, OBJPROP_ZORDER, settings.zorder_labels); //--- Setting z-order

   //--- added this tooltip feature
   ObjectSetString(0, objName, OBJPROP_TOOLTIP, tooltip == "" ? (selectable ? "Click to sort" : "Position data") : tooltip); //--- Setting tooltip text
   //---
   //--- the existing was a hardcoded to this
   // ObjectSetString(0, objName, OBJPROP_TOOLTIP, selectable ? "Click to sort" : "Position data"); //--- Set tooltip
   //---

   ChartRedraw(0);                                    //--- Redrawing chart
   return(true);                                      //--- Returning success
}

Для функции "createLABEL" мы улучшили логику всплывающих подсказок, чтобы она была более гибкой и пригодной для повторного использования в различных элементах пользовательского интерфейса. Ранее подсказка была жестко запрограммирована с помощью ObjectSetString, устанавливающего OBJPROP_TOOLTIP, либо на "Нажмите для сортировки" для выбираемых меток (labels), либо на "Данные о позиции" для невыбираемых меток, что ограничивало настройку. Теперь мы это изменяем, добавляя параметр "tooltip" с пустой строкой по умолчанию и используем троичное условие в "ObjectSetString" для "OBJPROP_TOOLTIP": если "tooltip" пуст, то по умолчанию используется "Нажмите для сортировки" для выбираемых меток или "Данные о позиции" для других; в противном случае используется предоставленное значение "tooltip". Это изменение позволит использовать специальные подсказки для таких элементов, как кнопки (например, "Свернуть панель" или "Закрыть панель"), сохраняя при этом значения по умолчанию для заголовков и меток данных, улучшая руководство пользователя и упрощая взаимодействие.

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

//+------------------------------------------------------------------+
//| Creating rectangle object                                        |
//+------------------------------------------------------------------+
bool createRectangle(string object_name, int x_distance, int y_distance, int x_size, int y_size,
                     color background_color, color border_color = clrBlack) {
   if(!ObjectCreate(0, object_name, OBJ_RECTANGLE_LABEL, 0, 0, 0)) { //--- Creating rectangle object
      Print(__FUNCTION__, ": Failed to create Rectangle: '", object_name, "'. Error code = ", GetLastError()); //--- Logging creation failure
      return(false);                                  //--- Returning failure
   }
   ObjectSetInteger(0, object_name, OBJPROP_XDISTANCE, x_distance);            //--- Setting x-coordinate
   ObjectSetInteger(0, object_name, OBJPROP_YDISTANCE, y_distance);            //--- Setting y-coordinate
   ObjectSetInteger(0, object_name, OBJPROP_XSIZE, x_size);                    //--- Setting width
   ObjectSetInteger(0, object_name, OBJPROP_YSIZE, y_size);                    //--- Setting height
   ObjectSetInteger(0, object_name, OBJPROP_CORNER, CORNER_LEFT_UPPER);        //--- Setting corner alignment
   ObjectSetInteger(0, object_name, OBJPROP_BGCOLOR, background_color);        //--- Setting background color
   ObjectSetInteger(0, object_name, OBJPROP_BORDER_COLOR, border_color);       //--- Setting border color
   ObjectSetInteger(0, object_name, OBJPROP_BORDER_TYPE, BORDER_FLAT);         //--- Setting border type
   ObjectSetInteger(0, object_name, OBJPROP_BACK, false);                      //--- Setting to foreground
   ObjectSetInteger(0, object_name, OBJPROP_ZORDER, settings.zorder_subpanel); //--- Setting z-order
   return(true);                                                               //--- Returning success
}

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

//+------------------------------------------------------------------+
//| Counting total positions for a symbol                            |
//+------------------------------------------------------------------+
string countPositionsTotal(string symbol) {
   int totalPositions = 0;                               //--- Initializing position counter
   int count_Total_Pos = PositionsTotal();               //--- Getting total positions
   for(int i = count_Total_Pos - 1; i >= 0; i--) {       //--- Iterating through positions
      ulong ticket = PositionGetTicket(i);               //--- Getting position ticket
      if(ticket > 0 && PositionSelectByTicket(ticket)) { //--- Checking if position selected
         if(PositionGetString(POSITION_SYMBOL) == symbol && (MagicNumber < 0 || PositionGetInteger(POSITION_MAGIC) == MagicNumber)) totalPositions++; //--- Checking symbol and magic
      }
   }
   return IntegerToString(totalPositions);               //--- Returning total as string
}

//+------------------------------------------------------------------+
//| Counting buy or sell positions for a symbol                      |
//+------------------------------------------------------------------+
string countPositions(string symbol, ENUM_POSITION_TYPE pos_type) {
   int totalPositions = 0;                               //--- Initializing position counter
   int count_Total_Pos = PositionsTotal();               //--- Getting total positions
   for(int i = count_Total_Pos - 1; i >= 0; i--) {       //--- Iterating through positions
      ulong ticket = PositionGetTicket(i);               //--- Getting position ticket
      if(ticket > 0 && PositionSelectByTicket(ticket)) { //--- Checking if position selected
         if(PositionGetString(POSITION_SYMBOL) == symbol && PositionGetInteger(POSITION_TYPE) == pos_type && (MagicNumber < 0 || PositionGetInteger(POSITION_MAGIC) == MagicNumber)) { //--- Checking symbol, type, magic
            totalPositions++;                            //--- Incrementing counter
         }
      }
   }
   return IntegerToString(totalPositions);               //--- Returning total as string
}

//+------------------------------------------------------------------+
//| Counting pending orders for a symbol                             |
//+------------------------------------------------------------------+
string countOrders(string symbol) {
   int total = 0;                                        //--- Initializing counter
   int tot = OrdersTotal();                              //--- Getting total orders
   for(int i = tot - 1; i >= 0; i--) {                   //--- Iterating through orders
      ulong ticket = OrderGetTicket(i);                  //--- Getting order ticket
      if(ticket > 0 && OrderSelect(ticket)) {            //--- Checking if order selected
         if(OrderGetString(ORDER_SYMBOL) == symbol && (MagicNumber < 0 || OrderGetInteger(ORDER_MAGIC) == MagicNumber)) total++; //--- Checking symbol and magic
      }
   }
   return IntegerToString(total);                        //--- Returning total as string
}

//+------------------------------------------------------------------+
//| Summing double property for positions of a symbol                |
//+------------------------------------------------------------------+
string sumPositionDouble(string symbol, ENUM_POSITION_PROPERTY_DOUBLE prop) {
   double total = 0.0;                                   //--- Initializing total
   int count_Total_Pos = PositionsTotal();               //--- Getting total positions
   for(int i = count_Total_Pos - 1; i >= 0; i--) {       //--- Iterating through positions
      ulong ticket = PositionGetTicket(i);               //--- Getting position ticket
      if(ticket > 0 && PositionSelectByTicket(ticket)) { //--- Checking if position selected
         if(PositionGetString(POSITION_SYMBOL) == symbol && (MagicNumber < 0 || PositionGetInteger(POSITION_MAGIC) == MagicNumber)) { //--- Checking symbol and magic
            total += PositionGetDouble(prop);            //--- Adding property value
         }
      }
   }
   return DoubleToString(total, 2);                      //--- Returning total as string
}

//+------------------------------------------------------------------+
//| Summing commission for positions of a symbol from history        |
//+------------------------------------------------------------------+
double sumPositionCommission(string symbol) {
   double total_comm = 0.0;                             //--- Initializing total commission
   int pos_total = PositionsTotal();                    //--- Getting total positions
   for(int p = 0; p < pos_total; p++) {                 //--- Iterating through positions
      ulong ticket = PositionGetTicket(p);              //--- Getting position ticket
      if(ticket > 0 && PositionSelectByTicket(ticket)) { //--- Checking if selected
         if(PositionGetString(POSITION_SYMBOL) == symbol && (MagicNumber < 0 || PositionGetInteger(POSITION_MAGIC) == MagicNumber)) { //--- Checking symbol and magic
            long pos_id = PositionGetInteger(POSITION_IDENTIFIER); //--- Getting position ID
            if(HistorySelectByPosition(pos_id)) {       //--- Selecting history by position
               int deals_total = HistoryDealsTotal();   //--- Getting total deals
               for(int d = 0; d < deals_total; d++) {   //--- Iterating through deals
                  ulong deal_ticket = HistoryDealGetTicket(d); //--- Getting deal ticket
                  if(deal_ticket > 0) {                 //--- Checking valid
                     total_comm += HistoryDealGetDouble(deal_ticket, DEAL_COMMISSION); //--- Adding commission
                  }
               }
            }
         }
      }
   }
   return total_comm;                                  //--- Returning total commission
}  

//+------------------------------------------------------------------+
//| Collecting active symbols with positions or orders               |
//+------------------------------------------------------------------+
void CollectActiveSymbols() {
   string symbols_temp[];                             //--- Temporary array for symbols
   int added = 0;                                     //--- Counter for added symbols
   // Collecting from positions
   int pos_total = PositionsTotal();                  //--- Getting total positions
   for(int i = pos_total - 1; i >= 0; i--) {          //--- Iterating through positions
      ulong ticket = PositionGetTicket(i);            //--- Getting position ticket
      if(ticket > 0 && PositionSelectByTicket(ticket)) { //--- Checking if position selected
         if(MagicNumber < 0 || PositionGetInteger(POSITION_MAGIC) == MagicNumber) { //--- Checking magic number
            string sym = PositionGetString(POSITION_SYMBOL); //--- Getting symbol
            bool found = false;                       //--- Flag for symbol found
            for(int k = 0; k < added; k++) {          //--- Checking existing symbols
               if(symbols_temp[k] == sym) {           //--- Symbol already added
                  found = true;                       //--- Setting found flag
                  break;                              //--- Exiting loop
               }
            }
            if(!found) {                              //--- If not found
               ArrayResize(symbols_temp, added + 1);  //--- Resizing array
               symbols_temp[added] = sym;             //--- Adding symbol
               added++;                               //--- Incrementing counter
            }
         }
      }
   }
   // Collecting from orders
   int ord_total = OrdersTotal();                     //--- Getting total orders
   for(int i = ord_total - 1; i >= 0; i--) {          //--- Iterating through orders
      ulong ticket = OrderGetTicket(i);               //--- Getting order ticket
      if(ticket > 0 && OrderSelect(ticket)) {         //--- Checking if order selected
         if(MagicNumber < 0 || OrderGetInteger(ORDER_MAGIC) == MagicNumber) { //--- Checking magic number
            string sym = OrderGetString(ORDER_SYMBOL); //--- Getting symbol
            bool found = false;                       //--- Flag for symbol found
            for(int k = 0; k < added; k++) {          //--- Checking existing symbols
               if(symbols_temp[k] == sym) {           //--- Symbol already added
                  found = true;                       //--- Setting found flag
                  break;                              //--- Exiting loop
               }
            }
            if(!found) {                             //--- If not found
               ArrayResize(symbols_temp, added + 1); //--- Resizing array
               symbols_temp[added] = sym;            //--- Adding symbol
               added++;                              //--- Incrementing counter
            }
         }
      }
   }
   // Setting symbol_data
   ArrayResize(symbol_data, added);                   //--- Resizing symbol data array
   for(int i = 0; i < added; i++) {                   //--- Iterating through added symbols
      symbol_data[i].name = symbols_temp[i];          //--- Setting symbol name
      symbol_data[i].buys = 0;                        //--- Initializing buys
      symbol_data[i].sells = 0;                       //--- Initializing sells
      symbol_data[i].trades = 0;                      //--- Initializing trades
      symbol_data[i].lots = 0.0;                      //--- Initializing lots
      symbol_data[i].profit = 0.0;                    //--- Initializing profit
      symbol_data[i].pending = 0;                     //--- Initializing pending
      symbol_data[i].swaps = 0.0;                     //--- Initializing swaps
      symbol_data[i].comm = 0.0;                      //--- Initializing commission
      symbol_data[i].buys_str = "0";                  //--- Initializing buys string
      symbol_data[i].sells_str = "0";                 //--- Initializing sells string
      symbol_data[i].trades_str = "0";                //--- Initializing trades string
      symbol_data[i].lots_str = "0.00";               //--- Initializing lots string
      symbol_data[i].profit_str = "0.00";             //--- Initializing profit string
      symbol_data[i].pending_str = "0";               //--- Initializing pending string
      symbol_data[i].swaps_str = "0.00";              //--- Initializing swaps string
      symbol_data[i].comm_str = "0.00";               //--- Initializing commission string
   }
}

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

//+------------------------------------------------------------------+
//| Creating full dashboard UI                                       |
//+------------------------------------------------------------------+
void createFullDashboard() {
   CollectActiveSymbols();                            //--- Collecting active symbols
   int num_rows = ArraySize(symbol_data);             //--- Getting number of rows
   int num_columns = ArraySize(headers);              //--- Getting number of columns
   int column_width_sum = 0;                          //--- Initializing column width sum
   for(int i = 0; i < num_columns; i++)               //--- Iterating through columns
      column_width_sum += column_widths[i];           //--- Adding column width
   int panel_width = MathMax(settings.header_x_distances[num_columns - 1] + column_widths[num_columns - 1], column_width_sum) + 20 + settings.label_x_offset; //--- Calculating panel width
   // Creating main panel
   string panel_name = PREFIX + PANEL;                //--- Defining main panel name
   createRectangle(panel_name, settings.panel_x, settings.panel_y, panel_width, (num_rows + 3) * settings.row_height, settings.bg_color, settings.border_color); //--- Creating main panel
   // Creating header panel
   string header_panel = PREFIX + HEADER_PANEL;       //--- Defining header panel name
   createRectangle(header_panel, settings.panel_x, settings.panel_y, panel_width, settings.row_height, settings.section_bg_color, settings.border_color); //--- Creating header panel
   // Creating header title
   createLABEL(PREFIX + HEADER_PANEL_TEXT, "Trading Dashboard", settings.panel_x + 10, settings.panel_y + 8 + settings.label_y_offset, clrBlack, 14, settings.font, ANCHOR_LEFT, "Dashboard Title"); //--- Creating header title
   // Creating export button
   createLABEL(PREFIX + EXPORT_BUTTON, CharToString(60), settings.panel_x + panel_width - 90, settings.panel_y + 12, clrBlack, 18, "Wingdings", ANCHOR_CENTER, "Click to export or press 'E' key to export", true); //--- Creating export button
   // Creating toggle button
   createLABEL(PREFIX + TOGGLE_BUTTON, CharToString('r'), settings.panel_x + panel_width - 60, settings.panel_y + 12, clrBlack, 18, "Wingdings", ANCHOR_CENTER, "Minimize dashboard", true); //--- Creating toggle button
   // Creating close button
   createLABEL(PREFIX + CLOSE_BUTTON, CharToString('r'), settings.panel_x + panel_width - 30, settings.panel_y + 12, clrBlack, 18, "Webdings", ANCHOR_CENTER, "Close dashboard", true); //--- Creating close button
   // Creating headers
   int header_y = settings.panel_y + settings.row_height + 8 + settings.label_y_offset; //--- Calculating header y-coordinate
   for(int i = 0; i < num_columns; i++) {             //--- Iterating through headers
      string header_name = PREFIX + HEADER + IntegerToString(i); //--- Defining header label name
      int header_x = settings.panel_x + settings.header_x_distances[i] + settings.label_x_offset; //--- Calculating header x-coordinate
      createLABEL(header_name, headers[i], header_x, header_y, settings.header_color, 12, settings.font, ANCHOR_LEFT, "Click to sort", true); //--- Creating header label
   }
   // Creating symbol and data labels
   int first_row_y = header_y + settings.row_height;  //--- Calculating first row y-coordinate
   int symbol_x = settings.panel_x + 10 + settings.label_x_offset; //--- Setting symbol x-coordinate
   for(int i = 0; i < num_rows; i++) {                //--- Iterating through rows
      string symbol_name = PREFIX + SYMB + IntegerToString(i); //--- Defining symbol label name
      createLABEL(symbol_name, symbol_data[i].name, symbol_x, first_row_y + i * settings.row_height + settings.label_y_offset, settings.text_color, settings.font_size, settings.font, ANCHOR_LEFT, "Symbol name"); //--- Creating symbol label
      int x_offset = settings.panel_x + 10 + column_widths[0] + settings.label_x_offset; //--- Setting data x-offset
      for(int j = 0; j < num_columns - 1; j++) {      //--- Iterating through data columns
         string data_name = PREFIX + DATA + IntegerToString(i) + "_" + IntegerToString(j); //--- Defining data label name
         color initial_color = data_default_colors[j]; //--- Setting initial color
         string initial_txt = (j <= 2 || j == 5) ? "0" : "0.00"; //--- Setting initial text
         createLABEL(data_name, initial_txt, x_offset, first_row_y + i * settings.row_height + settings.label_y_offset, initial_color, settings.font_size, settings.font, ANCHOR_RIGHT, "Data value"); //--- Creating data label
         x_offset += column_widths[j + 1];            //--- Updating x-offset
      }
   }
   // Creating footer panel
   int footer_y = settings.panel_y + (num_rows + 3) * settings.row_height - settings.row_height; //--- Calculating footer y-coordinate
   string footer_panel = PREFIX + FOOTER_PANEL;       //--- Defining footer panel name
   createRectangle(footer_panel, settings.panel_x, footer_y, panel_width, settings.row_height, settings.section_bg_color, settings.border_color); //--- Creating footer panel
   // Creating footer text and data
   int footer_text_x = settings.panel_x + 10 + settings.label_x_offset; //--- Setting footer text x-coordinate
   createLABEL(PREFIX + FOOTER_TEXT, "Total:", footer_text_x, footer_y + 8 + settings.label_y_offset, settings.text_color, settings.font_size, settings.font, ANCHOR_LEFT, "Totals"); //--- Creating footer text label
   int x_offset = settings.panel_x + 10 + column_widths[0] + settings.label_x_offset; //--- Setting footer data x-offset
   for(int j = 0; j < num_columns - 1; j++) {         //--- Iterating through footer data
      string footer_data_name = PREFIX + FOOTER_DATA + IntegerToString(j); //--- Defining footer data label name
      color footer_color = data_default_colors[j];    //--- Setting footer data color
      string initial_txt = (j <= 2 || j == 5) ? "0" : "0.00"; //--- Setting initial text
      createLABEL(footer_data_name, initial_txt, x_offset, footer_y + 8 + settings.label_y_offset, footer_color, settings.font_size, settings.font, ANCHOR_RIGHT, "Total value"); //--- Creating footer data label
      x_offset += column_widths[j + 1];               //--- Updating x-offset
   }
   // Creating account panel
   int account_panel_y = footer_y + settings.row_height + 5; //--- Calculating account panel y-coordinate
   string account_panel_name = PREFIX + ACCOUNT_PANEL; //--- Defining account panel name
   createRectangle(account_panel_name, settings.panel_x, account_panel_y, panel_width, settings.row_height, settings.section_bg_color, settings.border_color); //--- Creating account panel
   // Creating account text and data labels
   int acc_x = settings.panel_x + 10 + settings.label_x_offset; //--- Setting account label x-coordinate
   int acc_data_offset = 160;                         //--- Setting data offset
   int acc_spacing = (panel_width - 45) / ArraySize(account_items); //--- Calculating spacing
   for(int k = 0; k < ArraySize(account_items); k++) { //--- Iterating through account items
      string acc_text_name = PREFIX + ACC_TEXT + IntegerToString(k); //--- Defining account text label name
      int text_x = acc_x + k * acc_spacing;          //--- Calculating text x-coordinate
      createLABEL(acc_text_name, account_items[k] + ":", text_x, account_panel_y + 8 + settings.label_y_offset, settings.text_color, settings.font_size, settings.font, ANCHOR_LEFT, "Account info"); //--- Creating account text label
      string acc_data_name = PREFIX + ACC_DATA + IntegerToString(k); //--- Defining account data label name
      int data_x = text_x + acc_data_offset;         //--- Calculating data x-coordinate
      createLABEL(acc_data_name, "0.00", data_x, account_panel_y + 8 + settings.label_y_offset, settings.text_color, settings.font_size, settings.font, ANCHOR_RIGHT, "Account value"); //--- Creating account data label
   }
   ChartRedraw(0);                                    //--- Redrawing chart
}

//+------------------------------------------------------------------+
//| Creating minimized dashboard UI                                  |
//+------------------------------------------------------------------+
void createMinimizedDashboard() {
   int num_columns = ArraySize(headers);              //--- Getting number of columns
   int column_width_sum = 0;                          //--- Initializing column width sum
   for(int i = 0; i < num_columns; i++)               //--- Iterating through columns
      column_width_sum += column_widths[i];           //--- Adding column width
   int panel_width = MathMax(settings.header_x_distances[num_columns - 1] + column_widths[num_columns - 1], column_width_sum) + 20 + settings.label_x_offset; //--- Calculating panel width
   // Creating header panel
   createRectangle(PREFIX + HEADER_PANEL, settings.panel_x, settings.panel_y, panel_width, settings.row_height, settings.section_bg_color, settings.border_color); //--- Creating header panel
   // Creating header title
   createLABEL(PREFIX + HEADER_PANEL_TEXT, "Trading Dashboard", settings.panel_x + 10, settings.panel_y + 8 + settings.label_y_offset, clrBlack, 14, settings.font, ANCHOR_LEFT, "Dashboard Title"); //--- Creating header title
   // Creating export button
   createLABEL(PREFIX + EXPORT_BUTTON, CharToString(60), settings.panel_x + panel_width - 90, settings.panel_y + 12, clrBlack, 18, "Wingdings", ANCHOR_CENTER, "Click to export or press 'E' key to export", true); //--- Creating export button
   // Creating toggle button (maximize)
   createLABEL(PREFIX + TOGGLE_BUTTON, CharToString('o'), settings.panel_x + panel_width - 60, settings.panel_y + 12, clrBlack, 18, "Wingdings", ANCHOR_CENTER, "Maximize dashboard", true); //--- Creating toggle button
   // Creating close button
   createLABEL(PREFIX + CLOSE_BUTTON, CharToString('r'), settings.panel_x + panel_width - 30, settings.panel_y + 12, clrBlack, 18, "Webdings", ANCHOR_CENTER, "Close dashboard", true); //--- Creating close button
   ChartRedraw(0);                                    //--- Redrawing chart
}

//+------------------------------------------------------------------+
//| Deleting all dashboard objects                                   |
//+------------------------------------------------------------------+
void deleteAllObjects() {
   ObjectsDeleteAll(0, PREFIX, -1, -1);               //--- Deleting all objects with prefix
}

Реализуем функции "createFullDashboard", "createMinimizedDashboard" и "deleteAllObjects" для управления пользовательским интерфейсом, поддерживающим развернутый и свернутый виды с интерактивными элементами. В "createFullDashboard" вызываем "CollectActiveSymbols" для заполнения "symbol_data", рассчитываем "num_rows" и "num_columns" с помощью ArraySize и вычисляем "panel_width", используя "column_widths" и "settings.header_x_distances". Создаем основную панель с помощью "createRectangle" для "PREFIX + PANEL", панель заголовка с помощью "PREFIX + HEADER_PANEL" и название заголовка с помощью "createLABEL" для "PREFIX + HEADER_PANEL_TEXT" как "Торговая панель".

Добавляем кнопки с помощью "createLABEL" для "PREFIX + EXPORT_BUTTON" (Wingdings 60), "PREFIX + TOGGLE_BUTTON" (Wingdings 'r' для сворачивания) и "PREFIX + CLOSE_BUTTON" (Webdings 'r'), все с конкретными всплывающими подсказками и возможностью выбора значения true. Выбор стилей иконок зависит от вас. Вот краткое описание того, что вы могли бы использовать для шрифтов. Просто используйте точный символ и тип знаков.

SYMBOL FONTS

Затем создаем метки заголовков для "headers" в вычисляемых позициях, метки символов для "symbol_data[i].name", метки данных с начальными значениями, нижнюю панель с помощью "PREFIX + FOOTER_PANEL", текст нижней панели "Total:", метки данных нижней панели, панель информации о счёте с помощью "PREFIX + ACCOUNT_PANEL" и метки счетов для "account_items", все с использованием "createRectangle" и "createLABEL" с соответствующими координатами и цветами, за которыми следует функция ChartRedraw. 

В "createMinimizedDashboard" создаем компактный пользовательский интерфейс, содержащий только панель заголовка, используя "createRectangle" для "PREFIX + HEADER_PANEL", название заголовка с помощью "createLABEL" для "PREFIX + HEADER_PANEL_TEXT" и кнопки для экспорта (Wingdings 60), переключения (Wingdings 'o' для разворачивания) и закрытия (Webdings 'r'), обеспечивающий минимальное использование экрана, и перерисовываем.

Функция "deleteAllObjects" удаляет все объекты панели с помощью ObjectsDeleteAll, используя "PREFIX" для всех графиков и типов, обеспечивая чистый лист для обновлений пользовательского интерфейса или закрытия. Эти функции позволят создать гибкую панель с развернутым и свернутым состояниями, поддерживающую такие взаимодействия с пользователем, как перетаскивание и переключение. Теперь перейдем к обновлению функции панели, используя эти динамические функции.

//+------------------------------------------------------------------+
//| Updating dashboard data and visuals                              |
//+------------------------------------------------------------------+
void UpdateDashboard() {
   bool needs_redraw = false;                //--- Initializing redraw flag
   CollectActiveSymbols();                   //--- Collecting active symbols
   int current_num = ArraySize(symbol_data); //--- Getting current number of symbols
   if(current_num != prev_num_symbols) {     //--- Checking if symbol count changed
      deleteAllObjects();                    //--- Deleting all objects
      if(panel_minimized) {                  //--- Checking if minimized
         createMinimizedDashboard();         //--- Creating minimized dashboard
      } else {
         createFullDashboard();              //--- Creating full dashboard
      }
      prev_num_symbols = current_num;        //--- Updating previous symbol count
      needs_redraw = true;                   //--- Setting redraw flag
   }
   if(!panel_is_visible || panel_minimized) return; //--- Exiting if not visible or minimized
   // Resetting totals
   totalBuys = 0;     //--- Resetting total buys
   totalSells = 0;    //--- Resetting total sells
   totalTrades = 0;   //--- Resetting total trades
   totalLots = 0.0;   //--- Resetting total lots
   totalProfit = 0.0; //--- Resetting total profit
   totalPending = 0;  //--- Resetting total pending
   totalSwap = 0.0;   //--- Resetting total swap
   totalComm = 0.0;   //--- Resetting total commission
   // Calculating symbol data and totals
   for(int i = 0; i < current_num; i++) {           //--- Iterating through symbols
      string symbol = symbol_data[i].name;          //--- Getting symbol name
      for(int j = 0; j < 8; j++) {                  //--- Iterating through data columns
         string value = "";                         //--- Initializing value
         color data_color = data_default_colors[j]; //--- Setting default color
         double dval = 0.0;                         //--- Initializing double value
         int ival = 0;                              //--- Initializing integer value
         switch(j) {                                //--- Handling data type
            case 0:                                 // Buy positions
               value = countPositions(symbol, POSITION_TYPE_BUY); //--- Getting buy positions
               ival = (int)StringToInteger(value);  //--- Converting to integer
               if(value != symbol_data[i].buys_str) { //--- Checking if changed
                  symbol_data[i].buys_str = value;  //--- Updating buys string
                  symbol_data[i].buys = ival;       //--- Updating buys count
               }
               totalBuys += ival;                   //--- Adding to total buys
               break;
            case 1:                                 // Sell positions
               value = countPositions(symbol, POSITION_TYPE_SELL); //--- Getting sell positions
               ival = (int)StringToInteger(value);  //--- Converting to integer
               if(value != symbol_data[i].sells_str) { //--- Checking if changed
                  symbol_data[i].sells_str = value; //--- Updating sells string
                  symbol_data[i].sells = ival;      //--- Updating sells count
               }
               totalSells += ival; //--- Adding to total sells
               break;
            case 2: // Total trades
               value = countPositionsTotal(symbol); //--- Getting total trades
               ival = (int)StringToInteger(value);  //--- Converting to integer
               if(value != symbol_data[i].trades_str) { //--- Checking if changed
                  symbol_data[i].trades_str = value; //--- Updating trades string
                  symbol_data[i].trades = ival;     //--- Updating trades count
               }
               totalTrades += ival;                 //--- Adding to total trades
               break;
            case 3: // Lots
               value = sumPositionDouble(symbol, POSITION_VOLUME); //--- Getting total lots
               dval = StringToDouble(value);        //--- Converting to double
               if(value != symbol_data[i].lots_str) { //--- Checking if changed
                  symbol_data[i].lots_str = value; //--- Updating lots string
                  symbol_data[i].lots = dval;      //--- Updating lots value
               }
               totalLots += dval;                  //--- Adding to total lots
               break;
            case 4: // Profit
               value = sumPositionDouble(symbol, POSITION_PROFIT); //--- Getting total profit
               dval = StringToDouble(value);      //--- Converting to double
               data_color = (dval > 0) ? clrGreen : (dval < 0) ? clrRed : clrGray; //--- Setting color based on value
               if(value != symbol_data[i].profit_str) { //--- Checking if changed
                  symbol_data[i].profit_str = value; //--- Updating profit string
                  symbol_data[i].profit = dval;  //--- Updating profit value
               }
               totalProfit += dval;              //--- Adding to total profit
               break;
            case 5: // Pending
               value = countOrders(symbol);      //--- Getting pending orders
               ival = (int)StringToInteger(value); //--- Converting to integer
               if(value != symbol_data[i].pending_str) { //--- Checking if changed
                  symbol_data[i].pending_str = value; //--- Updating pending string
                  symbol_data[i].pending = ival; //--- Updating pending count
               }
               totalPending += ival;             //--- Adding to total pending
               break;
            case 6: // Swap
               value = sumPositionDouble(symbol, POSITION_SWAP); //--- Getting total swap
               dval = StringToDouble(value);     //--- Converting to double
               data_color = (dval > 0) ? clrGreen : (dval < 0) ? clrRed : clrPurple; //--- Setting color based on value
               if(value != symbol_data[i].swaps_str) { //--- Checking if changed
                  symbol_data[i].swaps_str = value;    //--- Updating swap string
                  symbol_data[i].swaps = dval;  //--- Updating swap value
               }
               totalSwap += dval; //--- Adding to total swap
               break;
            case 7: // Comm
               dval = sumPositionCommission(symbol); //--- Getting total commission
               value = DoubleToString(dval, 2);      //--- Formatting commission
               data_color = (dval > 0) ? clrGreen : (dval < 0) ? clrRed : clrBrown; //--- Setting color based on value
               if(value != symbol_data[i].comm_str) { //--- Checking if changed
                  symbol_data[i].comm_str = value;   //--- Updating commission string
                  symbol_data[i].comm = dval;        //--- Updating commission value
               }
               totalComm += dval;                    //--- Adding to total commission
               break;
         }
      }
   }
   // Sort after calculating values
   SortDashboard(); //--- Sorting dashboard data
   // Update header breathing effect
   glow_counter += MathMax(UpdateIntervalMs, 10); //--- Incrementing glow counter
   if(glow_counter >= GLOW_INTERVAL_MS) { //--- Checking if glow interval reached
      if(glow_direction) { //--- Checking if glowing forward
         glow_index++; //--- Incrementing glow index
         if(glow_index >= ArraySize(settings.header_shades) - 1) //--- Checking if at end
            glow_direction = false; //--- Reversing glow direction
      } else { //--- Glow backward
         glow_index--; //--- Decrementing glow index
         if(glow_index <= 0) //--- Checking if at start
            glow_direction = true; //--- Reversing glow direction
      }
      glow_counter = 0; //--- Resetting glow counter
   }
   color header_shade = settings.header_shades[glow_index]; //--- Getting current header shade
   for(int i = 0; i < ArraySize(headers); i++) { //--- Iterating through headers
      string header_name = PREFIX + HEADER + IntegerToString(i); //--- Defining header name
      ObjectSetInteger(0, header_name, OBJPROP_COLOR, header_shade); //--- Updating header color
      needs_redraw = true; //--- Setting redraw flag
   }
   // Update symbol and data labels
   bool labels_updated = false; //--- Initializing label update flag
   for(int i = 0; i < current_num; i++) { //--- Iterating through symbols
      string symbol = symbol_data[i].name; //--- Getting symbol name
      string symb_name = PREFIX + SYMB + IntegerToString(i); //--- Defining symbol label name
      string current_symb_txt = ObjectGetString(0, symb_name, OBJPROP_TEXT); //--- Getting current symbol text
      if(current_symb_txt != symbol) { //--- Checking if symbol changed
         ObjectSetString(0, symb_name, OBJPROP_TEXT, symbol); //--- Updating symbol text
         labels_updated = true; //--- Setting label updated flag
      }
      for(int j = 0; j < 8; j++) { //--- Iterating through data columns
         string data_name = PREFIX + DATA + IntegerToString(i) + "_" + IntegerToString(j); //--- Defining data label name
         string value; //--- Initializing value
         color data_color = data_default_colors[j]; //--- Setting default color
         switch(j) { //--- Handling data type
            case 0: //--- Buy positions
               value = symbol_data[i].buys_str; //--- Getting buys string
               data_color = clrRed; //--- Setting color to red
               break;
            case 1: //--- Sell positions
               value = symbol_data[i].sells_str; //--- Getting sells string
               data_color = clrGreen; //--- Setting color to green
               break;
            case 2: // Total trades
               value = symbol_data[i].trades_str;
               data_color = clrDarkGray;
               break;               
            case 3: //--- Lots
               value = symbol_data[i].lots_str; //--- Getting lots string
               data_color = clrOrange; //--- Setting color to orange
               break;
            case 4: //--- Profit
               value = symbol_data[i].profit_str; //--- Getting profit string
               data_color = (symbol_data[i].profit > 0) ? clrGreen : (symbol_data[i].profit < 0) ? clrRed : clrGray; //--- Setting color based on profit
               break;
            case 5: //--- Pending
               value = symbol_data[i].pending_str; //--- Getting pending string
               data_color = clrBlue; //--- Setting color to blue
               break;
            case 6: //--- Swap
               value = symbol_data[i].swaps_str; //--- Getting swap string
               data_color = (symbol_data[i].swaps > 0) ? clrGreen : (symbol_data[i].swaps < 0) ? clrRed : clrPurple; //--- Setting color based on swap
               break;
            case 7: //--- Comm
               value = symbol_data[i].comm_str; //--- Getting commission string
               data_color = (symbol_data[i].comm > 0) ? clrGreen : (symbol_data[i].comm < 0) ? clrRed : clrBrown; //--- Setting color based on commission
               break;
         }
         if(updateLABEL(data_name, value, data_color)) labels_updated = true; //--- Updating label if changed
      }
   }
   if(labels_updated) needs_redraw = true; //--- Setting redraw flag if labels updated
   // Updating totals
   string new_total_buys = IntegerToString(totalBuys); //--- Formatting total buys
   if(new_total_buys != total_buys_str) { //--- Checking if changed
      total_buys_str = new_total_buys; //--- Updating buys string
      if(updateLABEL(PREFIX + FOOTER_DATA + "0", new_total_buys, clrRed)) needs_redraw = true; //--- Updating label
   }
   string new_total_sells = IntegerToString(totalSells); //--- Formatting total sells
   if(new_total_sells != total_sells_str) { //--- Checking if changed
      total_sells_str = new_total_sells; //--- Updating sells string
      if(updateLABEL(PREFIX + FOOTER_DATA + "1", new_total_sells, clrGreen)) needs_redraw = true; //--- Updating label
   }
   string new_total_trades = IntegerToString(totalTrades); //--- Formatting total trades
   if(new_total_trades != total_trades_str) { //--- Checking if changed
      total_trades_str = new_total_trades; //--- Updating trades string
      if(updateLABEL(PREFIX + FOOTER_DATA + "2", new_total_trades, clrDarkGray)) needs_redraw = true; //--- Updating label
   }
   string new_total_lots = DoubleToString(totalLots, 2); //--- Formatting total lots
   if(new_total_lots != total_lots_str) { //--- Checking if changed
      total_lots_str = new_total_lots; //--- Updating lots string
      if(updateLABEL(PREFIX + FOOTER_DATA + "3", new_total_lots, clrOrange)) needs_redraw = true; //--- Updating label
   }
   string new_total_profit = DoubleToString(totalProfit, 2); //--- Formatting total profit
   color total_profit_color = (totalProfit > 0) ? clrGreen : (totalProfit < 0) ? clrRed : clrGray; //--- Setting color based on profit
   if(new_total_profit != total_profit_str) { //--- Checking if changed
      total_profit_str = new_total_profit; //--- Updating profit string
      if(updateLABEL(PREFIX + FOOTER_DATA + "4", new_total_profit, total_profit_color)) needs_redraw = true; //--- Updating label
   }
   string new_total_pending = IntegerToString(totalPending); //--- Formatting total pending
   if(new_total_pending != total_pending_str) { //--- Checking if changed
      total_pending_str = new_total_pending; //--- Updating pending string
      if(updateLABEL(PREFIX + FOOTER_DATA + "5", new_total_pending, clrBlue)) needs_redraw = true; //--- Updating label
   }
   string new_total_swap = DoubleToString(totalSwap, 2); //--- Formatting total swap
   color total_swap_color = (totalSwap > 0) ? clrGreen : (totalSwap < 0) ? clrRed : clrPurple; //--- Setting color based on swap
   if(new_total_swap != total_swap_str) { //--- Checking if changed
      total_swap_str = new_total_swap; //--- Updating swap string
      if(updateLABEL(PREFIX + FOOTER_DATA + "6", new_total_swap, total_swap_color)) needs_redraw = true; //--- Updating label
   }
   string new_total_comm = DoubleToString(totalComm, 2); //--- Formatting total commission
   color total_comm_color = (totalComm > 0) ? clrGreen : (totalComm < 0) ? clrRed : clrBrown; //--- Setting color based on commission
   if(new_total_comm != total_comm_str) { //--- Checking if changed
      total_comm_str = new_total_comm; //--- Updating commission string
      if(updateLABEL(PREFIX + FOOTER_DATA + "7", new_total_comm, total_comm_color)) needs_redraw = true; //--- Updating label
   }
   // Updating account info
   double balance = AccountInfoDouble(ACCOUNT_BALANCE); //--- Getting account balance
   double equity = AccountInfoDouble(ACCOUNT_EQUITY); //--- Getting account equity
   double free_margin = AccountInfoDouble(ACCOUNT_MARGIN_FREE); //--- Getting free margin
   string new_bal = DoubleToString(balance, 2); //--- Formatting balance
   if(new_bal != acc_bal_str) { //--- Checking if changed
      acc_bal_str = new_bal; //--- Updating balance string
      if(updateLABEL(PREFIX + ACC_DATA + "0", new_bal, clrBlack)) needs_redraw = true; //--- Updating label
   }
   string new_eq = DoubleToString(equity, 2); //--- Formatting equity
   color eq_color = (equity > balance) ? clrGreen : (equity < balance) ? clrRed : clrBlack; //--- Setting color based on equity
   if(new_eq != acc_eq_str) { //--- Checking if changed
      acc_eq_str = new_eq; //--- Updating equity string
      if(updateLABEL(PREFIX + ACC_DATA + "1", new_eq, eq_color)) needs_redraw = true; //--- Updating label
   }
   string new_free = DoubleToString(free_margin, 2); //--- Formatting free margin
   if(new_free != acc_free_str) { //--- Checking if changed
      acc_free_str = new_free; //--- Updating free margin string
      if(updateLABEL(PREFIX + ACC_DATA + "2", new_free, clrBlack)) needs_redraw = true; //--- Updating label
   }
   if(needs_redraw) { //--- Checking if redraw needed
      ChartRedraw(0); //--- Redrawing chart
   }
}

К функции "UpdateDashboard" добавляем вызовы "CollectActiveSymbols" в начале, поэтому мы всегда обновляем поля итоговых значений и баланса. Затем, когда количество символов изменяется, вызываем функцию "deleteAllObjects", чтобы уничтожить панель и воссоздать ее с помощью функции "createFullDashboard" или "createMinimizedDashboard". В цикле расчета данных мы добавили цветовую логику для прибыли/свопа/комиссии, которая ранее была частичной. Мы выделили области изменений для облегчения идентификации и внесения ясности. Наконец, теперь мы можем вызвать нашу логику при инициализации, чтобы увидеть достижение важного этапа.

//+------------------------------------------------------------------+
//| Initializing expert                                              |
//+------------------------------------------------------------------+
int OnInit() {
   createFullDashboard();                             //--- Creating full dashboard
   prev_num_symbols = ArraySize(symbol_data);         //--- Setting initial symbol count
   ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true);  //--- Enabling mouse move events
   EventSetMillisecondTimer(MathMax(UpdateIntervalMs, 10)); //--- Setting timer with minimum 10ms
   UpdateDashboard();                                 //--- Updating dashboard
   return(INIT_SUCCEEDED);                            //--- Returning success
}

//+------------------------------------------------------------------+
//| Deinitializing expert                                            |
//+------------------------------------------------------------------+
void OnDeinit(const int reason) {
   deleteAllObjects();                                //--- Deleting all objects
   ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, false); //--- Disabling mouse move events
   EventKillTimer();                                  //--- Stopping timer
}

//+------------------------------------------------------------------+
//| Handling timer for millisecond-based updates                     |
//+------------------------------------------------------------------+
void OnTimer() {
   if(panel_is_visible && !panel_minimized) {         //--- Checking if visible and not minimized
      UpdateDashboard();                              //--- Updating dashboard
   }
}

Здесь мы реализуем обработчики событий OnInit, "OnDeinit" и "OnTimer" для управления жизненным циклом и обновлениями панели, обеспечивая ее интерактивную и динамическую функциональность. В функции "OnInit" вызываем "createFullDashboard", чтобы создать полный пользовательский интерфейс, устанавливаем для "prev_num_symbols" размер "symbol_data", используя ArraySize для отслеживания начальных символов, включаем события перемещения мыши с помощью ChartSetInteger, устанавливая для CHART_EVENT_MOUSE_MOVE значение true для эффектов перетаскивания и наведения курсора мыши, устанавливаем таймер с помощью EventSetMillisecondTimer, используя максимальное значение из "UpdateIntervalMs" и 10 мс для периодических обновлений и вызываем "UpdateDashboard" для заполнения исходных данных, возвращая "INIT_SUCCEEDED" для успешной инициализации.

Функция OnDeinit выполняет очистку посредством вызова "deleteAllObjects", чтобы удалить все объекты панели с "PREFIX", отключая события перемещения мыши с помощью "ChartSetInteger", устанавливая для "CHART_EVENT_MOUSE_MOVE" значение false и останавливая таймер с помощью EventKillTimer для освобождения ресурсов.

В функции OnTimer проверяем, имеет ли "panel_is_visible" значение true, а "panel_minimized" значение false, затем вызываем "UpdateDashboard" для обновления данных только тогда, когда панель полностью видна, обеспечивая эффективное обновление без обработки в свернутом или скрытом состояниях. После компиляции получаем следующий результат.

INITIALIZATION FEATURES

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

//+------------------------------------------------------------------+
//| Updating panel object positions                                  |
//+------------------------------------------------------------------+
void updatePanelPositions() {
   int num_rows = ArraySize(symbol_data);             //--- Getting number of rows
   int num_columns = ArraySize(headers);              //--- Getting number of columns
   int column_width_sum = 0;                          //--- Initializing column width sum
   for(int i = 0; i < num_columns; i++)               //--- Iterating through columns
      column_width_sum += column_widths[i];           //--- Adding column width
   int panel_width = MathMax(settings.header_x_distances[num_columns - 1] + column_widths[num_columns - 1], column_width_sum) + 20 + settings.label_x_offset; //--- Calculating panel width
   // Updating header panel and buttons
   ObjectSetInteger(0, PREFIX + HEADER_PANEL, OBJPROP_XDISTANCE, settings.panel_x); //--- Updating header panel x-coordinate
   ObjectSetInteger(0, PREFIX + HEADER_PANEL, OBJPROP_YDISTANCE, settings.panel_y); //--- Updating header panel y-coordinate
   ObjectSetInteger(0, PREFIX + HEADER_PANEL_TEXT, OBJPROP_XDISTANCE, settings.panel_x + 10); //--- Updating header text x-coordinate
   ObjectSetInteger(0, PREFIX + HEADER_PANEL_TEXT, OBJPROP_YDISTANCE, settings.panel_y + 8 + settings.label_y_offset); //--- Updating header text y-coordinate
   ObjectSetInteger(0, PREFIX + EXPORT_BUTTON, OBJPROP_XDISTANCE, settings.panel_x + panel_width - 90); //--- Updating export button x-coordinate
   ObjectSetInteger(0, PREFIX + EXPORT_BUTTON, OBJPROP_YDISTANCE, settings.panel_y + 12); //--- Updating export button y-coordinate
   ObjectSetInteger(0, PREFIX + TOGGLE_BUTTON, OBJPROP_XDISTANCE, settings.panel_x + panel_width - 60); //--- Updating toggle button x-coordinate
   ObjectSetInteger(0, PREFIX + TOGGLE_BUTTON, OBJPROP_YDISTANCE, settings.panel_y + 12); //--- Updating toggle button y-coordinate
   ObjectSetInteger(0, PREFIX + CLOSE_BUTTON, OBJPROP_XDISTANCE, settings.panel_x + panel_width - 30); //--- Updating close button x-coordinate
   ObjectSetInteger(0, PREFIX + CLOSE_BUTTON, OBJPROP_YDISTANCE, settings.panel_y + 12); //--- Updating close button y-coordinate
   if(!panel_minimized) {                                               //--- Checking if not minimized
      // Updating main panel
      ObjectSetInteger(0, PREFIX + PANEL, OBJPROP_XDISTANCE, settings.panel_x); //--- Updating main panel x-coordinate
      ObjectSetInteger(0, PREFIX + PANEL, OBJPROP_YDISTANCE, settings.panel_y); //--- Updating main panel y-coordinate
      // Updating headers
      int header_y = settings.panel_y + settings.row_height + 8 + settings.label_y_offset; //--- Calculating header y-coordinate
      for(int i = 0; i < num_columns; i++) {                            //--- Iterating through headers
         string header_name = PREFIX + HEADER + IntegerToString(i);     //--- Defining header name
         int header_x = settings.panel_x + settings.header_x_distances[i] + settings.label_x_offset; //--- Calculating header x-coordinate
         ObjectSetInteger(0, header_name, OBJPROP_XDISTANCE, header_x); //--- Updating header x-coordinate
         ObjectSetInteger(0, header_name, OBJPROP_YDISTANCE, header_y); //--- Updating header y-coordinate
      }
      // Updating symbol and data labels
      int first_row_y = header_y + settings.row_height;                 //--- Calculating first row y-coordinate
      int symbol_x = settings.panel_x + 10 + settings.label_x_offset;   //--- Setting symbol x-coordinate
      for(int i = 0; i < num_rows; i++) {                               //--- Iterating through rows
         string symbol_name = PREFIX + SYMB + IntegerToString(i);       //--- Defining symbol label name
         ObjectSetInteger(0, symbol_name, OBJPROP_XDISTANCE, symbol_x); //--- Updating symbol x-coordinate
         ObjectSetInteger(0, symbol_name, OBJPROP_YDISTANCE, first_row_y + i * settings.row_height + settings.label_y_offset); //--- Updating symbol y-coordinate
         int x_offset = settings.panel_x + 10 + column_widths[0] + settings.label_x_offset; //--- Setting data x-offset
         for(int j = 0; j < num_columns - 1; j++) {                      //--- Iterating through data columns
            string data_name = PREFIX + DATA + IntegerToString(i) + "_" + IntegerToString(j); //--- Defining data label name
            ObjectSetInteger(0, data_name, OBJPROP_XDISTANCE, x_offset); //--- Updating data x-coordinate
            ObjectSetInteger(0, data_name, OBJPROP_YDISTANCE, first_row_y + i * settings.row_height + settings.label_y_offset); //--- Updating data y-coordinate
            x_offset += column_widths[j + 1];                            //--- Updating x-offset
         }
      }
      // Updating footer panel and labels
      int footer_y = settings.panel_y + (num_rows + 3) * settings.row_height - settings.row_height; //--- Calculating footer y-coordinate
      ObjectSetInteger(0, PREFIX + FOOTER_PANEL, OBJPROP_XDISTANCE, settings.panel_x); //--- Updating footer panel x-coordinate
      ObjectSetInteger(0, PREFIX + FOOTER_PANEL, OBJPROP_YDISTANCE, footer_y); //--- Updating footer panel y-coordinate
      ObjectSetInteger(0, PREFIX + FOOTER_TEXT, OBJPROP_XDISTANCE, settings.panel_x + 10 + settings.label_x_offset); //--- Updating footer text x-coordinate
      ObjectSetInteger(0, PREFIX + FOOTER_TEXT, OBJPROP_YDISTANCE, footer_y + 8 + settings.label_y_offset); //--- Updating footer text y-coordinate
      int x_offset = settings.panel_x + 10 + column_widths[0] + settings.label_x_offset; //--- Setting footer data x-offset
      for(int j = 0; j < num_columns - 1; j++) {                        //--- Iterating through footer data
         string footer_data_name = PREFIX + FOOTER_DATA + IntegerToString(j); //--- Defining footer data name
         ObjectSetInteger(0, footer_data_name, OBJPROP_XDISTANCE, x_offset);  //--- Updating footer data x-coordinate
         ObjectSetInteger(0, footer_data_name, OBJPROP_YDISTANCE, footer_y + 8 + settings.label_y_offset); //--- Updating footer data y-coordinate
         x_offset += column_widths[j + 1];                              //--- Updating x-offset
      }
      // Updating account panel and labels
      int account_panel_y = footer_y + settings.row_height + 5;         //--- Calculating account panel y-coordinate
      ObjectSetInteger(0, PREFIX + ACCOUNT_PANEL, OBJPROP_XDISTANCE, settings.panel_x); //--- Updating account panel x-coordinate
      ObjectSetInteger(0, PREFIX + ACCOUNT_PANEL, OBJPROP_YDISTANCE, account_panel_y);  //--- Updating account panel y-coordinate
      int acc_x = settings.panel_x + 10 + settings.label_x_offset;      //--- Setting account label x-coordinate
      int acc_data_offset = 160;                                        //--- Setting data offset
      int acc_spacing = (panel_width - 45) / ArraySize(account_items);  //--- Calculating spacing
      for(int k = 0; k < ArraySize(account_items); k++) { //--- Iterating through account items
         string acc_text_name = PREFIX + ACC_TEXT + IntegerToString(k); //--- Defining account text name
         int text_x = acc_x + k * acc_spacing;                          //--- Calculating text x-coordinate
         ObjectSetInteger(0, acc_text_name, OBJPROP_XDISTANCE, text_x); //--- Updating account text x-coordinate
         ObjectSetInteger(0, acc_text_name, OBJPROP_YDISTANCE, account_panel_y + 8 + settings.label_y_offset); //--- Updating account text y-coordinate
         string acc_data_name = PREFIX + ACC_DATA + IntegerToString(k); //--- Defining account data name
         int data_x = text_x + acc_data_offset;                         //--- Calculating data x-coordinate
         ObjectSetInteger(0, acc_data_name, OBJPROP_XDISTANCE, data_x); //--- Updating account data x-coordinate
         ObjectSetInteger(0, acc_data_name, OBJPROP_YDISTANCE, account_panel_y + 8 + settings.label_y_offset); //--- Updating account data y-coordinate
      }
   }
   ChartRedraw(0);                                    //--- Redrawing chart
}

Здесь мы реализуем функцию "updatePanelPositions", чтобы включить функцию перетаскивания улучшенной панели, гарантирующую согласованное перемещение всех элементов пользовательского интерфейса при перетаскивании панели. Рассчитываем "num_rows" и "num_columns", используя "ArraySize" для "symbol_data" и "headers", и вычисляем "panel_width", суммируя "column_widths" и используя MathMax с "settings.header_x_distances" плюс заполнение. Обновляем панель заголовка и кнопки, установив OBJPROP_XDISTANCE и "OBJPROP_YDISTANCE" для "PREFIX + HEADER_PANEL", "PREFIX + HEADER_PANEL_TEXT", "PREFIX + EXPORT_BUTTON", "PREFIX + TOGGLE_BUTTON" и "PREFIX + CLOSE_BUTTON", используя ObjectSetInteger с координатами "settings.panel_x" и "settings.panel_y".

Если значение параметра "panel_minimized" равно false, обновляем положение основной панели с помощью параметра "PREFIX + PANEL", заголовки по координате "header_y", рассчитанные на основе "settings.panel_y + settings.row_height + 8 + settings.label_y_offset", метки символов и данных по координате "first_row_y" с параметрами "symbol_x" и "x_offset", скорректированными на "column_widths", нижнюю панель и метки по координате "footer_y", рассчитанные на основе "num_rows + 3", а панель счёта и метки по координате "account_panel_y" с параметрами "acc_x" и "acc_spacing" для выравнивания, используя функцию ObjectSetInteger.  Для обновления изображения вызываем ChartRedraw. Это обеспечит плавное перемещение всей панели при перетаскивании, сохраняя целостность структуры. Нам нужно будет определить логику для отслеживания положения курсора над заголовком или кнопками при наведении курсора. Для реализации этого мы использовали следующую логику.

//+------------------------------------------------------------------+
//| Checking if cursor is inside header or buttons                   |
//+------------------------------------------------------------------+
bool isCursorInHeaderOrButtons(int mouse_x, int mouse_y) {
   int header_x = (int)ObjectGetInteger(0, PREFIX + HEADER_PANEL, OBJPROP_XDISTANCE);  //--- Getting header x-coordinate
   int header_y = (int)ObjectGetInteger(0, PREFIX + HEADER_PANEL, OBJPROP_YDISTANCE);  //--- Getting header y-coordinate
   int header_width = (int)ObjectGetInteger(0, PREFIX + HEADER_PANEL, OBJPROP_XSIZE);  //--- Getting header width
   int header_height = (int)ObjectGetInteger(0, PREFIX + HEADER_PANEL, OBJPROP_YSIZE); //--- Getting header height
   bool in_header = (mouse_x >= header_x && mouse_x <= header_x + header_width &&
                     mouse_y >= header_y && mouse_y <= header_y + header_height);      //--- Checking if in header
   int close_x = (int)ObjectGetInteger(0, PREFIX + CLOSE_BUTTON, OBJPROP_XDISTANCE);   //--- Getting close button x-coordinate
   int close_y = (int)ObjectGetInteger(0, PREFIX + CLOSE_BUTTON, OBJPROP_YDISTANCE);   //--- Getting close button y-coordinate
   int close_width = 20;                              //--- Setting close button width
   int close_height = 20;                             //--- Setting close button height
   bool in_close = (mouse_x >= close_x && mouse_x <= close_x + close_width &&
                    mouse_y >= close_y && mouse_y <= close_y + close_height);          //--- Checking if in close button
   int export_x = (int)ObjectGetInteger(0, PREFIX + EXPORT_BUTTON, OBJPROP_XDISTANCE); //--- Getting export button x-coordinate
   int export_y = (int)ObjectGetInteger(0, PREFIX + EXPORT_BUTTON, OBJPROP_YDISTANCE); //--- Getting export button y-coordinate
   int export_width = 20;                             //--- Setting export button width
   int export_height = 20;                            //--- Setting export button height
   bool in_export = (mouse_x >= export_x && mouse_x <= export_x + export_width &&
                     mouse_y >= export_y && mouse_y <= export_y + export_height);      //--- Checking if in export button
   int toggle_x = (int)ObjectGetInteger(0, PREFIX + TOGGLE_BUTTON, OBJPROP_XDISTANCE); //--- Getting toggle button x-coordinate
   int toggle_y = (int)ObjectGetInteger(0, PREFIX + TOGGLE_BUTTON, OBJPROP_YDISTANCE); //--- Getting toggle button y-coordinate
   int toggle_width = 20;                             //--- Setting toggle button width
   int toggle_height = 20;                            //--- Setting toggle button height
   bool in_toggle = (mouse_x >= toggle_x && mouse_x <= toggle_x + toggle_width &&
                     mouse_y >= toggle_y && mouse_y <= toggle_y + toggle_height);      //--- Checking if in toggle button
   return in_header || in_close || in_export || in_toggle;                             //--- Returning combined check
}

Реализуем функцию "isCursorInHeaderOrButtons" для определения присутствия курсора мыши над интерактивными элементами, позволяя перетаскивать их и нажимать кнопки. Извлекаем координаты и размеры для панели заголовка, используя ObjectGetInteger для "PREFIX + HEADER_PANEL" с "OBJPROP_XDISTANCE", "OBJPROP_YDISTANCE", "OBJPROP_XSIZE" и "OBJPROP_YSIZE", сохраняя их в "header_x", "header_y", "header_width" и "header_height", и проверяем, находится ли курсор ("mouse_x", "mouse_y") в пределах заголовка с "in_header".

Аналогично, получаем координаты для "PREFIX + CLOSE_BUTTON", "PREFIX + EXPORT_BUTTON" и "PREFIX + TOGGLE_BUTTON", используя "OBJPROP_XDISTANCE" и OBJPROP_YDISTANCE, установив для "close_width", "close_height", "export_width", "export_height", "toggle_width" и "toggle_height" значение 20, и проверяем, находится ли курсор в пределах каждой кнопки с помощью "in_close", "in_export" и "in_toggle". Возвращаем значение true, если курсор находится в заголовке или на любой кнопке, комбинируя условия с оператором OR. После обнаружения наведения курсора потребуется обновить обнаруженный заголовок или кнопки для визуальной обратной связи. Вот логика, которую мы реализуем для достижения этой цели.

//+------------------------------------------------------------------+
//| Updating button hover states                                     |
//+------------------------------------------------------------------+
void updateButtonHoverStates(int mouse_x, int mouse_y) {
   int close_x = (int)ObjectGetInteger(0, PREFIX + CLOSE_BUTTON, OBJPROP_XDISTANCE);     //--- Getting close button x-coordinate
   int close_y = (int)ObjectGetInteger(0, PREFIX + CLOSE_BUTTON, OBJPROP_YDISTANCE);     //--- Getting close button y-coordinate
   int close_width = 20;                              //--- Setting close button width
   int close_height = 20;                             //--- Setting close button height
   bool is_close_hovered = (mouse_x >= close_x && mouse_x <= close_x + close_width &&
                            mouse_y >= close_y && mouse_y <= close_y + close_height);     //--- Checking if close button hovered
   if(is_close_hovered != prev_close_hovered) {       //--- Checking hover change
      ObjectSetInteger(0, PREFIX + CLOSE_BUTTON, OBJPROP_COLOR, is_close_hovered ? clrRed : clrBlack); //--- Updating close button color
      ObjectSetInteger(0, PREFIX + CLOSE_BUTTON, OBJPROP_BGCOLOR, is_close_hovered ? clrDodgerBlue : clrNONE); //--- Updating close button background
      prev_close_hovered = is_close_hovered;          //--- Updating previous hover state
      ChartRedraw(0);                                 //--- Redrawing chart
   }
   int export_x = (int)ObjectGetInteger(0, PREFIX + EXPORT_BUTTON, OBJPROP_XDISTANCE);    //--- Getting export button x-coordinate
   int export_y = (int)ObjectGetInteger(0, PREFIX + EXPORT_BUTTON, OBJPROP_YDISTANCE);    //--- Getting export button y-coordinate
   int export_width = 20;                             //--- Setting export button width
   int export_height = 20;                            //--- Setting export button height
   bool is_export_hovered = (mouse_x >= export_x && mouse_x <= export_x + export_width &&
                             mouse_y >= export_y && mouse_y <= export_y + export_height); //--- Checking if export button hovered
   if(is_export_hovered != prev_export_hovered) {     //--- Checking hover change
      ObjectSetInteger(0, PREFIX + EXPORT_BUTTON, OBJPROP_COLOR, is_export_hovered ? clrOrange : clrBlack); //--- Updating export button color
      ObjectSetInteger(0, PREFIX + EXPORT_BUTTON, OBJPROP_BGCOLOR, is_export_hovered ? clrDodgerBlue : clrNONE); //--- Updating export button background
      prev_export_hovered = is_export_hovered;        //--- Updating previous hover state
      ChartRedraw(0);                                 //--- Redrawing chart
   }
   int toggle_x = (int)ObjectGetInteger(0, PREFIX + TOGGLE_BUTTON, OBJPROP_XDISTANCE);    //--- Getting toggle button x-coordinate
   int toggle_y = (int)ObjectGetInteger(0, PREFIX + TOGGLE_BUTTON, OBJPROP_YDISTANCE);    //--- Getting toggle button y-coordinate
   int toggle_width = 20;                             //--- Setting toggle button width
   int toggle_height = 20;                            //--- Setting toggle button height
   bool is_toggle_hovered = (mouse_x >= toggle_x && mouse_x <= toggle_x + toggle_width &&
                             mouse_y >= toggle_y && mouse_y <= toggle_y + toggle_height); //--- Checking if toggle button hovered
   if(is_toggle_hovered != prev_toggle_hovered) {     //--- Checking hover change
      ObjectSetInteger(0, PREFIX + TOGGLE_BUTTON, OBJPROP_COLOR, is_toggle_hovered ? clrBlue : clrBlack); //--- Updating toggle button color
      ObjectSetInteger(0, PREFIX + TOGGLE_BUTTON, OBJPROP_BGCOLOR, is_toggle_hovered ? clrDodgerBlue : clrNONE); //--- Updating toggle button background
      prev_toggle_hovered = is_toggle_hovered;        //--- Updating previous hover state
      ChartRedraw(0);                                 //--- Redrawing chart
   }
}

//+------------------------------------------------------------------+
//| Updating header hover state                                      |
//+------------------------------------------------------------------+
void updateHeaderHoverState(int mouse_x, int mouse_y) {
   int header_x = (int)ObjectGetInteger(0, PREFIX + HEADER_PANEL, OBJPROP_XDISTANCE);  //--- Getting header x-coordinate
   int header_y = (int)ObjectGetInteger(0, PREFIX + HEADER_PANEL, OBJPROP_YDISTANCE);  //--- Getting header y-coordinate
   int header_width = (int)ObjectGetInteger(0, PREFIX + HEADER_PANEL, OBJPROP_XSIZE);  //--- Getting header width
   int header_height = (int)ObjectGetInteger(0, PREFIX + HEADER_PANEL, OBJPROP_YSIZE); //--- Getting header height
   int close_x = (int)ObjectGetInteger(0, PREFIX + CLOSE_BUTTON, OBJPROP_XDISTANCE);   //--- Getting close button x-coordinate
   int close_y = (int)ObjectGetInteger(0, PREFIX + CLOSE_BUTTON, OBJPROP_YDISTANCE);   //--- Getting close button y-coordinate
   int close_width = 20;                              //--- Setting close button width
   int close_height = 20;                             //--- Setting close button height
   int export_x = (int)ObjectGetInteger(0, PREFIX + EXPORT_BUTTON, OBJPROP_XDISTANCE); //--- Getting export button x-coordinate
   int export_y = (int)ObjectGetInteger(0, PREFIX + EXPORT_BUTTON, OBJPROP_YDISTANCE); //--- Getting export button y-coordinate
   int export_width = 20;                             //--- Setting export button width
   int export_height = 20;                            //--- Setting export button height
   int toggle_x = (int)ObjectGetInteger(0, PREFIX + TOGGLE_BUTTON, OBJPROP_XDISTANCE); //--- Getting toggle button x-coordinate
   int toggle_y = (int)ObjectGetInteger(0, PREFIX + TOGGLE_BUTTON, OBJPROP_YDISTANCE); //--- Getting toggle button y-coordinate
   int toggle_width = 20;                             //--- Setting toggle button width
   int toggle_height = 20;                            //--- Setting toggle button height
   bool is_header_hovered = (mouse_x >= header_x && mouse_x <= header_x + header_width &&
                             mouse_y >= header_y && mouse_y <= header_y + header_height &&
                             !(mouse_x >= close_x && mouse_x <= close_x + close_width &&
                               mouse_y >= close_y && mouse_y <= close_y + close_height) &&
                             !(mouse_x >= export_x && mouse_x <= export_x + export_width &&
                               mouse_y >= export_y && mouse_y <= export_y + export_height) &&
                             !(mouse_x >= toggle_x && mouse_x <= toggle_x + toggle_width &&
                               mouse_y >= toggle_y && mouse_y <= toggle_y + toggle_height)); //--- Checking if header hovered
   if(is_header_hovered != prev_header_hovered && !panel_dragging) {                         //--- Checking hover change
      ObjectSetInteger(0, PREFIX + HEADER_PANEL, OBJPROP_BGCOLOR, is_header_hovered ? clrRed : settings.section_bg_color); //--- Updating header background
      prev_header_hovered = is_header_hovered;        //--- Updating previous hover state
      ChartRedraw(0);                                 //--- Redrawing chart
   }
   updateButtonHoverStates(mouse_x, mouse_y);         //--- Updating button hover states
}

Наконец, реализуем функции "updateButtonHoverStates" и "updateHeaderHoverState", чтобы добавить визуальную обратную связь для взаимодействия с пользователем, повышая отзывчивость кнопок и заголовка. В методе "updateButtonHoverStates" проверяем состояния наведения курсора на кнопки, получая координаты для "PREFIX + CLOSE_BUTTON", "PREFIX + EXPORT_BUTTON" и "PREFIX + TOGGLE_BUTTON" с помощью ObjectGetInteger с параметрами "OBJPROP_XDISTANCE" и "OBJPROP_YDISTANCE", устанавливая значения "close_width", "close_height", "export_width", "export_height", "toggle_width" и "toggle_height" равными 20.

Для кнопки закрытия устанавливаем значение "is_close_hovered", если значения "mouse_x" и "mouse_y" находятся в пределах её границ, а если они отличаются от "prev_close_hovered", обновляем "OBJPROP_COLOR" на "clrRed" или "clrBlack" и "OBJPROP_BGCOLOR" на "clrDodgerBlue" или "clrNONE" с помощью "ObjectSetInteger", обновляем "prev_close_hovered" и вызываем функцию ChartRedraw. Аналогично, для кнопки экспорта устанавливаем значение параметра "is_export_hovered" равным "clrOrange" или "clrBlack" и "clrDodgerBlue" или "clrNONE", обновляем "prev_export_hovered" и перерисовываем; для кнопки переключения используем значение параметра "clrBlue" или "clrBlack", обновляем значение "prev_toggle_hovered" и перерисовываем.

В "updateHeaderHoverState" получаем значения "header_x", "header_y", "header_width" и "header_height" для "PREFIX + HEADER_PANEL", а также координаты кнопки, проверяя "is_header_hovered", находится ли курсор внутри заголовка, но за пределами кнопки. Если значение "is_header_hovered" отличается от "prev_header_hovered", а "panel_dragging" равно false, обновляем "OBJPROP_BGCOLOR" объекта "PREFIX + HEADER_PANEL" до значения "clrRed" или "settings.section_bg_color" с помощью ObjectSetInteger, обновляем "prev_header_hovered", вызываем "ChartRedraw" и вызываем "updateButtonHoverStates". Эти функции обеспечивают динамические эффекты при наведении курсора мыши для интуитивно понятного взаимодействия с пользователем. Чтобы использовать эти функции, расширим функцию OnChartEvent, включив в нее логику визуальной обратной связи.

//+------------------------------------------------------------------+
//| Handling chart events for sorting, export, and UI interactions   |
//+------------------------------------------------------------------+
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) {
   if(id == CHARTEVENT_OBJECT_CLICK) {                //--- Handling object click
      if(sparam == PREFIX + CLOSE_BUTTON) {           //--- Checking close button click
         Print("Closing the dashboard");              //--- Logging closing
         PlaySound("alert.wav");                      //--- Playing alert sound
         panel_is_visible = false;                    //--- Setting panel invisible
         deleteAllObjects();                          //--- Deleting all objects
         ChartRedraw(0);                              //--- Redrawing chart
      }
      else if(sparam == PREFIX + EXPORT_BUTTON) {     //--- Checking export button click
         Print("Exporting dashboard to CSV");         //--- Logging exporting
         ExportToCSV();                               //--- Exporting to CSV
         ChartRedraw(0);                              //--- Redrawing chart
      }
      else if(sparam == PREFIX + TOGGLE_BUTTON) {     //--- Checking toggle button click
         deleteAllObjects();                          //--- Deleting all objects
         panel_minimized = !panel_minimized;          //--- Toggling minimized state
         if(panel_minimized) {                        //--- Checking if minimized
            Print("Minimizing the dashboard");        //--- Logging minimizing
            createMinimizedDashboard();               //--- Creating minimized dashboard
         } else {
            Print("Maximizing the dashboard");        //--- Logging maximizing
            createFullDashboard();                    //--- Creating full dashboard
            // Resetting string variables to force update
            total_buys_str = "";                      //--- Resetting buys string
            total_sells_str = "";                     //--- Resetting sells string
            total_trades_str = "";                    //--- Resetting trades string
            total_lots_str = "";                      //--- Resetting lots string
            total_profit_str = "";                    //--- Resetting profit string
            total_pending_str = "";                   //--- Resetting pending string
            total_swap_str = "";                      //--- Resetting swap string
            total_comm_str = "";                      //--- Resetting commission string
            acc_bal_str = "";                         //--- Resetting balance string
            acc_eq_str = "";                          //--- Resetting equity string
            acc_free_str = "";                        //--- Resetting free margin string
            UpdateDashboard();                        //--- Updating dashboard
         }
         prev_header_hovered = false;                 //--- Resetting header hover
         prev_close_hovered = false;                  //--- Resetting close hover
         prev_export_hovered = false;                 //--- Resetting export hover
         prev_toggle_hovered = false;                 //--- Resetting toggle hover
         ObjectSetInteger(0, PREFIX + HEADER_PANEL, OBJPROP_BGCOLOR, settings.section_bg_color); //--- Resetting header background
         ObjectSetInteger(0, PREFIX + CLOSE_BUTTON, OBJPROP_COLOR, clrBlack); //--- Resetting close button color
         ObjectSetInteger(0, PREFIX + CLOSE_BUTTON, OBJPROP_BGCOLOR, clrNONE); //--- Resetting close button background
         ObjectSetInteger(0, PREFIX + EXPORT_BUTTON, OBJPROP_COLOR, clrBlack); //--- Resetting export button color
         ObjectSetInteger(0, PREFIX + EXPORT_BUTTON, OBJPROP_BGCOLOR, clrNONE); //--- Resetting export button background
         ObjectSetInteger(0, PREFIX + TOGGLE_BUTTON, OBJPROP_COLOR, clrBlack); //--- Resetting toggle button color
         ObjectSetInteger(0, PREFIX + TOGGLE_BUTTON, OBJPROP_BGCOLOR, clrNONE); //--- Resetting toggle button background
         ChartRedraw(0);                              //--- Redrawing chart
      }
      else {
         for(int i = 0; i < ArraySize(headers); i++) { //--- Iterating through headers
            if(sparam == PREFIX + HEADER + IntegerToString(i)) { //--- Checking header click
               if(sort_column == i)                   //--- Checking if same column
                  sort_ascending = !sort_ascending;   //--- Toggling sort direction
               else {
                  sort_column = i;                    //--- Setting new sort column
                  sort_ascending = true;              //--- Setting to ascending
               }
               UpdateDashboard();                     //--- Updating dashboard
               break;                                 //--- Exiting loop
            }
         }
      }
   }
   else if(id == CHARTEVENT_KEYDOWN && lparam == 'E') { //--- Handling 'E' key press
      ExportToCSV();                                  //--- Exporting to CSV
   }
   else if(id == CHARTEVENT_MOUSE_MOVE && panel_is_visible) { //--- Handling mouse move
      int mouse_x = (int)lparam;                      //--- Getting mouse x-coordinate
      int mouse_y = (int)dparam;                      //--- Getting mouse y-coordinate
      int mouse_state = (int)sparam;                  //--- Getting mouse state
      if(mouse_x == last_mouse_x && mouse_y == last_mouse_y && !panel_dragging) { //--- Checking if mouse moved
         return;                                      //--- Exiting if no movement
      }
      last_mouse_x = mouse_x;                         //--- Updating last mouse x
      last_mouse_y = mouse_y;                         //--- Updating last mouse y
      updateHeaderHoverState(mouse_x, mouse_y);       //--- Updating header hover state
      int header_x = (int)ObjectGetInteger(0, PREFIX + HEADER_PANEL, OBJPROP_XDISTANCE); //--- Getting header x-coordinate
      int header_y = (int)ObjectGetInteger(0, PREFIX + HEADER_PANEL, OBJPROP_YDISTANCE); //--- Getting header y-coordinate
      int header_width = (int)ObjectGetInteger(0, PREFIX + HEADER_PANEL, OBJPROP_XSIZE); //--- Getting header width
      int header_height = (int)ObjectGetInteger(0, PREFIX + HEADER_PANEL, OBJPROP_YSIZE);//--- Getting header height
      int close_x = (int)ObjectGetInteger(0, PREFIX + CLOSE_BUTTON, OBJPROP_XDISTANCE);  //--- Getting close button x-coordinate
      int close_width = 20;                           //--- Setting close button width
      int export_x = (int)ObjectGetInteger(0, PREFIX + EXPORT_BUTTON, OBJPROP_XDISTANCE);//--- Getting export button x-coordinate
      int export_width = 20;                          //--- Setting export button width
      int toggle_x = (int)ObjectGetInteger(0, PREFIX + TOGGLE_BUTTON, OBJPROP_XDISTANCE);//--- Getting toggle button x-coordinate
      int toggle_width = 20;                          //--- Setting toggle button width
      if(prev_mouse_state == 0 && mouse_state == 1) { //--- Checking mouse click start
         if(mouse_x >= header_x && mouse_x <= header_x + header_width &&
            mouse_y >= header_y && mouse_y <= header_y + header_height &&
            !(mouse_x >= close_x && mouse_x <= close_x + close_width) &&
            !(mouse_x >= export_x && mouse_x <= export_x + export_width) &&
            !(mouse_x >= toggle_x && mouse_x <= toggle_x + toggle_width)) { //--- Checking if in draggable area
            panel_dragging = true;                     //--- Starting dragging
            panel_drag_x = mouse_x;                    //--- Setting drag start x
            panel_drag_y = mouse_y;                    //--- Setting drag start y
            panel_start_x = header_x;                  //--- Setting panel start x
            panel_start_y = header_y;                  //--- Setting panel start y
            ObjectSetInteger(0, PREFIX + HEADER_PANEL, OBJPROP_BGCOLOR, clrMediumBlue); //--- Setting dragging color
            ChartSetInteger(0, CHART_MOUSE_SCROLL, false); //--- Disabling chart scroll
         }
      }
      if(panel_dragging && mouse_state == 1) {        //--- Handling dragging
         int dx = mouse_x - panel_drag_x;             //--- Calculating x change
         int dy = mouse_y - panel_drag_y;             //--- Calculating y change
         settings.panel_x = panel_start_x + dx;       //--- Updating panel x
         settings.panel_y = panel_start_y + dy;       //--- Updating panel y
         int chart_width = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS);   //--- Getting chart width
         int chart_height = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS); //--- Getting chart height
         int num_columns = ArraySize(headers);        //--- Getting number of columns
         int column_width_sum = 0;                    //--- Initializing column width sum
         for(int i = 0; i < num_columns; i++) column_width_sum += column_widths[i]; //--- Adding column width
         int panel_width = MathMax(settings.header_x_distances[num_columns - 1] + column_widths[num_columns - 1], column_width_sum) + 20 + settings.label_x_offset; //--- Calculating panel width
         int panel_height = panel_minimized ? settings.row_height : (ArraySize(symbol_data) + 3) * settings.row_height; //--- Calculating panel height
         settings.panel_x = MathMax(0, MathMin(chart_width - panel_width, settings.panel_x)); //--- Constraining x
         settings.panel_y = MathMax(0, MathMin(chart_height - panel_height, settings.panel_y)); //--- Constraining y
         updatePanelPositions();                      //--- Updating object positions
         ChartRedraw(0);                              //--- Redrawing chart
      }
      if(mouse_state == 0 && prev_mouse_state == 1) { //--- Handling mouse release
         if(panel_dragging) {                         //--- Checking if was dragging
            panel_dragging = false;                   //--- Stopping dragging
            updateHeaderHoverState(mouse_x, mouse_y); //--- Updating hover state
            ChartSetInteger(0, CHART_MOUSE_SCROLL, true); //--- Enabling chart scroll
            ChartRedraw(0);                           //--- Redrawing chart
         }
      }
      prev_mouse_state = mouse_state;                //--- Updating previous mouse state
   }
}

Здесь мы расширяем функцию OnChartEvent для обработки интерактивных событий, управляя щелчками на закрытие, экспорт, переключение, сортировку и перемещениями мыши для перетаскивания. Для CHARTEVENT_OBJECT_CLICK проверяем, является ли "sparam" значением "PREFIX + CLOSE_BUTTON", выводим сообщение в лог с помощью функции "Print", воспроизводим "alert.wav" с помощью функции PlaySound, устанавливаем "panel_is_visible" в значение false, вызываем "deleteAllObjects" и перерисовываем график с помощью "ChartRedraw". Если "sparam" равно "PREFIX + EXPORT_BUTTON", регистрируем это в логе и вызываем функцию "ExportToCSV".

Для "PREFIX + TOGGLE_BUTTON" удаляем объекты, переключаем "panel_minimized", выводим в лог "Minimizing" или "Maximizing" с помощью функции "Print", вызываем "createMinimizedDashboard" или "createFullDashboard", сбрасываем строковые переменные, такие как "total_buys_str" и "acc_bal_str", вызываем "UpdateDashboard", сбрасываем состояния при наведении курсора ("prev_header_hovered", "prev_close_hovered" и т. д.), а также сбрасываем цвета для "PREFIX + HEADER_PANEL", "PREFIX + CLOSE_BUTTON", "PREFIX + EXPORT_BUTTON" и "PREFIX + TOGGLE_BUTTON" с помощью функции ObjectSetInteger. В результате вы получите примерно следующий результат.

MINIMIZED STATE

При кликах по заголовкам мы перебираем в цикле "headers", переключаем значение "sort_ascending", если совпадает с "sort_column", либо устанавливаем новые значения "sort_column" и "sort_ascending" в true, а затем вызываем функцию "UpdateDashboard". Для CHARTEVENT_KEYDOWN с 'E', вызываем "ExportToCSV". Для CHARTEVENT_MOUSE_MOVE, когда "panel_is_visible", получаем значения "mouse_x", "mouse_y" и "mouse_state", выходим, если значение не изменилось и перетаскивание не происходит, обновляем "last_mouse_x" и "last_mouse_y" и вызываем "updateHeaderHoverState".

Если "prev_mouse_state" равно 0, а "mouse_state" равно 1, проверяем наличие щелчков по перетаскиваемой области (исключая кнопки), устанавливаем "panel_dragging" в значение true, сохраняем координаты, устанавливаем цвет заголовка на "clrMediumBlue" и отключаем прокрутку с помощью функции ChartSetInteger. Если выполняется перетаскивание и значение параметра "mouse_state" равно 1, рассчитываем значения "dx" и "dy", обновляем значения параметров "settings.panel_x" и "settings.panel_y" в пределах границ графика, вызываем метод "updatePanelPositions" и выполняем перерисовку. Отпустив мышь, прекращаем перетаскивание, обновляем состояние при наведении курсора, снова включаем прокрутку и перерисовываем. Это обеспечивает динамическое взаимодействие с пользовательским интерфейсом для создания удобной панели. После компиляции получаем следующий результат состояния при наведении курсора на кнопку.

BUTTON HOVER STATES

Результат для развернутого состояния и состояния перетаскивания выглядит следующим образом.

FINAL DRAG STATE

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


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

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

BACKTEST


Заключение

В заключение, мы усовершенствовали информационную панель в MQL5 для части 8, добавив функции перетаскивания и сворачивания, интерактивные кнопки, такие как "CLOSE_BUTTON" и "TOGGLE_BUTTON", а также эффекты при наведении курсора для улучшения взаимодействия с пользователем, поддерживая надежное положение нескольких символов и мониторинг счёта. Мы подробно описали архитектуру и реализацию, используя такие функции, как "createFullDashboard", "updatePanelPositions" и OnChartEvent, чтобы создать гибкий, визуально адаптивный инструмент с обновлениями в реальном времени и экспортом в Excel в формате CSV. Вы можете настроить эту панель для оптимизации своего торгового процесса, сделав анализ позиций более интуитивно понятным и эффективным.

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

Прикрепленные файлы |
Создание торговой панели администратора на MQL5 (Часть XII): Интеграция форекс-калькулятора Создание торговой панели администратора на MQL5 (Часть XII): Интеграция форекс-калькулятора
Точный расчет ключевых торговых показателей — неотъемлемая часть рабочего процесса каждого трейдера. В этой статье мы рассмотрим интеграцию мощного инструмента — Форекс-калькулятора — в панель управления торговлей, что еще больше расширит функциональность нашей многопанельной системы администратора трейдера. Эффективное определение риска, размера позиции и потенциальной прибыли имеет важное значение при совершении сделок, и эта новая функция призвана сделать этот процесс более быстрым и интуитивно понятным прямо в панели. Присоединяйтесь к нам, чтобы изучить практическое применение MQL5 при создании продвинутых торговых панелей.
Автоматизация торговых стратегий на MQL5 (Часть 19): Envelopes Trend Bounce Scalping — Исполнение сделок и управление рисками (Часть II) Автоматизация торговых стратегий на MQL5 (Часть 19): Envelopes Trend Bounce Scalping — Исполнение сделок и управление рисками (Часть II)
В статье реализуется исполнение сделок и управление рисками для стратегии скальпинга на коррекции на основе конвертов (Envelopes Trend Bounce) на языке MQL5. Мы внедряем механизмы размещения ордеров и контроля рисков, такие как стоп-лосс и определение размера позиции. В заключение мы переходим к тестированию и оптимизации, опираясь на основы, заложенные в Части 18.
Знакомство с языком MQL5 (Часть 35): Освоение API и функции WebRequest в языке MQL5 (IX) Знакомство с языком MQL5 (Часть 35): Освоение API и функции WebRequest в языке MQL5 (IX)
Узнайте, как обнаруживать действия пользователей в MetaTrader 5, отправлять запросы в API искусственного интеллекта, извлекать ответы и реализовывать прокрутку текста на панели.
Моделирование рынка (Часть 20): Первые шаги на SQL (III) Моделирование рынка (Часть 20): Первые шаги на SQL (III)
Хотя мы можем выполнять операции с базой данных, содержащей около 10 записей, но материал усваивается гораздо лучше, когда мы работаем с файлом, который содержит более 15 тысяч записей. То есть, если бы мы попытались создать такое вручную, то эта задача была бы огромной. Однако трудно найти такую базу данных, даже для учебных целей, доступную для скачивания. Но на самом деле нам не нужно к этому прибегать, мы можем использовать MetaTrader 5 для создания базы данных для себя. В сегодняшней статье мы рассмотрим, как это сделать.