English Русский 中文 Deutsch 日本語 Português
Asesores Expertos basados en sistemas populares de trading, y un poco de alquimia en la optimización de robots

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

MetaTrader 4Sistemas comerciales | 8 diciembre 2015, 11:19
1 152 0
Nikolay Kositsin
Nikolay Kositsin

Introducción

Generalmente, la mayoría de libros de Forex expone sistemas de trading muy sencillos. Sin embargo, tales sistemas son más bien un conjunto de instrucciones genéricas que no ofrecen una implementación EA adecuada lista para operar en el mercado real. En consecuencia, no podemos afirmar que dichos ejemplos tengan un valor real práctico. Analizando los foros dedicados al desarrollo de EAs, llegamos a la conclusión que la mayoría de programadores de Asesores Expertos tiene que reinventar la rueda y desarrollar sus propios EAs desde el principio, basándose en sistemas de trading sencillos.

Quisiera pues poner en duda el valor de dicho trabajo. Creo que hace falta poner a disposición de los traders principiantes EAs desarrollados correctamente, comenzando desde el principio. En este artículo me gustaría ofrecer, a modo de variante, mi propia solución a este problema. En la construcción de mis Asesores Expertos he utilizado indicadores de mi propia librería, que, por otro lado, también he presentado en mi artículo Effective Averaging Algorithms with Minimal Lag: Use in Indicators.


Esquema de la estructura general de un EA

Antes de comenzar con la descripción principal, me gustaría señalar que todos los EAs del presente artículo se construyen siguiendo el mismo esquema:



Sistema de trading basado en el cambio de dirección del movimiento

Voy a intentar explicar detalladamente la idea del esquema del EA del ejemplo. En este sistema, un cambio en la dirección del movimiento, de abajo hacia arriba, indica una señal de compra:

De forma análoga, un cambio de dirección ascendente a descendente, indica una señal de venta.


Para el cálculo de las señales utilizamos el valor móvil de las barras primera, segunda y tercera. Los valores móviles de la barra cero no se tienen en cuenta, es decir, el sistema solo trabaja con barras cerradas. Se utiliza el indicador personalizado JFATL, un sencillo filtro digital FATL con un suavizado adicional JMA. A menudo encontramos afirmaciones en Internet que aseguran que colocar una orden en el cambio de dirección FATL asegura varios puntos de ganancia, y el lector se convence fácilmente de lo eficiente que es esta estrategia. A continuación se adjunta una implementación de dicho sistema en forma de Asesor Experto:



Código del Asesor Experto


//+==================================================================+
//|                                                        Exp_1.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 COLOCAR ÓRDENES DE COMPRA 
extern bool   Test_Up = true;// filtro para el cálculo de la dirección
extern int    Timeframe_Up = 240;
extern double Money_Management_Up = 0.1;
extern int    Length_Up = 4;  // profundidad de suavizado 
extern int    Phase_Up = 100; // cambio en el rango 
          //-100 ... +100, influye en la calidad del proceso; 
extern int    IPC_Up = 0;/* Selección de precios donde el indicador 
se calcula (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_Up = 50;  // stoploss
extern int    TAKEPROFIT_Up = 100; // takeprofit
extern bool   ClosePos_Up = true; // cierre forzado de posición permitido
//---- PARÁMETROS DE ENTRADA Y SALIDA DEL EA PARA COLOCAR ÓRDENES DE VENTA 
extern bool   Test_Dn = true;//filtro de cálculo de la dirección
extern int    Timeframe_Dn = 240;
extern double Money_Management_Dn = 0.1;
extern int    Length_Dn = 4;  // profundidad de suavizado 
extern int    Phase_Dn = 100; // cambio en el rango
         // -100 ... +100, influye en la calidad del proceso; 
extern int    IPC_Dn = 0;/* Selección de precios donde el indicador 
se calcula (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_Dn = 50;  // stoploss
extern int   TAKEPROFIT_Dn = 100; // takeprofit
extern bool   ClosePos_Dn = true; // cierre forzado de posición permitido
//---- Variables de tipo entero para las barras mínimas
int MinBar_Up, MinBar_Dn;
//+==================================================================+
//| Funciones del asesor experto                                     |
//+==================================================================+
#include <Lite_EXPERT1.mqh>
//+==================================================================+
//| Función de inicialización                                        |
//+==================================================================+
int init()
  {
//---- Comprobamos que el valor de la variable Timeframe_Up es correcto
   if (Timeframe_Up != 1)
    if (Timeframe_Up != 5)
     if (Timeframe_Up != 15)
      if (Timeframe_Up != 30)
       if (Timeframe_Up != 60)
        if (Timeframe_Up != 240)
         if (Timeframe_Up != 1440)
           Print(StringConcatenate("El parámetro Timeframe_Up no puede ",  
                                  "ser igual a ", Timeframe_Up, "!!!"));
//---- Comprobamos que el valor de la variable Timeframe_Dn es correcto 
   if (Timeframe_Dn != 1)
    if (Timeframe_Dn != 5)
     if (Timeframe_Dn != 15)
      if (Timeframe_Dn != 30)
       if (Timeframe_Dn != 60)
        if (Timeframe_Dn != 240)
         if (Timeframe_Dn != 1440)
           Print(StringConcatenate("El parámetro Timeframe_Dn no puede ",  
                                 "ser igual a ", Timeframe_Dn, "!!!")); 
//---- Inicialización de variables             
   MinBar_Up = 4 + 39 + 30;
   MinBar_Dn = 4 + 39 + 30;                                        
//---- fin de la inicialización
   return(0);
  }
//+==================================================================+
//| Función de desinicialización del Asesor 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
   int    bar;
   double Mov[3], dMov12, dMov23;
   //----+ Declaración de variables estáticas
   static int LastBars_Up, LastBars_Dn;
   static bool BUY_Sign, BUY_Stop, SELL_Sign, SELL_Stop;
   
   //----++ CÓDIGO PARA LAS POSICIONES LARGAS
   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_Sign = false;
           BUY_Stop = false;
           LastBars_Up = IBARS_Up; 
           
           //----+ CÁLCULO DE LOS VALORES DEL INDICADOR Y SUBIDA A LOS BÚFERES
           for(bar = 1; bar <= 3; bar++)
                     Mov[bar - 1]=                  
                         iCustom(NULL, Timeframe_Up, 
                                "JFatl", Length_Up, Phase_Up, 
                                                   0, IPC_Up, 0, bar);
           
           //----+ DEFINICIÓN DE SEÑALES 
           dMov12 = Mov[0] - Mov[1];
           dMov23 = Mov[1] - Mov[2]; 
                                          
           if (dMov23 < 0)
              if (dMov12 > 0)
                        BUY_Sign = true;
                          
           if (dMov12 < 0)
                        BUY_Stop = true;                                           
          }
          //----+ EJECUCIÓN DE ÓRDENES
          if (!OpenBuyOrder1(BUY_Sign, 1, Money_Management_Up, 
                                          STOPLOSS_Up, TAKEPROFIT_Up))
                                                                 return(-1);
          if (ClosePos_Up)
                if (!CloseOrder1(BUY_Stop, 1))
                                        return(-1);
        }
     }
     
   //----++ CÓDIGO DE LAS POSICIONES CORTAS
   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_Sign = false;
           SELL_Stop = false;
           LastBars_Dn = IBARS_Dn; 
           
           //----+ CÁLCULO DE LOS VALORES DEL INDICADOR Y SUBIDA A LOS BÚFERES        
           for(bar = 1; bar <= 3; bar++)
                     Mov[bar - 1]=                  
                         iCustom(NULL, Timeframe_Dn, 
                                "JFatl", Length_Dn, Phase_Dn, 
                                                   0, IPC_Dn, 0, bar);
           
           //----+ DEFINICIÓN DE SEÑALES
           dMov12 = Mov[0] - Mov[1];
           dMov23 = Mov[1] - Mov[2]; 
                                           
           if (dMov23 > 0)
              if (dMov12 < 0)
                       SELL_Sign = true;
                          
           if (dMov12 > 0)
                       SELL_Stop = true;                                           
          }
          //----+ EJECUCIÓN DE ÓRDENES
          if (!OpenSellOrder1(SELL_Sign, 2, Money_Management_Dn, 
                                            STOPLOSS_Dn, TAKEPROFIT_Dn))
                                                                   return(-1);
          if (ClosePos_Dn)
                if (!CloseOrder1(SELL_Stop, 2))
                                        return(-1);
        }
     }
//----+ 
    
    return(0);
  }
//+------------------------------------------------------------------+

Se utilizan dos algoritmos absolutamente independientes para obtener las señales de compraventa, cada uno con sus propios parámetros externos. Según mi experiencia esta aproximación es mucho más rentable que la variante de un solo algoritmo de detección de señales de compraventa. Los interesados en la variante de un solo algoritmo pueden estudiar el código del Asesor Experto EXP_0.mq4; continuaremos analizando el EA con dos movimientos. Dado un par de divisas, el Asesor Experto puede abrir simultáneamente una posición en la dirección de compra y una posición en la dirección de venta. El EA ejecuta órdenes del mercado. Las transacciones se cierran con órdenes StopLoss y TakeProfit. Si aparecen señales de tendencia contrarias a las posiciones abiertas, el EA permite el cierre forzado de posiciones. El método de recepción de señales de salida del mercado es análogo al que las obtiene para entrar, pero de naturaleza contraria.



Contenido del archivo Lite_EXPERT1.mqh

En bien de su propio trabajo, cuando escriba sus propios Asesores Expertos intente utilizar el número máximo de funciones definidas por el usuario. Y luego ensamble las partes de su EA, tal y como se hace en los equipos de producción de las fábricas, donde un nuevo producto contiene todos los detalles, estándares, bloques y módulos. Por esta razón se utilizan varias funciones definidas por el usuario en el bloque "Ejecución de operaciones", incluidas en el EA por medio de la directiva #include <Lite_EXPERT1.mqh>:


bool OpenBuyOrder1
( 
  bool BUY_Signal, int MagicNumber, 
             double Money_Management, int STOPLOSS, int TAKEPROFIT
)

bool OpenSellOrder1
( 
  bool SELL_Signal, int MagicNumber, 
             double Money_Management, int STOPLOSS, int TAKEPROFIT
)

CountLastTime
(
  int lastError
)

bool CloseOrder1
( 
  bool Stop_Signal, int MagicNumber
)

int StopCorrect
( 
  string symbol, int Stop
)

bool MarginCheck
(
  string symbol, int Cmd, double& Lot
)

double GetFreeMargin()

bool DeleteOrder1
(
  bool& CloseStop, int MagicNumber
)

bool OpenBuyLimitOrder1
(
  bool& Order_Signal, int MagicNumber, 
       double Money_Management, int STOPLOSS, int TAKEPROFIT,
                                      int LEVEL, datetime Expiration
)

bool OpenBuyStopOrder1
(
  bool& Order_Signal, int MagicNumber, 
       double Money_Management, int STOPLOSS, int TAKEPROFIT,
                                      int LEVEL, datetime Expiration
)

bool OpenSellLimitOrder1
(
  bool& Order_Signal, int MagicNumber, 
       double Money_Management, int STOPLOSS, int TAKEPROFIT,
                                      int LEVEL, datetime Expiration
)

bool OpenSellStopOrder1
(
  bool& Order_Signal, int MagicNumber, 
       double Money_Management, int STOPLOSS, int TAKEPROFIT,
                                      int LEVEL, datetime Expiration
)

bool OpenBuyOrder2
( 
  bool BUY_Signal, int MagicNumber, 
             double Money_Management, int STOPLOSS, int TAKEPROFIT
)

bool OpenSellOrder2
( 
  bool SELL_Signal, int MagicNumber, 
             double Money_Management, int STOPLOSS, int TAKEPROFIT
)

bool Make_TreilingStop
(
  int MagicNumber, int TRAILINGSTOP
)

La función OpenBuyOrder1(), cuyo número mágico de identificación es igual al valor de la variable MagicNumber, abre posiciones largas si el valor de la variable externa BUY_Signal es igual a true y no hay posiciones abiertas. En consecuencia, las variables externas STOPLOSS y TAKEPROFIT definen en puntos los valores de StopLoss y TakeProfit. El valor de la variable Money_Management puede variar de cero a uno. Esta variable indica qué parte del depósito disponible se utiliza para ejecutar las órdenes. Si el valor de esta variable es menor que cero, la función OpenBuyOrder1() utilizará dicho valor para el tamaño del lote. Las posiciones cortas se abren de manera análoga al llamar a la función OpenSellOrder1(). Ambas funciones abren posiciones con independencia entre sí, pero sólo se puede enviar una orden de ejecución al servidor en 11 segundos. Además de ejecutar órdenes, las funciones OpenBuyOrder1() y OpenSellOrder1() registran en un archivo de log la información sobre las posiciones abiertas.


Si la variable Stop_Signal recibe el valor true, la función CloseOrder1() cierra una posición con el número mágico igual al valor de MagicNumber.


La función StopCorrect() acepta como parámetro Stop el valor de StopLoss o TakeProfit, comprobando su correspondencia con el valor mínimo aceptado. Si es necesario, lo cambia al valor mínimo permitido, y lo devuelve, teniendo en cuenta posibles correcciones.


La asignación de la función MarginCheck() denota la reducción del tamaño del lote utilizado en una posición abierta hasta el tamaño máximo, donde el margen libre es suficiente para abrir la orden, en el caso en que el margen libre no es suficiente dado el tamaño actual del lote.


OpenBuyOrder1(), OpenSellOrder1() y CloseOrder1() se llaman desde la función start(), mientras que las funciones StopCorrect() y MarginCheck() se utilizan en el código de OpenBuyOrder1() y OpenSellOrder1().


Cuando OpenBuyOrder1(), OpenSellOrder1() y CloseOrder1() finalizan correctamente devuelven 'true'; en caso contrario, si ocurre un error en la ejecución de estas funciones, el valor devuelto es 'false'. Las tres funciones: OpenBuyOrder1(), OpenSellOrder1() y CloseOrder1() envían los valores de sus variables externas, BUY_Signal, SELL_Signal y Stop_Signal con valor 'false', en la ejecución de la orden.


La función GetFreeMargin() devuelve el tamaño del margen libre de la cuenta actual, permitiendo ganancias y pérdidas que pueden usarse para abrir posiciones. Se utiliza para calcular el tamaño del lote.


La función CountLastTime() inicializa la variable LastTime de acuerdo al error producido durante la ejecución de la orden. Esta función debe llamarse inmediatamente después de la ejecución de la orden, por ejemplo:


  //----+ Abrir posición de compra    
  ticket = OrderSend(Symb, OP_BUY, Lot, ask, 3, 
            Stoploss, TakeProfit, NULL, MagicNumber, 0, Lime);
  
  //---- Calculamos una pausa entre las operaciones de trading
  CountLastTime(GetLastError());

Además de las funciones expuestas arriba, el archivo Lite_EXPERT.mqh contiene cuatro funciones más que colocan órdenes pendientes, y otra función para borrar órdenes pendientes: OpenBuyLimitOrder(), OpenBuyStopOrder1(), OpenSellLimitOrder1(), OpenSellStopOrder1() y DeleteOrder1(). La asignación de variables externas de estas funciones es completamente análoga y puede deducirse a partir de sus nombres. Las nuevas variables LEVEL y Expiration sirven para enviar a una función la distancia en puntos al precio actual, donde se colocan las órdenes pendientes, así como la fecha de expiración de la orden pendiente.


El archivo tiene dos funciones más: OpenBuylOrder2() y OpenSellOrder2(), que, excepto en un detalle, son la contraparte de OpenBuyOrder1() y OpenSellOrder1(). Estas funciones abren primero posiciones sin StopLoss ni TakeProfit, y a continuación las modifican estableciendo el StopLoss y el TakeProfit. Esto es necesario para los EAs que operan en brokers que no permiten a sus clientes poner Stop Loss ni Take Profit en el momento de colocar una posición en el mercado, debido al tipo de ejecución "Observación del Mercado". En este tipo de brokers, el Stop Loss y el Take Profit se colocan modificando las posiciones abiertas.


Por último, se incluye la función Make_TreilingStop(). Esta función ejecuta un trailing stop estándar.


Además de las funciones, el archivo Lite_EXPERT.mqh incluye la variable LastTime, declarada a nivel global porque se utiliza en todas las funciones que abren órdenes.


En mi opinión, este juego de funciones es muy cómodo en la práctica. Ahorra tiempo a los programadores principiantes porque así no tienen que escribir todo el código de sus primeros EAs. Como ejemplo se puede tomar cualquier función del conjunto implementado:


//+==================================================================+
//| OpenBuyOrder1()                                                  |
//+==================================================================+
bool OpenBuyOrder1
        (bool& BUY_Signal, int MagicNumber, 
                double Money_Management, int STOPLOSS, int TAKEPROFIT)
{
//----+
  if (!BUY_Signal)
           return(true); 
  //---- Comprobamos la expiración del intervalo de tiempo mínimo 
                                    // entre dos operaciones de trading         
  if (TimeCurrent() < LastTime)
                          return(true); 
  int total = OrdersTotal();
  //---- Comprobación de la presencia de una operación abierta 
          // con número mágico igual al valor de la variable MagicNumber
  for(int ttt = total - 1; ttt >= 0; ttt--)     
      if (OrderSelect(ttt, SELECT_BY_POS, MODE_TRADES))
                      if (OrderMagicNumber() == MagicNumber)
                                                      return(true); 
  string OrderPrice, Symb = Symbol(); 
  int    ticket, StLOSS, TkPROFIT;
  double LOTSTEP, MINLOT, MAXLOT, MARGINREQUIRED;
  double FreeMargin, LotVel, Lot, ask, Stoploss, TakeProfit;                                                 
                                                      
  //----+ cálculo del tamaño del lote para abrir la posición
  if (Money_Management > 0)
    {        
      MARGINREQUIRED = MarketInfo(Symb, MODE_MARGINREQUIRED);
      if (MARGINREQUIRED == 0.0)
                    return(false);
                    
      LotVel = GetFreeMargin()
               * Money_Management / MARGINREQUIRED;         
    }
  else 
    LotVel = MathAbs(Money_Management);
  //----  
  LOTSTEP = MarketInfo(Symb, MODE_LOTSTEP);
  if (LOTSTEP <= 0)
              return(false);  
  //---- fijar el tamaño del lote para el valor estándar más cercano
  Lot = LOTSTEP * MathFloor(LotVel / LOTSTEP);  
  
  //----+ comprobación del lote para el valor mínimo aceptado
  MINLOT = MarketInfo(Symb, MODE_MINLOT);
  if (MINLOT < 0)
         return(false);
  if (Lot < MINLOT)
          return(true);
          
  //----+ comprobación del lote para el valor máximo aceptado
  MAXLOT = MarketInfo(Symb, MODE_MAXLOT);
  if (MAXLOT < 0)
         return(false);
  if (Lot > MAXLOT)
          Lot = MAXLOT;
          
  //----+ verificamos si el margen libre es suficiente para el tamaño del lote 
  if (!MarginCheck(Symb, OP_BUY, Lot))
                               return(false);
  if (Lot < MINLOT)
          return(true);
  //----
  ask = NormalizeDouble(Ask, Digits);
  if (ask == 0.0)
          return(false);
  //----             
  StLOSS = StopCorrect(Symb, STOPLOSS);
  if (StLOSS < 0)
          return(false);   
  //----
  Stoploss = NormalizeDouble(ask - StLOSS * Point, Digits);
  if (Stoploss < 0)
         return(false);
  //----       
  TkPROFIT = StopCorrect(Symb, TAKEPROFIT);
  if (TkPROFIT < 0)
          return(false);  
  //----               
  TakeProfit = NormalizeDouble(ask + TkPROFIT * Point, Digits);
  if (TakeProfit < 0)
         return(false);
  
  Print(StringConcatenate
         ("Abierto para ", Symb,
            " posición de compra con el número mágico ", MagicNumber));
            
  //----+ Abrir posición de compra
  ticket = OrderSend(Symb, OP_BUY, Lot, ask, 3, 
            Stoploss, TakeProfit, NULL, MagicNumber, 0, Lime);
  
  //---- Calculamos una pausa entre las operaciones de trading
  CountLastTime(GetLastError());
  //----
  if(ticket > 0)
   {
     if (OrderSelect(ticket, SELECT_BY_TICKET))
       {
         BUY_Signal = false;
         OrderPrice = DoubleToStr(OrderOpenPrice(), Digits);  
         Print(StringConcatenate(Symb, " orden de compra con el ticket número",
                ticket, " y número mágico ", OrderMagicNumber(), 
                                         " abierta con el precio ",OrderPrice));
         return(true);
       }
     else
       {
         Print(StringConcatenate("Fallo al abrir ", Symb, 
            " orden de compra con el número mágico ", MagicNumber, "!!!"));
         return(true);
       }
    }
  else
    {
      Print(StringConcatenate("Fallo al abrir ", Symb, 
           " orden de compra con el número mágico ", MagicNumber, "!!!"));
      return(true);
    }
  //---- 
  return(true);
//----+
}

Escribir un código como este, libre de errores, es un trabajo bastante complicado y extenso para un principiante de MQL4. Sin embargo, utilizarlo en un EA es muy sencillo:

          //----+ EJECUCIÓN DE ÓRDENES
          if (!OpenBuyOrder1(BUY_Sign, 1, Money_Management_Up, 
                                          STOPLOSS_Up, TAKEPROFIT_Up))
                                                                 return(-1);
          if (ClosePos_Up)
                if (!CloseOrder1(BUY_Stop, 1))
                                        return(-1);

Tan solo necesitamos unas cuantas líneas de código para llamar a las funciones definidas por el usuario, ¡y ya está! Así que solo hace falta entender cómo llamar a las funciones. El código del EA es sencillo y se entiende fácilmente; por lo que usted puede implementar cualquier estrategia de trading en sus propios Asesores Expertos. Las funciones del EA Exp_1.mq4 que abren órdenes pendientes y trailing stops no se utilizan, de modo que durante el proceso de compilación del Asesor Experto, MetaEditor mostrará una advertencia sobre el borrado de dichas funciones:


Explicaciones adicionales sobre el código del EA

Analicemos el código pendiente de nuestro EA. El EA está formado por dos algoritmos casi idénticos. Para entender correctamente los detalles basta con analizar, por ejemplo, la parte del EA que abre posiciones largas. El código tiene comentarios que explican el significado de los distintos fragmentos involucrados. Echemos pues un vistazo a los detalles que no están comentados. La variable MinBar_Up se inicializa en el bloque de inicialización:
//---- Inicialización de variables             
   MinBar_Up = 4 + 39 + 30;

El objetivo de esta variable es almacenar en la memoria del EA la cantidad mínima de barras; sin barras el EA no puede colocar órdenes largas. Este valor se define a partir del algoritmo del indicador personalizado JFATL.mq4. Hacen falta 39 barras para calcular un solo valor del filtro digital FATL. Para obtener JFATL suavizado con el algoritmo JMA se necesitan por lo menos 30 valores FATL; el algoritmo de detección de señales utiliza las tres últimas barras además de una cuarta: la barra cero. Lo siguiente comprueba si el número de barras es suficiente para los cálculos posteriores:

if (IBARS_Up >= MinBar_Up)

La comprobación

if (LastBars_Up != IBARS_Up)

hay que eliminar el recálculo de las señales del EA para entrar en el mercado en cada tick; el EA tiene que hacer esto sólo en el cambio de la barra, para optimizar tiempo y recursos. Por esta razón la variable LastBars_Up se declara estática; para poder recordar el número de barras del tick anterior de la función int start(). La inicialización de BUY_Sign y BUY_Stop, de entrada y salida, se realiza sólo una vez en la barra actual; estos valores deben mantenerse hasta que se ejecuta la orden, o hasta que ocurre otro cambio de barras. Por eso se declaran estáticas. Supongo que el resto de detalles del EA son suficientemente claros y pueden entenderse a partir del mismo código.



Realizando cambios en el Asesor Experto

Ahora me gustaría detenerme en la posibilidad de modificar el EA. Como ejemplo utilizaré el indicador personalizado J2JMA.mq4 de mi librería, y la llamada será como sigue:

           //----+ CÁLCULO DE LOS VALORES DEL INDICADOR Y SUBIDA A LOS BÚFERES
           for(bar = 1; bar <= 3; bar++)
                     Mov[bar - 1]=                  
                         iCustom(NULL, Timeframe_Up, 
                                "J2JMA", Length1_Up, Length2_Up,
                                             Phase1_Up, Phase2_Up,  
                                                  0, IPC_Up, 0, bar);

La tarea consiste en cambiar un poco el bloque de parámetros externos del EA (una vez más, el ejemplo solo describe una parte del algoritmo):

//---- PARÁMETROS DE ENTRADA DEL EA PARA COLOCAR ÓRDENES DE COMPRA
extern bool   Test_Up = true;//filtro de cálculo de dirección
extern int    Timeframe_Up=240;
extern double Money_Management_Up = 0.1;
extern int    Length1_Up = 4;  // profundidad del primer suavizado
extern int    Phase1_Up = 100; // parámetro del primer suavizado,
       // un cambio en el rango -100 ... +100 repercute en la calidad 
       // del proceso de promediación;
extern int    Length2_Up = 4;  // profundidad del segundo suavizado
extern int    Phase2_Up = 100; // parámetro del segundo suavizado,
       // un cambio en el rango -100 ... +100 repercute en la calidad 
       //del proceso de promediación;

En el bloque de inicialización cambiamos el valor de la variable:

//---- Inicialización de variables
   MinBar_Up = 4 + 30 + 30;

Ahora tenemos dos JMA consecutivas suavizadas, cada una de las cuales necesita por lo menos 30 barras y 4 barras en el algoritmo de cálculo del EA. Después de esto, la referencia del indicador personalizado se tiene que cambiar al colocado al comienzo del párrafo. En la segunda parte del algoritmo todo se realiza de la misma manera. Así, además de medias móviles, también podemos utilizar osciladores, que en ocasiones resultan muy útiles. El código del EA basado en J2JMA se incluye en el archivo EXP_2.mqh.


Optimización del EA


Ahora vamos a exponer algunos detalles acerca de la optimización de los EAs presentados en este artículo. Como se mencionó anteriormente, el EA tiene dos algoritmos independientes: uno que trabaja con posiciones largas y otro para las posiciones cortas. Naturalmente, lo más conveniente y rápido es optimizar el EA sólo en una dirección de compraventa. Para ello el EA incorpora dos variables externas: Test_Up y Test_Dn.

Asignando el valor 'false' a una de estas variables lógicas, excluimos todos los cálculos en esta dirección, y, como resultado, la optimización lleva menos tiempo. Después de optimizar los parámetros del EA en una dirección, podemos cambiar los valores de las variables Test_Up y Test_Dn en la dirección opuesta. Y sólo después de esto asignar 'true' a ambas variables, y comprobar el resultado. El proceso de optimización y pruebas se explica bien en el artículo Testing of Expert Advisors in MetaTrader 4 Client Terminal: an Outward Glance. La optimización de este Asesor Experto se realiza tal y como se describe en el mismo. Inicie el Probador de Estrategias, suba el EA, seleccione un par de divisas, periodo, método de optimización y periodo de tiempo para la optimización:

a continuación seleccione "Propiedades del experto" y abra la pestaña "Pruebas":

Aquí definimos el tamaño del depósito. Seleccione pues una dirección (larga o corta), así como el algoritmo genético de optimización. Vaya a la pestaña "Entradas":

Aquí asignamos 'true' a una de las variables externas, Test_Up y Test_Dn, y 'false' a la segunda. Ahora asigne a Timeframe_Up y a Timeframe_Dn valores de periodos del gráfico donde se llevará a cabo la optimización. En Money_Management_Up y Money_Management_Dn defina la parte del depósito que desee para ejecutar órdenes de compraventa.

Para las variables externas restantes, cambie los límites durante la optimización. Optimice las variables externas. Cierre la pestaña y comience la optimización clicando en el botón "Iniciar". Cuando la optimización termine


seleccione la pestaña "Resultados" en el Probador de Estrategias:

y suba los resultados de optimización satisfactorios. Después, siga los mismos pasos en la dirección opuesta. Como resultado obtenemos un EA con los parámetros cargados, rentable en el periodo de tiempo de la optimización. Tenga en cuenta que, en MetaTrader 4, la pestaña de propiedades del EA difiere de la que se muestra arriba:

En este caso conviene maximizar la ventana. Sin embargo el terminal cliente no permite maximizar la ventana, así que para ello se crea un archivo adicional, OpenExp.exe. Este archivo maximiza la ventana de propiedades de los EAs cuyo nombre comienza por Exp_ c (sensible a mayúsculas y minúsculas). El archivo debe iniciarse desde algún directorio, tras lo cual el módulo del programa esperará a que salga la ventana de propiedades EA, y cambiará el tamaño. En este momento no se recomienda mover el ratón.

Cualquier programador principiante de EAs que esté dando sus primeros pasos en la ejecución de las pruebas, querrá resolver algunas dudas. En primer lugar, vamos a decidir el período de nuestra simulación y de nuestra optimización. En mi opinión hay varias opciones que son igualmente válidas. En general, se considera que cuanto más grande es el período de tiempo, tanto más estables son los patrones observables del sistema de trading. Es como en el trading real. Pero a la larga lo más óptimo es comparar los resultados de varias variantes de simulación y optimización. Con el activo subyacente sucede lo mismo. Cada divisa tiene su propia personalidad. Sobre el modelado. La experiencia demuestra que los EAs que reciben señales para ejecutar órdenes en el momento que cambia la primera barra, están bien optimizados cuando se modelan sobre los puntos de control, sin pérdida significativa de calidad y cantidad. Claro que la experiencia de cada uno debe antes confirmar estas afirmaciones. Estoy convencido de que no tiene sentido optimizar tal EA modelando todos los ticks. Ahora vamos a hablar de otro parámetro importante: el período de tiempo donde se realiza la optimización:

Según el objetivo de la optimización del EA podemos tener varios valores. Lo importante aquí es que el período no exceda los datos históricos disponibles; de lo contrario puede suceder lo siguiente:

El límite inferior de los datos del historial, disponibles en el archivo, es como sigue:


Naturalmente, en las pruebas y en la optimización pueden ocurrir errores del siguiente tipo al ejecutar el Asesor Experto y los indicadores incluidos:

¡Estos errores no guardan relación con el EA! Es solo que se debe elegir un periodo de optimización entre las opciones disponibles, y no lo que se desea.

A continuación voy a explicar la pestaña Parámetros de entrada (Inputs) del Asesor Experto. Una vez más, analizaremos solo unos cuantos, relativos a las posiciones largas. Antes ya hemos mencionado Test_Up. El significado del parámetro Timeframe_Up es claro. Money_Management_Up también se ha descrito anteriormente. Echemos pues ahora un vistazo al parámetro Length_Up. Este parámetro es análogo al parámetro Period de medias móviles simples. Su valor puede estar comprendido entre uno e infinito. Así que no tiene sentido configurar el parámetro a más de 150, puesto que en la mayoría de los casos es más sencillo pasar a un plazo mayor. Al optimizar este parámetro tenga en cuenta que, cuanto mayor sea el valor, el indicador JFATL detectará tendencias a largo plazo más estables. Por lo tanto, en este sistema de trading hay una dependencia no evidente entre Length_Up, STOPLOSS_Up y TAKEPROFIT_Up. Lógicamente, el stop loss y el take profit dependen directamente de Length_Up. Por supuesto que podemos colocar órdenes independientemente de Length_Up. En este caso la rentabilidad del sistema no se define solo con las propiedades, sino con la situación actual del mercado, que, por otra parte, ¡no está definida en este sistema de trading! Esta afirmación se entenderá fácilmente con el siguiente ejemplo. Supongamos que hemos obtenido muy buenos resultados en la optimización de las pruebas de este EA, operando EUR/USD con estos parámetros:

extern bool   Test_Up = true;
extern int    Timeframe_Up = 1;
extern double Money_Management_Up = 0.1;
extern int    Length_Up = 4;
extern int    Phase_Up = 100;
extern int    IPC_Up = 0;
extern int    STOPLOSS_Up = 100;
extern int    TAKEPROFIT_Up = 200;
extern bool   ClosePos_Up = false;

JFATL con Length_Up igual a cuatro es un indicador de tendencias muy rápidas; lo que, en combinación con el gráfico de un minuto, donde el EA opera, permite que el sistema pueda arreglar la escala del precio en unos 10-15 puntos. Por eso el resultado de la prueba con los valores stop loss y take profit tan grandes solo indica que, durante la optimización, el mercado experimentó una fuerte tendencia, que no es detectada en el propio sistema. Por lo que debe entenderse que después de actualizar estos parámetros el EA apenas mostrará tan buenos resultados. Sin embargo, si detecta la presencia de una tendencia fuerte con otras herramientas, el uso de tales parámetros se puede justificar.

El rango de valores de la variable Phase_Up es de -100 a +100. Cuando los valores son iguales a -100, los procesos temporales de JFATL son de carácter mínimo, pero mi experiencia demuestra que los resultados óptimos se obtienen cuando el valor es +100. La variable IPC_Up define los precios que se utilizarán en el posterior procesamiento del algoritmo JFATL. La variable ClosePos_Up permite el cierre forzado de la posición si comienza una tendencia contra una posición abierta. Hay que señalar que si el take profit y el stop loss se sitúan demasiado lejos del mercado, todas las posiciones se cerrarán tras las señales de movimiento, y TP y SL no influirán si el valor de ClosePos_Up es igual a 'true'.



Conclusión

Llega el momento de finalizar esta explicación, puesto que el tema de la optimización es demasiado amplio. En un próximo artículo explicaré cualquier cosa que me haya podido dejar en el tintero. El objetivo principal del primer artículo no es otro que el de mostrar al programador inexperto mi método para escribir Asesores Expertos, así como ofrecer una manera sencilla y universal para construir EAs. Espero que se haya podido completar. En el siguiente artículo explicaré algunas cosas sobre el análisis y la optimización de los resultados, y ofreceré un ejemplo práctico. En cuanto a las EAs de ejemplo que se incluyen en el texto, por favor, tenga en cuenta que son simplificaciones; difícilmente se pueden utilizar en una estrategia automática real. Sin embargo, pueden resultar de utilidad para automatizar operaciones de trading independientes, y también son herramientas de trabajo especialmente útiles cuando el trader tiene que dejar por un momento su terminal cliente.

Traducción del ruso hecha por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/ru/articles/1516

Archivos adjuntos |
EXPERTS.zip (5.33 KB)
INCLUDE.zip (18.44 KB)
indicators.zip (8.95 KB)
TESTER.zip (3.11 KB)
MetaEditor: plantillas como punto de apoyo MetaEditor: plantillas como punto de apoyo
Posiblemente a muchos de nuestros lectores les sorprenderá que es posible preparar la escritura de un EA solo una vez, y a continuación utilizar el robot tantas veces como se desee.
A la caza de la tendencia A la caza de la tendencia
El presente artículo describe un algoritmo para aumentar el volumen de una operación ganadora. Se adjunta la implementación correspondiente en el lenguaje MQL4.
Cómo proteger su Asesor Experto cuando tradea en la Bolsa de Moscú Cómo proteger su Asesor Experto cuando tradea en la Bolsa de Moscú
En este artículo se describen detalladamente los métodos de trabajo que sirven para garantizar la seguridad de la ejecución de operaciones comerciales en los mercados bursátiles y en los mercados de baja liquidez tomando de ejemplo la Sección de Derivados de la Bolsa de Moscú. Este artículo es la continuación lógica del artículo “Principios de formación de precios en el mercado bursátil tomando de ejemplo la Sección de Derivados de la Bolsa de Moscú” que contiene los principios teóricos del trading bursátil, pero lleva el carácter más práctico.
Scalping Agradable Scalping Agradable
Este artículo describe un método para crear una herramienta de scalping. El enfoque de apertura de posiciones que presentaremos puede aplicarse a cualquier tipo de trading.