Selection and navigation utility in MQL5 and MQL4: Adding auto search for patterns and displaying detected symbols

Roman Klymenko | 18 February, 2019

Introduction

Many trading strategies require constant analysis of symbols when searching for various market entry patterns. While searching and analyzing symbols may be useful in terms of gaining insight into the market, sometimes, you may want a list of symbols featuring a necessary parameter to be simply displayed to you. In this article, we will try to develop such a tool for some intraday trading patterns.

More specifically, today we will expand the capabilities of our utility by adding the auto sorting of symbols by specific parameters. To do this, we will create a series of tabs searching for symbols currently featuring certain trading patterns: parabolic curves, air levels (flat ranges), gaps, etc. We will also learn how to add custom tabs, of course provided that you know the basics of MQL programming.

As in the previous article, our utility will work in both MQL4 and MQL5. Looking ahead, I should say that opening auto sorting tabs in MQL5 is slower than in MQL4. This happens when the requested symbol does not have a history up to the desired depth. In such cases, MetaTrader 5 requests the history from the trading server and builds the required timeframes.

Therefore, if nothing happens when you click the tab, or if all the buttons disappear, do not panic and just wait. In fifteen-twenty seconds, the necessary symbols will be displayed. It is still faster than viewing the charts of hundreds of symbols manually. On the other hand, in MetaTrader 4, you need to independently provide the history of the necessary symbols and timeframes, which also requires time and attention to such trifles.

Moreover, if your computer has a small amount of RAM (or you are working with a virtual machine, which has a limited amount of RAM, like 1 GB), then in MQL5, the EA operation may be interrupted by the insufficient memory error when opening the auto sorting tabs. MQL4 has no such issue since the entire timeframe history is uploaded independently and at different depths. In MQL5, the issue can be solved by limiting the "Max bars in chart" parameter.

Adding functionality to the auto sorting tabs

First, let's define how we can add the auto sorting tab to the utility. To do this, we need to understand how the mechanism of these tabs is designed from the inside.

We have already implemented the ability of adding tabs in the previous article. As you may remember, we do that from the loop. The names of the homework tabs are saved in the array. The auto sorting tab names are also saved in a separate array:

string panelNamesAddon[9]={"Air Level", "Parabolic", "Gap", "4 weeks Min/Max", "365 days Min/Max", "Round levels", "Mostly Up/Down", "All time high/low", "High=Close"};

In other words, in order to add a custom tab, we need to increase the array size by 1 and add the tab name at the end of the tabs list. After that, the new tab appears in the utility.

The show_panel_buttons function is responsible for displaying tabs. Its code has been modified, all auto sorting tabs are displayed in addition to the homework tabs:

void show_panel_buttons(){
   int btn_left=0;
   // define the maximum possible x coordinate, at which the tabs can be displayed.
   int btn_right=(int) ChartGetInteger(0, CHART_WIDTH_IN_PIXELS)-77;
   string tmpName="";
   
   for( int i=0; i<ArraySize(panelNames); i++ ){
      // if the start coordinate of the new button exceeds the maximum possible one,
      // move to the new line.
      if( btn_left>btn_right-BTN_WIDTH ){
         btn_line++;
         btn_left=0;
      }
      // if the "homework" tabs feature symbols, add their number
      // to the tab name
      tmpName=panelNames[i];
      if(i>0 && arrPanels[i].Total()>0 ){
         tmpName+=" ("+(string) arrPanels[i].Total()+")";
      }
      
      // display tab buttons
      ObjectCreate(0, exprefix+"panels"+(string) i, OBJ_BUTTON, 0, 0, 0);
      ObjectSetInteger(0,exprefix+"panels"+(string) i,OBJPROP_XDISTANCE,btn_left); 
      ObjectSetInteger(0,exprefix+"panels"+(string) i,OBJPROP_YDISTANCE,BTN_HEIGHT*btn_line); 
      ObjectSetInteger(0,exprefix+"panels"+(string) i,OBJPROP_XSIZE,BTN_WIDTH); 
      ObjectSetInteger(0,exprefix+"panels"+(string) i,OBJPROP_YSIZE,BTN_HEIGHT); 
      ObjectSetInteger(0,exprefix+"panels"+(string) i,OBJPROP_FONTSIZE,8); 
      ObjectSetInteger(0,exprefix+"panels"+(string) i,OBJPROP_COLOR,clrBlack); 
      ObjectSetInteger(0,exprefix+"panels"+(string) i,OBJPROP_BGCOLOR,clrSilver); 
      ObjectSetString(0,exprefix+"panels"+(string) i,OBJPROP_TEXT,tmpName);
      ObjectSetInteger(0,exprefix+"panels"+(string) i,OBJPROP_SELECTABLE,false);
      // if the button tab is currently active,
      // make it pressed
      if( cur_panel == i ){
         ObjectSetInteger(0,exprefix+"panels"+(string) i,OBJPROP_STATE, true);
      }
      
      btn_left+=BTN_WIDTH;
   }
   // If displaying auto sorting tabs is allowed by the input, display them
   if(useAddonsLevels){
      for( int i=0; i<ArraySize(panelNamesAddon); i++ ){
         if( btn_left>btn_right-BTN_WIDTH ){
            btn_line++;
            btn_left=0;
         }
         tmpName=panelNamesAddon[i];
         // If the tab is called Gap, display the current input parameter value
         // defining the gap percentage there
         if(tmpName=="Gap"){
            StringAdd(tmpName, " ("+(string) gap_min+"%)");
         }
         
         ObjectCreate(0, exprefix+"panels"+(string) (i+ArraySize(panelNames)), OBJ_BUTTON, 0, 0, 0);
         ObjectSetInteger(0,exprefix+"panels"+(string) (i+ArraySize(panelNames)),OBJPROP_XDISTANCE,btn_left); 
         ObjectSetInteger(0,exprefix+"panels"+(string) (i+ArraySize(panelNames)),OBJPROP_YDISTANCE,BTN_HEIGHT*btn_line); 
         ObjectSetInteger(0,exprefix+"panels"+(string) (i+ArraySize(panelNames)),OBJPROP_XSIZE,BTN_WIDTH); 
         ObjectSetInteger(0,exprefix+"panels"+(string) (i+ArraySize(panelNames)),OBJPROP_YSIZE,BTN_HEIGHT); 
         ObjectSetInteger(0,exprefix+"panels"+(string) (i+ArraySize(panelNames)),OBJPROP_FONTSIZE,8); 
         ObjectSetInteger(0,exprefix+"panels"+(string) (i+ArraySize(panelNames)),OBJPROP_COLOR,clrBlack); 
         ObjectSetInteger(0,exprefix+"panels"+(string) (i+ArraySize(panelNames)),OBJPROP_BGCOLOR,clrLightSteelBlue); 
         ObjectSetString(0,exprefix+"panels"+(string) (i+ArraySize(panelNames)),OBJPROP_TEXT,tmpName);
         ObjectSetInteger(0,exprefix+"panels"+(string) (i+ArraySize(panelNames)),OBJPROP_SELECTABLE,false);
         if( cur_panel == i+ArraySize(panelNames) ){
            ObjectSetInteger(0,exprefix+"panels"+(string) (i+ArraySize(panelNames)),OBJPROP_STATE, true);
         }
         
         btn_left+=BTN_WIDTH;
      }
   }

   // display the comment if set:
   if(StringLen(cmt)>0){
      string tmpCMT=cmt;
      if(from_txt){
         StringAdd(tmpCMT, ", from txt");
      }
      ObjectCreate(0, exprefix+"title", OBJ_EDIT, 0, 0, 0);
      ObjectSetInteger(0,exprefix+"title",OBJPROP_XDISTANCE,btn_left+11); 
      ObjectSetInteger(0,exprefix+"title",OBJPROP_YDISTANCE,BTN_HEIGHT*btn_line); 
      ObjectSetInteger(0,exprefix+"title",OBJPROP_XSIZE,133); 
      ObjectSetInteger(0,exprefix+"title",OBJPROP_YSIZE,BTN_HEIGHT); 
      ObjectSetInteger(0,exprefix+"title",OBJPROP_FONTSIZE,8); 
      ObjectSetInteger(0,exprefix+"title",OBJPROP_COLOR,clrGold); 
      ObjectSetInteger(0,exprefix+"title",OBJPROP_BGCOLOR,clrNONE); 
      ObjectSetInteger(0,exprefix+"title",OBJPROP_BORDER_COLOR,clrBlack);
      ObjectSetString(0,exprefix+"title",OBJPROP_TEXT,tmpCMT);
      ObjectSetInteger(0,exprefix+"title",OBJPROP_SELECTABLE,false);
   }
   

}

As we can see in the function code, auto sorting tabs are displayed only if that is allowed by the addons_infowatch input. In addition to it, we add two more parameters to configure the auto sorting tabs:

sinput string        delimeter_05="";      // --- Additional tabs ---
input bool           useAddonsLevels=true; // Display additional tabs
input bool           addons_infowatch=true;// Hide a symbol if not in the Market Watch
input int            addon_tabs_scale=3;   // Scale of additional tabs (0-5)

I believe, only the addons_infowatch parameter requires extra clarification here. If it is set, only symbols displayed in the Market Watch tab are sorted. Otherwise, sorting is performed for all symbols offered by your broker.

As a result, when launching a new version of the utility, you can see new auto sorting tabs apart from homework ones:

Adding auto sorting tabs

But let's return to our code. Now, we know how to add auto sorting tabs. However, no symbols are displayed when clicking on them so far, since the symbol display has not been implemented yet. All buttons of the currently open tab are displayed using the show_symbols function. We are going to add the display of auto sorting tabs here. As a result, the function looks as follows:

void show_symbols(){
   
   // initialize the variables for defining the X and Y coordinates
   int btn_left=0;
   int btn_right=(int) ChartGetInteger(0, CHART_WIDTH_IN_PIXELS)-77;
   btn_line++;
   
   
   
   if( cur_panel==0 ){
      ObjectCreate(0, exprefix+"clear_"+(string) cur_panel, OBJ_BUTTON, 0, 0, 0);
      ObjectSetInteger(0,exprefix+"clear_"+(string) cur_panel,OBJPROP_XDISTANCE,btn_left); 
      ObjectSetInteger(0,exprefix+"clear_"+(string) cur_panel,OBJPROP_YDISTANCE,BTN_HEIGHT*btn_line); 
      ObjectSetInteger(0,exprefix+"clear_"+(string) cur_panel,OBJPROP_XSIZE,BTN_WIDTH); 
      ObjectSetInteger(0,exprefix+"clear_"+(string) cur_panel,OBJPROP_YSIZE,BTN_HEIGHT); 
      ObjectSetInteger(0,exprefix+"clear_"+(string) cur_panel,OBJPROP_FONTSIZE,8); 
      ObjectSetInteger(0,exprefix+"clear_"+(string) cur_panel,OBJPROP_COLOR,clrBlack); 
      ObjectSetInteger(0,exprefix+"clear_"+(string) cur_panel,OBJPROP_BGCOLOR,clrPaleTurquoise); 
      ObjectSetInteger(0,exprefix+"clear_"+(string) cur_panel,OBJPROP_SELECTABLE,false);
      ObjectSetString(0,exprefix+"clear_"+(string) cur_panel,OBJPROP_TEXT,"Clear All");
      btn_left+=BTN_WIDTH;
      ObjectCreate(0, exprefix+"showpos", OBJ_BUTTON, 0, 0, 0);
      ObjectSetInteger(0,exprefix+"showpos",OBJPROP_XDISTANCE,btn_left); 
      ObjectSetInteger(0,exprefix+"showpos",OBJPROP_YDISTANCE,BTN_HEIGHT*btn_line); 
      ObjectSetInteger(0,exprefix+"showpos",OBJPROP_XSIZE,BTN_WIDTH); 
      ObjectSetInteger(0,exprefix+"showpos",OBJPROP_YSIZE,BTN_HEIGHT); 
      ObjectSetInteger(0,exprefix+"showpos",OBJPROP_FONTSIZE,8); 
      ObjectSetInteger(0,exprefix+"showpos",OBJPROP_COLOR,clrBlack); 
      ObjectSetInteger(0,exprefix+"showpos",OBJPROP_BGCOLOR,clrPaleTurquoise); 
      ObjectSetInteger(0,exprefix+"showpos",OBJPROP_SELECTABLE,false);
      ObjectSetString(0,exprefix+"showpos",OBJPROP_TEXT,"Show Pos");
      btn_left+=BTN_WIDTH;
   }else if( cur_panel<ArraySize(arrPanels) && arrPanels[cur_panel].Total()>0 ){
      ObjectCreate(0, exprefix+"clear_"+(string) cur_panel, OBJ_BUTTON, 0, 0, 0);
      ObjectSetInteger(0,exprefix+"clear_"+(string) cur_panel,OBJPROP_XDISTANCE,btn_left); 
      ObjectSetInteger(0,exprefix+"clear_"+(string) cur_panel,OBJPROP_YDISTANCE,BTN_HEIGHT*btn_line); 
      ObjectSetInteger(0,exprefix+"clear_"+(string) cur_panel,OBJPROP_XSIZE,BTN_WIDTH); 
      ObjectSetInteger(0,exprefix+"clear_"+(string) cur_panel,OBJPROP_YSIZE,BTN_HEIGHT); 
      ObjectSetInteger(0,exprefix+"clear_"+(string) cur_panel,OBJPROP_FONTSIZE,8); 
      ObjectSetInteger(0,exprefix+"clear_"+(string) cur_panel,OBJPROP_COLOR,clrBlack); 
      ObjectSetInteger(0,exprefix+"clear_"+(string) cur_panel,OBJPROP_BGCOLOR,clrPaleTurquoise); 
      ObjectSetInteger(0,exprefix+"clear_"+(string) cur_panel,OBJPROP_SELECTABLE,false);
      ObjectSetString(0,exprefix+"clear_"+(string) cur_panel,OBJPROP_TEXT,"Clear");
      btn_left+=BTN_WIDTH;
      ObjectCreate(0, exprefix+"new_"+(string) cur_panel, OBJ_BUTTON, 0, 0, 0);
      ObjectSetInteger(0,exprefix+"new_"+(string) cur_panel,OBJPROP_XDISTANCE,btn_left); 
      ObjectSetInteger(0,exprefix+"new_"+(string) cur_panel,OBJPROP_YDISTANCE,BTN_HEIGHT*btn_line); 
      ObjectSetInteger(0,exprefix+"new_"+(string) cur_panel,OBJPROP_XSIZE,BTN_WIDTH); 
      ObjectSetInteger(0,exprefix+"new_"+(string) cur_panel,OBJPROP_YSIZE,BTN_HEIGHT); 
      ObjectSetInteger(0,exprefix+"new_"+(string) cur_panel,OBJPROP_FONTSIZE,8); 
      ObjectSetInteger(0,exprefix+"new_"+(string) cur_panel,OBJPROP_COLOR,clrBlack); 
      ObjectSetInteger(0,exprefix+"new_"+(string) cur_panel,OBJPROP_BGCOLOR,clrPaleTurquoise); 
      ObjectSetInteger(0,exprefix+"new_"+(string) cur_panel,OBJPROP_SELECTABLE,false);
      ObjectSetString(0,exprefix+"new_"+(string) cur_panel,OBJPROP_TEXT,"Open All");
      btn_left+=BTN_WIDTH;
   }
   
   // if the current open tab index exceeds the number of elements of the homework tab array elements
   // and of the tab with all symbols,
   // then, this is the auto sorting tab
   if(cur_panel>(ArraySize(arrPanels)-1)){
      MqlRates rates[];
      ArraySetAsSeries(rates, true);
      int tmpNumAddon=cur_panel-ArraySize(arrPanels);
      addonArr.Resize(0);
      arrTT.Resize(0);
      string addonName;
      
      CArrayString tmpSymbols;
      if( tmpNumAddon==0 && air_only_home ){
         for( int j=1; j<ArraySize(arrPanels); j++ ){
            for( int k=0; k<arrPanels[j].Total(); k++ ){
               string curName=arrPanels[j].At(k);
               bool isYes=false;
               for( int r=0; r<tmpSymbols.Total(); r++ ){
                  if(tmpSymbols.At(r)==curName){
                     isYes=true;
                     break;
                  }
               }
               if(!isYes){
                  tmpSymbols.Add(arrPanels[j].At(k));
               }
            }
         }
      }else{
         if( ArraySize(result)>1 ){
            for(int j=0;j<ArraySize(result);j++){
               StringReplace(result[j], " ", "");
               if(StringLen(result[j])<1){
                  continue;
               }
               tmpSymbols.Add(onlySymbolsPrefix+result[j]+onlySymbolsSuffix);
            }
         }else{
            for( int i=0; i<SymbolsTotal(addons_infowatch); i++ ){
               tmpSymbols.Add(SymbolName(i, addons_infowatch));
            }
         }
      }
      
      switch(tmpNumAddon){
         case 0: // air levels
            // tab contents display code
            break;
         case 1: // parabolic
            // tab contents display code
            break;
         case 2: // Gap
            // tab contents display code
            break;
         case 3: //4 weeks min/max
            // tab contents display code
            break;
         case 4: //365 days min/max
            // tab contents display code
            break;
         case 5: // round levels
            // tab contents display code
            break;
         case 6: // mostly up/down
            // tab contents display code
            break;
         case 7: // all time high/low
            // tab contents display code
            break;
         case 8: // high=close
            // tab contents display code
            break;
      }
      
      // a button is displayed on the chart for each symbol in the array
      // the symbol name is added to the button
      for( int i=0; i<addonArr.Total(); i++ ){
         
         if( btn_left>btn_right-BTN_WIDTH ){
            btn_line++;
            btn_left=0;
         }
         
         ObjectCreate(0, exprefix+"btn"+(string) i, OBJ_BUTTON, 0, 0, 0);
         ObjectSetInteger(0,exprefix+"btn"+(string) i,OBJPROP_XDISTANCE,btn_left); 
         ObjectSetInteger(0,exprefix+"btn"+(string) i,OBJPROP_YDISTANCE,BTN_HEIGHT*btn_line); 
         ObjectSetInteger(0,exprefix+"btn"+(string) i,OBJPROP_XSIZE,BTN_WIDTH); 
         ObjectSetInteger(0,exprefix+"btn"+(string) i,OBJPROP_YSIZE,BTN_HEIGHT); 
         ObjectSetInteger(0,exprefix+"btn"+(string) i,OBJPROP_FONTSIZE,8); 
         ObjectSetInteger(0,exprefix+"btn"+(string) i,OBJPROP_COLOR,clrBlack); 
         ObjectSetString(0,exprefix+"btn"+(string) i,OBJPROP_TEXT,addonArr.At(i));    
         ObjectSetInteger(0,exprefix+"btn"+(string) i,OBJPROP_SELECTABLE,false);
         if( arrTT.At(i)>0 ){
            ObjectSetString(0,exprefix+"btn"+(string) i,OBJPROP_TOOLTIP,(string) arrTT.At(i));    
         }
   
         if( checkSYMBwithPOS(addonArr.At(i)) ){
            ObjectSetInteger(0,exprefix+"btn"+(string) i,OBJPROP_BGCOLOR,clrPeachPuff);
         }
         
         btn_left+=BTN_WIDTH;
      }
      
      // since the buttons are already displayed, exit the function
      return;
   }
   
   // display the button on the chart for each symbol in the array
   // of the currently active tab
   // the symbol name is added to the button
   for( int i=0; i<arrPanels[cur_panel].Total(); i++ ){
      
      if( btn_left>btn_right-BTN_WIDTH ){
         btn_line++;
         btn_left=0;
      }
      
      ObjectCreate(0, exprefix+"btn"+(string) i, OBJ_BUTTON, 0, 0, 0);
      ObjectSetInteger(0,exprefix+"btn"+(string) i,OBJPROP_XDISTANCE,btn_left); 
      ObjectSetInteger(0,exprefix+"btn"+(string) i,OBJPROP_YDISTANCE,BTN_HEIGHT*btn_line); 
      ObjectSetInteger(0,exprefix+"btn"+(string) i,OBJPROP_XSIZE,BTN_WIDTH); 
      ObjectSetInteger(0,exprefix+"btn"+(string) i,OBJPROP_YSIZE,BTN_HEIGHT); 
      ObjectSetInteger(0,exprefix+"btn"+(string) i,OBJPROP_FONTSIZE,8); 
      ObjectSetInteger(0,exprefix+"btn"+(string) i,OBJPROP_COLOR,clrBlack); 
      ObjectSetString(0,exprefix+"btn"+(string) i,OBJPROP_TEXT,arrPanels[cur_panel].At(i));    
      ObjectSetInteger(0,exprefix+"btn"+(string) i,OBJPROP_SELECTABLE,false);

      if( !noSYMBwithPOS || cur_panel>0 ){
         if( checkSYMBwithPOS(arrPanels[cur_panel].At(i)) ){
            ObjectSetInteger(0,exprefix+"btn"+(string) i,OBJPROP_BGCOLOR,clrPeachPuff);
         }
      }
      
      btn_left+=BTN_WIDTH;
   }
   
}

As we can see, the code for displaying the tab contents is located inside the switch operator. The index of the array element containing the tab name is indicated in the case operator. Thus, in order to add a custom tab after adding its name to the array, simply add the new case with index 1 exceeding the last applied index.

We will have more look at the code samples for auto sorting symbols by certain parameters when considering specific tabs. But we can already see that the code of all tabs starts in similar way.

The list of all symbols to be sorted is already present in the tmpSymbols array. Therefore, the code of each tab starts from the for loop:

            for( int i=0; i<tmpSymbols.Total(); i++ ){
               addonName=tmpSymbols[i];
               // code defining if a symbol should be displayed
            }

Air levels (flat ranges)

When trading from levels, all entries are performed near flat ranges, i.e. when the price touches the same or approximately the same price by its high or low at each bar. The example is displayed in the below image:

Sample flat range

This may not be the perfect example since the obtained level experiences constant false breakthroughs. But on the other hand, it is believed that the presence of false breakthroughs reinforces the level. =)

Searching for such situations is quite a tedious task, so let's try to automate it.

The search for flat ranges is usually performed on M5 charts, though it is not a mandatory condition. Some traders work on M15 or M30 charts. It is believed that the higher the timeframe a flat range is detected on, the better the potential entry.

Therefore, let's add the inputs allowing us to define the necessary timeframes:

sinput string        delimeter_06=""; // --- Additional Air levels tab ---
input bool           air_level_m5=true; // Search for air levels on M5
input bool           air_level_m15=true; // Search for air levels on M15
input bool           air_level_m30=false; // Search for air levels on M30
input bool           air_level_h1=false; // Search for air levels on H1
input bool           air_level_h4=false; // Search for air levels on H4
input bool           air_level_d1=false; // Search for air levels on D1

It should be remembered that the more timeframes you select, the slower the search process will be. Therefore, it is better to limit the search by one or two timeframes.

Besides, let's add some other inputs:

input uchar          air_level_count=4; // Number of level bars
input uchar          air_level_offset=3; // Offset relative to level in points
input bool           air_level_cur_day=true; // Only levels in the current day's direction
input bool           air_level_prev_day=true; // Only levels in the previous day's direction
input bool           air_only_home=false; // Search only in "homework"

The Number of level bars parameter allows specifying the number of bars that should touch a closely located price in order to assume this a flat range. The optimal value for this parameter is 4 bars. The greater the number of bars, the higher the flat range quality, but the less frequently they are found.

The Offset relative to level in points parameter allows specifying the range of points, within which the price should lie to be assumed "closely located". In other words, if the parameter is 0, the bars should touch exactly the same price up to cents. Generally, there will be fewer such cases, especially on higher timeframes. In most cases, the price cannot reach the specified level stopping at a few points from it, therefore, the default parameter value is 3 points.

If you trade only in the current and/or previous day's direction, the Only levels in the current day's direction and Only levels in the previous day's direction allow sorting symbols, on which a detected flat range has the direction opposite to the price movement.

The price movement direction is defined by the ratio of a close price relative to an open one. If the previous day bar's close price exceeds the open one, search only for flat ranges in Long, i.e. the ones touching the closely located price by 'low'.

Finally, let's have a look at the final parameter – Search only in "homework". If during a working day, you trade only symbols that you added to the homework tabs, set the parameter so that the utility looks for flat ranges only on symbols currently added to the homework tabs.

Now let's finally look at the code that sorts the symbols currently featuring flat ranges. The code for M5 timeframe is provided below:

               if(air_level_m5 && CopyRates(addonName, PERIOD_M5, 0, air_level_count+1, rates)==air_level_count+1){
                  if( (!air_level_cur_day || getmeinfoday_symbol(addonName, 0)<=0) && (!air_level_prev_day || getmeinfoday_symbol(addonName, 1)<=0) && rates[0].high<rates[1].high ){
                     bool isOk=true;
                     for( int j=1; j<air_level_count; j++ ){
                        if( MathAbs(rates[1].high-rates[j+1].high) <= air_level_offset*SymbolInfoDouble(addonName, SYMBOL_POINT) ){
                        }else{
                           isOk=false;
                        }
                     }
                     if(isOk && !skip_symbol(addonName)){
                        addonArr.Add(addonName+" (M5)");
                        arrTT.Add(rates[1].high);
                     }
                  }else if( (!air_level_cur_day || getmeinfoday_symbol(addonName, 0)>=0) && (!air_level_prev_day || getmeinfoday_symbol(addonName, 1)>=0) && rates[0].low>rates[1].low ){
                     bool isOk=true;
                     for( int j=1; j<air_level_count; j++ ){
                        if( MathAbs(rates[1].low-rates[j+1].low) <= air_level_offset*SymbolInfoDouble(addonName, SYMBOL_POINT) ){
                        }else{
                           isOk=false;
                        }
                     }
                     if(isOk && !skip_symbol(addonName)){
                        addonArr.Add(addonName+" (M5)");
                        arrTT.Add(rates[1].low);
                     }
                  }
               }

Like in all other code samples of the auto sorting tabs, here we start working with the CopyRates function call results immediately after the call itself. This is not entirely correct. MQL Help recommends waiting a while for the utility to receive and write data to the array. But even if we wait for 50 milliseconds, checking 100 symbols means an additional delay of 5 seconds, while many brokers offer hundreds of stock market symbols. As a result, when using a delay, displaying a tab contents may take significantly more time.

Therefore, we immediately start working with the array of results. In practice, there are no issues with this, except maybe the one caused by the absence of delay.

The fact is that the actual data are not always transferred to the array as a result of the CopyRates function. Sometimes, the sorting uses obsolete data. In this case, simply update the tab (R key) to get the relevant symbol list.

Let's get back to the code. If you decide to add custom auto sorting tab, pay attention to how the selection of symbols takes place.

If the symbol fits our conditions, we place its name to the addonArr array. Besides, we can specify the timeframe to be used when opening a symbol chart instead of a default timeframe in brackets.

We also need to enter the value to the arrTT array. If we set the value of 0 in the array, nothing happens. But if we add some price instead, the horizontal line is built at the specified price level when opening a chart of the appropriate symbol. This is done for convenience, so that you can immediately see the price the flat range is detected at.

Parabolic roundings

Parabolic roundings appear after a directional movement when the price starts moving in the opposite direction, and each new bar's high or low is higher or lower than a previous one. It is believed that in this case, the price is more likely to go towards increasing low or decreasing high. In this case, it is possible to use a small stop loss.

In other words, after decreasing, the price starts moving up, and each bar's low is higher than the previous one. In this case, enter Long placing a stop level behind at least one of the previous bars' low.

Consider the following chart as an example:

Sample parabolic rounding

Sample parabolic roundings are marked with arrows.

We will search for parabolic roundings on M5 using the following code:

               if(CopyRates(addonName, PERIOD_M5, 0, 6, rates)==6){
                  if( rates[0].low>rates[1].low && rates[1].low>rates[2].low && rates[2].low<=rates[3].low && rates[3].low<=rates[4].low && rates[4].low<=rates[5].low ){
                     if(!skip_symbol(addonName)){
                        addonArr.Add(addonName+" (M5)");
                        arrTT.Add(0);
                     }
                  }else if( rates[0].high<rates[1].high && rates[1].high<rates[2].high && rates[2].high>=rates[3].high && rates[3].high>=rates[4].high && rates[4].high>=rates[5].high ){
                     if(!skip_symbol(addonName)){
                        addonArr.Add(addonName+" (M5)");
                        arrTT.Add(0);
                     }
                  }
               }

In other words, if the low of the current bar exceeds the low of the previous one, the low of that bar exceeds the low of its own previous bar. while the remaining 3 previous bars are either equal to each other by their lows or each of them is smaller than the other. In other words, if 3 bars go down and are followed by 2 up ones, this is considered as a beginning of a Long parabolic rounding.

Gaps

If you use trading strategies that work with stocks featuring gaps in one direction or another, the Gap tab will help you select the necessary stocks. It displays symbols featuring gaps during the current day. You can change the minimum gap (in percentage of the current price) using the Minimum gap size input.

By default, it displays only symbols featuring gaps of at least 1%.

The tab source code is simple:

               if(CopyRates(addonName, PERIOD_D1, 0, 2, rates)==2){
                  if( rates[0].open>rates[1].close+(rates[0].open*(gap_min/100)) || rates[0].open<rates[1].close-(rates[0].open*(gap_min/100)) ){
                     if(!skip_symbol(addonName)){
                        addonArr.Add(addonName+" (M5)");
                        arrTT.Add(0);
                     }
                  }
               }

4-week highs/lows

The 4 weeks Min/Max tab provides the list of symbols having their current price at the highest/lowest point within 4 weeks. Its code is as follows:

               if(CopyRates(addonName, PERIOD_W1, 0, 4, rates)==4){
                  bool newMin=true;
                  bool newMax=true;
                  if( rates[0].close!=rates[0].high && rates[0].close!=rates[0].low ){
                     newMin=false;
                     newMax=false;
                  }else{
                     for( int j=1; j<4; j++ ){
                        if( rates[0].high < rates[j].high ){
                           newMax=false;
                        }
                        if( rates[0].low > rates[j].low ){
                           newMin=false;
                        }
                     }
                  }
                  if( newMin || newMax ){
                     if(!skip_symbol(addonName)){
                        addonArr.Add(addonName+" (M5)");
                        arrTT.Add(0);
                     }
                  }
               }

Year highs/lows

Similarly to the previous tab, the current one displays symbols with their prices at the highest/lowest point within a year:

               if(CopyRates(addonName, PERIOD_W1, 0, 52, rates)==52){
                  bool newMin=true;
                  bool newMax=true;
                  if( rates[0].close!=rates[0].high && rates[0].close!=rates[0].low ){
                     newMin=false;
                     newMax=false;
                  }else{
                     for( int j=1; j<52; j++ ){
                        if( rates[0].high < rates[j].high ){
                           newMax=false;
                        }
                        if( rates[0].low > rates[j].low ){
                           newMin=false;
                        }
                     }
                  }
                  if( newMin || newMax ){
                     if(!skip_symbol(addonName)){
                        addonArr.Add(addonName+" (M5)");
                        arrTT.Add(0);
                     }
                  }
               }

Price near round levels

It is believed that round stock prices are "natural" support/resistance levels. Therefore, some trading systems focus on instruments currently trading at their round levels.

A round price is one that ends in 0 or 50 cents, for example, 125 dollars 0 cents or 79 dollars 50 cents.

As a result, we have the following code:

               switch((int) SymbolInfoInteger(addonName, SYMBOL_DIGITS)){
                  case 0:
                     break;
                  case 2:
                     if(CopyRates(addonName, PERIOD_M5, 0, 1, rates)==1){
                        double tmpRound=rates[0].close - (int) rates[0].close;
                        if( (tmpRound>0.46 && tmpRound<0.54) || tmpRound>0.96 || tmpRound<0.04 ){
                           if(!skip_symbol(addonName)){
                              addonArr.Add(addonName+" (M5)");
                              arrTT.Add(0);
                           }
                        }
                     }
                     break;
               }
In other words, we will define round prices only for symbols with their prices featuring two decimal places. If you work with other instruments, just add your own check for them in a similar way.

Up/down most of the time

Instruments moving up or down most of the time may also be of particular interest. Let's add the following inputs to find them:

sinput string        delimeter_08=""; // --- Additional Mostly Up/Down tab ---
input int            mostly_count=15; // Check the last specified number of days
input int            mostly_percent=90; // Specified percentage in one direction exceeded

The Check the last specified number of days parameter allows defining the number of days, during which we look for necessary instruments. In other words, we will look for unidirectional movements mostly on D1.

The Specified percentage in one direction exceeded allows specifying the minimum percentage, by which one movement direction exceeds another. The default value is 90. This means, in order to get to this tab, the price should move in a certain direction 90% of days out of the entire analyzed period.

Generally, the number of such symbols is small. Therefore, it may be necessary to reduce this percentage.

The tab code is as follows:

               if(CopyRates(addonName, PERIOD_D1, 1, mostly_count, rates)==mostly_count){
                     int mostlyLong=0;
                     int mostlyShort=0;
                     for( int j=0; j<mostly_count; j++ ){
                        if(rates[j].close>rates[j].open){
                           mostlyLong++;
                        }else if(rates[j].close<rates[j].open){
                           mostlyShort++;
                        }
                     }
                     if( !mostlyLong || !mostlyShort ){
                        addonArr.Add(addonName);
                        arrTT.Add(0);
                     }else if( ((mostlyLong*100)/(mostlyLong+mostlyShort)) >= mostly_percent ){
                        addonArr.Add(addonName);
                        arrTT.Add(0);
                     }else if( ((mostlyShort*100)/(mostlyLong+mostlyShort)) >= mostly_percent ){
                        addonArr.Add(addonName);
                        arrTT.Add(0);
                     }
               }

Each bar with a new high/low

This tab allows detecting the accumulation process within a symbol, i.e. the period when the price moves in one direction slowly but consistently. Generally, accumulation ends with a breakthrough (big bars) in its direction.

The following inputs help us detect unidirectional movements:

sinput string        delimeter_09=""; // --- Additional All time High/Low tab ---
input ENUM_TIMEFRAMES alltime_period=PERIOD_D1; // Period
input int            alltime_count=15; // Check the last specified number of bars

The sorting code is as follows:

               if(CopyRates(addonName, alltime_period, 1, alltime_count, rates)==alltime_count){
                     bool alltimeHigh=true;
                     bool alltimeLow=true;
                     for( int j=1; j<alltime_count; j++ ){
                        if(rates[j].high>rates[j-1].high){
                           alltimeHigh=false;
                        }
                        if(rates[j].low<rates[j-1].low){
                           alltimeLow=false;
                        }
                     }
                     if( alltimeHigh || alltimeLow ){
                        addonArr.Add(addonName);
                        arrTT.Add(0);
                     }
               }

Session closed on day's highs/lows

This is the last tab we are going to add. Depending on the time of the call, it allows you to detect:

It is believed that if the price closed at the day's high/low, then a buyer/seller has not yet had time to implement their plans. This means, the price will go the same direction the next day.

The following parameters will help us search for suitable symbols:

sinput string        delimeter_10=""; // --- Additional High=Close tab ---
input ENUM_TIMEFRAMES highclose_period=PERIOD_D1; // Period
input int            highclose_offset=0; // High/low offset in points

When searching for day's high/low, the price may not necessarily close directly on an extreme point. A roll-back of a few or even a dozen points from a border price is possible. The Error margin of highs/lows in points parameter allows defining an allowable roll-back in points.

The tab code is as follows:

               if(CopyRates(addonName, highclose_period, 0, 1, rates)==1){
                     if( rates[0].close+highclose_offset >= rates[0].high ){
                        addonArr.Add(addonName);
                        arrTT.Add(0);
                     }else if( rates[0].close-highclose_offset <= rates[0].low ){
                        addonArr.Add(addonName);
                        arrTT.Add(0);
                     }
               }

Adding custom tabs

Of course, we have not considered all possible patterns. If you use other patterns and have programming skills in MQL, you can easily add your own tabs to the utility. You are welcome to post the codes of your own tabs with descriptions of what patterns they search for down in the comments.

Suggestions on improving the above code are also appreciated.

In conclusion, let me remind you how to add your own auto sorting tab to the utility. This is done in two steps.

First, add the name of the new tab to the panelNamesAddon tab name array. Do not forget to increase the array size by 1.

Second, the switch operator of the show_symbols function should feature the new case with its value exceeding the highest of the applied ones by 1. The code for checking whether the current symbol fits the conditions is implemented inside the case operator. The code template is as follows:

         case index: // tab name
            for( int i=0; i<tmpSymbols.Total(); i++ ){
               addonName=tmpSymbols[i];

               // check code
            }
            
            break;

Conclusion

We have further expanded the functionality of our utility. I believe, it has become more useful for traders.

In this article, we have not rewritten the code depending on the MQL language version, since it works both in MQL4 and MQL5.

As you can see, the development of cross-platform utilities in MQL is not a great challenge. Most of the MQL5 functionality is supported in MQL4 in a similar way. Therefore, it may be worthwhile to temporarily forget about all sorts of classes and other unique features of MQL5, and let your work be available to as many traders as possible.

Of course, I do not declare war on classes. Classes are already present in MQL4 and codes with them appear in CodeBase. I just propose to postpone this feature of the MQL5 language for a while.