preview
MQL5 Trading Tools (Part 4): Improving the Multi-Timeframe Scanner Dashboard with Dynamic Positioning and Toggle Features

MQL5 Trading Tools (Part 4): Improving the Multi-Timeframe Scanner Dashboard with Dynamic Positioning and Toggle Features

MetaTrader 5Trading |
1 997 2
Allan Munene Mutiiria
Allan Munene Mutiiria

Introduction

In our previous article (Part 3), we developed a Multi-Timeframe Scanner Dashboard in MetaQuotes Language 5 (MQL5), displaying Relative Strength Index (RSI), Stochastic, Commodity Channel Index (CCI), Average Directional Index (ADX), and Awesome Oscillator (AO) indicators across multiple timeframes to identify trading signals for the current symbol. In Part 4, we enhance this dashboard by adding dynamic positioning to allow dragging across the chart and a toggle feature to minimize or maximize the display, improving usability and screen management. We will cover the following topics:

  1. Understanding the Dynamic Positioning and Toggle Architecture
  2. Implementation in MQL5
  3. Backtesting
  4. Conclusion

By the end, you’ll have an advanced MQL5 dashboard with flexible positioning and toggle functionality, ready for testing and further customization—let’s get started!


Understanding the Dynamic Positioning and Toggle Architecture

We’re enhancing our Multi-Timeframe Scanner Dashboard by adding dynamic positioning, allowing it to be dragged across the chart, and a toggle to minimize or maximize it, thereby improving usability. These features are essential for avoiding chart clutter and optimizing screen space for efficient trading analysis. We will implement mouse-driven dragging to reposition the dashboard and a toggle button to switch between compact and full views, maintaining seamless indicator updates for better trading decisions. In a nutshell, here is a visualization of what we aim to achieve.

POSITIONING & DRAG STATES ARCHITECTURE


Implementation in MQL5

To implement the enhancements in MQL5, we will define the extra toggle button object that we will use later for switching between maximized and minimized states, as the hover and drag will be done on the header object, which we already have in the implementation.

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

//--- THE REST OF THE EXISTING OBJECTS

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

//--- THE REST OF THE EXISTING OBJECTS

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

We start the enhancement of our Multi-Timeframe Scanner Dashboard by updating the User Interface (UI) element definitions to include a new identifier for the toggle button, supporting our goal of adding minimize/maximize functionality. We retain the existing definitions to maintain the dashboard’s core structure. The key change is the addition of the "TOGGLE_BUTTON" identifier, defined as "BUTTON_TOGGLE", which will allow us to create a button for toggling the dashboard between minimized and maximized states.

This addition is crucial as it will enable the new toggle feature, giving us the ability to collapse the dashboard to save screen space or expand it for full visibility, improving usability without altering the existing indicator display logic. Then, we will need some more control over global variables, which we will use to store the dashboard states.

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

To help track the dashboard states, we add some global variables. We introduce "panel_minimized" to track the minimized state, "panel_x" and "panel_y" for the dashboard’s position, and "panel_dragging", "panel_drag_x", "panel_drag_y", "panel_start_x", and "panel_start_y" for dragging. We also add "prev_mouse_state", "header_hovered", "toggle_hovered", "close_hovered", "last_mouse_x", "last_mouse_y", "prev_header_hovered", "prev_toggle_hovered", and "prev_close_hovered" to manage mouse events and hover states. These will enable a draggable, interactive dashboard with toggle support.

We can then move on to adding the toggle button to the dashboard. To manage the switch states, we will need to have the creation logic in separate functions for both the maximized and minimized panels. Let's start with the maximized dashboard.

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

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

We implement the dashboard states by creating the "create_full_dashboard" function, moving the static creation logic to it, and updating it to support dynamic positioning and toggle functionality. The primary change is integrating the "panel_x" and "panel_y" variables into the positioning of all dashboard elements, allowing the dashboard to be placed anywhere on the chart. We create the main panel with "create_rectangle" using "MAIN_PANEL", "panel_x", and "panel_y" for the position, maintaining the 617x374 size. Similarly, we position the header panel, icon, and title using "create_rectangle" and "create_label" for "HEADER_PANEL", "HEADER_PANEL_ICON", and "HEADER_PANEL_TEXT", adjusting their x and y coordinates relative to "panel_x" and "panel_y".

We add the new "TOGGLE_BUTTON" with "create_label", placing it at "panel_x - 570" and "panel_y + 14", displaying a minimize symbol ('r' in Webdings). The symbol rectangle and label ("SYMBOL_RECTANGLE" and "SYMBOL_TEXT") and header rectangles and labels ("HEADER_RECTANGLE" and "HEADER_TEXT") use "panel_x" and "panel_y" for their x and y offsets, ensuring they move with the panel. For each timeframe in "timeframes_array", we create timeframe rectangles and labels ("TIMEFRAME_RECTANGLE" and "TIMEFRAME_TEXT") and indicator cells ("RSI_RECTANGLE", "STOCH_RECTANGLE", etc.) with positions calculated relative to "panel_x" and "panel_y", preserving the layout but making it relocatable. We call the ChartRedraw function to update the display. Unlike the previous version’s fixed coordinates (e.g., 632, 40), this function uses dynamic coordinates, enabling the dashboard to be dragged, and adds a toggle button for minimizing/maximizing. We will get something that resembles the below sample.

MAXIMIZED STATE

We can then have a function to create the minimized panel.

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

To support toggling to a compact view, we define the "create_minimized_dashboard" function. We create the header panel with "create_rectangle" for "HEADER_PANEL" at "panel_x" and "panel_y", add "HEADER_PANEL_ICON" and "HEADER_PANEL_TEXT" with "create_label" for the icon and title, include "CLOSE_BUTTON", and add "TOGGLE_BUTTON" with a maximize symbol ('o' in Webdings) at "panel_x - 570" and "panel_y + 14". We call "ChartRedraw" to update the display, enabling a movable, minimized dashboard state. As you might have noticed, we chose the Wingdings font for both the maximize and minimize buttons because of consistency. You can choose any of your like. In our case, the icon characters are 'r' and 'o' respectively. Here is a centralized view of them.

WINGDINGS FONT CHARACTERS

When you run the minimized panel state, you get the following outcome.

MINIMIZED STATE

Armed with the functions, we can choose whichever to use based on the user command. Now, since we created several objects, we can centralize the deletion of all the objects in a function because we will need to delete the objects and create them again automatically.

//+------------------------------------------------------------------+
//| Delete all dashboard objects                                     |
//+------------------------------------------------------------------+
void delete_all_objects() {
   ObjectDelete(0, MAIN_PANEL);                                  //--- Delete main panel
   ObjectDelete(0, HEADER_PANEL);                                //--- Delete header panel
   ObjectDelete(0, HEADER_PANEL_ICON);                           //--- Delete header icon
   ObjectDelete(0, HEADER_PANEL_TEXT);                           //--- Delete header title
   ObjectDelete(0, CLOSE_BUTTON);                                //--- Delete close button
   ObjectDelete(0, TOGGLE_BUTTON);                               //--- Delete toggle button

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

To have more control over when to delete our objects, we create and update the "delete_all_objects" function to include the removal of the new "TOGGLE_BUTTON", supporting our toggle functionality improvement. We add the ObjectDelete function for "TOGGLE_BUTTON" to the list of objects being removed, ensuring the toggle button is properly deleted when the dashboard is closed or toggled. We retain the deletion of all other objects, such as "MAIN_PANEL", "HEADER_PANEL", "HEADER_PANEL_ICON", "HEADER_PANEL_TEXT", "CLOSE_BUTTON", and all symbol, timeframe, header, indicator, and signal rectangles and labels using the ObjectsDeleteAll function. This change ensures our movable and minimizable dashboard cleans up all components, including the new toggle button, maintaining a tidy chart when the dashboard is hidden or reinitialized.

To enhance dynamic positioning, we will need to create a function that will create and update the dashboard elements based on the cursor position. Here is the logic we use to achieve that.

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

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

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

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

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

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

To support dynamic positioning of the dashboard, we introduce a new "update_panel_positions" function. The function adjusts the positions of all dashboard elements based on the current "panel_x" and "panel_y" coordinates, enabling the dashboard to be dragged across the chart. We update the header panel, icon, title, close button, and toggle button ("HEADER_PANEL", "HEADER_PANEL_ICON", "HEADER_PANEL_TEXT", "CLOSE_BUTTON", "TOGGLE_BUTTON") using the ObjectSetInteger function with OBJPROP_XDISTANCE and "OBJPROP_YDISTANCE", setting their positions relative to "panel_x" and "panel_y".

If "panel_minimized" is false, we reposition the main panel ("MAIN_PANEL"), symbol rectangle and label ("SYMBOL_RECTANGLE", "SYMBOL_TEXT"), header rectangles and labels ("HEADER_RECTANGLE", "HEADER_TEXT"), and timeframe rectangles, labels, and indicator cells ("TIMEFRAME_RECTANGLE", "TIMEFRAME_TEXT", "BUY_RECTANGLE", "RSI_RECTANGLE", etc.) using calculated offsets from "panel_x" and "panel_y". We call the ChartRedraw function to refresh the display. This function will ensure all elements move together when the dashboard is dragged, a critical feature for our movable dashboard enhancement. We can then try testing the dashboard. For this, we will call the function to create the full dashboard in the OnInit event handler and call the destroy function in the OnDeinit event handler.

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

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

Here, we just call the respective functions during initialization and deinitialization of the program to enable us to test the basic responses before we move on to the next parts. It is always a better programming manner to compile your program stepwise to ascertain that it is working. We have the following outcome upon compilation.

INIT AND DEINIT GIF

From the visualization, we can see that we can initialize and remove the panel successfully. Now, let us move on to making the dashboard responsive. We first need to get the cursor position to determine if it is in the header or in the buttons, so that we know specifically what the user might want to do. We will also get the button movement path so we can know and visualize hover states and click states by changing the color, but first, let us define a function to determine the cursor position relative to the dashboard elements.

//+------------------------------------------------------------------+
//| Check if cursor is inside header or buttons                      |
//+------------------------------------------------------------------+
bool is_cursor_in_header_or_buttons(int mouse_x, int mouse_y) {
   int chart_width = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS);
   
   // Header panel bounds
   int header_x = (int)ObjectGetInteger(0, HEADER_PANEL, OBJPROP_XDISTANCE);
   int header_y = (int)ObjectGetInteger(0, HEADER_PANEL, OBJPROP_YDISTANCE);
   int header_width = (int)ObjectGetInteger(0, HEADER_PANEL, OBJPROP_XSIZE);
   int header_height = (int)ObjectGetInteger(0, HEADER_PANEL, OBJPROP_YSIZE);
   int header_left = chart_width - header_x;
   int header_right = header_left + header_width;
   bool in_header = (mouse_x >= header_left && mouse_x <= header_right && 
                     mouse_y >= header_y && mouse_y <= header_y + header_height);

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

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

   return in_header || in_close || in_toggle;
}

First, we introduce a new "is_cursor_in_header_or_buttons" function to support dynamic positioning and toggle functionality. The function will check if the mouse cursor is over the header panel, close button, or toggle button, enabling interactive dragging and button actions. We start by retrieving the chart width using ChartGetInteger with CHART_WIDTH_IN_PIXELS. For the header panel, we get "header_x", "header_y", "header_width", and "header_height" using ObjectGetInteger with "OBJPROP_XDISTANCE", "OBJPROP_YDISTANCE", "OBJPROP_XSIZE", and OBJPROP_YSIZE for "HEADER_PANEL", calculating "header_left" and "header_right" relative to the chart width. We check if "mouse_x" and "mouse_y" fall within these bounds, setting "in_header" to true if they do.

For the close button, we fetch "close_x" and "close_y" for "CLOSE_BUTTON", define a 20x20 pixel area, and calculate "close_left" and "close_right", setting "in_close" to true if the cursor is within this area. Similarly, for the toggle button, we get "toggle_x" and "toggle_y" for "TOGGLE_BUTTON", define a 20x20 area, and set "in_toggle" to true if the cursor is inside. We return true if the cursor is in any of these areas ("in_header", "in_close", or "in_toggle"). This function will be crucial for detecting mouse interactions with the dashboard’s draggable header and buttons, enabling our enhanced movable and interactive features. We can then update the hover states by visualizing the colors for easier recognition.

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

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

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

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

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

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

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

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

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

   update_button_hover_states(mouse_x, mouse_y);
}

Here, we introduce two new functions, "update_button_hover_states" and "update_header_hover_state," to add visual feedback for user interactions, improving the dashboard’s usability. We start with the "update_button_hover_states" function, which takes "mouse_x" and "mouse_y" to detect hover over the close and toggle buttons. For the "CLOSE_BUTTON", we fetch "close_x" and "close_y" using ObjectGetInteger with "OBJPROP_XDISTANCE" and "OBJPROP_YDISTANCE", calculate a 20x20 pixel area relative to the chart width from ChartGetInteger with "CHART_WIDTH_IN_PIXELS", and set "is_close_hovered" if the cursor is within this area.

If "is_close_hovered" differs from "prev_close_hovered", we update "CLOSE_BUTTON" with "ObjectSetInteger", setting "OBJPROP_COLOR" to "clrWhite" and OBJPROP_BGCOLOR to "clrDodgerBlue" when hovered, or "clrYellow" and "clrNONE" when not, update "prev_close_hovered", and call the ChartRedraw function. Similarly, for "TOGGLE_BUTTON", we fetch "toggle_x" and "toggle_y", check the 20x20 area, and update its colors and "prev_toggle_hovered" if "is_toggle_hovered" changes, ensuring responsive button feedback.

Next, we create the "update_header_hover_state" function, also taking "mouse_x" and "mouse_y". We get "header_x", "header_y", "header_width", and "header_height" for "HEADER_PANEL" using "ObjectGetInteger", calculate the header’s bounds, and exclude the areas of "CLOSE_BUTTON" and "TOGGLE_BUTTON" (20x20 pixels each) to avoid overlap. If "is_header_hovered" differs from "prev_header_hovered" and "panel_dragging" is false, we update "HEADER_PANEL"’s "OBJPROP_BGCOLOR" to "clrRed" when hovered or "C'60,60,60'" when not, update "prev_header_hovered", and call "ChartRedraw". We then call "update_button_hover_states" to ensure button states are updated. These functions will provide visual cues for dragging and button interactions, enhancing the dashboard’s interactivity. We can then use the functions in the OnChartEvent event handler for full implementation. Here is the logic we apply.

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

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

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

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

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

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

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

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

         int chart_width = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS); //--- Get chart width
         int chart_height = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS); //--- Get chart height
         panel_x = MathMax(617, MathMin(chart_width, panel_x));  //--- Keep panel within right edge
         panel_y = MathMax(0, MathMin(chart_height - (panel_minimized ? 27 : 374), panel_y)); //--- Adjust height based on state

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

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

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

Here, we enhance our program by updating the OnChartEvent event handler to support dynamic positioning and toggle functionality, significantly improving on the previous version. We retain the handling of CHARTEVENT_OBJECT_CLICK for the "CLOSE_BUTTON", which logs closure with Print, plays a sound with PlaySound, sets "panel_is_visible" to false, calls the "delete_all_objects" function, and redraws the chart.

The new addition is the handling of clicks on the "TOGGLE_BUTTON", where we call the "delete_all_objects" function, toggle "panel_minimized", and either create a minimized dashboard with "create_minimized_dashboard" (logging "Minimizing the panel") or a full dashboard with "create_full_dashboard" (logging "Maximizing the panel"). We reset hover states ("prev_header_hovered", "prev_close_hovered", "prev_toggle_hovered") to false, restore default colors for "HEADER_PANEL", "CLOSE_BUTTON", and "TOGGLE_BUTTON" using the ObjectSetInteger function, and redraw the chart.

For dynamic positioning, we add CHARTEVENT_MOUSE_MOVE handling when "panel_is_visible" is true. We get "mouse_x", "mouse_y", and "mouse_state" from event parameters, skip redundant updates if coordinates match "last_mouse_x" and "last_mouse_y" and "panel_dragging" is false, and update these coordinates. We call "update_header_hover_state" to manage hover effects. If "prev_mouse_state" is 0 and "mouse_state" is 1, we check if the cursor is over "HEADER_PANEL" (excluding "CLOSE_BUTTON" and "TOGGLE_BUTTON" areas) using "ObjectGetInteger" and ChartGetInteger, then set "panel_dragging" to true, store coordinates in "panel_drag_x", "panel_drag_y", "panel_start_x", and "panel_start_y", set "HEADER_PANEL" color to "clrMediumBlue", and disable chart scrolling with "ChartSetInteger".

While "panel_dragging" and "mouse_state" are 1, we calculate displacement, update "panel_x" and "panel_y" within chart bounds, call "update_panel_positions", and redraw. On mouse release, we stop dragging, update hover states, re-enable scrolling, and redraw. We update "prev_mouse_state". These changes will now enable dragging and toggling, unlike the previous static click-only handling. Upon compilation, we get the following outcome.

DRAG, HOVER AND VISIBILITY STATES

From the visualization, we can see the dashboard is coming to life, but we need to take care of the conflicts between the event handlers. We need to give one event handler the right of way so it can be prioritized to increase productivity. For example, when we are hovering or dragging, or even in minimized mode, we need to prioritize the chart events. If we are in the dormant state or maximized state, we need to prioritize the dashboard updates. We will achieve that by altering the tick processing event, since that is where we update the dashboard.

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

We update the OnTick event handler to optimize indicator updates. Unlike the previous version, which called the "updateIndicators" function solely based on "panel_is_visible", we now add conditions to only update indicators when "panel_is_visible" is true, "panel_minimized" is false, and the cursor is not over the header or buttons, checked using "is_cursor_in_header_or_buttons" with "last_mouse_x" and "last_mouse_y". This change will ensure that indicator updates are paused when the dashboard is minimized or the user is interacting with the header, close button, or toggle button, reducing unnecessary processing and improving performance during dragging or toggling actions. Upon compilation, we have the following outcome.

IMPROVED PERFORMANCE GIF

From the visualization, we can see that there is improved performance in the dashboard, and all the objectives have been met. What now remains is testing the workability of the project, and that is handled in the preceding section.


Backtesting

We did the testing, and below is the compiled visualization in a single Graphics Interchange Format (GIF) bitmap image format.

DASHBOARD BACKTEST


Conclusion

In conclusion, we’ve enhanced our Multi-Timeframe Scanner Dashboard in MQL5 by adding dynamic positioning and toggle functionality, building on Part 3 with a movable interface, minimize/maximize toggle, and interactive hover effects for better user control. We’ve demonstrated how to implement these improvements using functions like "create_minimized_dashboard" and "update_header_hover_state", ensuring seamless integration with the existing indicator grid for real-time trading insights. You can further customize this dashboard to suit your trading needs, boosting your ability to monitor market signals efficiently across multiple timeframes. 

Last comments | Go to discussion (2)
linfo2
linfo2 | 17 Jul 2025 at 17:38
Thank you Allan , That is pretty cool , well documented and covers features I didn't know much appreciated
Allan Munene Mutiiria
Allan Munene Mutiiria | 17 Jul 2025 at 18:58
linfo2 #:
Thank you Allan , That is pretty cool , well documented and covers features I didn't know much appreciated
@linfo2 so much welcomed. Thank you.
Implementing Practical Modules from Other Languages in MQL5 (Part 02): Building the REQUESTS Library, Inspired by Python Implementing Practical Modules from Other Languages in MQL5 (Part 02): Building the REQUESTS Library, Inspired by Python
In this article, we implement a module similar to requests offered in Python to make it easier to send and receive web requests in MetaTrader 5 using MQL5.
Cycles and trading Cycles and trading
This article is about using cycles in trading. We will consider building a trading strategy based on cyclical models.
MQL5 Wizard Techniques you should know (Part 75): Using Awesome Oscillator and the Envelopes MQL5 Wizard Techniques you should know (Part 75): Using Awesome Oscillator and the Envelopes
The Awesome Oscillator by Bill Williams and the Envelopes Channel are a pairing that could be used complimentarily within an MQL5 Expert Advisor. We use the Awesome Oscillator for its ability to spot trends, while the envelopes channel is incorporated to define our support/resistance levels. In exploring this indicator pairing, we use the MQL5 wizard to build and test any potential these two may possess.
From Basic to Intermediate: Recursion From Basic to Intermediate: Recursion
In this article we will look at a very interesting and quite challenging programming concept, although it should be treated with great caution, as its misuse or misunderstanding can turn relatively simple programs into something unnecessarily complex. But when used correctly and adapted perfectly to equally suitable situations, recursion becomes an excellent ally in solving problems that would otherwise be much more laborious and time-consuming. The materials presented here are intended for educational purposes only. Under no circumstances should the application be viewed for any purpose other than to learn and master the concepts presented.