English Русский 中文 Deutsch 日本語 Português 한국어 Français Italiano Türkçe
Optimizando la optimización: algunas sencillas ideas

Optimizando la optimización: algunas sencillas ideas

MetaTrader 5Ejemplos | 26 noviembre 2014, 13:17
1 836 7
Jose Miguel Soriano
Jose Miguel Soriano

Introducción

Una vez que hemos definido la estrategia con la que nuestro EA da órdenes de compra y de venta, ¿ejecutamos el EA en EURUSD... como siempre?. ¿Habrá otro par de divisas en el que la estrategia sea más rentable?. ¿Habrá un conjunto de pares que aproveche la estrategia y multiplique los resultados sin necesidad de recurrir a volumen de lote en progresión geométrica?.…

¿Y si elegimos por inercia EURUSD, elegimos también por inercia como marco temporal del gráfico H1 y luego, si nos defraudan los resultados, cambiamos a EURJPY y H4?.

Además, si pensamos que un 64 bit nos permite despreocuparnos del consumo computacional, ¿nos olvidamos de las combinaciones de parámetros que son ilógicas pero que producen pasos completos del EA en el optimizador y cuyos resultados tenemos que borrar en la hoja de cálculo generada posteriormente?.

Estos "problemillas" los he ido resolviendo en la soledad del programador y, humildemente, apunto las soluciones que uso y que me funcionan, sabiendo de antemano que habrá otras más optimas.

Optimización del periodo temporal o Timeframes

Mql5 permite una gama completa de periodos temporales del gráfico: M1, M2 , M3, M4,... H1, H2,... hasta gráficos de mes; 21 marcos temporales en total (o Timeframes; TF en adelante). Pero en la práctica, cuando optimizamos, nos interesa detectar en qué plazo temporal se desarrolla mejor la estrategia: ¿en plazos muy cortos M1 o M5?; ¿en plazos medios H2, H4?; ¿o en plazos largos D1, W1?. En una primera aproximación, no nos hace falta tanta variedad de opciones; en todo caso, si detectamos que la estrategia va mejor en periodo M5, en un segundo paso podremos estudiar si funciona bien en M3 o M6.

Si usamos como parámetro de entrada una variable tipo ENUM_TIMEFRAMES...

input ENUM_TIMEFRAMES marcoTF= PERIOD_M5; 

El optimizador nos permitirá un intervalo de optimización de 21 opciones: ¿necesitamos tanto?.


Inicialmente, no. ¿Cómo simplifico ?. Defino la enumeración...

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
};

donde en cada momento introduzco o anulo los TFs que interesen. Para optimizar, al inicio del código defino la variable de entrada...

input mis_MarcosTMP marcoTiempo= _H1;

y en una librería .mqh tengo definida la función...

//----------------------------------------- DEFINE MARCO TIEMPO ----------------------------------------------------------
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);
}

 

En la zona de declaración de variables globales defino...

ENUM_TIMEFRAMES marcoTmp= defMarcoTiempo(marcoTiempo);          //marcoTmp definida como variable global

"marcoTmp" es la variable global que usará el EA para fijar el TF de gráfico en que debe trabajar. De esta manera, puedo analizar los resultados del EA en diferentes TFs porque en el cuadro de parámetros del optimizador puedo definir el intervalo de ejecución de la variable "marcoTiempo" que ahora estará ceñido sólo a los pasos que me interesan, sin perder tiempo ni recursos en analizar M6 o M12, por ejemplo.


Claro, que también se puede resolver con...

ENUM_TIMEFRAMES marcoTmp= (ENUM_TIMEFRAMES)marcoTiempo;

Pero de esto te das cuenta cuando llevas meses o años programando, eres perfeccionista y haces un n-simo repaso del código buscando su simplificación... o has contratado VPS y tratas de reducir tu factura optimizando el consumo computacional.


Optimización del símbolo o conjunto de símbolos

MT5 tiene una opción de optimización que permite comprobar el EA con todos los símbolos seleccionados a voluntad en la "ventana de observación" o Marketwacht, pero esa opción no permite optimizar como si el símbolo seleccionado fuera un parámetro más, de manera que si hay 15 símbolos seleccionados, el optimizador hará 15 pasos. ¿Cómo estudiamos cuál es el par o símbolo para el que mejor se comporta nuestro EA?. Y si es multisímbolo, ¿qué grupo de activos dan mejor resultado y con qué conjunto de parámetros?. Las variables string (cadena) no son optimizables  en mql5. ¿Cómo hacerlo?.

Yo convierto el símbolo o par de trabajo en un parámetro variable más de la siguiente forma: en parámetros de entrada y variables globales declaro...

input int selecDePar= 0;

string cadParesFX= selecPares(selecDePar);

Uso "selecDePar" como opción de entrada al optimizador y luego para uso en todo el EA "cadParesFX" me almacena el/los pares (para mi código es indiferente si el EA es multisímbolo o no) con los que estudiamos la estrategia en el paso actual del optimizador.

//------------------------------------- SELECCIONA COMBINACIÓN DE PARES -------------------------------------
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);
}

Según me interese, en la anterior función defino distintas combinaciones de pares y al optimizador le informo del intervalo a analizar: ¿Quiero comprobar tríos de símbolos?... le pido que optimize entre 15 y 22 el parámetro "selecDePar" (ver imagen siguiente). ¿Quiero comprobar resultados monodivisa?, compruebo entre 0 y 9.


El EA recibe, por ejemplo, la opción cadParesFX= "EURUSD;EURJPY;GBPCHF". En OnInit() ejecuto la función "cargaPares()" que me pasa cada conjunto de letras (separado por ";") a una matriz dinámica redimensionada previamente con el número de ";" más uno. Todas las variables globales del EA se han de cargar en matrices dinámicas que respetan el valor para cada símbolo, incluyendo el control de apertura de barra del símbolo, si es el caso. Si trabajamos con un sólo símbolo, las matrices serán de dimensión uno.

//-------------------------------- CONVERSIÓN CADENA DE PARES FX A 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);
}

La llamada a esta función en OnInit() la implemento tal que así...

string ar_ParesFX[];    //array que contendrá el nombre de los pares con que trabajará el EA
int numSimbs= 1;        //variable que informa a todo el programa del número de símbolos con los que trabaja

int OnInit()
{
   .../...
   numSimbs= cargaPares(cadParesFX, ar_ParesFX);     //devuelve array ar_ParesFX con los pares a trabajar EA
   .../...
}

Si numSimbs>1 el programa entrará en OnChartEvent(), trabajando en multidivisa y si no, entrará en OnTick().

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

//+------------------------------------------------------------------+
//| MANEJADOR DE EVENTOS                                             |
//+------------------------------------------------------------------+
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);
      .../...
   }
}
  

 

Esto implica que todas las funciones deben llevar como parámetro de entrada, al menos, el símbolo del que se pide información. Ya no vale usar Digits(), por ejemplo; tendremos que usar...

//--------------------------------- DÍGITOS DEL SÍMBOLO ---------------------------------------
int digitosSimb(string simb= NULL)
{
   int numDig= (int)SymbolInfoInteger(simb, SYMBOL_DIGITS);
   return(numDig);
}

Es decir, nos olvidamos de la función Symbol() o de Point() y de otras variables habituales en MT4, como Ask o Bid...

//----------------------------------- VALOR DEL PUNTO en precio (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);
}

Y hemos de olvidarnos expresamente de la función de control de apertura de barra que circula en tantos códigos. Si en el segundo actual, los ticks recibidos en EURUSD informan a través de esa función de que se acaba de abrir una nueva barra, los ticks de USDJPY puede que no se reciban desde hace 2 segundos y, por tanto, en el siguiente tick de USDJPY el EA debe detectar que para este símbolo se abre nueva barra aunque para el EURUSD lo haya detectado hace 2 segundos.

//------------------------------------- NUEVA VELA MULTIDIVISA -------------------------------------
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);    //obtiene la hora de apertura de la vela actual
        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); 
}

El caso es que, de la manera descrita, puedo comprobar en una sóla optimización que un EA funciona muy bien en EURUSD, muy mal en EURJPY, regular en USDJPY y muy bien en el conjunto EURUSD, GBPCHF, EURJPY (caso real) y ello en plazos de M5, pero no en H1 o H2 y para un determinado conjunto del resto de los parámetros a optimizar.

Sólo una pega: no se porqué y lo he preguntado al servicio soporte, los resultados varían ligeramente según qué símbolo seleccionemos en el "probador de estrategias", por lo que para comparar resultados mantengo fijo ese par para todo el estudio y procuro que sea uno de los que se están analizando en el optimizador.

Optimización de la combinación de parámetros

A veces, de todas las combinaciones de parámetros que evalúa el optimizador, resultan algunas que no son lógicas durante la ejecución del EA o que la misma estrategia las hace absurdas. Por ejemplo, si la variable input "maxSpread" define el valor de spread admitido para operar y optimizamos esa variable para varios pares en los que el spread medio del broker es menor de 30 pero XAUUSD lo tiene en 400, es absurdo analizar esos pares por encima de 50 o XAUUSD por debajo de 200. Al dar datos al optimizador diremos "evalua maxSpread entre 0 y 600, dando saltos de 20" pero esta orden, en combinación con la dada a otros parámetros,  produce infinidad de combinaciones absurdas.

Siguiendo el esquema descrito en los apartados anteriores, hemos definido los pares a optimizar en la función "selecPares()" y EURUSD ocupa la opción 0 y XAUUSD la 9. Definimos a continuación la variable global booleana "paramCorrect"...

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

En OnInit() entraremos si paramCorrect tiene valor cierto (true)...

int OnInit()
{   
   ENUM_INIT_RETCODE resp= paramCorrect? INIT_SUCCEEDED: INIT_PARAMETERS_INCORRECT;
   if (paramCorrect)
   {
      //...
      nSimbs= cargaPares(cadParesFX, nombreParesFX);     //devuelve array nombreParesFX con los pares a trabajar EA
      //... funciones de inicialziación del EA
   }
   return(resp);
}

Si paramCorrect tiene valor falso (false), el EA no se ejecuta y OnInit() devuelve el código INIT_PARAMETERS_INCORRECT. Cuando el Probador de Estrategias recibe este valor, nunca va a pasar esta tarea a otros agentes para que vuelvan a ejecutarlo y la fila en la tabla de resultados del optimizador resulta a cero en todos sus datos y se visualiza con trasfondo en rojo (ver imagén abajo).

Resultados de parámetros incorrectos 

La respuesta de salida que proporciona OnInit() se facilita como variable de entrada a OnDeInit() y ello nos permitirá estudiar el motivo de cierre del EA... pero este es otro tema.

void OnDeinit(const int motivo)
{
   if(paramCorrect)
   {
      
      //funciones de cierre de programa
      
   }
   infoDeInit(motivo);
   return;
}

//+-------------------------------------- INFORMACIÓN CIERRE PROGRAMA ----------------------------
string infoDeInit(int codDeInit)
{                       //informa de las causas de cierre del programa
   string texto= "Programa ejecutado...", text1= "CIERRE por: ";
   switch(codDeInit)
   {
      case REASON_PROGRAM:     texto= text1+"EA finalizó su trabajo llamando a la función ExpertRemove()"; break;  //0
      case REASON_ACCOUNT:     texto= text1+"Una cuenta nueva ha sido activada"; break;      //6
      case REASON_CHARTCHANGE: texto= text1+"Símbolo o período del gráfico ha sido modificado"; break;  //3
      case REASON_CHARTCLOSE:  texto= text1+"Gráfico ha sido cerrado"; break;  //4
      case REASON_PARAMETERS:  texto= text1+"Parámetros de entrada han sido cambiados por el usuario"; break;  //5
      case REASON_RECOMPILE:   texto= text1+"Programa fue recompilado"; break;  //2
      case REASON_REMOVE:      texto= text1+"Programa ha sido removido del gráfico"; break;  //1
      case REASON_TEMPLATE:    texto= text1+"Una nueva plantilla del gráfico ha sido aplicada"; break;  //7
      case REASON_CLOSE:       texto= text1+"El terminal ha sido cerrado"; break;  //9
      case REASON_INITFAILED:  texto= text1+"El manejador OnInit() ha devuelto valor no nulo"; break;  //8
      default:                 texto= text1+"Otra razón no prevista";
   }
   Print(texto);
   return(texto);
}

El caso es que si el conjunto de parámetros que evalúa el optimizador en un paso dado sitúa "paramCorrect" en false (por ejemplo, que el spread de EURUSD sea evaluado en 100), no ejecutamos el EA y el paso del optimizador resultará a cero sin haber consumido recursos computacionales o del crédito de nuestra cuenta en MQL-Community. 

Por supuesto, lo dicho se puede resolver con OnTesterInit() y las funciones ParameterGetRange() y ParameterSetRange(), pero el esquema que he descrito me parece más sencillo y... siempre me ha funcionado. Con OnTesterInit() a veces no.

Conclusión

Hemos visto como "agilizar" la optimización de los diferentes marcos temporales del gráfico que admite MT5; cómo optimizar el parámetro "símbolo" cuando MT5 no permite optimizar variables tipo cadena y cómo hacerlo indiferente a si el EA es mono o multisímbolo. Por último, cómo reducir el número de pasos del optimizador eliminando aquellos pasos del algoritmo optimizador que resultan absurdos para la lógica del mercado, del EA o de nuestros recursos computacionales.

Las ideas expuestas no pretenden sorprender y el artículo lo definiría como de "nivel principiante", si acaso como "nivel medio". Son ideas que resultan de horas y horas de búsqueda de información y de uso del depurador. Son ideas sencillas pero que funcionan. ¿Porqué las comparto cuando pretendo que mi dedicación a MQL5 sea lucrativa?... tal vez, por romper la "soledad" en la que se suele mover un programador.

Gracias por su atención si ha llegado en su lectura hasta aquí y, si es programador experimentado, sea indulgente con lo expuesto.

Otros artículos del autor

inver61
inver61 | 4 mar. 2015 en 20:18
josemiguel1812:

Si eres español... hablamos en español.

Entiendo que preguntas si optimizo los periodos de entrada.

Así es y por eso se trata el asunto; el problema es que todas las funciones del código deben tener un parámetro que le informe del marco temporal en que trabaja el EA. No vale dejar PERIOD_CURRENT por defecto, hay que pasar a todas las funciones que usan el periodo de tiempo una variable global (marcoTF, por ejemplo) que almacena el marco temporal en que trabaja el EA.

Para mi código es indiferente en qué gráfico cargues el EA. Siempre trabaja en el marco que le indico en el parámetro de entrada que informa posteriormente a "marcoTF".  

 

If you are Spanish ... speak in Spanish.

I understand that questions if I optimize input periods.

So it is and why the matter is; the problem is that all functions of the code must have a parameter to report to the timeframe in which the EA works. You can not use PERIOD_CURRENT default, pass all functions using the time a global variable (marcoTF, for example) that stores the time frame in which the EA works.

For my code is indifferent under what graphic upload the EA. Always work within the framework indicating on the input parameter subsequently informs "marcoTF". 

inver61
inver61 | 4 mar. 2015 en 20:20
hola, Jose miguel, Me gustaría poder ponerme en contacto por correo contigo, es posible?, un saludo
Jose
Jose | 27 oct. 2015 en 23:02

Un buen artículo. Me resulta curioso que yo he enfrentado este problema también con soluciones muy parecidas a las tuyas. Otra línea interesante es la optimización "virtual" que se presenta en este artículo:

https://www.mql5.com/en/articles/143 

En cualquier caso, gracias por luchar contra la soledad del programador :) 

Jose Miguel Soriano
Jose Miguel Soriano | 29 oct. 2015 en 19:52
Jose:

Un buen artículo. Me resulta curioso que yo he enfrentado este problema también con soluciones muy parecidas a las tuyas. Otra línea interesante es la optimización "virtual" que se presenta en este artículo:

https://www.mql5.com/en/articles/143 

En cualquier caso, gracias por luchar contra la soledad del programador :) 

Hago programación estructurada y no "leo" bien la orientada a objetos.

No entiendo la estrategia en si porque no veo cómo obtiene el resultado virtual de todas las estrategias en la vela 1 para elegir lo que hago al abrir la vela 0. En la vela 100 (numerando del presente al pasado), por ejemplo, si puedo estimar el resultado en la vela 98 "avanzando" hacia el futuro con el historial conocido y el precioBID que me dará el sistema probador de MT5... ¿pero en la vela1 cómo estimo el resultado virtual?

Michael Jimenez
Michael Jimenez | 10 dic. 2023 en 01:50

Hola Jose, 

Excelente publicaciòn!

Me gustaria ponerme en comunicacion contigo con relacion a esta publicacion la cual estoy tratando de replicar ya que la veo super util y necesaria jeje.

Saludos.

Recetas MQL5 - procesamiento de eventos personalizados del gráfico Recetas MQL5 - procesamiento de eventos personalizados del gráfico
En este artículo vamos a estudiar varios aspectos sobre la confección de proyectos y el procesamiento de los eventos personalizados del gráfico en el entorno MQL5. Se propondrá un ejemplo de aproximación para la clasificación de eventos. Asimismo, se muestra el código de programa de la clase de evento y la clase de procesador de eventos personalizados.
Por qué el hosting virtual en Meta Trader 4 y MetaTrader 5 es mejor que los VPS habituales Por qué el hosting virtual en Meta Trader 4 y MetaTrader 5 es mejor que los VPS habituales
La red Virtual Hosting Cloud ha sido especialmente desarrollada para MetaTrader 4 y MetaTrader 5, y posee todas las ventajas de la solución original. ¡Alquile ahora mismo un servidor virtual y ponga a prueba su funcionamiento, le damos 24 horas gratuitas!
Trabajo con el SGBD MySQL desde MQL5 (MQL4) Trabajo con el SGBD MySQL desde MQL5 (MQL4)
Este artículo está dedicado al desarrollo de la interfaz entre MQL y SGBD MySQL. En el artículo se consideran las soluciones prácticas que existen en actualidad y se propone la versión más cómoda de ejecución de la biblioteca para el trabajo con SGBD. El artículo contiene la descripción detallada de las funciones, estructura de la interfaz, se dan los ejemplos y se describen algunas detalles a la hora de trabajar con MySQL. En cuanto a la solución de programa, han sido adjuntados los archivos con bibliotecas dinámicas, documentación y los ejemplos de los scripts para los lenguajes MQL4 y MQL5.
Principios de programación en MQL5 - Variables globales del terminal Principios de programación en MQL5 - Variables globales del terminal
En este artículo se demuestran las posibilidades orientadas a objetos del lenguaje MQL5 en cuanto a creación de objetos responsables del funcionamiento con las variables globales del programa. Como ejemplo práctico se verá una situación en la que las variables globales pueden usarse como puntos de control en la ejecución de etapas del programa.