Download MetaTrader 5

Optimization. A Few Simple Ideas

5 March 2015, 09:32
Jose Miguel Soriano
0
3 455

Introduction

Having found a consistent strategy for the EA to trade, we launch it on the EURUSD chart, right? Can the strategy be more profitable on other currency pairs? Are there any other currency pairs on which the strategy will give better results without necessity to increase the lot in geometric progression?

What will happen if we choose EURUSD and the standard H1 timeframe and then, if we are not happy with the result, change it for EURJPY on H4?

Besides, if we have a 64 bit operation system, which allows us to stop worrying about the speed of testing the system, do we forget about ludicrous combinations of the trading system entry parameters, which take part in the complete enumeration during optimization and which results we have to neglect in the final reports?

I have solved these "minor issues" myself and in this article share the effective solutions. I appreciate though that there may be other more optimal solutions.


Optimization by Timeframes

MQL5 provides a complete set of timeframes: from M1, M2 , M3, M4,... H1, H2,... to monthly charts. In total, there are 21 timeframes. In the process of optimization, however, we want to know what timeframes suit our strategy most of all - short ones like М1 and М5, medium ones - like H2 and H4 or long ones - D1 and W1.

Initially we do not need this diversity of options. In any case, if we can see that the strategy proves effective on the М5 timeframe, then at the next stage of optimization we can check if it is going to work on М3 or М6.

If we use a variable of the ENUM_TIMEFRAMES type as an input parameter:

input ENUM_TIMEFRAMES marcoTF= PERIOD_M5; 

then the Optimizer will offer 21 optimization variations. Do we really need this amount?

Standard options of a timeframe

Initially we do not. How can we simplify the optimization? At first we can define the enumeration:

enum mis_MarcosTMP
{
   _M1= PERIOD_M1,
   _M5= PERIOD_M5,
   _M15=PERIOD_M15,
//   _M20=PERIOD_M20,
   _M30=PERIOD_M30,
   _H1= PERIOD_H1,
   _H2= PERIOD_H2,
   _H4= PERIOD_H4,
//   _H8= PERIOD_H8,
   _D1= PERIOD_D1,
   _W1= PERIOD_W1,
   _MN1=PERIOD_MN1
};

where we can add or delete the timeframes of interest. For optimization, define the input variable in the beginning of the code:

input mis_MarcosTMP timeframe= _H1;

and define a new function in the library .mqh:

//----------------------------------------- DEFINE THE TIMEFRAME ----------------------------------------------------------
ENUM_TIMEFRAMES defMarcoTiempo(mi_MARCOTMP_CORTO marco)
{
   ENUM_TIMEFRAMES resp= _Period;
   switch(marco)
   {
      case _M1: resp= PERIOD_M1; break;
      case _M5: resp= PERIOD_M5; break;
      case _M15: resp= PERIOD_M15; break;
      //case _M20: resp= PERIOD_M20; break;
      case _M30: resp= PERIOD_M30; break;
      case _H1: resp= PERIOD_H1; break;
      case _H2: resp= PERIOD_H2; break;
      case _H4: resp= PERIOD_H4; break;
      //case _H8: resp= PERIOD_H8; break;
      case _D1: resp= PERIOD_D1; break;
      case _W1: resp= PERIOD_W1; break;
      case _MN1: resp= PERIOD_MN1;
   }
return(resp);
}

Declare a new variable in the scope of global variables:

ENUM_TIMEFRAMES marcoTmp= defMarcoTiempo(marcoTiempo);          //timeframe is defined as a global variable

"marcoTmp" is a global variable, which is going to be used by the EA to define a required chart timeframe. In the table of the Optimizer parameters, the interval of launching the "marcoTiempo" variable can be defined. This will cover only the steps of our interest without spending time and resources on analyzing М6 or М12. This way we can analyze results of the EA's work on different timeframes.

User-specific options of a timeframe

Surely, it can be done with

ENUM_TIMEFRAMES marcoTmp= (ENUM_TIMEFRAMES)marcoTiempo;

This gets obvious after months, even years of programming if you are a perfectionist and go through the code a lot trying to simplify it. Or if you are using a VPS and trying to keep your bill down by optimizing computer performance.


Optimizing a Symbol or a Set of Symbols

In the MetaTrader 5 Strategy Tester there is an optimization mode, which facilitates running the EA on all symbols selected in the MarketWatch window. This function, however, does not allow to arrange optimization as if the selected symbol is another parameter. So if there are 15 selected symbols, then the Tester will arrange 15 runs. How can we find out which symbol is the best for our EA? If this is a multi-currency EA, then what group of symbols give the best result and with what parameter set? String variables do not get optimized in MQL5. How can it be done?

Code a symbol or a couple of symbols by the input parameter value the following way:

input int selecDePar= 0;

string cadParesFX= selecPares(selecDePar);

The "selecDePar" parameter is used as an optimization parameter, which is converted into a string variable. Use the "cadParesFX" variable in the EA. The currency pair/pairs (for this code it is irrelevant whether the strategy is multi-currency or not) are going to be stored in this variable together with other regular optimization parameters.

//------------------------------------- SELECT THE SET OF PAIRS -------------------------------------
string selecPares(int combina= 0)
{
   string resp="EURUSD";
   switch(combina)               
      {
         case 1: resp= "EURJPY"; break;
         case 2: resp= "USDJPY"; break;
         case 3: resp= "USDCHF"; break;      
         case 4: resp= "GBPJPY"; break;
         case 5: resp= "GBPCHF"; break;      
         case 6: resp= "GBPUSD"; break;
         case 7: resp= "USDCAD"; break;
         case 8: resp= "CADJPY"; break;      
         case 9: resp= "XAUUSD"; break;
       
         case 10: resp= "EURJPY;USDJPY"; break;
         case 11: resp= "EURJPY;GBPJPY"; break;
         case 12: resp= "GBPCHF;GBPJPY"; break;
         case 13: resp= "EURJPY;GBPCHF"; break;
         case 14: resp= "USDJPY;GBPCHF"; break;

         case 15: resp= "EURUSD;EURJPY;GBPJPY"; break;
         case 16: resp= "EURUSD;EURJPY;GBPCHF"; break;
         case 17: resp= "EURUSD;EURJPY;USDJPY"; break;
         case 18: resp= "EURJPY;GBPCHF;USDJPY"; break;
         case 19: resp= "EURJPY;GBPUSD;GBPJPY"; break;
         case 20: resp= "EURJPY;GBPCHF;GBPJPY"; break;
         case 21: resp= "USDJPY;GBPCHF;GBPJPY"; break;
         case 22: resp= "EURUSD;USDJPY;GBPJPY"; break;
       
         case 23: resp= "EURUSD;EURJPY;USDJPY;GBPUSD;USDCHF;USDCAD"; break;
         case 24: resp= "EURUSD;EURJPY;USDJPY;GBPUSD;USDCHF;USDCAD;AUDUSD"; break;
      }
   return(resp);
}

Depending on what our goal is, define pair combinations and inform the Tester of the interval to be analyzed. Give the Strategy Tester a command to optimize the "selecDePar" parameter on the interval from 15 to 22 (see the picture below). What do we do when we want to compare results for a single currency? In that case we run optimization on the interval from 0 to 9.

Optimization of a Set of Pairs

For example, the EA receives the value of the cadParesFX= "EURUSD;EURJPY;GBPCHF" parameter. In OnInit() call the "cargaPares()" function, which fills the arrayPares[] dynamic array with strings, divided by the ";" symbol in the cadParesFX parameter. All global variables have to be loaded into dynamic arrays, which save the values of every symbol including the control of opening a new bar on a symbol if possible. In the case we are working with one symbol, the dimensions of array will be equal to one.

//-------------------------------- STRING CONVERSION FROM CURRENCY PAIRS INTO AN ARRAY  -----------------------------------------------
int cargaPares(string cadPares, string &arrayPares[])
{            //convierte "EURUSD;GBPUSD;USDJPY" a {"EURUSD", "GBPUSD", "USDJPY"}; devuelve el número de paresFX
   string caract= "";
   int i= 0, k= 0, contPares= 1, longCad= StringLen(cadPares);
   if(cadPares=="")
   {
      ArrayResize(arrayPares, contPares);
      arrayPares[0]= _Symbol;
   }
   else
   {
      for (k= 0; k<longCad; k++) if (StringSubstr(cadPares, k, 1)==";") contPares++;
      ArrayResize(arrayPares, contPares);    
      ZeroMemory(arrayPares);
      for(k=0; k<longCad; k++)
      {
         caract= StringSubstr(cadPares, k, 1);
         if (caract!=";") arrayPares[i]= arrayPares[i]+caract;
         else i++;
      }
    }
   return(contPares);
}

In OnInit() this function is implemented the following way:

string ar_ParesFX[];    //array, containing names of the pairs for the EA to work with
int numSimbs= 1;        //variable, containing information about the number of symbols it works with

int OnInit()
{
   
   //...
   numSimbs= cargaPares(cadParesFX, ar_ParesFX);     //returns the ar_ParesFX array with pairs for work in the EA
   //...
   
}

If numSimbs>1, the OnChartEvent() function is called. This works with a multi-currency system. Otherwise, the OnTick() function is used:

void OnTick()
{
   string simb="";
   bool entrar= (nSimbs==1);
   if(entrar)
   {   
      .../...
      simb= ar_ParesFX[0];
      gestionOrdenes(simb);
      .../...
   }
   return;
}

//+------------------------------------------------------------------+
//| EVENT HANDLER                                                   |
//+-----------------------------------------------------------------+
void OnChartEvent(const int idEvento, const long& lPeriodo, const double& dPrecio, const string &simbTick)
{
   bool entrar= nSimbs>1 && (idEvento>=CHARTEVENT_CUSTOM);
   if(entrar)      
   {
      .../...
      gestionOrdenes(simbTick);
      .../...
   }
}
  

This means that all functions in the role of the input parameter must contain at least the symbol under inquiry. For instance, instead of the Digits() function, we must use the following:

//--------------------------------- SYMBOLS OF A SYMBOL ---------------------------------------
int digitosSimb(string simb= NULL)
{
   int numDig= (int)SymbolInfoInteger(simb, SYMBOL_DIGITS);
   return(numDig);
}

In other words, we must forget about the functions Symbol() or Point(), as well as other variables traditional for МetaТtarder 4 such as Ask or Bid.

//----------------------------------- POINT VALUE in price (Point())---------------------------------
double valorPunto(string simb= NULL) 
{
   double resp= SymbolInfoDouble(simb, SYMBOL_POINT);
   return(resp);
}
//--------------------------- precio ASK-BID  -----------------------------------------
double precioAskBid(string simb= NULL, bool ask= true)
{
   ENUM_SYMBOL_INFO_DOUBLE precioSolic= ask? SYMBOL_ASK: SYMBOL_BID;
   double precio= SymbolInfoDouble(simb, precioSolic);
   return(precio);
}

We also forgot about the function of control over the bar opening, which is present is such codes. If the ticks received in EURUSD are telling about opening of a new bar,  then the USDJPY ticks might not be received in the next 2 sec. As a result, on the next USDJPY tick the EA is to discover that a new bar is opening for this symbol even if for EURUSD this event took place 2 sec ago.

//------------------------------------- NEW MULTI-CURRENCY CANDLESTICK -------------------------------------
bool nuevaVelaMD(string simb= NULL, int numSimbs= 1, ENUM_TIMEFRAMES marcoTmp= PERIOD_CURRENT)
{
        static datetime arrayHoraNV[];
        static bool primVez= true;
        datetime horaVela= iTime(simb, marcoTmp, 0);    //received opening time of the current candlestick
        bool esNueva= false;
        int codS= buscaCadArray(simb, nombreParesFX);      
        if(primVez)
        {
           ArrayResize(arrayHoraNV, numSimbs);
           ArrayInitialize(arrayHoraNV, 0);     
           primVez= false;
        }
        esNueva= codS>=0? arrayHoraNV[codS]!= horaVela: false;
        if(esNueva) arrayHoraNV[codS]= horaVela;
        return(esNueva); 
}

This method allowed me to discover during one optimization pass that:

  • the EA works well in EURUSD,
  • works very badly in EURJPY,
  • and works satisfactory in USDJPY
  • On the EURUSD, GBPCHF, EURJPY pairs it works very well (real case).

This is true for the period of М5 and a certain combination of other optimization parameters, but not for Н1 or Н2.

There is an awkward moment here. I asked technical support about it. I don't know why this is happening but the optimization result differs depending on the symbol we select in the Strategy Tester. That is why for checking the result I keep this pair fixed through the strategy development and make sure that this is one of those pairs that can be analyzed in the Optimizer.


Optimization of a Parameter Combination

Sometimes, some illogical combinations out of all parameter combinations that take part in the optimization turn out to be suitable. Some of them make the strategy unreasonable. For example, if the variable of the entry "maxSpread" defines the value of the spread set for a trading operation, we optimize this variable for various pairs where the average broker's spread is less than 30 and XAUUSD is 400. It is absurd to analyze those pairs if they exceed 50 and XAUUSD is less than 200. Having passed the data to the optimizer, set "evalua maxSpread between 0 and 600 with the interval 20", but such a set together with other parameters produces numerous combinations that do not make sense.

Following the pattern described in the previous section, we have defined pairs for optimization in the "selecPares()" function. EURUSD is assigned option 0 and XAUUSD is assigned option 9. Then define a global variable of the bool "paramCorrect" type.

bool paramCorrect= (selecDePar<9 && maxSpread<50) ||
                   (selecDePar==9 && maxSpread>200);

Carry out action in OnInit() only if paramCorrect is in the correct position true.

int OnInit()
{   
   ENUM_INIT_RETCODE resp= paramCorrect? INIT_SUCCEEDED: INIT_PARAMETERS_INCORRECT;
   if (paramCorrect)
   {
      //...
      nSimbs= cargaPares(cadParesFX, nombreParesFX);     //return the array nombreParesFX containing pairs for work in the EA
      //... function of the EA initialization
   }
   return(resp);
}

If the paramCorrect is in the incorrect position false, then the EA does not perform any action in OnInit() and returns the INIT_PARAMETERS_INCORRECT to the Strategy Tester, which means an incorrect input data set. When the Strategy Tester receives the INIT_PARAMETERS_INCORRECT value from OnInit(), then this parameter set does not get passed to other testing agents for implementation and the line in the table with optimization results is filled with zeros and highlighted in red (see the picture below).

Results of using incorrect parameters

The reason of the program shutdown is passed to OnDeinit() as an input variable and it helps to understand the reason of the EA closing. This is a different matter though.

void OnDeinit(const int motivo)
{
   if(paramCorrect)
   {
      
      //functions of the program shutdown
      
   }
   infoDeInit(motivo);
   return;
}

//+-------------------------------------- INFORMATION ABOUT THE PROGRAM SHUTDOWN----------------------------
string infoDeInit(int codDeInit)
{                       //informs of the reason of the program shutdown
   string texto= "program initialization...", text1= "CIERRE por: ";
   switch(codDeInit)
   {
      case REASON_PROGRAM:     texto= text1+"The EA finished its work with the ExpertRemove() function"; break;  //0
      case REASON_ACCOUNT:     texto= text1+"The account was changed"; break;                                    //6
      case REASON_CHARTCHANGE: texto= text1+"Symbol or timeframe change"; break;                                 //3
      case REASON_CHARTCLOSE:  texto= text1+"The chart was closed"; break;                                       //4
      case REASON_PARAMETERS:  texto= text1+"Input parameters changed by the user"; break;                       //5
      case REASON_RECOMPILE:   texto= text1+"The program was recompiled"; break //2
      case REASON_REMOVE:      texto= text1+"The program was deleted from the chart"; break;                     //1
      case REASON_TEMPLATE:    texto= text1+"Another chart template was used"; break;                            //7
      case REASON_CLOSE:       texto= text1+"The terminal was closed"; break;                                    //9
      case REASON_INITFAILED:  texto= text1+"The OnInit() handler returned non-zero value"; break;               //8
      default:                 texto= text1+"Other reason";
   }
   Print(texto);
   return(texto);
}

The thing is that if the parameter set received by the Optimizer at the specified stage sets "paramCorrect" to false (for example if the EURUSD spread was set to 100 points), then we do not run the EA and this optimization step becomes zero without unnecessary use of your computer resources and expenses of renting agents for your MQL5.сommunity account.

Surely, all said above can be implemented with OnTesterInit() and the ParameterGetRange() and ParameterSetRange() functions, but the described pattern seems to be simpler. This is guaranteed to work whereas the pattern with OnTesterInit() is not as consistent.


Conclusion

We have discussed speeding up a search for optimal timeframes in МetaТrader 5, optimizing the "symbol" parameter, when МetaТrader 5 does not allow to optimize string variables and making it indifferent to the number of symbols the EA is using. We have also seen an illustration of how to reduce the number of optimization steps dropping illogical sets of input parameters maintaining your computer performance and saving the funds.

The above ideas are not new and they can be implemented by a novice or a programmer with some experience. These ideas were a result of a long search for information and usage of the debugging program. These are very simple but efficient ideas. You could ask me why I am sharing them if I want MQL5 to generate profit? The answer is to overcome the "solitude" of a programmer.

Thank you for your attention. If you read it up to the end and if you are an experienced programmer, please don't judge me too harshly.

Translated from Spanish by MetaQuotes Software Corp.
Original article: https://www.mql5.com/es/articles/1052

Step on New Rails: Custom Indicators in MQL5 Step on New Rails: Custom Indicators in MQL5

I will not list all of the new possibilities and features of the new terminal and language. They are numerous, and some novelties are worth the discussion in a separate article. Also there is no code here, written with object-oriented programming, it is a too serous topic to be simply mentioned in a context as additional advantages for developers. In this article we will consider the indicators, their structure, drawing, types and their programming details, as compared to MQL4. I hope that this article will be useful both for beginners and experienced developers, maybe some of them will find something new.

Here Comes the New MetaTrader 5 and MQL5 Here Comes the New MetaTrader 5 and MQL5

This is just a brief review of MetaTrader 5. I can't describe all the system's new features for such a short time period - the testing started on 2009.09.09. This is a symbolical date, and I am sure it will be a lucky number. A few days have passed since I got the beta version of the MetaTrader 5 terminal and MQL5. I haven't managed to try all its features, but I am already impressed.

Using text files for storing input parameters of Expert Advisors, indicators and scripts Using text files for storing input parameters of Expert Advisors, indicators and scripts

The article describes the application of text files for storing dynamic objects, arrays and other variables used as properties of Expert Advisors, indicators and scripts. The files serve as a convenient addition to the functionality of standard tools offered by MQL languages.

How to create an indicator of non-standard charts for MetaTrader Market How to create an indicator of non-standard charts for MetaTrader Market

Through offline charts, programming in MQL4, and reasonable willingness, you can get a variety of chart types: "Point & Figure", "Renko", "Kagi", "Range bars", equivolume charts, etc. In this article, we will show how this can be achieved without using DLL, and therefore such "two-for-one" indicators can be published and purchased from the Market.