preview
MQL5 Trading Tools (Part 3): Building a Multi-Timeframe Scanner Dashboard for Strategic Trading

MQL5 Trading Tools (Part 3): Building a Multi-Timeframe Scanner Dashboard for Strategic Trading

MetaTrader 5Trading | 4 June 2025, 08:25
2 759 0
Allan Munene Mutiiria
Allan Munene Mutiiria

Introduction

In our previous article, Part 2, we enhanced a Trade Assistant Tool in MetaQuotes Language 5 (MQL5) with dynamic visual feedback for improved interactivity. Now, we focus on building a multi-timeframe scanner dashboard to deliver real-time trading signals for strategic decision-making. We introduce a grid-based interface with indicator-driven signals and a close button, covering these advancements through the following subtopics:

  1. The plan of the Scanner Dashboard
  2. Implementation in MQL5
  3. Backtesting
  4. Conclusion

These sections guide us toward creating an intuitive and powerful trading dashboard.


The plan of the Scanner Dashboard

We aim to create a multi-timeframe scanner dashboard that provides clear, real-time trading signals to enhance strategic decision-making. The dashboard will feature a grid layout displaying buy and sell signals across multiple timeframes, allowing us to quickly assess market conditions without switching charts. A close button will be included to enable easy panel dismissal, ensuring a clean and flexible user experience that adapts to our trading needs.

We will incorporate signals from key indicators, including the Relative Strength Index (RSI), Stochastic Oscillator (STOCH), Commodity Channel Index (CCI), Average Directional Index (ADX), and Awesome Oscillator (AO), which are designed to identify potential trade opportunities with customizable strength thresholds. Still, the choice of the indicators or price action data to use is upon you. This setup will help us spot trends and reversals across timeframes, supporting both short-term and long-term strategies. Our goal is a streamlined, intuitive tool that delivers actionable insights while remaining user-friendly, paving the way for future enhancements like automated alerts or additional indicators. Below is a visualization of what we aim to achieve.

IMPLEMENTATION PLAN


Implementation in MQL5

To create the program in MQL5, we will need to define the program metadata, and then define some object name constants, that will help us refer to the dashboard objects and manage them easily.

//+------------------------------------------------------------------+
//|                             TimeframeScanner Dashboard EA.mq5    |
//|                           Copyright 2025, Allan Munene Mutiiria. |
//+------------------------------------------------------------------+
#property copyright "Copyright 2025, Allan Munene Mutiiria."
#property link      "https://t.me/Forex_Algo_Trader"
#property version   "1.00"

// Define identifiers and properties for UI elements
#define MAIN_PANEL              "PANEL_MAIN"                     //--- Main panel rectangle identifier
#define HEADER_PANEL            "PANEL_HEADER"                   //--- Header panel rectangle identifier
#define HEADER_PANEL_ICON       "PANEL_HEADER_ICON"              //--- Header icon label identifier
#define HEADER_PANEL_TEXT       "PANEL_HEADER_TEXT"              //--- Header title label identifier
#define CLOSE_BUTTON            "BUTTON_CLOSE"                   //--- Close button identifier
#define SYMBOL_RECTANGLE        "SYMBOL_HEADER"                  //--- Symbol rectangle identifier
#define SYMBOL_TEXT             "SYMBOL_TEXT"                    //--- Symbol text label identifier
#define TIMEFRAME_RECTANGLE     "TIMEFRAME_"                     //--- Timeframe rectangle prefix
#define TIMEFRAME_TEXT          "TIMEFRAME_TEXT_"                //--- Timeframe text label prefix
#define HEADER_RECTANGLE        "HEADER_"                        //--- Header rectangle prefix
#define HEADER_TEXT             "HEADER_TEXT_"                   //--- Header text label prefix
#define RSI_RECTANGLE           "RSI_"                           //--- RSI rectangle prefix
#define RSI_TEXT                "RSI_TEXT_"                      //--- RSI text label prefix
#define STOCH_RECTANGLE         "STOCH_"                         //--- Stochastic rectangle prefix
#define STOCH_TEXT              "STOCH_TEXT_"                    //--- Stochastic text label prefix
#define CCI_RECTANGLE           "CCI_"                           //--- CCI rectangle prefix
#define CCI_TEXT                "CCI_TEXT_"                      //--- CCI text label prefix
#define ADX_RECTANGLE           "ADX_"                           //--- ADX rectangle prefix
#define ADX_TEXT                "ADX_TEXT_"                      //--- ADX text label prefix
#define AO_RECTANGLE            "AO_"                            //--- AO rectangle prefix
#define AO_TEXT                 "AO_TEXT_"                       //--- AO text label prefix
#define BUY_RECTANGLE           "BUY_"                           //--- Buy rectangle prefix
#define BUY_TEXT                "BUY_TEXT_"                      //--- Buy text label prefix
#define SELL_RECTANGLE          "SELL_"                          //--- Sell rectangle prefix
#define SELL_TEXT               "SELL_TEXT_"                     //--- Sell text label prefix
#define WIDTH_TIMEFRAME         90                               //--- Width of timeframe and symbol rectangles
#define WIDTH_INDICATOR         70                               //--- Width of indicator rectangles
#define WIDTH_SIGNAL            90                               //--- Width of BUY/SELL signal rectangles
#define HEIGHT_RECTANGLE        25                               //--- Height of all rectangles
#define COLOR_WHITE             clrWhite                         //--- White color for text and backgrounds
#define COLOR_BLACK             clrBlack                         //--- Black color for borders and text
#define COLOR_LIGHT_GRAY        C'230,230,230'                   //--- Light gray color for signal backgrounds
#define COLOR_DARK_GRAY         C'105,105,105'                   //--- Dark gray color for indicator backgrounds

We start by establishing the user interface framework for our multi-timeframe scanner dashboard by using the #define directive to create constants like "MAIN_PANEL" and "HEADER_PANEL" for the main and header panel rectangles, and "HEADER_PANEL_ICON", "HEADER_PANEL_TEXT", and "CLOSE_BUTTON" for the header’s icon, title, and close button elements.

We define identifiers for the dashboard’s grid structure. For the symbol, we set "SYMBOL_RECTANGLE" and "SYMBOL_TEXT", while "TIMEFRAME_RECTANGLE" and "TIMEFRAME_TEXT" prefixes handle timeframe rows. We use "HEADER_RECTANGLE" and "HEADER_TEXT" prefixes for column headers, and prefixes like "RSI_RECTANGLE", "STOCH_RECTANGLE", "BUY_RECTANGLE", with corresponding "RSI_TEXT", "STOCH_TEXT", and "BUY_TEXT" for indicator and signal cells.

We configure sizes with "WIDTH_TIMEFRAME" (90 pixels), "WIDTH_INDICATOR" (70 pixels), "WIDTH_SIGNAL" (90 pixels), and "HEIGHT_RECTANGLE" (25 pixels). We define colors using "COLOR_WHITE" and "COLOR_BLACK" for text and borders, "COLOR_LIGHT_GRAY" ("C'230,230,230'") for signal backgrounds, and "COLOR_DARK_GRAY" ("C'105,105,105'") for indicators, ensuring a uniform and clear layout. We then need to define some more global variables that we will throughout the program.

bool panel_is_visible = true;                                    //--- Flag to control panel visibility

// Define the timeframes to be used
ENUM_TIMEFRAMES timeframes_array[] = {PERIOD_M1, PERIOD_M5, PERIOD_M15, PERIOD_M20, PERIOD_M30, 
                                      PERIOD_H1, PERIOD_H2, PERIOD_H3, PERIOD_H4, PERIOD_H8, 
                                      PERIOD_H12, PERIOD_D1, PERIOD_W1}; //--- Array of timeframes for scanning

// Global variables for indicator values
double rsi_values[];                                             //--- Array to store RSI values
double stochastic_values[];                                      //--- Array to store Stochastic signal line values
double cci_values[];                                             //--- Array to store CCI values
double adx_values[];                                             //--- Array to store ADX values
double ao_values[];                                              //--- Array to store AO values

Here, we declare the boolean variable "panel_is_visible" and set it to true, which determines whether the dashboard is displayed on the chart. This flag allows us to toggle the dashboard’s visibility as needed, especially when we don't need data updates. We then define the array "timeframes_array" using the ENUM_TIMEFRAMES type, listing periods from "PERIOD_M1" (1-minute) to "PERIOD_W1" (weekly). This array specifies the timeframes the dashboard will analyze, enabling us to scan market signals across multiple time horizons in a structured manner. If you don't need some or need twisting, just modify the enumerations.

To store indicator data, we create double arrays "rsi_values", "stochastic_values", "cci_values", "adx_values", and "ao_values". These arrays hold the calculated values for the Relative Strength Index, Stochastic Oscillator, Commodity Channel Index, Average Directional Index, and Awesome Oscillator, respectively, allowing us to process and display trading signals for each timeframe efficiently. We can now define some helper functions that we will use to determine the direction and truncation of the retrieved chart symbol as follows.

//+------------------------------------------------------------------+
//| Truncate timeframe enum to display string                        |
//+------------------------------------------------------------------+
string truncate_timeframe_name(int timeframe_index)              //--- Function to format timeframe name
{
   string timeframe_string = StringSubstr(EnumToString(timeframes_array[timeframe_index]), 7); //--- Extract timeframe name
   return timeframe_string;                                      //--- Return formatted name
}

//+------------------------------------------------------------------+
//| Calculate signal strength for buy/sell                           |
//+------------------------------------------------------------------+
string calculate_signal_strength(double rsi, double stochastic, double cci, double adx, double ao, bool is_buy) //--- Function to compute signal strength
{
   int signal_strength = 0;                                      //--- Initialize signal strength counter
   
   if(is_buy && rsi < 40) signal_strength++;                     //--- Increment for buy if RSI is oversold
   else if(!is_buy && rsi > 60) signal_strength++;               //--- Increment for sell if RSI is overbought
   
   if(is_buy && stochastic < 40) signal_strength++;              //--- Increment for buy if Stochastic is oversold
   else if(!is_buy && stochastic > 60) signal_strength++;        //--- Increment for sell if Stochastic is overbought
   
   if(is_buy && cci < -70) signal_strength++;                    //--- Increment for buy if CCI is oversold
   else if(!is_buy && cci > 70) signal_strength++;               //--- Increment for sell if CCI is overbought
   
   if(adx > 40) signal_strength++;                               //--- Increment if ADX indicates strong trend
   
   if(is_buy && ao > 0) signal_strength++;                       //--- Increment for buy if AO is positive
   else if(!is_buy && ao < 0) signal_strength++;                 //--- Increment for sell if AO is negative
   
   if(signal_strength >= 3) return is_buy ? "Strong Buy" : "Strong Sell"; //--- Return strong signal if 3+ conditions met
   if(signal_strength >= 2) return is_buy ? "Buy" : "Sell";      //--- Return regular signal if 2 conditions met
   return "Neutral";                                             //--- Return neutral if insufficient conditions
}

Here, we define the "truncate_timeframe_name" function, which takes an integer parameter "timeframe_index" to format timeframe names for display. Inside, we use the StringSubstr function to extract a substring from the result of the EnumToString function applied to "timeframes_array[timeframe_index]", starting at position 7, and store it in "timeframe_string". We then return "timeframe_string", providing a clean, user-readable timeframe name.

We create the "calculate_signal_strength" function to determine buy or sell signals based on indicator values. We initialize an integer "signal_strength" to zero to count matching conditions. For the Relative Strength Index, we increment "signal_strength" if "is_buy" is true and "rsi" is below 40 (oversold) or if "is_buy" is false and "rsi" exceeds 60 (overbought). Similarly, we check "stochastic" (below 40 or above 60), "CCI" (below -70 or above 70), and "ao" (positive for buy, negative for sell), incrementing "signal_strength" for each met condition.

We also evaluate the Average Directional Index, incrementing "signal_strength" if "adx" exceeds 40, indicating a strong trend for both buy and sell scenarios. If "signal_strength" reaches 3 or more, we return “Strong Buy” for "is_buy" true or “Strong Sell” otherwise. If it’s 2, we return “Buy” or “Sell”, and for fewer, we return “Neutral”, enabling clear signal classification for the dashboard. Finally, we can now define the functions that will enable us to create the objects.

//+------------------------------------------------------------------+
//| Create a rectangle for the UI                                    |
//+------------------------------------------------------------------+
bool create_rectangle(string object_name, int x_distance, int y_distance, int x_size, int y_size, 
                      color background_color, color border_color = COLOR_BLACK) //--- Function to create a rectangle
{
   ResetLastError();                                                            //--- Reset error code
   if(!ObjectCreate(0, object_name, OBJ_RECTANGLE_LABEL, 0, 0, 0)) {            //--- Create rectangle object
      Print(__FUNCTION__, ": failed to create Rectangle: ERR Code: ", GetLastError()); //--- Log creation failure
      return(false);                                                            //--- Return failure
   }
   ObjectSetInteger(0, object_name, OBJPROP_XDISTANCE, x_distance);             //--- Set x position
   ObjectSetInteger(0, object_name, OBJPROP_YDISTANCE, y_distance);             //--- Set y position
   ObjectSetInteger(0, object_name, OBJPROP_XSIZE, x_size);                     //--- Set width
   ObjectSetInteger(0, object_name, OBJPROP_YSIZE, y_size);                     //--- Set height
   ObjectSetInteger(0, object_name, OBJPROP_CORNER, CORNER_RIGHT_UPPER);        //--- Set corner to top-right
   ObjectSetInteger(0, object_name, OBJPROP_BGCOLOR, background_color);         //--- Set background color
   ObjectSetInteger(0, object_name, OBJPROP_BORDER_COLOR, border_color);        //--- Set border color
   ObjectSetInteger(0, object_name, OBJPROP_BORDER_TYPE, BORDER_FLAT);          //--- Set flat border style
   ObjectSetInteger(0, object_name, OBJPROP_BACK, false);                       //--- Set to foreground
   
   ChartRedraw(0);                                                              //--- Redraw chart
   return(true);                                                                //--- Return success
}

//+------------------------------------------------------------------+
//| Create a text label for the UI                                   |
//+------------------------------------------------------------------+
bool create_label(string object_name, string text, int x_distance, int y_distance, int font_size = 12, 
                  color text_color = COLOR_BLACK, string font = "Arial Rounded MT Bold") //--- Function to create a label
{
   ResetLastError();                                                               //--- Reset error code
   if(!ObjectCreate(0, object_name, OBJ_LABEL, 0, 0, 0)) {                         //--- Create label object
      Print(__FUNCTION__, ": failed to create Label: ERR Code: ", GetLastError()); //--- Log creation failure
      return(false);                                                               //--- Return failure
   }
   ObjectSetInteger(0, object_name, OBJPROP_XDISTANCE, x_distance);                //--- Set x position
   ObjectSetInteger(0, object_name, OBJPROP_YDISTANCE, y_distance);                //--- Set y position
   ObjectSetInteger(0, object_name, OBJPROP_CORNER, CORNER_RIGHT_UPPER);           //--- Set corner to top-right
   ObjectSetString(0, object_name, OBJPROP_TEXT, text);                            //--- Set label text
   ObjectSetString(0, object_name, OBJPROP_FONT, font);                            //--- Set font
   ObjectSetInteger(0, object_name, OBJPROP_FONTSIZE, font_size);                  //--- Set font size
   ObjectSetInteger(0, object_name, OBJPROP_COLOR, text_color);                    //--- Set text color
   ObjectSetInteger(0, object_name, OBJPROP_ANCHOR, ANCHOR_CENTER);                //--- Center text
   
   ChartRedraw(0);                                                                 //--- Redraw chart
   return(true);                                                                   //--- Return success
}

To enable the creation of the objects, we define the "create_rectangle" function with parameters "object_name", "x_distance", "y_distance", "x_size", "y_size", "background_color", and "border_color". We use the ResetLastError function, create an OBJ_RECTANGLE_LABEL with the ObjectCreate function, and log errors with the "Print" function if it fails, returning false.

We set rectangle properties with the ObjectSetInteger function for the position, size, "CORNER_RIGHT_UPPER", "background_color", "border_color", and "BORDER_FLAT", ensuring foreground display. We use the ChartRedraw function and return true. For text, we define the "create_label" function with "object_name", "text", "x_distance", "y_distance", "font_size", "text_color", and "font".

We use the "ResetLastError" function, create an "OBJ_LABEL" with the "ObjectCreate" function, and log errors if it fails. We use the "ObjectSetInteger" function for position, size, color, and "ANCHOR_CENTER", and the ObjectSetString function for "text" and "font". We use the "ChartRedraw" function and return true. Armed with these functions, we can now create the initial panel objects to give us a starting point in the "OnInit" event handler.

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()                                                                             //--- Initialize EA
{
   create_rectangle(MAIN_PANEL, 632, 40, 617, 374, C'30,30,30', BORDER_FLAT);            //--- Create main panel background
   create_rectangle(HEADER_PANEL, 632, 40, 617, 27, C'60,60,60', BORDER_FLAT);           //--- Create header panel background
   create_label(HEADER_PANEL_ICON, CharToString(91), 620, 54, 18, clrAqua, "Wingdings"); //--- Create header icon
   create_label(HEADER_PANEL_TEXT, "TimeframeScanner", 527, 52, 13, COLOR_WHITE);        //--- Create header title
   create_label(CLOSE_BUTTON, CharToString('r'), 32, 54, 18, clrYellow, "Webdings");     //--- Create close button

   // Create header rectangle and label
   create_rectangle(SYMBOL_RECTANGLE, 630, 75, WIDTH_TIMEFRAME, HEIGHT_RECTANGLE, clrGray); //--- Create symbol rectangle
   create_label(SYMBOL_TEXT, _Symbol, 585, 85, 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 = (630 - 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, 75, width, HEIGHT_RECTANGLE, clrGray);             //--- Create header rectangle
      create_label(HEADER_TEXT + IntegerToString(header_index), header_names[header_index], x_offset - width/2, 85, 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), 630, (75 + 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), 585, (85 + 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 = (630 - 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, (75 + 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, (85 + HEIGHT_RECTANGLE) + timeframe_index * HEIGHT_RECTANGLE - (1 + timeframe_index), 10, COLOR_WHITE);                  //--- Create cell label
      }
   }
   
   // Initialize indicator arrays
   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
    
   return(INIT_SUCCEEDED);                                   //--- Return initialization success
}

In the OnInit event handler, we initialize the multi-timeframe scanner dashboard’s user interface. We use the "create_rectangle" function to draw the "MAIN_PANEL" at (632, 40) with size 617x374 pixels in “C’30,30,30’” and the "HEADER_PANEL" at the same position with a 27-pixel height in “C’60,60,60’”. We use the "create_label" function to add the "HEADER_PANEL_ICON" with a Wingdings character at (620, 54). We use the default characters in MQL5 and use the CharToString function to convert the character code to a string. Here is the character code we used, 91, but you can use any of your liking.

MQL5 WINGDINGS

We then create the "HEADER_PANEL_TEXT" with “TimeframeScanner” at (527, 52), and "CLOSE_BUTTON" at (32, 54), but this time round we advance to using a different font and map the letter "r" to the string. Here is a visualization of the different font symbols you can use.

SYMBOLS FONT

We set up the symbol display using the "create_rectangle" function for the "SYMBOL_RECTANGLE" at (630, 75), sized "WIDTH_TIMEFRAME" by "HEIGHT_RECTANGLE", in gray. We use the "create_label" function to place the "SYMBOL_TEXT" at (585, 85) with the current symbol. For headers, we define the "header_names" array with titles like “BUY” and “RSI”, looping to create "HEADER_RECTANGLE" at y=75 with x-offsets based on "WIDTH_SIGNAL" and "WIDTH_INDICATOR", and "HEADER_TEXT" labels at y=85 using the "create_label" function.

We build the timeframe grid by looping through "timeframes_array". We use the "create_rectangle" function for "TIMEFRAME_RECTANGLE" at x=630, y-offsets from (75 + "HEIGHT_RECTANGLE") adjusted by -(1 + "timeframe_index"), colored with "timeframe_background". We use the "create_label" function for "TIMEFRAME_TEXT" with names from the "truncate_timeframe_name" function. For cells, we loop to create "BUY_RECTANGLE", "RSI_RECTANGLE", etc., with the "create_rectangle" function, using "cell_background", and add “-/-” labels with the "create_label" function. We initialize indicator arrays like "rsi_values" using the ArraySetAsSeries function, setting them as time series for data handling. We return INIT_SUCCEEDED to confirm successful initialization, establishing the dashboard’s layout and data structure. Upon compilation, we have the following outcome.

STATIC DASHBOARD

From the image, we can see that we have the dashboard ready. All we have to do now is add the indicator values and use them for analysis. We already have a function for the analysis, we just need to get the data. To achieve that easily, we create a function to handle all the dynamic update logic.

//+------------------------------------------------------------------+
//| Update indicator values                                          |
//+------------------------------------------------------------------+
void updateIndicators()                                                                               //--- Update dashboard indicators
{
   for(int timeframe_index = 0; timeframe_index < ArraySize(timeframes_array); timeframe_index++) {   //--- Loop through timeframes
      // Initialize indicator handles
      int rsi_indicator_handle = iRSI(_Symbol, timeframes_array[timeframe_index], 14, PRICE_CLOSE);   //--- Create RSI handle
      int stochastic_indicator_handle = iStochastic(_Symbol, timeframes_array[timeframe_index], 14, 3, 3, MODE_SMA, STO_LOWHIGH); //--- Create Stochastic handle
      int cci_indicator_handle = iCCI(_Symbol, timeframes_array[timeframe_index], 20, PRICE_TYPICAL); //--- Create CCI handle
      int adx_indicator_handle = iADX(_Symbol, timeframes_array[timeframe_index], 14);                //--- Create ADX handle
      int ao_indicator_handle = iAO(_Symbol, timeframes_array[timeframe_index]);                      //--- Create AO handle
      
      // Check for valid handles
      if(rsi_indicator_handle == INVALID_HANDLE || stochastic_indicator_handle == INVALID_HANDLE || 
         cci_indicator_handle == INVALID_HANDLE || adx_indicator_handle == INVALID_HANDLE || 
         ao_indicator_handle == INVALID_HANDLE) {                                                             //--- Check if any handle is invalid
         Print("Failed to create indicator handle for timeframe ", truncate_timeframe_name(timeframe_index)); //--- Log failure
         continue;                                                                                            //--- Skip to next timeframe
      }
      
      // Copy indicator values
      if(CopyBuffer(rsi_indicator_handle, 0, 0, 1, rsi_values) <= 0 ||                            //--- Copy RSI value
         CopyBuffer(stochastic_indicator_handle, 1, 0, 1, stochastic_values) <= 0 ||              //--- Copy Stochastic signal line value
         CopyBuffer(cci_indicator_handle, 0, 0, 1, cci_values) <= 0 ||                            //--- Copy CCI value
         CopyBuffer(adx_indicator_handle, 0, 0, 1, adx_values) <= 0 ||                            //--- Copy ADX value
         CopyBuffer(ao_indicator_handle, 0, 0, 1, ao_values) <= 0) {                              //--- Copy AO value
         Print("Failed to copy buffer for timeframe ", truncate_timeframe_name(timeframe_index)); //--- Log copy failure
         continue;                                                                                //--- Skip to next timeframe
      }
      
      // Update RSI
      color rsi_text_color = (rsi_values[0] < 30) ? clrBlue : (rsi_values[0] > 70) ? clrRed : COLOR_WHITE;         //--- Set RSI text color
      update_label(RSI_TEXT + IntegerToString(timeframe_index), DoubleToString(rsi_values[0], 2), rsi_text_color); //--- Update RSI label
      
      // Update Stochastic (Signal Line only)
      color stochastic_text_color = (stochastic_values[0] < 20) ? clrBlue : (stochastic_values[0] > 80) ? clrRed : COLOR_WHITE;    //--- Set Stochastic text color
      update_label(STOCH_TEXT + IntegerToString(timeframe_index), DoubleToString(stochastic_values[0], 2), stochastic_text_color); //--- Update Stochastic label
      
      // Update CCI
      color cci_text_color = (cci_values[0] < -100) ? clrBlue : (cci_values[0] > 100) ? clrRed : COLOR_WHITE;      //--- Set CCI text color
      update_label(CCI_TEXT + IntegerToString(timeframe_index), DoubleToString(cci_values[0], 2), cci_text_color); //--- Update CCI label
      
      // Update ADX
      color adx_text_color = (adx_values[0] > 25) ? clrBlue : COLOR_WHITE;                                         //--- Set ADX text color
      update_label(ADX_TEXT + IntegerToString(timeframe_index), DoubleToString(adx_values[0], 2), adx_text_color); //--- Update ADX label
      
      // Update AO
      color ao_text_color = (ao_values[0] > 0) ? clrGreen : (ao_values[0] < 0) ? clrRed : COLOR_WHITE;          //--- Set AO text color
      update_label(AO_TEXT + IntegerToString(timeframe_index), DoubleToString(ao_values[0], 2), ao_text_color); //--- Update AO label
      
      // Update Buy/Sell signals
      string buy_signal = calculate_signal_strength(rsi_values[0], stochastic_values[0], cci_values[0], 
                                                   adx_values[0], ao_values[0], true);                          //--- Calculate buy signal
      string sell_signal = calculate_signal_strength(rsi_values[0], stochastic_values[0], cci_values[0], 
                                                    adx_values[0], ao_values[0], false);                        //--- Calculate sell signal
      
      color buy_text_color = (buy_signal == "Strong Buy") ? COLOR_WHITE : COLOR_WHITE;                          //--- Set buy text color
      color buy_background = (buy_signal == "Strong Buy") ? clrGreen : 
                            (buy_signal == "Buy") ? clrSeaGreen : COLOR_DARK_GRAY;                              //--- Set buy background color
      update_rectangle(BUY_RECTANGLE + IntegerToString(timeframe_index), buy_background);                       //--- Update buy rectangle
      update_label(BUY_TEXT + IntegerToString(timeframe_index), buy_signal, buy_text_color);                    //--- Update buy label
      
      color sell_text_color = (sell_signal == "Strong Sell") ? COLOR_WHITE : COLOR_WHITE;                       //--- Set sell text color
      color sell_background = (sell_signal == "Strong Sell") ? clrRed : 
                             (sell_signal == "Sell") ? clrSalmon : COLOR_DARK_GRAY;                             //--- Set sell background color
      update_rectangle(SELL_RECTANGLE + IntegerToString(timeframe_index), sell_background);                     //--- Update sell rectangle
      update_label(SELL_TEXT + IntegerToString(timeframe_index), sell_signal, sell_text_color);                 //--- Update sell label
      
      // Release indicator handles
      IndicatorRelease(rsi_indicator_handle);                   //--- Release RSI handle
      IndicatorRelease(stochastic_indicator_handle);            //--- Release Stochastic handle
      IndicatorRelease(cci_indicator_handle);                   //--- Release CCI handle
      IndicatorRelease(adx_indicator_handle);                   //--- Release ADX handle
      IndicatorRelease(ao_indicator_handle);                    //--- Release AO handle
   }
}

To easily manage the dashboard values update, we implement the "updateIndicators" function to refresh the indicator values and signals. We loop through "timeframes_array" using "timeframe_index", processing each timeframe. We use the iRSI, "iStochastic", "iCCI", iADX, and "iAO" functions to create indicator handles like "rsi_indicator_handle" for the current symbol and timeframe, configuring parameters such as a 14-period RSI and a 20-period CCI. All the indicator settings are customizable to fit your needs, so don't limit yourself to the default values.

We then check if any handle, such as "rsi_indicator_handle", equals INVALID_HANDLE, indicating a creation failure. If so, we use the "Print" function to log the error with the "truncate_timeframe_name" function’s output and skip to the next timeframe. We use the CopyBuffer function to fetch the latest values into arrays like "rsi_values", and if any fail, we log the error and continue. We update indicator displays using the "update_label" function. For example, we set "rsi_text_color" based on "rsi_values[0]" (blue if <30, red if >70, else "COLOR_WHITE") and update "RSI_TEXT" with the "DoubleToString" function’s formatted value. We repeat this for "stochastic_values", "cci_values", "adx_values", and "ao_values", applying color logic (e.g., green for positive "ao_values").

We calculate signals using the "calculate_signal_strength" function, passing "rsi_values[0]" and others, to get "buy_signal" and "sell_signal". We set "buy_background" (e.g., green for “Strong Buy”) and use the "update_rectangle" function for "BUY_RECTANGLE", updating "BUY_TEXT" with "update_label". We do the same for "sell_background" and "SELL_TEXT". Finally, we use the IndicatorRelease function to free handles like "rsi_indicator_handle", ensuring efficient resource management. The helper functions that we used, are defined as below.

//+------------------------------------------------------------------+
//| Update rectangle background color                                |
//+------------------------------------------------------------------+
bool update_rectangle(string object_name, color background_color)//--- Function to update rectangle color
{
   int found = ObjectFind(0, object_name);                       //--- Find rectangle object
   if(found < 0) {                                               //--- Check if object not found
      ResetLastError();                                          //--- Reset error code
      Print("UNABLE TO FIND THE RECTANGLE: ", object_name, ". ERR Code: ", GetLastError()); //--- Log error
      return(false);                                             //--- Return failure
   }
   ObjectSetInteger(0, object_name, OBJPROP_BGCOLOR, background_color); //--- Set background color
   
   ChartRedraw(0);                                               //--- Redraw chart
   return(true);                                                 //--- Return success
}

//+------------------------------------------------------------------+
//| Update label text and color                                      |
//+------------------------------------------------------------------+
bool update_label(string object_name, string text, color text_color) //--- Function to update label
{
   int found = ObjectFind(0, object_name);                       //--- Find label object
   if(found < 0) {                                               //--- Check if object not found
      ResetLastError();                                          //--- Reset error code
      Print("UNABLE TO FIND THE LABEL: ", object_name, ". ERR Code: ", GetLastError()); //--- Log error
      return(false);                                             //--- Return failure
   }
   ObjectSetString(0, object_name, OBJPROP_TEXT, text);          //--- Set label text
   ObjectSetInteger(0, object_name, OBJPROP_COLOR, text_color);  //--- Set text color
   
   ChartRedraw(0);                                               //--- Redraw chart
   return(true);                                                 //--- Return success
}

We define the "update_rectangle" function, taking "object_name" and "background_color" as parameters to modify a rectangle’s appearance. We use the ObjectFind function to locate the rectangle, storing the result in "found". If "found" is less than 0, indicating the object is missing, we use the ResetLastError function, log the error with the "Print" function and "GetLastError", and return false. We update the rectangle’s background by using the "ObjectSetInteger" function to set "OBJPROP_BGCOLOR" to "background_color". We use the ChartRedraw function to refresh the chart and return true for success. For text updates, we define the "update_label" function with parameters "object_name", "text", and "text_color".

We use the "ObjectFind" function to check for the label’s existence, and if "found" is negative, we use the "ResetLastError" function, log the error with the "Print" function, and return false. We use the ObjectSetString function to set "OBJPROP_TEXT" to "text" and the ObjectSetInteger function to set "OBJPROP_COLOR" to "text_color". We use the "ChartRedraw" function to update the chart and return true, enabling dynamic label updates. We can now call the update function on the tick to make updates to the dashboard.

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()                                                    //--- Handle tick events
{
   if (panel_is_visible) {                                       //--- Check if panel is visible
      updateIndicators();                                        //--- Update indicators
   }
}

Here, in the OnTick event handler, we just call the "updateIndicators" function if the panel is visible to apply the updates. We finally need to delete the objects that we created so that they are removed from that chart when we no longer need them.

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)                                  //--- Deinitialize EA
{
   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

   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
   
   ChartRedraw(0);                                               //--- Redraw chart
}

Finally, we implement the cleanup process using the OnDeinit function, which runs when the Expert Advisor is removed. We use the ObjectDelete function to remove individual UI elements, starting with the "MAIN_PANEL" rectangle, followed by the "HEADER_PANEL", "HEADER_PANEL_ICON", "HEADER_PANEL_TEXT", and "CLOSE_BUTTON", ensuring the main panel and header components are cleared from the chart.

We systematically remove all dashboard objects by using the ObjectsDeleteAll function for each element type. We delete all rectangles and labels associated with "SYMBOL_RECTANGLE" and "SYMBOL_TEXT", "TIMEFRAME_RECTANGLE" and "TIMEFRAME_TEXT", and "HEADER_RECTANGLE" and "HEADER_TEXT", clearing the symbol, timeframe, and header displays. We also remove indicator-related objects, including "RSI_RECTANGLE", "STOCH_RECTANGLE", "CCI_RECTANGLE", "ADX_RECTANGLE", and "AO_RECTANGLE", along with their respective text labels like "RSI_TEXT".

We complete the cleanup by using the "ObjectsDeleteAll" function to delete all "BUY_RECTANGLE" and "SELL_RECTANGLE" objects, along with their "BUY_TEXT" and "SELL_TEXT" labels, removing all signal-related elements. Finally, we use the ChartRedraw function to refresh the chart, ensuring a clean visual state after deinitialization. Finally, we need to take care of the cancel button so that when clicked, we close the dashboard and disable further updates.

//+------------------------------------------------------------------+
//| Expert chart event handler                                       |
//+------------------------------------------------------------------+
void OnChartEvent(const int       event_id,                      //--- Event ID
                  const long&     long_param,                    //--- Long parameter
                  const double&   double_param,                  //--- Double parameter
                  const string&   string_param)                  //--- String parameter
{
   if (event_id == CHARTEVENT_OBJECT_CLICK) {                    //--- Check for 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
         
         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
      
         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
         
         ChartRedraw(0);                                         //--- Redraw chart
      }
   }
}

In the OnChartEvent event handler, we listen to object clicks when the event id is CHARTEVENT_OBJECT_CLICK, and the object clicked is the cancel button, and we play an alert sound using the PlaySound function to alert the user that the panel is being disabled, then disable the visibility of the panel, and use the same logic we used to clear the chart on OnDeinit to clear the dashboard. Upon compilation, we have the following outcome.

UPDATED DASHBOARD

From the image, we can see that we have the dashboard updated with the indicator data and trading directions indicated. 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.

TESTING GIF


Conclusion

In conclusion, we’ve developed a multi-timeframe scanner dashboard in MQL5, integrating a structured grid layout, real-time indicator signals, and an interactive close button to enhance strategic trading decisions. We’ve shown the design and implementation of these features, ensuring their effectiveness through robust initialization and dynamic updates tailored to our trading requirements. You can adapt this dashboard to fit your preferences, greatly improving your ability to monitor and act on market signals across multiple timeframes.

Introduction to MQL5 (Part 17): Building Expert Advisors Using Technical Chart Patterns (II) Introduction to MQL5 (Part 17): Building Expert Advisors Using Technical Chart Patterns (II)
This article teaches beginners how to build an Expert Advisor (EA) in MQL5 that trades based on chart pattern recognition using trend line breakouts and reversals. By learning how to retrieve trend line values dynamically and compare them with price action, readers will be able to develop EAs capable of identifying and trading chart patterns such as ascending and descending trend lines, channels, wedges, triangles, and more.
Neural Networks in Trading: Contrastive Pattern Transformer Neural Networks in Trading: Contrastive Pattern Transformer
The Contrastive Transformer is designed to analyze markets both at the level of individual candlesticks and based on entire patterns. This helps improve the quality of market trend modeling. Moreover, the use of contrastive learning to align representations of candlesticks and patterns fosters self-regulation and improves the accuracy of forecasts.
Data Science and ML (Part 42): Forex Time series Forecasting using ARIMA in Python, Everything you need to Know Data Science and ML (Part 42): Forex Time series Forecasting using ARIMA in Python, Everything you need to Know
ARIMA, short for Auto Regressive Integrated Moving Average, is a powerful traditional time series forecasting model. With the ability to detect spikes and fluctuations in a time series data, this model can make accurate predictions on the next values. In this article, we are going to understand what is it, how it operates, what you can do with it when it comes to predicting the next prices in the market with high accuracy and much more.
Price Action Analysis Toolkit Development (Part 25): Dual EMA Fractal Breaker Price Action Analysis Toolkit Development (Part 25): Dual EMA Fractal Breaker
Price action is a fundamental approach for identifying profitable trading setups. However, manually monitoring price movements and patterns can be challenging and time-consuming. To address this, we are developing tools that analyze price action automatically, providing timely signals whenever potential opportunities are detected. This article introduces a robust tool that leverages fractal breakouts alongside EMA 14 and EMA 200 to generate reliable trading signals, helping traders make informed decisions with greater confidence.