Asesores Expertos basados en sistemas populares de trading, y un poco de alquimia en la optimización de robots (Parte II)

Nikolay Kositsin | 6 enero, 2016

Introducción

La optimización de sistemas de trading es un tema ampliamente discutido en la literatura, de modo que no tiene mucho sentido reescribirlo en este espacio porque uno puede encontrar fácilmente información abundante sobre el mismo en Internet. Así que solamente vamos a analizar los detalles de algunas ideas fundamentales con el objetivo de descubrir la lógica subyacente, y también optimizaremos nuestros sistemas automáticos con los resultados obtenidos.

Supongo que ya sabe cómo cargar un Asesor Experto en el Probador de Estrategias, y que puede optimizarlo con los datos históricos hasta obtener resultados increíbles en las pruebas de backtesting. Llegados pues a este punto surge la siguiente pregunta. ¿Cómo se relaciona el resultado de un sistema automático con su comportamiento, sobre todo cuando el resultado obtenido es excelente, pero se consigue cuando se ajustan los datos históricos al sistema?

Esta pregunta es muy importante. Lo cierto es que las primeras optimizaciones exitosas realizadas por desarrolladores principiantes de EAs, suelen inducir un uso irreflexivo de los sistemas comerciales. En efecto, el principiante puede hacerse una idea equivocada del trading real, lo que derivará en consecuencias perjudiciales. Los programadores inexpertos de Asesores Expertos corren el riesgo de perder su dinero, o bien el dinero de aquellos que utilizan sus programas. Así que empezaré mi artículo tratando este tema.


Pruebas de optimización de resultados (backtesting), pero sin fanatismo

Generalmente, las primeras experiencias de optimización de EA invitan a querer construir estrategias que buscan el máximo beneficio, con descensos mínimos. El programador sin experiencia esperará que su sistema se comporte de forma óptima con cualquier parámetro de entrada, no solo en el periodo de optimización, sino también en el futuro. Esto se puede representar esquemáticamente de esta manera:


Esta es la lógica empleada por la mayoría de programadores de EA tras su primer contacto con el Probador de Estrategias de trading. Pero nada más lejos de la realidad. Lamentablemente no podemos predecir el futuro y definir la eficiencia de un sistema aplicando esta lógica. Por otro lado, es muy tedioso probar una determinada estrategia en una cuenta demo, en tiempo real, simulando el mercado real, y almacenar los resultados de las operaciones.

Claro que si alguien tiene la suficiente paciencia como para comprobar los resultados durante años, ¡entonces no hay ningún problema! Pero como después de un tiempo los sistemas de trading pueden volverse inutilizables, sería decepcionante malgastar tanto tiempo y ancho de banda en conexiones a Internet. Por otra parte, este enfoque no permite probar muchas estrategias comerciales. Como mucho una o dos. Y por último, probar estrategias así impide que los creadores de robots puedan estimar sus resultados de forma crítica.

Aunque en tales situaciones es difícil afrontar la realidad desde un punto de vista psicológico, y admitir que la estrategia resultante es una completa pérdida de tiempo y esfuerzo. Obviamente, derrochar esfuerzo y tiempo no sirve para crear asesores expertos rentables.

En consecuencia el único camino pasa por modelar el futuro a partir de los datos históricos. En realidad es muy sencillo. Tan solo hay que desplazar a la izquierda los límites de tiempo del esquema anterior. En cuyo caso, después de ejecutar las pruebas habrá tiempo suficiente para estimar los resultados aplicados a los datos recientes, más cercanos al momento presente que los datos de la optimización.


En muchos casos, como el descrito anteriormente, este enfoque permite estimar la eficacia real de las estrategias, al utilizar los resultados de optimización en determinados Asesores Expertos con parámetros optimizados. Esquemáticamente el significado de dichos análisis de estrategias de optimización se presenta de la siguiente forma:



Supongo que el diagrama es lo suficientemente claro. Por ejemplo, tomemos como periodo de análisis el año 2007. En el primer periodo la optimización será del 01.01.2007 al 31.03.2007, y el periodo de prueba irá del 01.04.2007 al 31.05.2007. Tras realizar las pruebas y registrar los resultados, desplazamos un mes hacia adelante la optimización y los períodos de prueba. Ahora el período de optimización es del 01.02.2007 al 30.04.2007, y el periodo de prueba comprende entre el 01.05.2007 y el 30.06.2007. Y así sucesivamente. Como resultado obtenemos una tabla que registra los resultados de cada optimización, por ejemplo:


Parámetro de prueba final/Ejecución 01 02 03 04 05 06 07 08 09 10 11 12
Beneficio neto











Beneficio total











Pérdida total











Rentabilidad











Disminución absoluta











Disminución relativa











Operaciones totales












Por supuesto, después de ejecutar las optimizaciones hay que rellenar las celdas de la tabla. Analizar la tabla y procesar la información que contiene es sencillo; saque sus propias conclusiones a partir de este planteamiento. Supongo que este análisis del comportamiento del Asesor Experto, con los datos históricos disponibles, sirve para estimar correctamente los resultados de optimización del EA, y evita que uno se haga falsas expectativas. En comparación con las pruebas realizadas en una cuenta demo, ¡el tiempo ahorrado es fabuloso!

Seguro que los desarrolladores principiantes de asesores expertos entenderán sin problemas cómo funciona la técnica de backtesting, aunque esta investigación también requiere su tiempo y esfuerzo. No se disguste si obtiene resultados pobres en sus pruebas; las pérdidas virtuales son siempre preferibles a ejecutar en vivo estrategias perdedoras que parecían rentables.

Tenga esto en cuenta. Cabe señalar que seleccionar el máximo capital con la disminución mínima no es la única estrategia de optimización. Esta explicación es solo para introducir el procedimiento de backtesting. Los esfuerzos de backtesting suelen centrarse en la optimización. En efecto, probar las estrategias requiere muy poco tiempo, y conviene probar varias a la vez para producir material estadístico, para sacar conclusiones. Por lo tanto la tabla de resultados de las pruebas será más grande.


Estrategia de trading del oscilador

Tomando como base los osciladores pueden construirse un montón de estrategias diferentes. En este artículo voy a describir un sistema que consiste en entrar y salir del mercado en las zonas de sobrecompra y sobreventa:



La mayoría de osciladores varía de un determinado mínimo hasta un cierto máximo, teniendo cada oscilador sus propios valores. Los niveles UpLevel y DownLevel se colocan a cierta distancia de los extremos

En dicho sistema se produce una señal de compra si el oscilador sale de la zona de no tendencia y entra en la zona de sobrecompra:

La señal de venta ocurre cuando el oscilador abandona la zona de no tendencia, entrando en la zona de sobreventa.

Además de estas señales, que son las principales, el sistema tiene unas señales adicionales de corrección que aparecen cuando el oscilador sale de las zonas de sobreventa y sobrecompra, entrando en la zona de no tendencia.

La señal de compra se produce cuando el oscilador sale de la zona de sobreventa y entra en la zona de no tendencia, produciéndose así la corrección de una tendencia bajista:

La señal de venta sucede cuando abandona el área de sobrecompra y entra en la zona de no tendencia (corrección de una tendencia alcista):


Código del Asesor Experto que implementa el sistema de trading del oscilador

Así, tenemos cuatro algoritmos de entrada al mercado. Si observamos detenidamente las dos variantes de los algoritmos para posiciones largas, concluiremos que son absolutamente idénticas. Solo difieren en la posición de un nivel de ruptura, lo que es totalmente irrelevante desde el punto de vista de la escritura del código del programa. En las posiciones cortas sucede lo mismo. A continuación adjunto mi variante que implementa el sistema comercial del oscilador:
//+==================================================================+
//|                                                        Exp_3.mq4 |
//|                             Copyright © 2007,   Nikolay Kositsin | 
//|                              Khabarovsk,   farria@mail.redcom.ru | 
//+==================================================================+
#property copyright "Copyright © 2007, Nikolay Kositsin"
#property link "farria@mail.redcom.ru"
//----+ +--------------------------------------------------------------------------+
//---- PARÁMETROS DE ENTRADA DEL EA PARA LAS POSICIONES DE COMPRA 
extern bool   Test_Up1 = true;//filtro de dirección de cálculo
extern int    Timeframe_Up1 = 240;
extern double Money_Management_Up1 = 0.1;
extern double IndLevel_Up1 = 0.8; // nivel de ruptura del indicador
extern int    JLength_Up1 = 8;  // profundidad del suavizado JJMA de entrada de precio
extern int    XLength_Up1 = 8;  // profundidad del suavizado JurX del indicador obtenido 
extern int    Phase_Up1 = 100; // parámetro que varía en el rango -100 ... +100, 
                              // influencia en la calidad de los procesos temporales de suavizado
extern int    IPC_Up1 = 0;/* Selección de los precios donde se calcula el indicador
(0-CLOSE, 1-OPEN, 2-HIGH, 3-LOW, 4-MEDIAN, 5-TYPICAL, 6-WEIGHTED, 7-Heiken Ashi Close, 
8-SIMPL, 9-TRENDFOLLOW, 10-0.5*TRENDFOLLOW, 11-Heiken Ashi Low, 12-Heiken Ashi High,  
13-Heiken Ashi Open, 14-Heiken Ashi Close.) */
extern int    STOPLOSS_Up1 = 50;  // stoploss
extern int    TAKEPROFIT_Up1 = 100; // takeprofit
extern int    TRAILINGSTOP_Up1 = 0; // trailing stop
extern bool   ClosePos_Up1 = true; // cierre de posición forzado permitido
//----+ +--------------------------------------------------------------------------+
///---- PARÁMETROS DE ENTRADA DEL EA PARA LAS POSICIONES DE COMPRA 
extern bool   Test_Up2 = true;//filtro de dirección de cálculo
extern int    Timeframe_Up2 = 240;
extern double Money_Management_Up2 = 0.1;
extern double IndLevel_Up2 = -0.8; // nivel de ruptura del indicador
extern int    JLength_Up2 = 8;  // profundidad del suavizado JJMA de entrada de precio
extern int    XLength_Up2 = 8;  // profundidad del suavizado JurX del indicador obtenido
extern int    Phase_Up2 = 100; // parámetro que varía en el rango -100 ... +100, 
                              //influencia en la calidad de los procesos temporales de suavizado
extern int    IPC_Up2 = 0;/* Selección de los precios donde se calcula el indicador 
(0-CLOSE, 1-OPEN, 2-HIGH, 3-LOW, 4-MEDIAN, 5-TYPICAL, 6-WEIGHTED, 7-Heiken Ashi Close, 
8-SIMPL, 9-TRENDFOLLOW, 10-0.5*TRENDFOLLOW, 11-Heiken Ashi Low, 12-Heiken Ashi High,  
13-Heiken Ashi Open, 14-Heiken Ashi Close.) */
extern int    STOPLOSS_Up2 = 50;  // stoploss
extern int    TAKEPROFIT_Up2 = 100; // takeprofit
extern int    TRAILINGSTOP_Up2 = 0; // trailing stop
extern bool   ClosePos_Up2 = true; // cierre de posición forzado permitido
//----+ +--------------------------------------------------------------------------+
//---- PARÁMETROS DE ENTRADA DEL EA PARA LAS POSICIONES DE VENTA 
extern bool   Test_Dn1 = true;// filtro de dirección de cálculo
extern int    Timeframe_Dn1 = 240;
extern double Money_Management_Dn1 = 0.1;
extern double IndLevel_Dn1 = 0.8; // nivel de ruptura del indicador
extern int    JLength_Dn1 = 8;  // profundidad del suavizado JJMA de entrada de precio
extern int    XLength_Dn1 = 8;  // profundidad del suavizado JurX del indicador obtenido 
extern int    Phase_Dn1 = 100; // parámetro que varía en el rango -100 ... +100, 
                              //influencia en la calidad de los procesos temporales de suavizado
extern int    IPC_Dn1 = 0;/* Selección de los precios donde se calcula el indicador 
(0-CLOSE, 1-OPEN, 2-HIGH, 3-LOW, 4-MEDIAN, 5-TYPICAL, 6-WEIGHTED, 7-Heiken Ashi Close, 
8-SIMPL, 9-TRENDFOLLOW, 10-0.5*TRENDFOLLOW, 11-Heiken Ashi Low, 12-Heiken Ashi High,  
13-Heiken Ashi Open, 14-Heiken Ashi Close.) */
extern int    STOPLOSS_Dn1 = 50;  // stoploss
extern int    TAKEPROFIT_Dn1 = 100; // takeprofit
extern int    TRAILINGSTOP_Dn1 = 0; // trailing stop
extern bool   ClosePos_Dn1 = true; // cierre de posición forzado permitido
//----+ +--------------------------------------------------------------------------+
//---- PARÁMETROS DE ENTRADA DEL EA PARA LAS POSICIONES DE VENTA
extern bool   Test_Dn2 = true;// filtro de dirección de cálculo
extern int    Timeframe_Dn2 = 240;
extern double Money_Management_Dn2 = 0.1;
extern double IndLevel_Dn2 = -0.8; // nivel de ruptura del indicador
extern int    JLength_Dn2 = 8;  // profundidad del suavizado JJMA de entrada de precio
extern int    XLength_Dn2 = 8;  // profundidad del suavizado JurX del indicador obtenido 
extern int    Phase_Dn2 = 100; // parámetro que varía en el rango -100 ... +100, 
                              //influencia en la calidad de los procesos temporales de suavizado
extern int    IPC_Dn2 = 0;/* Selección de los precios donde se calcula el indicador 
(0-CLOSE, 1-OPEN, 2-HIGH, 3-LOW, 4-MEDIAN, 5-TYPICAL, 6-WEIGHTED, 7-Heiken Ashi Close, 
8-SIMPL, 9-TRENDFOLLOW, 10-0.5*TRENDFOLLOW, 11-Heiken Ashi Low, 12-Heiken Ashi High,  
13-Heiken Ashi Open, 14-Heiken Ashi Close.) */
extern int    STOPLOSS_Dn2 = 50;  // stoploss
extern int    TAKEPROFIT_Dn2 = 100; // takeprofit
extern int    TRAILINGSTOP_Dn2 = 0; // trailing stop
extern bool   ClosePos_Dn2 = true; // cierre de posición forzado permitido
//----+ +--------------------------------------------------------------------------+
//---- Variables enteras para el cálculo mínimo de las barras
int MinBar_Up1, MinBar_Dn1;
int MinBar_Up2, MinBar_Dn2;
//+==================================================================+
//| Funciones TimeframeCheck()                                       |
//+==================================================================+
void TimeframeCheck(string Name, int Timeframe)
  {
//----+
   //---- Comprobación de la exactitud del valor de la variable Timeframe
   if (Timeframe != 1)
    if (Timeframe != 5)
     if (Timeframe != 15)
      if (Timeframe != 30)
       if (Timeframe != 60)
        if (Timeframe != 240)
         if (Timeframe != 1440)
           Print(StringConcatenate("Parámetro ",Name,
                     " no puede ", "ser igual a ", Timeframe, "!!!"));    
//----+ 
  }
//+==================================================================+
//| Funciones personalizadas del Asesor Experto                      |
//+==================================================================+
#include <Lite_EXPERT1.mqh>
//+==================================================================+
//| Función de inicialización del Asesor Experto                     |
//+==================================================================+
int init()
  {
//---- Comprobación de la exactitud del valor de la variable Timeframe_Up1
   TimeframeCheck("Timeframe_Up1", Timeframe_Up1);                             
//---- Comprobación de la exactitud del valor de la variable Timeframe_Up2 
   TimeframeCheck("Timeframe_Up2", Timeframe_Up2);                              
//---- Comprobación de la exactitud del valor de la variable Timeframe_Dn1  
   TimeframeCheck("Timeframe_Dn1", Timeframe_Dn1);  
//---- Comprobación de la exactitud del valor de la variable Timeframe_Dn2  
   TimeframeCheck("Timeframe_Dn2", Timeframe_Dn2);  
//---- Inicialización de variables
   MinBar_Up1 = 3 + 3 * XLength_Up1 + 30;
   MinBar_Up2 = 3 + 3 * XLength_Up2 + 30;
   MinBar_Dn1 = 3 + 3 * XLength_Dn1 + 30;
   MinBar_Dn2 = 3 + 3 * XLength_Dn2 + 30;                                     
//---- fin de la inicialización
   return(0);
  }
//+==================================================================+
//| función de desinicialización del experto                         |
//+==================================================================+  
int deinit()
  {
//----+
   
    //---- Fin de la desinicialización del EA
    return(0);
//----+ 
  }
//+==================================================================+
//| Función de iteración del Asesor Experto                          |
//+==================================================================+
int start()
  {
   //----+ Declaración de variables locales
   double Osc1, Osc2;
   //----+ Declaración de variables estáticas
   //----+ +---------------------------------------------------------------+
   static int LastBars_Up1, LastBars_Dn1;
   static bool BUY_Sign1, BUY_Stop1, SELL_Sign1, SELL_Stop1;
   //----+ +---------------------------------------------------------------+
   static int LastBars_Up2, LastBars_Dn2;
   static bool BUY_Sign2, BUY_Stop2, SELL_Sign2, SELL_Stop2;
   //----+ +---------------------------------------------------------------+
   //----++ CÓDIGO DE LAS POSICIONES LARGAS 1
   if (Test_Up1) 
    {
      int IBARS_Up1 = iBars(NULL, Timeframe_Up1);
      
      if (IBARS_Up1 >= MinBar_Up1)
       {
         if (LastBars_Up1 != IBARS_Up1)
          {
           //----+ Inicialización de variables 
           BUY_Sign1 = false;
           BUY_Stop1 = false;
           LastBars_Up1 = IBARS_Up1; 
           
           //----+ CÁLCULO DE LOS VALORES DEL INDICADOR Y SUBIDA A LOS BÚFERES        
           Osc1 = iCustom(NULL, Timeframe_Up1, 
                 "JCCIX", JLength_Up1, XLength_Up1,
                                Phase_Up1, IPC_Up1, 0, 1);
                                
           Osc2 = iCustom(NULL, Timeframe_Up1, 
                 "JCCIX", JLength_Up1, XLength_Up1,
                                Phase_Up1, IPC_Up1, 0, 2);
           
           //----+ DEFINICIÓN DE SEÑALES DE TRADING                                           
           if (Osc2 < IndLevel_Up1)
             if (Osc1 > IndLevel_Up1)
                          BUY_Sign1 = true;
                          
           if (Osc1 < IndLevel_Up1)
                          BUY_Stop1 = true;                                           
          }
          //----+ EJECUCIÓN DE OPERACIONES
          if (!OpenBuyOrder1(BUY_Sign1, 1, Money_Management_Up1, 
                                          STOPLOSS_Up1, TAKEPROFIT_Up1))
                                                                 return(-1);
          if (ClosePos_Up1)
                if (!CloseOrder1(BUY_Stop1, 1))
                                        return(-1);
                                        
          if (!Make_TreilingStop(1, TRAILINGSTOP_Up1))
                                                  return(-1);
        }
     }
   //----+ +---------------------------------------------------------------+
   //----++ CÓDIGO DE LAS POSICIONES LARGAS 2
   if (Test_Up2) 
    {
      int IBARS_Up2 = iBars(NULL, Timeframe_Up2);
      
      if (IBARS_Up2 >= MinBar_Up2)
       {
         if (LastBars_Up2 != IBARS_Up2)
          {
           //----+ Inicialización de variables 
           BUY_Sign2 = false;
           BUY_Stop2 = false;
           LastBars_Up2 = IBARS_Up2; 
           
           //----+ CÁLCULO DE LOS VALORES DEL INDICADOR Y SUBIDA A LOS BÚFERES        
           Osc1 = iCustom(NULL, Timeframe_Up2, 
                 "JCCIX", JLength_Up2, XLength_Up2,
                                Phase_Up2, IPC_Up2, 0, 1);
                                
           Osc2 = iCustom(NULL, Timeframe_Up2, 
                 "JCCIX", JLength_Up2, XLength_Up2,
                                Phase_Up2, IPC_Up2, 0, 2);
           
           //----+ DEFINICIÓN DE SEÑALES DE TRADING                                           
           if (Osc2 < IndLevel_Up2)
             if (Osc1 > IndLevel_Up2)
                          BUY_Sign2 = true;
                          
           if (Osc1 < IndLevel_Up2)
                          BUY_Stop2 = true;                                           
          }
          //----+ EJECUCIÓN DE OPERACIONES
          if (!OpenBuyOrder1(BUY_Sign2, 2, Money_Management_Up2, 
                                          STOPLOSS_Up2, TAKEPROFIT_Up2))
                                                                 return(-1);
          if (ClosePos_Up2)
                if (!CloseOrder1(BUY_Stop2, 2))
                                        return(-1);
                                        
          if (!Make_TreilingStop(2, TRAILINGSTOP_Up2))
                                                  return(-1);
        }
     }
   //----+ +---------------------------------------------------------------+ 
   //----++ CÓDIGO DE LAS OPERACIONES CORTAS 1
   if (Test_Dn1) 
    {
      int IBARS_Dn1 = iBars(NULL, Timeframe_Dn1);
      
      if (IBARS_Dn1 >= MinBar_Dn1)
       {
         if (LastBars_Dn1 != IBARS_Dn1)
          {
           //----+ Inicialización de variables 
           SELL_Sign1 = false;
           SELL_Stop1 = false;
           LastBars_Dn1 = IBARS_Dn1; 
           
           //----+ CÁLCULO DE LOS VALORES DEL INDICADOR Y SUBIDA A LOS BÚFERES        
           Osc1 = iCustom(NULL, Timeframe_Dn1, 
                 "JCCIX", JLength_Dn1, XLength_Dn1,
                                Phase_Dn1, IPC_Dn1, 0, 1);
                                
           Osc2 = iCustom(NULL, Timeframe_Dn1, 
                 "JCCIX", JLength_Dn1, XLength_Dn1,
                                Phase_Dn1, IPC_Dn1, 0, 2);
           
           //----+ DEFINICIÓN DE SEÑALES DE TRADING                                           
           if (Osc2 > IndLevel_Dn1)
             if (Osc1 < IndLevel_Dn1)
                          SELL_Sign1 = true;
                          
           if (Osc1 > IndLevel_Dn1)
                          SELL_Stop1 = true;                                           
          }
          //----+ EJECUCIÓN DE OPERACIONES
          if (!OpenSellOrder1(SELL_Sign1, 3, Money_Management_Dn1, 
                                          STOPLOSS_Dn1, TAKEPROFIT_Dn1))
                                                                 return(-1);
          if (ClosePos_Dn1)
                if (!CloseOrder1(SELL_Stop1, 3))
                                        return(-1);
                                        
          if (!Make_TreilingStop(3, TRAILINGSTOP_Dn1))
                                                  return(-1);
        }
     }
   //----+ +---------------------------------------------------------------+
   //----++ CÓDIGO DE LAS OPERACIONES CORTAS 2
   if (Test_Dn2) 
    {
      int IBARS_Dn2 = iBars(NULL, Timeframe_Dn2);
      
      if (IBARS_Dn2 >= MinBar_Dn2)
       {
         if (LastBars_Dn2 != IBARS_Dn2)
          {
           //----+ Inicialización de variables 
           SELL_Sign2 = false;
           SELL_Stop2 = false;
           LastBars_Dn2 = IBARS_Dn2; 
           
           //----+ CÁLCULO DE LOS VALORES DEL INDICADOR Y SUBIDA A LOS BÚFERES        
           Osc1 = iCustom(NULL, Timeframe_Dn2, 
                 "JCCIX", JLength_Dn2, XLength_Dn2,
                                Phase_Dn2, IPC_Dn2, 0, 1);
                                
           Osc2 = iCustom(NULL, Timeframe_Dn2, 
                 "JCCIX", JLength_Dn2, XLength_Dn2,
                                Phase_Dn2, IPC_Dn2, 0, 2);
           
           //----+ DEFINICIÓN DE SEÑALES DE TRADING                                           
           if (Osc2 > IndLevel_Dn2)
             if (Osc1 < IndLevel_Dn2)
                          SELL_Sign2 = true;
                          
           if (Osc1 > IndLevel_Dn2)
                          SELL_Stop2 = true;                                           
          }
          //----+ EJECUCIÓN DE OPERACIONES
          if (!OpenSellOrder1(SELL_Sign2, 4, Money_Management_Dn2, 
                                          STOPLOSS_Dn2, TAKEPROFIT_Dn2))
                                                                 return(-1);
          if (ClosePos_Dn2)
                if (!CloseOrder1(SELL_Stop2, 4))
                                        return(-1);
                                        
           if (!Make_TreilingStop(4, TRAILINGSTOP_Dn2))
                                                  return(-1);
        }
     }
   //----+ +---------------------------------------------------------------+ 
//----+ 
    
    return(0);
  }
//+------------------------------------------------------------------+

Este código es dos veces más extenso que el presentado en el artículo anterior. Por cierto, imagine qué largo sería el código de este EA si implementara el código del programa competo, en lugar de realizar llamadas a las funciones de gestión de órdenes. Cabe señalar que este Asesor Experto tiene nuevos parámetros de entrada: IndLevel_Up1, IndLevel_Up2, IndLevel_Dn1, IndLevel_Dn2. IndLevel_Up1 y IndLevel_Up2 definen los valores de Uplevel y DownLevel en los dos algoritmos de las posiciones largas, mientras que IndLevel_Dn1 y IndLevel_Dn2 definen los valores de DownLevel y Uplevel en los dos algoritmos de las posiciones cortas.


El valor de estos niveles puede variar de -1.0 a +1.0 durante la optimización del EA. Si desea sustituir el oscilador JCCIX por otro oscilador, tenga en cuenta que los valores máximo y mínimo de estos niveles puede ser diferente. El indicador fuente JCCIX es análogo al indicador CCI, donde el algoritmo de suavizado con medias móviles se sustituye por el JMA y el suavizado ultralineal. El Asesor Experto utiliza Trailing Stops cuyos valores se definen en los parámetros de entrada del EA, por ejemplo: TRAILINGSTOP_Up1, TRAILINGSTOP_Up2, TRAILINGSTOP_Dn1, TRAILINGSTOP_Dn2. Por todo lo demás, este EA es completamente análogo al descrito en el artículo anterior.


Cambiando la entrada inmediata al mercado por órdenes pendientes

En muchos casos, dentro del contexto de los EAs equivalentes al descrito anteriormente, el cambio de entrada inmediata al mercado en órdenes pendientes permite entrar al mercado de forma más precisa, y adquirir un beneficio mayor, con una probabilidad menor de llegar al Stop Loss. El archivo Lite_EXPERT1.mqh contiene un grupo de funciones que lleva a cabo fácilmente la sustitución anterior.

Solo hay que sustituir las funciones OpenBuyOrder1() y OpenSellOrder1() por OpenBuyLimitOrder1() y OpenSellLimitOrder1(), respectivamente. Durante la sustitución de las funciones hay que inicializar nuevas variables de entrada: LEVEL y Expiration. Por ejemplo, podemos construir una estrategia de trading donde la variable LEVEL se define con los parámetros de entrada del EA. La fecha de cancelación de la orden pendiente puede configurarse como la hora del próximo cambio de la barra actual.

No volveré a repetir el mismo código en esta sección. El código cambiado del EA anterior se adjunta en este artículo (archivo Exp_4.mq4). Para ilustrar el uso de las órdenes pendientes he descrito este sistema basado en el oscilador OSMA:

// Compruebe que la carpeta Metatrader\EXPERTS\indicators contiene 
// el indicador 5c_OsMA.mq4 para que el EA pueda realizar las operaciones
//+==================================================================+
//|                                                        Exp_5.mq4 |
//|                             Copyright © 2008,   Nikolay Kositsin | 
//|                              Khabarovsk,   farria@mail.redcom.ru | 
//+==================================================================+
#property copyright "Copyright © 2008, Nikolay Kositsin"
#property link "farria@mail.redcom.ru"
//----+ +--------------------------------------------------------------------------+
//---- PARÁMETROS DE ENTRADA PARA LAS OPERACIONES DE COMPRA 
extern bool   Test_Up = true;// filtro de la dirección de cálculo
extern int    Timeframe_Up = 240;
extern double Money_Management_Up = 0.1;
extern double IndLevel_Up = 0; // nivel de ruptura del indicador
extern int    FastEMA_Up = 12;  // periodo de la EMA rápida
extern int    SlowEMA_Up = 26;  // periodo de la EMA lenta
extern int    SignalSMA_Up = 9;  // periodo de la señal SMA
extern int    STOPLOSS_Up = 50;  // stoploss
extern int    TAKEPROFIT_Up = 100; // takeprofit
extern int    TRAILINGSTOP_Up = 0; // trailing stop
extern int    PriceLevel_Up =40; // diferencia entre el precio actual y
                                         // el precio de disparo de la orden pendiente
extern bool   ClosePos_Up = true; // cierre forzado de la posición permitido
//----+ +--------------------------------------------------------------------------+
//---- PARÁMETROS DE ENTRADA DEL EA PARA LAS POSICIONES DE VENTA 
extern bool   Test_Dn = true;// filtro de la dirección de cálculo
extern int    Timeframe_Dn = 240;
extern double Money_Management_Dn = 0.1;
extern double IndLevel_Dn = 0; // nivel de ruptura del indicador
extern int    FastEMA_Dn = 12;  // periodo de la EMA rápida
extern int    SlowEMA_Dn = 26;  // periodo de la EMA lenta
extern int    SignalSMA_Dn = 9;  // periodo de la señal SMA
extern int    STOPLOSS_Dn = 50;  // stoploss
extern int    TAKEPROFIT_Dn = 100; // takeprofit
extern int    TRAILINGSTOP_Dn = 0; // trailing stop
extern int    PriceLevel_Dn = 40; // diferencia entre el precio actual y 
                                         // el precio de disparo de la orden pendiente
extern bool   ClosePos_Dn = true; // cierre forzado de la posición permitido
//----+ +--------------------------------------------------------------------------+
//---- Variables enteras para el cálculo mínimo de las barras
int MinBar_Up, MinBar_Dn;
//+==================================================================+
//| Funciones TimeframeCheck()                                       |
//+==================================================================+
void TimeframeCheck(string Name, int Timeframe)
  {
//----+
   //---- Comprobación de la exactitud del valor de la variable Timeframe
   if (Timeframe != 1)
    if (Timeframe != 5)
     if (Timeframe != 15)
      if (Timeframe != 30)
       if (Timeframe != 60)
        if (Timeframe != 240)
         if (Timeframe != 1440)
           Print(StringConcatenate("Parámetro ",Name,
                     " no puede ", "ser igual a ", Timeframe, "!!!"));    
//----+ 
  }
//+==================================================================+
//| Funciones personalizadas del Asesor Experto                      |
//+==================================================================+
#include <Lite_EXPERT1.mqh>
//+==================================================================+
//| Función de inicialización del Asesor Experto                     |
//+==================================================================+
int init()
  {
//---- Comprobación de la exactitud del valor de la variable Timeframe_Up 
   TimeframeCheck("Timeframe_Up", Timeframe_Up);                                                          
//---- Comprobación de la exactitud del valor de la variable Timeframe_Dn  
   TimeframeCheck("Timeframe_Dn", Timeframe_Dn);  
//---- Inicialización de variables
   MinBar_Up  = 3 + MathMax(FastEMA_Up, SlowEMA_Up) + SignalSMA_Up;
   MinBar_Dn  = 3 + MathMax(FastEMA_Dn, SlowEMA_Dn) + SignalSMA_Dn;                               
//---- fin de la inicialización
   return(0);
  }
//+==================================================================+
//| función de desinicialización del experto                         |
//+==================================================================+  
int deinit()
  {
//----+
   
    //---- Fin de la desinicialización del EA
    return(0);
//----+ 
  }
//+==================================================================+
//| Función de iteración del Asesor Experto                          |
//+==================================================================+
int start()
  {
   //----+ Declaración de variables locales
   double Osc1, Osc2;
   //----+ Declaración de variables estáticas
   //----+ +---------------------------------------------------------------+
   static datetime StopTime_Up, StopTime_Dn; 
   static int LastBars_Up, LastBars_Dn;
   static bool BUY_Sign1, BUY_Stop1, SELL_Sign1, SELL_Stop1;
   //----+ +---------------------------------------------------------------+
   //----++ CÓDIGO DE LAS POSICIONES LARGAS 1
   if (Test_Up) 
    {
      int IBARS_Up = iBars(NULL, Timeframe_Up);
      
      if (IBARS_Up >= MinBar_Up)
       {
         if (LastBars_Up != IBARS_Up)
          {
           //----+ Inicialización de variables 
           BUY_Sign1 = false;
           BUY_Stop1 = false;
           LastBars_Up = IBARS_Up;
           StopTime_Up = iTime(NULL, Timeframe_Up, 0)
                                            + 60 * Timeframe_Up;
           
           //----+ CÁLCULO DE LOS VALORES DEL INDICADOR Y SUBIDA A LOS BÚFERES        
           Osc1 = iCustom(NULL, Timeframe_Up, 
                         "5c_OsMA", FastEMA_Up, SlowEMA_Up,
                                               SignalSMA_Up, 5, 1);
                                
           Osc2 = iCustom(NULL, Timeframe_Up, 
                         "5c_OsMA", FastEMA_Up, SlowEMA_Up,
                                               SignalSMA_Up, 5, 2);
           
           //----+ DEFINICIÓN DE SEÑALES DE TRADING                                           
           if (Osc2 < IndLevel_Up)
             if (Osc1 > IndLevel_Up)
                          BUY_Sign1 = true;
                          
           if (Osc1 < IndLevel_Up)
                          BUY_Stop1 = true;                                           
          }
          //----+ EJECUCIÓN DE SEÑALES
          if (!OpenBuyLimitOrder1(BUY_Sign1, 1, 
              Money_Management_Up, STOPLOSS_Up, TAKEPROFIT_Up,
                                            PriceLevel_Up, StopTime_Up))
                                                                 return(-1);
          if (ClosePos_Up)
                if (!CloseOrder1(BUY_Stop1, 1))
                                        return(-1);
                                        
          if (!Make_TreilingStop(1, TRAILINGSTOP_Up))
                                                  return(-1);
        }
     }
   //----+ +---------------------------------------------------------------+
   //----++ CÓDIGO DE LAS POSICIONES CORTAS 1
   if (Test_Dn) 
    {
      int IBARS_Dn = iBars(NULL, Timeframe_Dn);
      
      if (IBARS_Dn >= MinBar_Dn)
       {
         if (LastBars_Dn != IBARS_Dn)
          {
           //----+ Inicialización de variables 
           SELL_Sign1 = false;
           SELL_Stop1 = false;
           LastBars_Dn = IBARS_Dn;
           StopTime_Dn = iTime(NULL, Timeframe_Dn, 0) 
                                            + 60 * Timeframe_Dn; 
           
           //----+ CÁLCULO DE LOS VALORES DEL INDICADOR Y SUBIDA A LOS BÚFERES        
           Osc1 = iCustom(NULL, Timeframe_Dn, 
                         "5c_OsMA", FastEMA_Dn, SlowEMA_Dn,
                                               SignalSMA_Dn, 5, 1);
                                
           Osc2 = iCustom(NULL, Timeframe_Dn, 
                         "5c_OsMA", FastEMA_Dn, SlowEMA_Dn,
                                               SignalSMA_Dn, 5, 2);
           
           //----+ DEFINICIÓN DE SEÑALES DE TRADING                                           
           if (Osc2 > IndLevel_Dn)
             if (Osc1 < IndLevel_Dn)
                          SELL_Sign1 = true;
                          
           if (Osc1 > IndLevel_Dn)
                          SELL_Stop1 = true;                                           
          }
          //----+ EJECUCIÓN DE SEÑALES
          if (!OpenSellLimitOrder1(SELL_Sign1, 2, 
              Money_Management_Dn, STOPLOSS_Dn, TAKEPROFIT_Dn,
                                            PriceLevel_Dn, StopTime_Dn))
                                                                 return(-1);
          if (ClosePos_Dn)
                if (!CloseOrder1(SELL_Stop1, 2))
                                        return(-1);
                                        
          if (!Make_TreilingStop(2, TRAILINGSTOP_Dn))
                                                  return(-1);
        }
     }
   //----+ +---------------------------------------------------------------+
//----+ 
    
    return(0);
  }
//+------------------------------------------------------------------+

En lugar de dos niveles de ruptura, UpLevel y DownLevel, se utiliza un nivel. Por esta razón el EA solo contiene dos algoritmos para manejar las posiciones. Generalmente, en un sistema de trading conectado con OsMA este nivel se selecciona con un valor igual a cero; sin embargo he decidido dejarlo en variables externas del EA, para que pueda actualizarse. O dicho de otra forma, tenga en cuenta que el indicador OSMA no tiene ningún rango de valores definido por un máximo y por un mínimo, donde el indicador puede cambiar; en consecuencia el nivel de ruptura no está limitado por dichos valores. Aunque generalmente es igual a cero, como he indicado antes. Para definir la hora de cancelación de las órdenes pendientes se utilizan las variables estáticas StopTime_Up y StopTime_Dn, en el cambio de las barras se inicializan con la hora de la próxima barra.


Conclusión

Ya para terminar, me gustaría añadir que los sistemas comerciales basados en osciladores dan muchas señales falsas acerca de la tendencia actual del mercado. Esos EAs operan mejor en períodos de mercado plano, o también pueden utilizarse para abrir posiciones solo por tendencia.

Por lo que respecta al backtesting, aprovecho esta ocasión para indicar una vez mas que lo mejor que pueden hacer los programadores noveles para conseguir más experiencia al principio es, probablemente, estimar correctamente los resultados de optimización. No hay problema con los EAs que muestran resultados excepcionales cuando se ajuntan al historial. Sin embargo, entender cómo hay que ajustar los asesores expertos para evitar las situaciones donde el mercado se aleja de los parámetros optimizados, es más complicado.