Developing the symbol selection and navigation utility in MQL5 and MQL4

24 January 2019, 09:27
Roman Klymenko
0
4 002

Introduction

Experienced traders are well aware of the fact that most time-consuming things in trading are not opening and tracking positions but selecting symbols and looking for entry points.

Of course, these are not huge issues if you work only with 1-2 symbols. But if your trading toolkit consists of hundreds of stocks and dozens of Forex symbols, it may take several hours only to find suitable entry points.

In this article, we will develop an EA simplifying the search for stocks. The EA is to be helpful in three ways:

  • it is to pre-filter stocks providing us with a list of the ones that meet our conditions;
  • is should simplify navigation through the generated stock list;
  • it is to display additional data necessary to make a decision.

Initial EA template

Initially, we are going to develop the EA on MQL5. However, since many brokers still do not offer MetaTrader 5 accounts, we will re-develop the EA so that it also works in MetaTrader 4 at the end of the article.

So, let's prepare the template that is almost no different from the one that can be generated by the MQL5 wizard:

//+------------------------------------------------------------------+
//|                                                     _finder.mq5  |
//|                        Copyright 2018, MetaQuotes Software Corp. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Klymenko Roman (needtome@icloud.com)"
#property link      "https://logmy.net"
#property version   "1.00"
#property strict

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {

//--- create timer
   EventSetTimer(1);
      
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   EventKillTimer();
  }
//+------------------------------------------------------------------+
//| Timer function                                                   |
//+------------------------------------------------------------------+
void OnTimer()
  {
  }
//+------------------------------------------------------------------+

void OnChartEvent(const int id,         // event ID   
                  const long& lparam,   // event parameter of the long type 
                  const double& dparam, // event parameter of the double type 
                  const string& sparam) // event parameter of the string type 
  { 

}

In this template, we register the timer when creating an EA. Our timer is activated every second. This means the OnTimer standard function is to be called once per second.

The only string different from a typical template generated by the MQL5 wizard is #property strict. This string is necessary for the EA to work correctly in MetaTrader 4. Since it does not have a significant effect on MetaTrader 5, we add it to our template in advance.

We will apply the following standard functions:

  • OnInit: display the buttons of trading instruments meeting our conditions on the chart;
  • OnDeinit: remove the timer and all graphical objects created by the EA;
  • OnTimer: the timer is to be used to determine clicks on graphical objects on the chart created by the EA;
  • OnChartEvent: response to clicking on graphical objects created on the chart the EA is launched on.

The list of symbols satisfying our conditions will be stored in the CArrayString type object. Therefore, include the MQH file with the object description to our EA:

#include <Arrays\ArrayString.mqh>

We will also need the CChart type object to work with charts. Let's include its definition as well:

#include <Charts\Chart.mqh>

All that is done at the beginning of our template, outside of any functions, after the #property block of strings.

Next, we need to decide on the width and height of all the buttons created by the EA. Set these values into constants specifying them after the #include block of strings:

#define BTN_HEIGHT                        (20)
#define BTN_WIDTH                         (100)

Inputs

The EA is to be managed via the inputs. Let's have a look at them so that we can immediately define what functionality we will implement later in the article:

sinput string        delimeter_01="";        // --- Filtration settings ---
input bool           noSYMBmarketWath=true;  // Hide if not in the Market Watch
input bool           noSYMBwithPOS=true;     // Hide if there are positions
input ValueOfSpread  hide_SPREAD=spread_b1;  // Hide in case of a spread
input uint           hide_PRICE_HIGH=0;      // Hide if the price is higher
input uint           hide_PRICE_LOW=0;       // Hide if the price is lower
input bool           hideProhibites=true;    // Hide if trading is disabled
input bool           hideClosed=true;        // Hide if the market is closed
input StartHour      hide_HOURS=hour_any;    // Show if there is an open time
input double         hideATRcents=0.00;      // Hide if ATR is less than the set value in dollars
sinput string        delimeter_02="";        // --- Chart settings ---
input bool           addInfoWatch=false;     // Add the chart to the Market Watch
input bool           viewCandle=true;        // Open candlestick charts
input bool           viewVolumes=true;       // Show tick volumes
input bool           showInfoSymbol=true;    // Show movement direction
input bool           showNameSymbol=true;    // Show symbol name

We can immediately notice that two inputs have a custom type. Therefore, let's add the definition of these types before the inputs. Both custom types are enumerations.

The ValueOfSpread enumeration defines possible conditions concerning a spread value for symbols to be displayed by the EA:

enum ValueOfSpread
  {
   spread_no,//No
   spread_b05,// > 0.05%
   spread_b1,// > 0.1% 
   spread_b15,// > 0.15% 
   spread_l15,// < 0.15% 
   spread_l1,// < 0.1% 
  }; 

A spread exceeding 0.1% of the price is considered increased. Therefore, we will only display symbols having a spread less than 0.1% by default. Thus, the value of the Hide in case of a spread parameter is > 0.1%. But if the list of such symbols provided by your broker is too small, you may choose another value for this parameter.

The StartHour enumeration contains the list of the main periods, during which some of the markets open:

enum StartHour
  {
   hour_any, //Any time
   hour_9am, // 9 am
   hour_10am,// 10 am 
   hour_4pm, // 4 pm 
   hour_0am, // Midnight
  }; 

9 am (or any other value) does not mean that only symbols opening at exactly the specified time are to be displayed. Instead, it means that the symbols that open at this hour (for example, at 9:05) are to be displayed.

Accordingly, 4 pm means that only US stock market shares opened at 16:30 are to be displayed.

10 am mostly relates to Russian and European stock market shares.

9 am is an opening time of some indices.

Finally, midnight is a Forex market open time since it works round the clock.

Global variables

Before we start working with the contents of standard functions, we only need to declare a series of variables whose visibility scope is our EA. Let's add them after the inputs:

// prefix to be added to the names of all graphical objects created by the EA:
string exprefix="finder";
// array of symbols fitting our conditions:
CArrayString arrPanel1;
// index of the current symbol in the arrPanel1 array:
int panel1val;
// array that is to store charts created by the EA (there is only one chart here at the moment):
CChart charts[];
// array that is to store pointers to charts created by the EA (there is only one pointer here at the moment):
long curChartID[];

The comments should clarify why we need these variables.

All preparations are complete. We are now ready to start developing the EA. But first, let's have a look at the result:

//+------------------------------------------------------------------+
//|                                                     _finder.mq5  |
//|                        Copyright 2018, MetaQuotes Software Corp. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Klymenko Roman (needtome@icloud.com)"
#property link      "https://logmy.net"
#property version   "1.00"
#property strict

#include <Arrays\ArrayString.mqh>
#include <Charts\Chart.mqh>

#define BTN_HEIGHT                        (20)
#define BTN_WIDTH                         (100)

enum ValueOfSpread
  {
   spread_no,//No
   spread_b05,// > 0.05 %
   spread_b1,// > 0.1 %
   spread_b15,// > 0.15 %
   spread_l15,// < 0.15 %
   spread_l1,// < 0.1 %
  }; 
enum StartHour
  {
   hour_any,//Any time
   hour_9am,// 9 am
   hour_10am,// 10 am 
   hour_4pm,// 4 pm 
   hour_0am,// Midnight
  }; 

input bool           noSYMBmarketWath=true; // Hide symbols not present in the Market Watch panel
input bool           noSYMBwithPOS=true;    // Hide symbols having positions
input ValueOfSpread  hide_SPREAD=spread_b1; // Hide symbols with a spread
input uint           hide_PRICE_HIGH=0;     // Hide symbols if their price is higher (0 - not hide)
input uint           hide_PRICE_LOW=0;      // Hide symbols if their price is lower (0 - not hide)
input bool           hideProhibites=true;   // Hide symbols unavailable for trading
input StartHour      hide_HOURS=hour_any;   // Show symbols opening only at
input bool           viewCandle=true;       // Open candlestick charts

// prefix to be added to the names of all graphical objects created by the EA:
string exprefix="finder";
// array of symbols fitting our conditions:
CArrayString arrPanel1;
// index of the current symbol in the arrPanel1 array:
int panel1val;
// array that is to store charts created by the EA (there is only one chart here at the moment):
CChart charts[];
// array that is to store pointers to charts created by the EA (there is only one pointer here at the moment):
long curChartID[];

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- create timer
   EventSetTimer(1);
      
//---
   return(INIT_SUCCEEDED);
  }
  
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   if(reason!=REASON_CHARTCHANGE){
      ObjectsDeleteAll(0, exprefix);
   }
   EventKillTimer();
  }

//+------------------------------------------------------------------+
//| Timer function                                                   |
//+------------------------------------------------------------------+
void OnTimer()
  {
  }

void OnChartEvent(const int id,         // event ID   
                  const long& lparam,   // event parameter of the long type 
                  const double& dparam, // event parameter of the double type 
                  const string& sparam) // event parameter of the string type 
  { 

}

Symbols filtration function

We will start from the function that displays the buttons of symbols meeting our conditions on the chart. Let's name this function start_symbols. The function is called inside the OnInit function. As a result, the OnInit function takes its final form:

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
   start_symbols();

//--- create timer
   EventSetTimer(1);
      
//---
   return(INIT_SUCCEEDED);
  }

Why do we need a separate function if all can be implemented inside OnInit? All is simple. We will call this function not only when launching the EA, but also when pressing the R key. Thus, we will be able to easily update the list of characters without having to remove the EA from the chart and reopen it.

Since the spread is constantly changing, we will have to refresh the symbol list quite often. Besides, the presence of open positions on certain symbols is also changing. Therefore, before using the previously launched EA again, do not forget to update the symbol list (by pressing R) to see the current data.

Let's have a look at the start_symbols function. It also serves as a wrapper for launching other functions:

void start_symbols(){
   // set the index of the current symbol in the list to zero (the first symbol in the array):
   panel1val=0;
   // prepare the symbol list:
   prepare_symbols();
   // remove previously created symbol buttons from the chart:
   ObjectsDeleteAll(0, exprefix);
   // display symbol list:
   show_symbols();
   // update the chart to see changes:
   ChartRedraw(0);
}

We met two more custom functions: prepare_symbols and show_symbols. The first one forms the array of symbols fitting our conditions. The second one displays the buttons of these symbols on the chart where the EA is running.

Displaying the buttons on the chart is simple. First, we find the X and Y coordinates used to display a button so that it does not overlap with other buttons. Then we should display it:

void show_symbols(){
   
   // initialize the variables for defining the X and Y coordinates
   int btn_left=0;
   int btn_line=1;
   int btn_right=(int) ChartGetInteger(0, CHART_WIDTH_IN_PIXELS)-77;
   
   // display the button on the chart for each symbol in the array
   // write a symbol name on the button
   for( int i=0; i<arrPanel1.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,arrPanel1.At(i));    
      ObjectSetInteger(0,exprefix+"btn"+(string) i,OBJPROP_SELECTABLE,false);
      
      
      btn_left+=BTN_WIDTH;
   }
   
}

As a result, symbols that meet our conditions will appear on the chart:

Displaying symbol buttons on the chart

Now, let's focus on forming the conditions used to select symbols (the prepare_symbols function). First, let's add all symbols to the list:

void prepare_symbols(){
   // variable for temporary storage of a symbol name
   string name;
   // variable for storing the last symbol quotes
   MqlTick lastme;
   
   // reset the symbol array if it contains any values
   arrPanel1.Resize(0);
   
   // form the temporary tmpSymbols array
   // it will contain all available symbols
   CArrayString tmpSymbols;
   for( int i=0; i<SymbolsTotal(noSYMBmarketWath); i++ ){
      tmpSymbols.Add(SymbolName(i, noSYMBmarketWath));
   }
   
   // conditions are to be checked here
   // and a symbol is to be included into the symbol list
   // if it fits the conditions
   for( int i=0; i<tmpSymbols.Total(); i++ ){
      name=tmpSymbols[i];
      
      // remove excessive spaces from a symbol name,
      // since we do not know for sure where it came from
      StringTrimLeft(name);
      StringTrimRight(name);
      if( !StringLen(name) ){
         continue;
      }
      
      // main filtration of symbols is performed further on
      // ...

      
      // if a symbol fits all our conditions, add it to the list
      arrPanel1.Add(name);
   }
}

First, place all symbols into a temporary array. The initial filtering already occurs at this point by the Hide symbols absent in the Market Watch panel input.

Placing symbols into the temporary arrays is not necessary. Instead, we can place the necessary symbols into the main list. But in that case, we would need to re-write the code, for example, in case we need to add an input adding only the symbols to be displayed in the list. In other words, we would need to use custom symbols in the necessary order instead of taking all symbols offered by the broker.

For the same reasons, a symbol name is first cleared from spaces in the loop that enumerates all the symbols from the temporary array. If you want to implement the input described above, you cannot do without filtering the custom input.

Now, let's sort the obtained symbols based on the inputs we have. The code blocks provided below are added below the main filtration of symbols is performed further on comment string of the prepare_symbols function (in the loop of adding symbols to the list).

Hide symbols having positions:

      // Hide symbols having positions
      bool isskip=false;
      if( noSYMBwithPOS ){
         // view the list of all open positions
         int cntMyPos=PositionsTotal();
         for(int ti=cntMyPos-1; ti>=0; ti--){
            // skip if there is a position for the current symbol
            if(PositionGetSymbol(ti) == name ){
               isskip=true;
               break;
            }
         }
         if(!isskip){
            int cntMyPosO=OrdersTotal();
            if(cntMyPosO>0){
               for(int ti=cntMyPosO-1; ti>=0; ti--){
                  ulong orderTicket=OrderGetTicket(ti);
                  if( OrderGetString(ORDER_SYMBOL) == name ){
                     isskip=true;
                     break;
                  }
               }
            }
         }
      }

First, check if there is a position on a symbol. If there is no position, check if there is a limit order. If an open position or a limit order is present, skip the symbol.

Hide symbols with a spread:

      // if the inputs using the value of the symbol's current price are active,
      // attempt to get the current values
      if(hide_PRICE_HIGH>0 || hide_PRICE_LOW>0 || hide_SPREAD>0 ){
         SymbolInfoTick(name, lastme);
         if( lastme.bid==0 ){
            Alert("Failed to get BID value. Some filtration functions may not work.");
         }
      }
      if(hide_SPREAD>0 && lastme.bid>0){
         switch(hide_SPREAD){
            // if the current spread exceeds 0.05% of the price, skip the symbol
            case spread_b05:
               if( ((SymbolInfoInteger(name, SYMBOL_SPREAD)*SymbolInfoDouble(name, SYMBOL_POINT))/lastme.bid)*100 > 0.05 ){
                  isskip=true;
               }
               break;
            // if the current spread exceeds 0.1% of the price, skip the symbol
            case spread_b1:
               if( ((SymbolInfoInteger(name, SYMBOL_SPREAD)*SymbolInfoDouble(name, SYMBOL_POINT))/lastme.bid)*100 > 0.1 ){
                  isskip=true;
               }
               break;
            // if the current spread exceeds 0.15% of the price, skip the symbol
            case spread_b15:
               if( ((SymbolInfoInteger(name, SYMBOL_SPREAD)*SymbolInfoDouble(name, SYMBOL_POINT))/lastme.bid)*100 > 0.15 ){
                  isskip=true;
               }
               break;
            // if the current spread is less 0.15% of the price, skip the symbol
            case spread_l15:
               if( ((SymbolInfoInteger(name, SYMBOL_SPREAD)*SymbolInfoDouble(name, SYMBOL_POINT))/lastme.bid)*100 < 0.15 ){
                  isskip=true;
               }
               break;
            // if the current spread is less than 0.1% of the price, skip the symbol
            case spread_l1:
               if( ((SymbolInfoInteger(name, SYMBOL_SPREAD)*SymbolInfoDouble(name, SYMBOL_POINT))/lastme.bid)*100 < 0.1 ){
                  isskip=true;
               }
               break;
         }
      }
      if(isskip){
         continue;
      }

The smaller the spread, the better. From this point of view, it is best to work with symbols having a spread is less than 0.05% of the price. Not all brokers offer such good conditions, especially when trading in the stock market.

Hide symbols with the higher price (0 - do not hide):

      // Hide symbols with the higher price (0 - do not hide)
      if(hide_PRICE_HIGH>0 && lastme.bid>0 && lastme.bid>hide_PRICE_HIGH){
         continue;
      }

Hide symbols with the lower price (0 - do not hide):

      if(hide_PRICE_LOW>0 && lastme.bid>0 && lastme.bid<hide_PRICE_LOW){
         continue;
      }

Hide symbols unavailable for trading:

      if(hideProhibites){
         // skip if the minimum position volume on a symbol is 0
         if( SymbolInfoDouble(name, SYMBOL_VOLUME_MIN)==0 ) continue;
         // skip if opening positions is disabled on a symbol
         if(SymbolInfoInteger(name, SYMBOL_TRADE_MODE)==SYMBOL_TRADE_MODE_DISABLED || SymbolInfoInteger(name, SYMBOL_TRADE_MODE)==SYMBOL_TRADE_MODE_CLOSEONLY ){
            continue;
         }
      }

Of course, it is possible to hide the symbols trading is disabled for without any conditions from the inputs. But you may still want to have such symbols in the list. This is why we add this input.

Show symbols opening only at a specified hour:

      // get the current day in the curDay variable
      MqlDateTime curDay;
      TimeCurrent(curDay);
      MqlDateTime curDayFrom;
      datetime dfrom;
      datetime dto;
      // if there is a limitation on market open time 
      // and we managed to get open time of the current stock for the current day, then...
      if( hide_HOURS!=hour_any && SymbolInfoSessionTrade(name, (ENUM_DAY_OF_WEEK) curDay.day_of_week, 0, dfrom, dto)){
         TimeToStruct(dfrom, curDayFrom);
         if(hide_HOURS==hour_9am && curDayFrom.hour != 9){
            continue;
         }
         if(hide_HOURS==hour_10am && curDayFrom.hour != 10){
            continue;
         }
         if(hide_HOURS==hour_4pm && curDayFrom.hour != 16){
            continue;
         }
         if(hide_HOURS==hour_0am && curDayFrom.hour != 0){
            continue;
         }
      }

Hide if the market is closed. If you launch the EA on Sunday, you hardly want to analyze a stock market. Most likely, you want to select symbols available on Sunday, like TA25 index or cryptocurrencies. This input parameter allows us to do this.

Of course, it would be possible only to display symbols traded today rather than introducing a separate input. But what if it is Sunday and we still want to get ready for the next trading day by selecting suitable shares, etc? Let's implement this ability as an input parameter.

To define if the market will be open today, we will need the SymbolInfoSessionTrade function. If it returns false, then, apparently, the symbol is not available for trading today. To avoid calling the function twice, we will need to re-write the code showing only symbols opening in:

      MqlDateTime curDay;
      TimeCurrent(curDay);
      MqlDateTime curDayFrom;
      datetime dfrom;
      datetime dto;
      
      bool sessionData=SymbolInfoSessionTrade(name, (ENUM_DAY_OF_WEEK) curDay.day_of_week, 0, dfrom, dto);

      // hide a symbol if the market is closed today
      if( hideClosed && !sessionData ){
         continue;
      }
      
      // Show only symbols opening in
      // get the current day to the curDay variable
      // if there is a limitation on market open time, 
      // and we managed to get open time of the current stock for the current day, then...
      if( hide_HOURS!=hour_any && sessionData){
         TimeToStruct(dfrom, curDayFrom);
         if(hide_HOURS==hour_9am && curDayFrom.hour != 9){
            continue;
         }
         if(hide_HOURS==hour_10am && curDayFrom.hour != 10){
            continue;
         }
         if(hide_HOURS==hour_4pm && curDayFrom.hour != 16){
            continue;
         }
         if(hide_HOURS==hour_0am && curDayFrom.hour != 0){
            continue;
         }
      }

Hide if ATR is less than the set value in dollars. If you trade intraday and wait for the price to move for at least 50-90 cents, you are unlikely to need symbols that statistically move for no more than 30 cents per day. This parameter allows us to sort out such symbols by specifying the necessary minimum size of the price daily movement:

      // Hide if ATR is less than the set value in dollars
      if(hideATRcents>0){
         MqlRates rates[];
         ArraySetAsSeries(rates, true);
         double atr;
         if(CopyRates(name, PERIOD_D1, 1, 5, rates)==5){
            atr=0;
            for(int j=0; j<5; j++){
               atr+=rates[j].high-rates[j].low;
            }
            atr/=5;
            if( atr>0 && atr<hideATRcents ){
               continue;
            }
         }
      }

I think, this is enough for full filtering by most parameters. But if you need other filtration conditions, you can always add them inside the prepare_symbols function loop.

Opening charts

We have already learned how to draw symbol buttons fitting our conditions. We could stop at this point and open charts of obtained symbols manually. But this is inconvenient. Fortunately, we can simplify the process and open a necessary chart when clicking the button.

To let the action occur when clicking the button, that action should be described in the OnChartEvent MQL language standard function. The function intercepts any chart event. In other words, it is called at any event happening on the chart.

The OnChartEvent function features four inputs. The very first parameter (id) contains the ID of an event that has currently been intercepted by the OnChartEvent function. To understand that the OnChartEvent function was called exactly after clicking a chart button, compare the parameter value with the necessary one.

The button click event has the CHARTEVENT_OBJECT_CLICK ID. Thus, let's add the following code to the OnChartEvent function in order to handle button clicks on the chart:

void OnChartEvent(const int id,         // event ID   
                  const long& lparam,   // event parameter of the long type 
                  const double& dparam, // event parameter of the double type 
                  const string& sparam) // event parameter of the string type 
  { 
   switch(id){
      case CHARTEVENT_OBJECT_CLICK:
         // code executed when clicking the button
         break;
   }

}

How do we understand exactly which button was pressed on the chart? The second parameter of the OnChartEvent function (sparam) can help us with that. For the CHARTEVENT_OBJECT_CLICK event, it contains the button name clicked by a user. We just have to compare this name with the one generated by our EA. If this is the EA button, open the necessary symbol chart. A name of an opened symbol is taken from the text written on the button. As a result, we have the following code that should be placed inside the case CHARTEVENT_OBJECT_CLICK condition:

         // if the button name contains a line present in all graphical objects
         // created by your EA, then...
         if( StringFind(sparam, exprefix+"btn")>=0 ){
            // place the current button index
            // (position of a current symbol in the symbol list) to the panel1val variable
            string tmpme=sparam;
            StringReplace(tmpme, exprefix+"btn", "");
            panel1val=(int) tmpme;
            
            // open the current symbol chart
            showcharts(ObjectGetString(0,sparam,OBJPROP_TEXT));
         }

The chart of a selected symbol is opened using the showcharts custom function. The function not only opens the necessary chart but also additionally:

  • closes charts previously opened by the EA;
  • adds a symbol to the Market Watch panel if it is not there;
  • if necessary, switches the chart to the candlestick mode;
  • changes the chart scale (I added this feature simply because I use the custom scale rather than the default one).
void showcharts(string name){
   // if charts were already open, close them
   closecharts();
   
   // Add a symbol to the "Market Watch" panel if it is not there
   // and if the "Add chart to Market Watch" input is 'true'
   if( addInfoWatch ){
      SymbolSelect(name, true);
   }
   
   // open the chart and place the chart ID to the curChartID array
   curChartID[ArrayResize(curChartID,ArraySize(curChartID)+1)-1]=charts[(uchar) ArrayResize(charts,ArraySize(charts)+1)-1].Open( name, PERIOD_D1 );
   
   // if the "Open candlestick charts" input is 'true',
   // switch the chart to the candlestick mode
   if(viewCandle){
      ChartSetInteger( curChartID[ArraySize(curChartID)-1], CHART_MODE, CHART_CANDLES);
   }
   // if the "Show tick volumes" input is 'true',
   // display the tick volumes
   if(viewVolumes){
      ChartSetInteger( curChartID[ArraySize(curChartID)-1], CHART_SHOW_VOLUMES, CHART_VOLUME_TICK);
   }
   // change the chart scale to the most convenient one
   ChartSetInteger( curChartID[ArraySize(curChartID)-1], CHART_SCALE, 2);
      
   // wait for one third of a second till all the changes are implemented
   Sleep(333);
   // update the open chart to make all the changes
   ChartRedraw(curChartID[ArraySize(curChartID)-1]);
   
}
The closecharts function closes all charts previously opened by the EA. Its code is very simple:
void closecharts(){
   // if the array of charts opened by the EA is not empty, then...
   if(ArraySize(charts)){
      // close all the charts consistently
      for( int i=0; i<ArraySize(charts); i++ ){
         charts[i].Close();
      }
      // clear the array of charts
      ArrayFree(charts);
   }
   // if the array of IDs of the chart opened by the EA is not empty, clear it
   if(ArraySize(curChartID)){
      ArrayFree(curChartID);
   }
}

Displaying additional symbol info

It would be good not only to open a symbol chart, but also to display auxiliary data on it, like description (some brokers make symbol names so incomprehensible that they seem to be some kind of cipher) and symbol movement direction for the last day and hour.

This info can be displayed using graphical objects. However, we will use a simpler approach. We will display the info in the chart comments.

To do this, add the following code to the showcharts function before calling the Sleep function:

   //show additional info on the chart
   string msg="";
   if(showNameSymbol){
      StringAdd(msg, getmename_symbol(name)+"\r\n");
   }
   if(showInfoSymbol){
      StringAdd(msg, getmeinfo_symbol(name, false)+"\r\n");
   }
   if( StringLen(msg)>0 ){
      ChartSetString(curChartID[ArraySize(curChartID)-1], CHART_COMMENT, msg);
   }

If the showNameSymbol input is true, call the getmename_symbol function that returns the line with a symbol name. If the showInfoSymbol input is true, call the getmeinfo_symbol function that returns the line with a symbol name:

string getmename_symbol(string symname){
   return SymbolInfoString(symname, SYMBOL_DESCRIPTION);
}
string getmeinfo_symbol(string symname, bool show=true){
   MqlRates rates2[];
   ArraySetAsSeries(rates2, true);
   string msg="";

   if(CopyRates(symname, PERIOD_D1, 0, 1, rates2)>0){
      if(show){
         StringAdd(msg, (string) symname+": ");
      }
      StringAdd(msg, "D1 ");
      if( rates2[0].close > rates2[0].open ){
         StringAdd(msg, "+"+DoubleToString(((rates2[0].close-rates2[0].open)/rates2[0].close)*100, 2) +"%");
      }else{
         if( rates2[0].close < rates2[0].open ){
            StringAdd(msg, "-"+DoubleToString(((rates2[0].open-rates2[0].close)/rates2[0].close)*100, 2) +"%");
         }else{
            StringAdd(msg, "0%");
         }
      }
   }
   if(CopyRates(symname, PERIOD_H1, 0, 1, rates2)>0){
      StringAdd(msg, ", H1 ");
      if( rates2[0].close > rates2[0].open ){
         StringAdd(msg, "+"+DoubleToString(((rates2[0].close-rates2[0].open)/rates2[0].close)*100, 2)+"% (+"+DoubleToString(rates2[0].close-rates2[0].open, (int) SymbolInfoInteger(symname, SYMBOL_DIGITS))+" "+SymbolInfoString(symname, SYMBOL_CURRENCY_PROFIT)+")");
      }else{
         if( rates2[0].close < rates2[0].open ){
            StringAdd(msg, "-"+DoubleToString(((rates2[0].open-rates2[0].close)/rates2[0].close)*100, 2)+"% (-"+DoubleToString(rates2[0].open-rates2[0].close, (int) SymbolInfoInteger(symname, SYMBOL_DIGITS))+" "+SymbolInfoString(symname, SYMBOL_CURRENCY_PROFIT)+")");
         }else{
            StringAdd(msg, "0%");
         }
      }
   }
   
   return msg;
}

As a result, we will see the following info on a newly opened chart:

Displaying additional symbol info

Managing the EA from keyboard

While we still in the OnChartEvent function, let's add a response to pressing some keys there:

  • when pressing R, update the list of symbols fitting our conditions:
  • when pressing X, remove the EA from the chart.

The event having the CHARTEVENT_KEYDOWN ID handles pressing keys. The code of a pressed key is passed in the already mentioned sparam parameter. Therefore, we simply need to add the following condition to the switch operator:

      case CHARTEVENT_KEYDOWN:
         switch((int) sparam){
            case 45: //x
               ExpertRemove();
               break;
            case 19: //r
               start_symbols();
               break;
         }
         break;

As you can see, when pressing R, we simply call the start_symbols function created earlier.

Adding chart navigation

We have already learned not only how to show symbol buttons but also how to open charts of necessary symbols when clicking that buttons. However, we are not done here yet. Our utility is still inconvenient to use. After opening a symbol chart, we need to close it manually and click the next chart button. This should be done every time we need to move to the next symbol making the work quite tedious. Let's add symbol list navigation buttons to open charts.

We will only add three buttons: to move to the next chart, to move to the previous chart and to close the chart.

It only remains to decide how to implement them. We can add the buttons directly in the showcharts function when creating a new chart. But the number of buttons may increase in the future. Creating the buttons and other graphical objects may slow down opening the chart which is undesirable.

Therefore, we will create the buttons in the standard OnTimer function. We will periodically check if a chart with the EA is open, and if the chart is open, whether it has buttons. If there are no buttons, create them:

void OnTimer()
  {
   // if the open charts ID array contains values, then...
   uchar tmpCIDcnt=(uchar) ArraySize(curChartID);
   if(tmpCIDcnt>0 ){
      // if the last ID in the array is not corrupted, then...
      if(curChartID[tmpCIDcnt-1]>0){
         // if the chart with the ID has no buttons, create them
         if(ObjectFind(curChartID[tmpCIDcnt-1], exprefix+"_p_btn_next")<0){
            createBTNS(curChartID[tmpCIDcnt-1]);
         }
      }
   }
   
  }

The buttons on the chart are created in the createBTNS custom function. Its code is very simple:

void createBTNS(long CID){
   ObjectCreate(CID, exprefix+"_p_btn_prev", OBJ_BUTTON, 0, 0, 0);
   ObjectSetInteger(CID,exprefix+"_p_btn_prev",OBJPROP_XDISTANCE,110); 
   ObjectSetInteger(CID,exprefix+"_p_btn_prev",OBJPROP_YDISTANCE,90); 
   ObjectSetInteger(CID,exprefix+"_p_btn_prev",OBJPROP_XSIZE,BTN_WIDTH); 
   ObjectSetInteger(CID,exprefix+"_p_btn_prev",OBJPROP_YSIZE,BTN_HEIGHT); 
   ObjectSetInteger(CID,exprefix+"_p_btn_prev",OBJPROP_CORNER,CORNER_LEFT_LOWER); 
   ObjectSetString(CID,exprefix+"_p_btn_prev",OBJPROP_TEXT,"Prev chart");
   ObjectSetInteger(CID,exprefix+"_p_btn_prev",OBJPROP_SELECTABLE,false); 
      
   ObjectCreate(CID, exprefix+"_p_btn_next", OBJ_BUTTON, 0, 0, 0);
   ObjectSetInteger(CID,exprefix+"_p_btn_next",OBJPROP_XDISTANCE,110); 
   ObjectSetInteger(CID,exprefix+"_p_btn_next",OBJPROP_YDISTANCE,65); 
   ObjectSetInteger(CID,exprefix+"_p_btn_next",OBJPROP_XSIZE,BTN_WIDTH); 
   ObjectSetInteger(CID,exprefix+"_p_btn_next",OBJPROP_YSIZE,BTN_HEIGHT); 
   ObjectSetInteger(CID,exprefix+"_p_btn_next",OBJPROP_CORNER,CORNER_LEFT_LOWER); 
   ObjectSetString(CID,exprefix+"_p_btn_next",OBJPROP_TEXT,"Next chart");
   ObjectSetInteger(CID,exprefix+"_p_btn_next",OBJPROP_SELECTABLE,false); 
      
   ObjectCreate(CID, exprefix+"_p_btn_close", OBJ_BUTTON, 0, 0, 0);
   ObjectSetInteger(CID,exprefix+"_p_btn_close",OBJPROP_XDISTANCE,110); 
   ObjectSetInteger(CID,exprefix+"_p_btn_close",OBJPROP_YDISTANCE,40); 
   ObjectSetInteger(CID,exprefix+"_p_btn_close",OBJPROP_XSIZE,BTN_WIDTH); 
   ObjectSetInteger(CID,exprefix+"_p_btn_close",OBJPROP_YSIZE,BTN_HEIGHT); 
   ObjectSetInteger(CID,exprefix+"_p_btn_close",OBJPROP_CORNER,CORNER_LEFT_LOWER); 
   ObjectSetString(CID,exprefix+"_p_btn_close",OBJPROP_TEXT,"Close chart");
   ObjectSetInteger(CID,exprefix+"_p_btn_close",OBJPROP_SELECTABLE,false); 
   
   // update the chart to see the implemented changes
   ChartRedraw(CID);
}

As a result, a new chart takes the following form:

Symbol list navigation buttons

Adding a response to button pressing

So far, the buttons added to the chart are just a decoration. Nothing happens when pressing them. Let's instruct them on how to respond to pressing.

Unfortunately, the standard OnChartEvent function is of no help to us here, since it reacts only to the events that happened on a chart the EA is launched on, while the buttons are added to a new chart.

Perhaps, there are some more convenient ways. I came up with only one way to respond to changes that occurred on another chart. It involves the OnTimer standard function. If the chart features the buttons, we will check if some of them is pressed. If yes, a necessary action is performed. As a result, the condition:

         if(ObjectFind(curChartID[tmpCIDcnt-1], exprefix+"_p_btn_next")<0){
            createBTNS(curChartID[tmpCIDcnt-1]);
         }

...is rewritten as follows:

         if(ObjectFind(curChartID[tmpCIDcnt-1], exprefix+"_p_btn_next")<0){
            createBTNS(curChartID[tmpCIDcnt-1]);
         }else{
            if(ObjectGetInteger(curChartID[tmpCIDcnt-1],exprefix+"_p_btn_prev",OBJPROP_STATE)==true ){
               prevchart();
               return;
            }
            if(ObjectGetInteger(curChartID[tmpCIDcnt-1],exprefix+"_p_btn_next",OBJPROP_STATE)==true ){
               nextchart();
               return;
            }
            if(ObjectGetInteger(curChartID[tmpCIDcnt-1],exprefix+"_p_btn_close",OBJPROP_STATE)==true ){
               closecharts();
               return;
            }
         }

When pressing the Prev chart button, call the prevchart function. When pressing the Next chart button, call the nextchart function. When pressing the Close chart button, call the closecharts function mentioned above. The prevchart and nextchart functions are similar:

void nextchart(){
   // if a symbol list features the next symbol, open its chart
   // otherwise, close the current chart
   if(arrPanel1.Total()>(panel1val+1)){
      panel1val++;
      showcharts(arrPanel1[panel1val]);
   }else{
      closecharts();
   }
}
void prevchart(){
   // if a symbol list features the previous symbol, open its chart
   // otherwise, close the current chart
   if(arrPanel1.Total()>(panel1val-1) && (panel1val-1)>=0){
      panel1val--;
      showcharts(arrPanel1[panel1val]);
   }else{
      closecharts();
   }
}

Conclusion

That's it. As you can see, the amount of the overall code is not overwhelming, while the advantage is evident. We no longer need to open charts and close them repeatedly. Instead, we can click on the necessary button, and all is done for us.

Of course, there may be more ways to improve our EA. But in its current form, it is already a full-fledged product that significantly simplifies selection of shares.

Moving the utility to MQL4

Now, let's try to move our utility to MQL4. Surprisingly, we only need to re-write a single code block. This will take about five minutes.

First, create a new EA in MetaEditor 4. After that, move the MQL5 EA's source code to it.

Compile the EA. The attempt ends in an error, of course. But as a result, we get a list of errors to fix. There are only three of them:

  • 'PositionsTotal' - function not defined
  • 'PositionGetSymbol' - function not defined
  • 'OrderGetTicket' - function not defined

Double-click on the first error to move to the appropriate EA string.

'PositionsTotal' - function not defined. The error is detected in the following block of the prepare_symbols function code:

         int cntMyPos=PositionsTotal();
         for(int ti=cntMyPos-1; ti>=0; ti--){
            // skip if there is a position on the current symbol
            if(PositionGetSymbol(ti) == name ){
               isskip=true;
               break;
            }
         }
         if(!isskip){
            int cntMyPosO=OrdersTotal();
            if(cntMyPosO>0){
               for(int ti=cntMyPosO-1; ti>=0; ti--){
                  ulong orderTicket=OrderGetTicket(ti);
                  if( OrderGetString(ORDER_SYMBOL) == name ){
                     isskip=true;
                     break;
                  }
               }
            }
         }

One of the significant differences between the MQL4 and MQL5 languages is handling positions and orders. Therefore, we should rewrite the code block the following way to let the EA work in MetaTrader 4 correctly:

         int cntMyPos=OrdersTotal();
         for(int ti=cntMyPos-1; ti>=0; ti--){
            if(OrderSelect(ti,SELECT_BY_POS,MODE_TRADES)==false) continue;
            if(OrderSymbol() == name ){
               isskip=true;
               break;
            }
         }

Since MQL4 has no differentiation between positions and orders, the resulting code is much smaller.

The remaining errors are fixed automatically since they occurred in the code block we fixed.


Translated from Russian by MetaQuotes Software Corp.
Original article: https://www.mql5.com/ru/articles/5348

Attached files |
_finder.mq5 (40.88 KB)
_finder4.mq4 (40.09 KB)
_finder.ex5 (56.21 KB)
_finder4.ex4 (30.97 KB)
Using OpenCL to test candlestick patterns Using OpenCL to test candlestick patterns

The article describes the algorithm for implementing the OpenCL candlestick patterns tester in the "1 minute OHLC" mode. We will also compare its speed with the built-in strategy tester launched in the fast and slow optimization modes.

DIY multi-threaded asynchronous MQL5 WebRequest DIY multi-threaded asynchronous MQL5 WebRequest

The article describes the library allowing you to increase the efficiency of working with HTTP requests in MQL5. Execution of WebRequest in non-blocking mode is implemented in additional threads that use auxiliary charts and Expert Advisors, exchanging custom events and reading shared resources. The source codes are applied as well.

Applying the probability theory to trading gaps Applying the probability theory to trading gaps

In this article, we will apply the probability theory and mathematical statistics methods to creating and testing trading strategies. We will also look for optimal trading risk using the differences between the price and the random walk. It is proved that if prices behave like a zero-drift random walk (with no directional trend), then profitable trading is impossible.

How to create and test custom MOEX symbols in MetaTrader 5 How to create and test custom MOEX symbols in MetaTrader 5

The article describes the creation of a custom exchange symbol using the MQL5 language. In particular, it considers the use of exchange quotes from the popular Finam website. Another option considered in this article is the possibility to work with an arbitrary format of text files used in the creation of the custom symbol. This allows working with any financial symbols and data sources. After creating a custom symbol, we can use all the capabilities of the MetaTrader 5 Strategy Tester to test trading algorithms for exchange instruments.