preview
Price Action Analysis Toolkit Development (Part 79): Extending the Indicator Search Panel with Dynamic Input Parameter Configuration

Price Action Analysis Toolkit Development (Part 79): Extending the Indicator Search Panel with Dynamic Input Parameter Configuration

MetaTrader 5Indicators |
531 0
Christian Benjamin
Christian Benjamin

Contents


Introduction

In Part 77 of this series, we introduced an indicator search panel that made it easier to locate and launch MetaTrader 5's built-in technical indicators. Part 78 expanded this functionality by allowing indicators to be attached to any selected symbol, eliminating the need to manually switch between charts.

Although these enhancements streamlined the process of finding and opening indicators, one important limitation remained. Indicators were still attached with default parameters. To customize settings, traders had to open the indicator properties window manually.

In this article, we address that limitation by extending the search panel with a dynamic parameter configuration system. Instead of immediately attaching the selected indicator, users are first presented with a configuration dialog where they can adjust the indicator's input values before it is created.

By integrating parameter configuration directly into the search workflow, our application moves beyond simple indicator discovery and provides a smoother workflow for searching, configuring, and attaching indicators.


From Indicator Discovery to Indicator Configuration

The Limitation of Default Indicator Parameters

By the end of Part 78, our indicator launcher had become a practical tool for everyday chart analysis. From a single panel, we could quickly search for built-in indicators and attach them to any symbol available in Market Watch.


Figure 1: Selecting a different symbol before launching an indicator.

Traders rarely use indicators with their default settings. A Moving Average period may need to be increased, the averaging method changed from SMA to EMA, the applied price switched from Close to Typical Price, or the Bollinger Bands deviation adjusted. These small changes are often necessary before meaningful analysis can begin.

Whenever different settings were required, the process remained the same:

  • Search for the indicator.
  • Attach it to the chart.
  • Open its properties.
  • Modify the input parameters.
  • Apply the changes.

Although the search panel had simplified the first part of this process, parameter configuration still relied on MetaTrader 5's built-in properties window. This meant leaving the panel each time an indicator needed anything other than its default settings.

That naturally raised a simple question:

Why not configure the indicator before attaching it?

This question became the motivation for this enhancement. Instead of attaching an indicator first and editing it afterwards, the goal is to present its configurable inputs immediately after it is selected. Once the parameters are confirmed, the indicator can be created using those values, keeping the entire process within the same interface.

Designing the Configuration Workflow

Instead of attaching an indicator straight away, we first present its configurable inputs. Once the required values have been entered, the indicator is created using those settings, removing the extra step of editing its properties afterwards.

The figure below illustrate how the workflow changes.


Figure 2. Previous vs. New Workflow


Designing a Metadata-Driven Parameter System

Why We Need a Parameter Definition Model

Every built-in indicator accepts a different set of input parameters. RSI requires a period and an applied price, Moving Average adds a shift and averaging method, while Bollinger Bands introduce a deviation value. Other indicators, such as Awesome Oscillator, do not require any configurable inputs.

Table 1:

Indicator

Required Parameters

RSI Period, Applied Price
Moving Average
Period, Shift, MA Method, Applied Price
Bollinger Bands
Period, Deviation, Shift, Applied Price
Awesome Oscillator
None

Building a separate dialog for every indicator would duplicate the same logic repeatedly. The labels and controls may differ, but the process remains identical: display the inputs, collect user values, validate them, and pass them to IndicatorCreate(). Each input parameter is described by a single structure. The dialog reads these definitions and builds the required controls at runtime. This allows one interface to support all indicators.

//+------------------------------------------------------------------+
//|                                         ParameterDefinitions.mqh |
//|                                  Copyright 2026, MetaQuotes Ltd. |
//|                          https://www.mql5.com/en/users/lynnchris |
//+------------------------------------------------------------------+
#property copyright "Copyright 2026, MetaQuotes Ltd."
#property link      "https://www.mql5.com/en/users/lynnchris"
#property version   "3.0"

#ifndef _PARAMETER_DEFINITIONS_MQH
#define _PARAMETER_DEFINITIONS_MQH

#include <Indicators\Indicators.mqh>

//--- Parameter type enumeration
enum ENUM_PARAM_TYPE
  {
   PARAM_INTEGER,   
   PARAM_DOUBLE,   
   PARAM_ENUM       
  };

//--- Structure describing a single parameter
struct SParameterDef
  {
   string            name;           
   ENUM_PARAM_TYPE   type;           
   int               default_int;    
   double            default_double; 
   int               default_enum;   
   int               min_value;     
   int               max_value;      
   string            enum_values[];  
   int               enum_codes[];   
   string            description;    
  };

Each member describes one characteristic of a parameter.

Table 2:

Member Purpose
name Parameter name displayed in the dialog.
type Integer, double, or enumeration.
default_int
Default integer value.
default_double Default double value.
default_enum Default enumeration value.
min_value / max_value Integer validation limits.
enum_values Text shown for enumeration choices. 
enum_codes MetaTrader 5 enumeration values passed to IndicatorCreate().
description Optional description of the parameter.

Parameter types are represented by the following enumeration.

//--- Parameter type enumeration
enum ENUM_PARAM_TYPE
  {
   PARAM_INTEGER,   // integer (whole number) value
   PARAM_DOUBLE,    // double value
   PARAM_ENUM       // enumeration (e.g., MA method, applied price)
  };

Enumeration parameters require both display text and the corresponding MetaTrader 5 enumeration values. Rather than defining these lists repeatedly, helper functions populate them whenever they are needed.

For example, applied price options are defined once and reused by every indicator that accepts an applied price.

//+------------------------------------------------------------------+
//| Helper to fill an SParameterDef with applied price enum values   |
//+------------------------------------------------------------------+
void AddPriceEnums(SParameterDef &def)
  {
//--- 6 possible applied price options
   ArrayResize(def.enum_values, 6);
   ArrayResize(def.enum_codes, 6);
   def.enum_values[0] = "Close";
   def.enum_codes[0] = PRICE_CLOSE;
   def.enum_values[1] = "Open";
   def.enum_codes[1] = PRICE_OPEN;
   def.enum_values[2] = "High";
   def.enum_codes[2] = PRICE_HIGH;
   def.enum_values[3] = "Low";
   def.enum_codes[3] = PRICE_LOW;
   def.enum_values[4] = "Median";
   def.enum_codes[4] = PRICE_MEDIAN;
   def.enum_values[5] = "Typical";
   def.enum_codes[5] = PRICE_TYPICAL;
  }

Moving average methods are handled in the same way.

//+------------------------------------------------------------------+
//| Helper to fill an SParameterDef with MA method enum values       |
//+------------------------------------------------------------------+
void AddMAMethodEnums(SParameterDef &def)
  {
//--- 4 standard moving average methods
   ArrayResize(def.enum_values, 4);
   ArrayResize(def.enum_codes, 4);
   def.enum_values[0] = "SMA";
   def.enum_codes[0] = MODE_SMA;
   def.enum_values[1] = "EMA";
   def.enum_codes[1] = MODE_EMA;
   def.enum_values[2] = "SMMA";
   def.enum_codes[2] = MODE_SMMA;
   def.enum_values[3] = "LWMA";
   def.enum_codes[3] = MODE_LWMA;
  }

This approach keeps the parameter definitions focused on the indicator itself instead of repeatedly defining the same enumeration values.


Figure 3. Parameter Configuration Architecture

The next step is to organize these parameter definitions so they can be retrieved for any built-in indicator.


Creating a Repository of Indicator Parameter Definitions

Building the Parameter Repository

The parameter model defines how a single input is described, but we also need a way to determine which inputs belong to each built-in indicator. To achieve this, we introduce the CParameterDefinitions class, which exposes a single static function for retrieving an indicator's parameter definitions.

class CParameterDefinitions
  {
public:
   static int GetDefinitions(const ENUM_INDICATOR type,
                             SParameterDef &defs[]);
  };

GetDefinitions() takes an ENUM_INDICATOR value and fills an array of SParameterDef definitions. It returns the number of configurable parameters for that indicator.

Implementing GetDefinitions()

The implementation starts by clearing the output array before processing the requested indicator.

//+------------------------------------------------------------------+
//| Returns the parameter definition array for the indicator type    |
//+------------------------------------------------------------------+
int CParameterDefinitions::GetDefinitions(const ENUM_INDICATOR type,
                                          SParameterDef &defs[])
  {
   ArrayResize(defs, 0);   
   int count = 0;

//--- Each indicator defines its own configurable parameters
   switch(type)
     {
      // ... indicator cases ...
     }

   return(count);
  }

The switch statement maps each supported indicator to its corresponding parameter definitions. Each case specifies the inputs required by an indicator, including their names, data types, default values, validation ranges, and enumeration options where applicable.

Moving Average (4 Parameters)

A Moving Average exposes four configurable inputs: the averaging period, shift, moving average method, and applied price.

//--- Moving Average – 4 parameters
case IND_MA:
   ArrayResize(defs, 4);

   defs[0].name = "Period";
   defs[0].type = PARAM_INTEGER;
   defs[0].default_int = 10;
   defs[0].min_value = 1;
   defs[0].max_value = 1000;

   defs[1].name = "Shift";
   defs[1].type = PARAM_INTEGER;
   defs[1].default_int = 0;
   defs[1].min_value = -100;
   defs[1].max_value = 100;

   defs[2].name = "MA Method";
   defs[2].type = PARAM_ENUM;
   defs[2].default_enum = MODE_SMA;
   AddMAMethodEnums(defs[2]);

   defs[3].name = "Applied Price";
   defs[3].type = PARAM_ENUM;
   defs[3].default_enum = PRICE_CLOSE;
   AddPriceEnums(defs[3]);

   count = 4;
   break;

RSI (2 Parameters)

The Relative Strength Index requires only two configurable inputs: the calculation period and the applied price.

//--- RSI – 2 parameters
case IND_RSI:
   ArrayResize(defs, 2);

   defs[0].name = "Period";
   defs[0].type = PARAM_INTEGER;
   defs[0].default_int = 14;
   defs[0].min_value = 1;
   defs[0].max_value = 1000;

   defs[1].name = "Applied Price";
   defs[1].type = PARAM_ENUM;
   defs[1].default_enum = PRICE_CLOSE;
   AddPriceEnums(defs[1]);

   count = 2;
   break;

Bollinger Bands (4 Parameters)

Bollinger Bands combines integer, double, and enumeration inputs to define its behavior.

//--- Bollinger Bands – 4 parameters
case IND_BANDS:
   ArrayResize(defs, 4);

   defs[0].name = "Period";
   defs[0].type = PARAM_INTEGER;
   defs[0].default_int = 20;
   defs[0].min_value = 1;
   defs[0].max_value = 1000;

   defs[1].name = "Deviation";
   defs[1].type = PARAM_DOUBLE;
   defs[1].default_double = 2.0;

   defs[2].name = "Shift";
   defs[2].type = PARAM_INTEGER;
   defs[2].default_int = 0;
   defs[2].min_value = -100;
   defs[2].max_value = 100;

   defs[3].name = "Applied Price";
   defs[3].type = PARAM_ENUM;
   defs[3].default_enum = PRICE_CLOSE;
   AddPriceEnums(defs[3]);

   count = 4;
   break;

Indicators with No Configurable Parameters

Some built-in indicators do not expose configurable inputs. For these, the function returns a parameter count of zero, allowing the application to bypass the configuration dialog and attach the indicator immediately.

//--- Zero‑parameter indicators – no configurable parameters
case IND_AO:
case IND_AC:
case IND_AD:
case IND_OBV:
case IND_BWMFI:
case IND_GATOR:
case IND_VOLUMES:
case IND_FRACTALS:
   count = 0;
   break;

Supported Indicator Definitions

The complete ParameterDefinitions.mqh file contains definitions for more than thirty built-in indicators. Table 3 summarizes the indicators currently supported by the repository.

Table 3:

Indicators Parameters Count
Moving Average (MA)
Period, Shift, Method, Price
4
RSI Period, Price
2
MACD Fast, Slow, Signal, Price
4
Bollinger Bands
Period, Deviation, Shift, Price
4
Stochastic
%K, %D, Slowing, Method, Price  5
ADX / ADXW / ATR
Period  1
CCI Period, Price 2
Momentum / TRIX
Period, Price
WPR / DeMarker
Period  1
Envelopes
Period, Shift, Method, Price, Deviation
Ichimoku
Tenkan, Kijun, Senkou B, Chikou, Offset 5
SAR
Step, Maximum Step
Alligator
Jaw, Teeth, Lips (Periods and Shifts)  6
DEMA / TEMA
Period, Shift, Method, Price
4
VIDYA
Period, Volatility, Price
3
AMA
Period, Fast, Slow, Efficiency, Price
5
Force Index
Period, Method, Volume  3
RVI Period, Method
2
OSMA
Fast, Slow, Signal, Price 
Chaikin
Fast, Slow, Method, Volume
4
Bears Power / Bulls Power
Period, Price 
Standard Deviation
Period, Shift, Price 
MFI
Period, Volume
2
AO / AC / AD / OBV / BWMFI / GATOR / Volumes / Fractals
None

All indicator-specific information is now maintained in one place. Whenever the search panel needs to configure an indicator, it simply calls GetDefinitions() to retrieve the corresponding metadata.


Building the Parameter Configuration Dialog

The parameter definitions tell us which inputs belong to an indicator, but users still need a way to review and modify those values before the indicator is attached to the chart. Instead of creating a separate dialog for every built-in indicator, we build a single dialog that adapts itself according to the parameter definitions returned by GetDefinitions().

The same dialog can configure indicators with different numbers and types of inputs. Whether an indicator has one parameter or several, the dialog builds the required interface dynamically from the available metadata.


Figure 4. Dynamic Dialog Generation

The dialog is implemented by the CParameterConfigDialog class.

//+------------------------------------------------------------------+
//| Parameter configuration dialog – shows editable parameter fields |
//+------------------------------------------------------------------+
class CParameterConfigDialog : public CAppDialog
  {
private:
   //--- Layout constants (spacing and control sizes)
   enum { MARGIN=10, LABEL_WIDTH=120, CONTROL_WIDTH=150, CONTROL_HEIGHT=22, ROW_HEIGHT=28 };

   //--- Dynamic control arrays (one set per parameter)
   CLabel            m_labels[];       
   CEdit             m_editInt[];     
   CEdit             m_editDouble[];   
   CEdit             m_editEnum[];     

   SParameterDef     m_defs[];         
   int               m_paramCount;     

   ENUM_INDICATOR    m_indicatorType;  
   string            m_targetSymbol;   
   CSearchPanel      *m_parentPanel;    

   CButton           m_btnOK;          
   CButton           m_btnCancel;     

   void              BuildControls();          
   void              ReadValues(MqlParam &params[]);   
   bool              ValidateValues();         

public:
                     CParameterConfigDialog();
                    ~CParameterConfigDialog();

   bool              Create(const long chart, const string name, const ENUM_INDICATOR indicatorType);
   void              SetIndicatorInfo(const ENUM_INDICATOR type, const string symbol);
   void              SetParentPanel(CSearchPanel *parent) { m_parentPanel = parent; }
   void              DetachParent() { m_parentPanel = NULL; }
   void              CloseDialog();     

   virtual bool      OnEvent(const int id, const long &lparam, const double &dparam, const string &sparam);
   void              ChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam);
   bool              IsVisible() const { return CAppDialog::IsVisible(); }

   void              DestroyControls();  
  };

The dialog stores its labels and edit boxes in dynamic arrays. The arrays are resized according to the number of parameter definitions returned for the selected indicator, allowing the same implementation to support indicators with different input requirements.

Separate edit arrays are maintained for integer, double, and enumeration values. This keeps each parameter type independent while allowing every control to be initialized using its corresponding default value.

The dialog also stores the selected indicator type, the target symbol, and a pointer to the parent search panel. These members are used later when the configured parameters are validated and the indicator is attached to the chart.

The dialog is created via Create(). The function retrieves parameter definitions, calculates the dialog size, builds the controls, and adds OK/Cancel buttons.

Creating the Controls Dynamically

Once the dialog has been created, the BuildControls() function constructs the interface using the parameter definitions returned by GetDefinitions(). It begins by resizing the control arrays so that one set of controls is available for each parameter.

//+------------------------------------------------------------------+
//| Builds the parameter controls (labels + edit boxes)              |
//+------------------------------------------------------------------+
void CParameterConfigDialog::BuildControls()
  {
   if(m_paramCount == 0)
      return;

//--- Resize control arrays to match the number of parameters
   ArrayResize(m_labels, m_paramCount);
   ArrayResize(m_editInt, m_paramCount);
   ArrayResize(m_editDouble, m_paramCount);
   ArrayResize(m_editEnum, m_paramCount);

   int yPos = MARGIN + 10;

   for(int i = 0; i < m_paramCount; i++)
     {
      //--- Create a label for the parameter name
      if(!m_labels[i].Create(0, "lbl" + IntegerToString(i), 0, MARGIN, yPos, MARGIN + LABEL_WIDTH, yPos + CONTROL_HEIGHT))
         continue;
      if(!Add(m_labels[i]))
         continue;
      m_labels[i].Text(m_defs[i].name);

      //--- Create the appropriate edit control based on parameter type
      switch(m_defs[i].type)
        {
         case PARAM_INTEGER:
           {
            //--- Simple edit box for integer values
            if(!m_editInt[i].Create(0, "edt" + IntegerToString(i), 0, MARGIN + LABEL_WIDTH + 5, yPos,
                                    MARGIN + LABEL_WIDTH + 5 + CONTROL_WIDTH, yPos + CONTROL_HEIGHT))
               break;
            if(!Add(m_editInt[i]))
               break;
            m_editInt[i].Text(IntegerToString(m_defs[i].default_int));
            break;
           }
         case PARAM_DOUBLE:
           {
            //--- Edit box for double values
            if(!m_editDouble[i].Create(0, "edtd" + IntegerToString(i), 0, MARGIN + LABEL_WIDTH + 5, yPos,
                                       MARGIN + LABEL_WIDTH + 5 + CONTROL_WIDTH, yPos + CONTROL_HEIGHT))
               break;
            if(!Add(m_editDouble[i]))
               break;
            m_editDouble[i].Text(DoubleToString(m_defs[i].default_double, 2));
            break;
           }
         case PARAM_ENUM:
           {
            //--- Enum parameters use a simple edit box where the user types the value name
            //--- (We avoid combo box because of event handling differences across MT5 builds)
            if(!m_editEnum[i].Create(0, "ede" + IntegerToString(i), 0, MARGIN + LABEL_WIDTH + 5, yPos,
                                     MARGIN + LABEL_WIDTH + 5 + CONTROL_WIDTH, yPos + CONTROL_HEIGHT))
               break;
            if(!Add(m_editEnum[i]))
               break;
            //--- Set the default text from the enum definition
            string defaultText = "";
            for(int j = 0; j < ArraySize(m_defs[i].enum_values); j++)
              {
               if(m_defs[i].enum_codes[j] == m_defs[i].default_enum)
                 {
                  defaultText = m_defs[i].enum_values[j];
                  break;
                 }
              }
            m_editEnum[i].Text(defaultText);
            break;
           }
        }
      yPos += ROW_HEIGHT;
     }
  }

For every parameter, the function first creates a label using the display name stored in the parameter definition. After creating the label, the function determines which edit control should be displayed according to the parameter type.

Each control is initialized with the default value stored in the corresponding SParameterDef structure. Integer parameters display their default integer values, double parameters display their default decimal values, while enumeration parameters display the default enumeration name rather than its underlying numeric code.

Because the controls are generated from metadata, the dialog remains independent of any particular indicator.


Reading and Validating User Input

The configuration dialog displays editable controls for every parameter, but the entered values cannot be passed directly to IndicatorCreate(). The MQL5 API expects every input to be supplied as an array of MqlParam structures, with each element containing the correct data type and value.

The dialog therefore performs two tasks before an indicator is attached:

  • It converts the contents of the controls into an MqlParam array.
  • It validates the entered values before they are passed to IndicatorCreate().

Converting Control Values into MqlParam Objects

The conversion is performed by the ReadValues() function. It reads every control created by the dialog, converts its contents according to the parameter definition, and stores the result in an MqlParam array.

//+------------------------------------------------------------------+
//| Reads the values from the controls into a MqlParam array         |
//+------------------------------------------------------------------+
void CParameterConfigDialog::ReadValues(MqlParam &params[])
  {
   ArrayResize(params, m_paramCount);
   for(int i = 0; i < m_paramCount; i++)
     {
      switch(m_defs[i].type)
        {
         case PARAM_INTEGER:
            params[i].type = TYPE_INT;
            params[i].integer_value = (int)StringToInteger(m_editInt[i].Text());
            break;
         case PARAM_DOUBLE:
            params[i].type = TYPE_DOUBLE;
            params[i].double_value = StringToDouble(m_editDouble[i].Text());
            break;
         case PARAM_ENUM:
           {
            //--- Match the typed text to one of the enum values
            string text = m_editEnum[i].Text();
            int foundCode = m_defs[i].default_enum;
            for(int j = 0; j < ArraySize(m_defs[i].enum_values); j++)
              {
               if(StringCompare(m_defs[i].enum_values[j], text, false) == 0)
                 {
                  foundCode = m_defs[i].enum_codes[j];
                  break;
                 }
              }
            params[i].type = TYPE_INT;
            params[i].integer_value = foundCode;
            break;
           }
        }
     }
  }

The conversion performed depends on the parameter type:

  • Integer parameters are converted using StringToInteger() and stored as TYPE_INT.
  • Double parameters are converted using StringToDouble() and stored as TYPE_DOUBLE.

Enumeration parameters require an additional lookup. Although the user enters a readable value such as EMA, LWMA, or Close, IndicatorCreate() expects the corresponding enumeration constant. The function searches the available enumeration definitions, retrieves the matching code, and stores that numeric value in the parameter array.

After the loop completes, every configured input has been converted into the format required by IndicatorCreate().

Validating the Input Values

Before the parameter array is used, the dialog verifies that the entered values satisfy the constraints defined in the parameter metadata. This prevents invalid input from being passed to the indicator creation routine.

The validation is implemented in the ValidateValues() function.

//+------------------------------------------------------------------+
//| Validates integer values (range check)                           |
//+------------------------------------------------------------------+
bool CParameterConfigDialog::ValidateValues()
  {
   for(int i = 0; i < m_paramCount; i++)
     {
      switch(m_defs[i].type)
        {
         case PARAM_INTEGER:
           {
            int val = (int)StringToInteger(m_editInt[i].Text());
            if(val < m_defs[i].min_value || val > m_defs[i].max_value)
              {
               Print("Invalid value for ", m_defs[i].name, ": ", val);
               return(false);
              }
            break;
           }
         default:
            break;
        }
     }
   return(true);
  }

For integer parameters, the entered value is compared with the minimum and maximum limits defined in the corresponding SParameterDef structure. If the value falls outside the permitted range, the function reports the error and immediately returns false. Otherwise, the validation continues until every parameter has been checked successfully.


Extending the Chart Launcher

The configuration dialog now produces a validated MqlParam array containing the values entered by the user. The remaining task is to pass those values to IndicatorCreate() when attaching the selected indicator.

Our existing chart launcher already knows how to open charts, determine the correct destination window, and attach indicators. However, it always builds its own default parameters internally. To support user-defined settings without affecting the existing functionality, we overload the AttachIndicator() function.

Attaching an Indicator with Custom Parameters

The new overload accepts an additional MqlParam array. Apart from creating the indicator with the supplied parameters, the remaining workflow is identical to the original implementation.

//+------------------------------------------------------------------+
//| Attaches an indicator with custom parameters                     |
//+------------------------------------------------------------------+
bool AttachIndicator(const ENUM_INDICATOR type, const string symbol, const MqlParam &params[])
  {
   string targetSymbol = (symbol == "") ? Symbol() : symbol;

   if(!SymbolSelect(targetSymbol, true))
      return(false);

   int bars = BarsAvailable(targetSymbol, PERIOD_CURRENT);
   if(bars < 10)
      return(false);

   long chartId = FindOrOpenChart(targetSymbol);
   if(chartId == -1)
      return(false);

//--- Use the provided parameter array instead of building defaults
   int paramCount = ArraySize(params);
   int handle = IndicatorCreate(targetSymbol, PERIOD_CURRENT, type, paramCount, params);
   if(handle == INVALID_HANDLE)
      return(false);

   int window = GetTargetWindow(type);
   if(!ChartIndicatorAdd(chartId, window, handle))
     {
      IndicatorRelease(handle);
      return(false);
     }

   ChartRedraw(chartId);
   Print("Indicator ", EnumToString(type), " attached to ", targetSymbol, " using custom parameters");
   return(true);
  }

The same sequence used by the original launcher is followed:

  1. Verify that the selected symbol is available.
  2. Ensure sufficient historical data has been loaded.
  3. Locate an existing chart or open a new one.
  4. Create the indicator handle.
  5. Attach the indicator to the appropriate chart window.
  6. Refresh the chart.

The only difference is how the indicator handle is created. Rather than constructing a predefined parameter list, the function passes the MqlParam array generated by the configuration dialog directly to IndicatorCreate(). This allows every configurable value entered by the user to be applied during indicator creation.

Keeping the original overload unchanged also preserves backward compatibility. Indicators that do not expose configurable parameters continue to use the existing implementation with default values, while configurable indicators use the new overload. Both approaches share the same chart management logic, avoiding code duplication while extending the launcher with parameter support.


The search panel is responsible for handling indicator selection. In the previous articles, selecting an indicator immediately called AttachIndicator(). With the introduction of configurable parameters, the panel first determines whether the selected indicator requires user input before it can be created.

This behavior is implemented inside CSearchPanel::OpenSelected().


Figure 5. Relationship Between Components

Opening the Configuration Dialog

Execution begins by validating the selected item and retrieving the corresponding indicator type. Before creating a new dialog, it also ensures that any previously opened dialog has been removed.

//+------------------------------------------------------------------+
//| Opens the selected indicator (launches parameter dialog)         |
//+------------------------------------------------------------------+
void CSearchPanel::OpenSelected(int index)
  {
   if(index < 0 || index >= ArraySize(m_filtered))
      return;

//--- If a previous parameter dialog is still open, close it first
   if(g_paramDialog != NULL)
     {
      g_paramDialog.DetachParent();
      delete g_paramDialog;
      g_paramDialog = NULL;
     }

   string symbol = m_targetSymbol;
   if(symbol == "")
      symbol = Symbol();

   ENUM_INDICATOR type = m_filtered[index].type;

//--- Check if this indicator has configurable parameters
   SParameterDef defs[];
   int paramCount = CParameterDefinitions::GetDefinitions(type, defs);

   if(paramCount > 0)
     {
      //--- Launch the parameter configuration dialog
      Print("Launching parameter configuration dialog for ", m_filtered[index].name);
      //--- Hide the main panel while the dialog is open
      Hide();

      CParameterConfigDialog *dlg = new CParameterConfigDialog();
      if(!dlg.Create(0, "Configure Indicator", type))
        {
         //--- Dialog creation failed – fall back to default parameters
         Print("Dialog creation failed – using defaults");
         delete dlg;
         ShowPanel();  
         AttachIndicator(type, symbol);
         return;
        }

      //--- Pass the parent panel pointer so the dialog can re‑show it on close
      dlg.SetParentPanel(GetPointer(this));
      dlg.SetIndicatorInfo(type, symbol);
      g_paramDialog = dlg;  
      dlg.Show();
     }
   else
     {
      //--- No parameters – attach directly with defaults
      AttachIndicator(type, symbol);
     }
  }

The first decision is made by calling GetDefinitions(). If the selected indicator has one or more configurable parameters, the search panel creates a CParameterConfigDialog and temporarily hides itself. The dialog receives both the selected indicator and the target symbol before being displayed.

Indicators without configurable parameters follow the original workflow. Since there is nothing for the user to modify, they are attached immediately using the existing launcher.

Both workflows coexist without requiring separate implementations.

Returning to the Search Panel

After the user finishes configuring the indicator, the dialog closes and returns control to the search panel. Instead of destroying the dialog immediately, the application simply hides it and restores the main panel.

//+------------------------------------------------------------------+
//| Closes the parameter dialog                                      |
//+------------------------------------------------------------------+
void CParameterConfigDialog::CloseDialog()
  {
   if(m_parentPanel != NULL)
      m_parentPanel.ShowPanel();

   g_paramDialog = NULL;
   Hide();
  }

Using Hide() keeps the Expert Advisor running while allowing the dialog to disappear from view. The main search panel becomes available again, enabling another indicator to be selected without restarting the application.

By integrating the parameter dialog into OpenSelected(), the search panel controls the entire indicator launch process in one place. Whether an indicator requires configuration or not, the user follows the same sequence—search, select the symbol, choose the indicator, and continue from there—while the application automatically determines the appropriate path.


Testing

To verify the implementation, I tested the enhanced indicator search panel on a live chart using several built-in indicators. The objective was to confirm that the complete workflow—from selecting a target symbol to attaching an indicator with custom parameters—operated as intended.

Figure 6. Testing the enhanced system

In this demonstration, I searched for the Parabolic SAR indicator before selecting EURUSD as the target symbol. After selecting the indicator, the parameter configuration dialog was displayed, allowing the Step and Maximum Step values to be reviewed before creating the indicator. Once the parameters were confirmed, the indicator was successfully attached to the EURUSD chart using the specified settings.

This test also confirmed that the entire process is completed within the search panel. The parameter dialog opens only when required, the configured values are passed correctly to IndicatorCreate(), and the search panel is restored after the dialog is closed without interrupting the running Expert Advisor.


Conclusion

In this article, we extended the indicator search panel by introducing parameter configuration before indicator attachment. The implementation combines a metadata repository, a dynamic configuration dialog, input validation, and MqlParam generation to create indicators using user-defined settings rather than default values.

The result is an indicator launcher that allows traders to search for an indicator, select a target symbol, configure its inputs, and attach it to the chart from a single workflow.


Attachments

File
File Type Description
IndicatorSearchEA.mq5
Expert Advisor Main Expert Advisor that creates and manages the indicator search panel.
SearchPanel.mqh
Include
Implements the graphical user interface, symbol selection controls, search functionality, and user interactions.
ChartLauncher.mqh
Include
Creates indicator handles, switches charts when necessary, and attaches built-in indicators to the selected symbol.
SearchEngine.mqh
Include
Performs case-insensitive filtering of the built-in indicator catalog based on the user's search input.
IndicatorCatalog.mqh
Include
Stores the catalog of supported built-in indicators and their corresponding ENUM_INDICATOR mappings.
ParameterDefinitions.mqh Include Defines the metadata for configurable indicator parameters, including names, data types, default values, validation ranges, and enumeration options.
ParameterConfigDialog.mqh Include Implements the dynamic parameter configuration dialog, validates user input, converts values into MqlParam arrays, and passes the configured parameters to the chart launcher.
MQL5.zip Root Archive
Complete project archive containing all source files arranged in the required MetaTrader 5 directory structure. Extract the archive into the terminal's Data Folder so all files are placed automatically in their correct locations. 

Attached files |
ChartLauncher.mqh (15.9 KB)
SearchEngine.mqh (2.43 KB)
SearchPanel.mqh (13.18 KB)
MQL5.zip (17.09 KB)
Features of Custom Indicators Creation Features of Custom Indicators Creation
Creation of Custom Indicators in the MetaTrader trading system has a number of features.
Implementing a Trade Throttle and Rate Limiter in MQL5 Implementing a Trade Throttle and Rate Limiter in MQL5
We build a trade throttle for MQL5 EAs using a token bucket with a priority queue to control order submission rate. Tokens refill at a configurable per‑second rate, allowing short bursts up to capacity and then enforcing sustained throughput. When the bucket is empty, requests are queued and later released by priority with FIFO tiebreaks. This keeps execution within safe limits without discarding valid signals under load.
Features of Experts Advisors Features of Experts Advisors
Creation of expert advisors in the MetaTrader trading system has a number of features.
Larry Williams Market Secrets (Part 17) : Detecting Oops Signals Using a Custom Indicator Larry Williams Market Secrets (Part 17) : Detecting Oops Signals Using a Custom Indicator
This article implements an MQL5 custom indicator that detects Larry Williams Oops gap reversals and marks bullish and bearish arrows on the chart. It details configurable gap and validity thresholds, same-bar or later confirmation, first-fill-only logic, historical backfilling, and incremental updates so signals remain consistent on both history and newly completed bars.