Asesor Experto multiplataforma: Filtros temporales

Enrico Lambino | 5 septiembre, 2017


Índice

  1. Introducción
  2. Objetivos
  3. Clase base
  4. Clases y tipos del filtro temporal
  5. Contenedores de los filtros temporales
  6. Subfiltros (CTimeFilter)
  7. Ejemplo
  8. Conclusión

Introducción

La filtración temporal se usa cuando el Asesor Experto (EA) tiene que comprobar si se encuentra un determinado período de tiempo dentro del intervalo definido. Dependiendo de la satisfacción (o no satisfacción) de esta condición, algunas funciones pueden activarse (o desactivarse). Eso es muy útil cuando una determinada función del EA está configurada para funcionar en horarios específicos (periódicamente, o continuamente a excepción de algunas condiciones). Voy a mostrar algunos ejemplos cuando la filtración temporal puede ser aplicada.

  1. Evitación de ciertos períodos de tiempo (por ejemplo, los períodos del flat o una alta volatilidad en el mercado)
  2. Establecimiento de la hora de vencimiento para una orden de mercado o posición (salida del mercado cuando se termine un plazo determinado)
  3. Cierre de las transacciones al final de la semana de trading

Son algunas de las funciones más comunes utilizadas por los traders. Pero también hay otras opciones.

Objetivos

  • Comprender y aplicar los métodos más comunes de la filtración temporal;
  • Permitir que los EAs usen fácilmente varios filtros temporales a la vez;
  • Asegurar la compatibilidad con MQL4 y MQL5.

Clase base

La clase CTime va a servir de la clase base para otros objetos de los filtros temporales que se consideran en nuestro EA. La definición de la clase CTimeBase (donde se encuentra CTime) se muestra en el fragmento del código de abajo:

class CTimeBase : public CObject
  {
protected:
   bool              m_active;
   bool              m_reverse;
   CSymbolManager   *m_symbol_man;
   CEventAggregator *m_event_man;
   CObject          *m_container;
public:
                     CTimeBase(void);
                    ~CTimeBase(void);
   virtual int       Type(void) const {return CLASS_TYPE_TIME;}
   //--- inicialización
   virtual bool      Init(CSymbolManager*,CEventAggregator*);
   virtual CObject *GetContainer(void);
   virtual void      SetContainer(CObject*);
   virtual bool      Validate(void);
   //--- métodos del establecimiento y obtención
   bool              Active(void) const;
   void              Active(const bool);
   bool              Reverse(void);
   void              Reverse(const bool);
   //--- verificación
   virtual bool      Evaluate(datetime)=0;
  };

La clase base tiene tres miembros de los tipos primitivos de datos.

  • m_active se utiliza para activar o desactivar el objeto de la clase.
  • m_reverse se utiliza para invertir la muestra del objeto de la clase (devuelve true si la muestra original es false, y devuelve false si la muestra original es true).
  • m_time_start significa la creación de la instancia de la clase, independientemente de que si haya sido creado durante la ejecución de OnInit o después, durante la ejecución del EA.

Clases y tipos del filtro temporal

La filtración temporal por un determinado intervalo de fechas

Es el método más simple de la filtración temporal. Para comprobar la fecha/hora usando este método, sólo hay que tener la fecha inicial y la fecha final. Si la fecha/hora especificada cae en el intervalo entre estas fechas, el método muestra true. De lo contrario, devuelve false.

Este método está implementado por la clase CTimeRange. El siguiente código muestra la definición de CTimeRangeBase, en el que se basa CTimeRange:

class CTimeRangeBase : public CTime
  {
protected:
   datetime          m_begin;
   datetime          m_end;
public:
                     CTimeRangeBase(void);
                     CTimeRangeBase(datetime,datetime);
                    ~CTimeRangeBase(void);
   //--- initialization                    datetime, datetime
   virtual bool      Set(datetime,datetime);
   virtual bool      Validate(void);
   //--- métodos del establecimiento y obtención
   datetime          Begin(void) const;
   void              Begin(const datetime);
   datetime          End(void) const;
   void              End(const datetime);
   //--- processing
   virtual bool      Evaluate(datetime);
  };

La fecha inicial y final deben definirse en el constructor de la clase. La llamada al método Evaluate de la clase define la fecha/hora real que va a comprobarse con estos dos valores. Si este intervalo no está definido o es igual a cero, el método utiliza la fecha/hora actual en el momento de la llamada:

bool CTimeRangeBase::Evaluate(datetime current=0)
  {
   if(!Active())
      return true;
   if(current==0)
      current=TimeCurrent();
   bool result=current>=m_begin && current<m_end;
   return Reverse()?!result:result;
  }

La filtración temporal por el día de la semana

La filtración por el día de la semana es uno de los métodos más simples y más comunes de la filtración temporal. Normalmente, este filtro se utiliza para prohibir o permitir el funcionamiento del EA en determinados días de la semana.

Esta clase específica puede implementarse de varias maneras. Un método es prever la función personalizada para TimeDayOfWeek que está disponible en MQL4, pero no está en MQL5. Otro método consiste en convertir la fecha/hora a comprobar en la estructura MqlDateTime, y luego comprobar si su parámetro day_of_week está definido en las banderas establecidas anteriormente. He elegido el último método y lo recomiendo, ya que nos permite colocar todos los métodos de la clase utilizados en la clase base.

Este método está representado en nuestro EA como la clase CTimeDays. El siguiente fragmento del código muestra la definición de CTimeDaysBase, en el que se basa CTimeDays:

class CTimeDaysBase : public CTime
  {
protected:
   long              m_day_flags;
public:
                     CTimeDaysBase(void);
                     CTimeDaysBase(const bool sun=false,const bool mon=true,const bool tue=true,const bool wed=true,
                                   const bool thu=true,const bool fri=true,const bool sat=false);
                    ~CTimeDaysBase(void);
   //--- inicialización                    
   virtual bool      Validate(void);
   virtual bool      Evaluate(datetime);
   virtual void      Set(const bool,const bool,const bool,const bool,const bool,const bool,const bool);
   //--- métodos del establecimiento y obtención
   bool              Sunday(void) const;
   void              Sunday(const bool);
   bool              Monday(void) const;
   void              Monday(const bool);
   bool              Tuesday(void) const;
   void              Tuesday(const bool);
   bool              Wednesday(void) const;
   void              Wednesday(const bool);
   bool              Thursday(void) const;
   void              Thursday(const bool);
   bool              Friday(void) const;
   void              Friday(const bool);
   bool              Saturday(void) const;
   void              Saturday(const bool);
  };

Como se muestra en la definición, esta clase tiene sólo un miembro de la clase tipo long. La clase va a usarlo para establecer las banderas para los días de la semana cuando tendrá que devolver true. Se supone que vamos a usar las operaciones bit a bit, por eso también necesitamos declarar una enumeración personalizada cuyos miembros representan cada uno de siete días de la semana.

enum ENUM_TIME_DAY_FLAGS
  {
   TIME_DAY_FLAG_SUN=1<<0,
   TIME_DAY_FLAG_MON=1<<1,
   TIME_DAY_FLAG_TUE=1<<2,
   TIME_DAY_FLAG_WED=1<<3,
   TIME_DAY_FLAG_THU=1<<4,
   TIME_DAY_FLAG_FRI=1<<5,
   TIME_DAY_FLAG_SAT=1<<6
  };

Las banderas para los días de la semana se establecen (o se quitan) usando el método Set. Por conveniencia, este método se invoca en uno de los constructores de la clase como la medida para prevenir la evaluación accidental de la instancia de la clase sin la definición previa de la bandera.

void CTimeDaysBase::Set(const bool sun=false,const bool mon=true,const bool tue=true,const bool wed=true,
                        const bool thu=true,const bool fri=true,const bool sat=false)
  {
   Sunday(sun);
   Monday(mon);
   Tuesday(tue);
   Wednesday(wed);
   Thursday(thu);
   Friday(fri);
   Saturday(sat);
  }

También se puede establecer las banderas de manera individual. Es útil si necesitamos modificar sólo una bandera en vez de llamar a la función Set que establece la bandera para todos los 7 días (lo que puede ser perjudicial en algunas situaciones). El fragmento del código de abajo muestra el método Monday que se utiliza para establecer o cancelar la segunda bandera de la lista. Los métodos del establecimiento y obtención para otros días se programan de la misma forma.

void CTimeDaysBase::Monday(const bool set)
  {
   if(set)
      m_day_flags|=TIME_DAY_FLAG_MON;
   else
      m_day_flags &=~TIME_DAY_FLAG_MON;
  }

Junto con los métodos para establecer las banderas, nuestro siguiente método se refiere a la evaluación real del filtro, es decir, él verifica si la hora establecida corresponde a los límites de un determinado día de la semana o no.

bool CTimeDaysBase::Evaluate(datetime current=0)
  {
   if(!Active())
      return true;
   bool result=false;
   MqlDateTime time;
   if(current==0)
      current=TimeCurrent();
   TimeToStruct(current,time);
   switch(time.day_of_week)
     {
      case 0: result=Sunday();      break;
      case 1: result=Monday();      break;
      case 2: result=Tuesday();     break;
      case 3: result=Wednesday();   break;
      case 4: result=Thursday();    break;
      case 5: result=Friday();      break;
      case 6: result=Saturday();    break;
     }
   return Reverse()?!result:result;
  }

Como ya hemos considerado brevemente antes, el método primero obtiene el parámetro tipo datetime. Si el argumento es cero cuando se invoca el método, se usa la fecha/hora actual. Luego, el método transforma esta fecha/hora en el formato MqlDateTime y obtiene su miembro day_of_week que luego se compara con el valor actual del único miembro de la clase (m_day_flags).

Este método se utiliza a menudo para realizar las tareas de los traders como «no trader el viernes», o durante otros días cuando los brókers están activos pero el trader no los considera favorables para el trading.

Uso del temporizador

Otro método de la filtración temporal es el uso del temporizador. En el temporizador, la fecha/hora actual se compara con un determinado punto de tiempo en el pasado. Si la fecha/hora todavía permanece dentro de los límites del plazo establecido, el método devuelve true, en caso contrario, es false. El método se representa por la clase CTimer. El fragmento del código de abajo muestra la definición de CTimerBase en el que se basa CTimer:

class CTimerBase : public CTime
  {
protected:
   uint              m_years;
   uint              m_months;
   uint              m_days;
   uint              m_hours;
   uint              m_minutes;
   uint              m_seconds;
   int               m_total;
   int               m_elapsed;
   datetime          m_time_start;
public:
                     CTimerBase(const int);
                     CTimerBase(const uint,const uint,const uint,const uint,const uint,const uint);                     
                    ~CTimerBase(void);
   //--- inicialización
   virtual bool      Set(const uint,const uint,const uint,const uint,const uint,const uint);
   virtual bool      Validate(void);
   //--- métodos de obtención y colocación
   uint              Year(void) const;
   void              Year(const uint);
   uint              Month(void) const;
   void              Month(const uint);
   uint              Days(void) const;
   void              Days(const uint);
   uint              Hours(void) const;
   void              Hours(const uint);
   uint              Minutes(void) const;
   void              Minutes(const uint);
   uint              Seconds(void) const;
   void              Seconds(const uint);
   bool              Total(void) const;
   datetime          TimeStart(void) const;
   void              TimeStart(const datetime);
   //--- processing   
   virtual bool      Elapsed(void) const;
   virtual bool      Evaluate(datetime);
   virtual void      RecalculateTotal(void);
  };

Los argumentos del constructor se utilizan para construir el tiempo total transcurrido o el tiempo transcurrido desde el momento inicial. Se almacena en el miembro de la clase m_total. Por motivos de conveniencia, vamos a declarar las constantes basadas en el número de segundos para determinados períodos de tiempo, de un año a un minuto.

#define YEAR_SECONDS 31536000
#define MONTH_SECONDS 2419200
#define DAY_SECONDS 86400
#define HOUR_SECONDS 3600
#define MINUTE_SECONDS 60

El constructor de la clase requiere tiempo transcurrido desde el inicio del cuento del temporizador, expresado en períodos de años a segundos.

CTimerBase::CTimerBase(const uint years,const uint months,const uint days,const uint hours,const uint minutes,const uint seconds) : m_years(0),
                                                                                                                                    m_months(0),
                                                                                                                                    m_days(0),
                                                                                                                                    m_hours(0),
                                                                                                                                    m_minutes(0),
                                                                                                                                    m_seconds(0),
                                                                                                                                    m_total(0),
                                                                                                                                    m_time_start(0)
  {
   Set(years,months,days,hours,minutes,seconds);
  }

Podemos construir la instancia de la clase de otra manera, usando el valor preferido para m_total como argumento único:

CTimerBase::CTimerBase(const int total_time) : m_years(0),
                                               m_months(0),
                                               m_days(0),
                                               m_hours(0),
                                               m_minutes(0),
                                               m_seconds(0),
                                               m_total(0),
                                               m_time_start(0)
  {
   m_total=total_time;
  }

El método Evaluate invocado inmediatamente después de la creación de la instancia de la clase va a comparar m_total con el momento del inicio de la fecha/hora UNIX, en el que se basa el tipo de datos datetime. De esta manera, antes de llamar al método Evaluate, es necesario establecer la fecha/hora deseada del inicio (excepto los casos cuando el inicio es la medianoche del 1 de enero de 1970 por UTC/GMT). Abajo se muestran los métodos del establecimiento y obtención para el miembro de la clase m_time_start, usando la sobrecarga del método TimeStart:

datetime CTimerBase::TimeStart(void) const
  {
   return m_time_start;
  }

void CTimerBase::TimeStart(const datetime time_start)
  {
   m_time_start=time_start;
  }

El método Evaluate de esta clase es muy simple: obtiene la diferencia entre la fecha/hora inicial y el tiempo transcurrido como argumento del método (normalmente, es la hora actual). Éste será el tiempo expirado.  Si excede el tiempo total permitido (m_total), el método devuelve false.

bool CTimerBase::Evaluate(datetime current=0)
  {
   if(!Active())
      return true;
   bool result=true;
   if(current==0)
      current= TimeCurrent();
   m_elapsed=(int)(current-m_time_start);
   if(m_elapsed>=m_total) result=false;
   return Reverse()?!result:result;
  }

Este método de la filtración se utiliza en diferentes versiones: por ejemplo, el establecimiento del período máximo de la expiración para determinadas funciones del EA o el establecimiento de la expiración de una orden de mercado o posición (semejante al trading con opciones binarias). Este filtro temporal parece aproximadamente al uso del evento Timer (compatible con MQL4 y MQL5). Pero dado que a través de esta función del evento se puede configurar sólo un evento del temporizador, CTimer puede ser necesario sólo en el caso cuando el EA necesita los temporizadores adicionales.

Filtración utilizada para el horario intradía

El filtro utilizado para el horario intradía es uno de los más populares entre los tráders. El filtro utiliza el horario de 24 horas, y el EA selecciona (según sus parámetros) un determinado gráfico de acuerdo con el cual se ejecutan las operaciones (normalmente, tradeando a abse de las señales de entrada). Este método de la filtración se representa pos la clase CTimeFilter. El fragmento del código de abajo muestra la definición de CTimeFilterBase en el que se basa CTimeFilter:

class CTimeFilterBase : public CTime
  {
protected:
   MqlDateTime       m_filter_start;
   MqlDateTime       m_filter_end;
   CArrayObj         m_time_filters;
public:
                     CTimeFilterBase(void);
                     CTimeFilterBase(const int,const int,const int,const int,const int,const int,const int);
                    ~CTimeFilterBase(void);
   virtual bool      Init(CSymbolManager*,CEventAggregator*);
   virtual bool      Validate(void);
   virtual bool      Evaluate(datetime);
   virtual bool      Set(const int,const int,const int,const int,const int,const int,const int);
   virtual bool      AddFilter(CTimeFilterBase*);
  };

La clase tiene dos miembros tipo MqlDateTime y un miembro tipo CArrayObj. El intervalo dentro de un período de 24 horas se almacena en dos estructuras, mientras que el miembro del objeto se usa para almacenar los subfiltros.

A través del constructor de la clase, nosotros obtendremos el tiempo inicial y final expresado en horas, minutos y segundos. Luego, estos valores se guardan en los miembros de la clase m_filter_start y m_filter_end a través del método Set de la clase. El parámetro gmt se usa para registrar el desplazamiento de la hora del bróker respecto a GMT.

bool CTimeFilterBase::Set(const int gmt,const int starthour,const int endhour,const int startminute=0,const int endminute=0,
                          const int startseconds=0,const int endseconds=0)
  {
   m_filter_start.hour=starthour+gmt;
   m_filter_start.min=startminute;
   m_filter_start.sec=startseconds;
   m_filter_end.hour=endhour+gmt;
   m_filter_end.min=endminute;
   m_filter_end.sec=endseconds;
   return true;
  }

Luego pasamos al método Evaluate de la clase. Durante la inicialización, los datos de dos parámetros de MqlDateTime están expresados sólo en horas, minutos y segundos dentro del período de 24 horas. No contiene otros datos (por ejemplo, años o meses). Para comparar la hora inicial y final con la hora indicada (o con la hora actual si el argumento es predefinido), hay por lo menos dos métodos:

  1. expresar la hora indicada en los valores de las horas, minutos y segundos, y después de eso compararlos con los parámetros de la estructura;
  2. actualizar los parámetros omitidos de las estructuras usando la hora actual, convertir las estructuras en el formato UNIX (tipo datetime), y compararla con la hora establecida.

He escogido la segunda opción y la he implementado en el método Evaluate que se muestra a continuación:

bool CTimeFilterBase::Evaluate(datetime current=0)
  {
   if(!Active())
      return true;
   bool result=true;
   MqlDateTime time;
   if(current==0)
      current=TimeCurrent();
   TimeToStruct(current,time);
   m_filter_start.year= time.year;
   m_filter_start.mon = time.mon;
   m_filter_start.day = time.day;
   m_filter_start.day_of_week = time.day_of_week;
   m_filter_start.day_of_year = time.day_of_year;
   m_filter_end.year= time.year;
   m_filter_end.mon = time.mon;
   m_filter_end.day = time.day;
   m_filter_end.day_of_week = time.day_of_week;
   m_filter_end.day_of_year = time.day_of_year;
   /*
     other tasks here
   */
  }

La comparación no incluye la hora de finalización. Por ejemplo, si queremos que el EA tradee sólo entre las 08:00 y las 17:00, puede empezar a tradear directamente a partir de las 8:00, partiendo del comienzo de la barra de ocho horas, pero puede tradear sólo hasta las 17:00. Entonces, la última operación puede abrirse sólo hasta las 16:59 (inclusive).

Puesto que las estructuras no contienen los intervalos temporales más de una hora, es necesario obtener los datos omitidos desde la hora actual (o especificada). No obstante, vamos a necesitar algunas correcciones si la hora inicial y final se encuentra dentro del período de 24 horas pero pertenece a los días diferentes. En el ejemplo anterior, las 08:00 son las 08:00 АМ, y las 17:00 son las 5:00 PM. En este caso, ambas horas se encuentran dentro de los límites del mismo día. Pero supongamos que hemos cambiado la hora inicial a las 5:00 PM, y la hora final, a las 8:00 AM. Si la hora del inicio es más tarde que la hora de la finalización del trading, eso quiere decir que el intervalo de tiempo cubre el día siguiente. De esta manera, la hora de finalización se refiere a otro día en comparación con la hora inicial. Entonces, tenemos una de dos situaciones:
  1. la hora de inicio cae en el día actual (o el día de la hora indicada), la hora final pertenece al siguiente día.
  2. la hora inicial pertenece al día de ayer (o al día indicado), y la hora final pertenece al día de hoy (o al día indicado).

Los ajustes necesarios van a depender de la hora actual (o especificada). Supongamos que son las 5:01 PM (17:01). En este caso, la hora de inicio va a encontrarse dentro del mismo día que la hora especificada. Pero la hora final va a pertenecer al día siguiente. Pero si la hora indicada es la 01:00 o 1:00 AM, va a encontrarse dentro del mismo día que la hora final, pero la hora inicial va a referirse al día anterior. Por tanto, las estructuras calculadas anteriormente de MqlDateTime tendrán que configurarse de la siguiente manera:

  1. si la hora del inicio pertenece al mismo día que la hora indicada, añadimos un día a la hora final.
  2. si la hora final pertenece al mismo día que la hora indicada, restamos un día de la hora inicial.

Eso se aplica sólo si la hora inicial y la hora final no pertenecen al mismo día, el filtro determina eso según el principio «la hora inicial es mayor que la hora final». Las correcciones se implementan dentro del método Evaluate de la siguiente manera:

if(m_filter_start.hour>=m_filter_end.hour)
  {
   if(time.hour>=m_filter_start.hour)
     {
      m_filter_end.day++;
      m_filter_end.day_of_week++;
      m_filter_end.day_of_year++;
     }
   else if(time.hour<=m_filter_end.hour)
     {
      m_filter_start.day--;
      m_filter_start.day_of_week--;
      m_filter_start.day_of_year--;
     }
  }

La variable devuelta está definida inicialmente como true. Así que la comprobación real del filtro temporal va a depender de que si la hora a determinar pertenece al intervalo entre la hora inicial y final. El cálculo en el método Evaluate asegura que la hora del inicio siempre sea menos (o igual) que la hora de finalización. Si la hora inicial equivale a la hora final (en cuanto a los valores de las horas, minutos y segundos), el método seguirá devolviendo true. Por ejemplo, si la hora inicial es igual a las 05:00 y la hora final son las 05:00, el filtro tratará esta situación de tal manera que estas dos horas correspondan al mismo día, y en este caso, va a abarcar el intervalo completo de 24 horas.

Contenedores de los filtros temporales

Igual que otros objetos de la clase, los filtros temporales discutidos en este artículo también van a contener los contenedores para almacenar sus punteros. Eso permitirá realizar la evaluación llamando al método Evaluate desde el contenedor. Si el método Evaluate de todos los filtros temporales devuelve true (es decir, todas las condiciones de la filtración temporal están satisfechas), el contenedor también devuelve true. Eso está implementado en la clase CTimes. El fragmento del código de abajo muestra la definición de CTimesBase, en el que se basa CTimes:

class CTimesBase : public CArrayObj
  {
protected:
   bool              m_active;
   int               m_selected;
   CEventAggregator *m_event_man;
   CObject          *m_container;
public:
                     CTimesBase(void);
                    ~CTimesBase(void);
   virtual int       Type(void) const {return CLASS_TYPE_TIMES;}
   //-- initialization
   virtual bool      Init(CSymbolManager*,CEventAggregator*);
   virtual CObject *GetContainer(void);
   virtual void      SetContainer(CObject*);
   virtual bool      Validate(void) const;
   //--- activation and deactivation
   bool              Active(void) const;
   void              Active(const bool);
   int               Selected(void);
   //--- verificación
   virtual bool      Evaluate(datetime) const;
   //--- recovery
   virtual bool      CreateElement(const int);
  };

Subfiltros (CTimeFilter)

Para que el método Evaluate de los contenedores de los filtros temporales devuelva true, todos sus miembros primarios deben devolver true. La mayoría de los objetos de los filtros temporales necesitan no más de una instancia dentro del mismo EA. La excepción es CTimeFilter que, por cierto, es uno de los filtros más usados de todos. Vamos a ver este código:

CTimes times = new CTimes();
CTimeFilter time1 = new CTimeFilter(gmt,8,17);
times.Add(GetPointer(time1));

Supongamos que el filtro temporal se usa para la entrada en el mercado. En este caso, el array dinámico de los punteros del contenedor de los filtros contiene sólo un puntero. Debajo de esta configuración, cuando se invoca el método Evaluate, el resultado final dependerá del hecho de que si una determinada hora se encuentre en el intervalo entre las 08:00 y las 17:00.

Ahora analizaremos el caso cuando el EA no está configurado para tradear interumpidamente desde las 8:00 de la mañana hasta las 5:00 de la tarde, sino para el trading con el descanso para la hora de la comida. Es decir, el trading va a realizarse desde las 8:00 AM hasta las 12:00 PM, y desde la 1:00 PM hasta las 5:00 PM. Ahora, la escala de tiempo no es continua: está dividida en dos partes. Puede surgir la tentación de alterar el código fuente: usar dos instancias de CTimeFilter en vez de una.

CTimes times = new CTimes();
CTimeFilter time1 = new CTimeFilter(gmt,8,12);
CTimeFilter time2 = new CTimeFilter(gmt,13,17);
times.Add(GetPointer(time1));
times.Add(GetPointer(time2));

Pero el código de arriba no será correcto funcionalmente. Siempre va a devolver false, porque el contenedor de los filtros exige que todas sus instancias de los filtros temporales primarios devuelvan true. Mientras que en caso de la configuración de arriba, un filtro siempre va a devolver true y el otro va a devolver false, y viceversa. La situación se hace aún más complicada cuando se usan más de dos filtros temporales. La única opción cuando todo va a funcionar correctamente es la siguiente: cuando uno de los filtros se encuentro activo, y los demás no lo están.

Tenemos la siguiente solución: hagamos que el contenedor de los filtros temporales siempre contenga sólo un puntero a CTimeFilter. Si necesitamos más de una instancia de CTimeFilter, las demás deben añadirse como subfiltro a otra instancia de CTimeFilter. Los punteros a los subfiltros se almacenan dentro del miembro de la clase CTimeFilter, m_time_filters, y se añaden a través de su método AddFilter. El código de la evaluación de los subfiltros se encuentra dentro del método Evaluate de la clase:

if(!result)
  {
   for(int i=0;i<m_time_filters.Total();i++)
     {
      CTimeFilter *filter=m_time_filters.At(i);
      if(filter.Evaluate(current))
      {
         return true;
      }   
     }
  }

Se activa sólo en el caso cuando el filtro principal devuelve false. O sea, este método es la instancia suprema que determina la presencia de las excepciones para la evaluación inicial. Si por lo menos un subfiltro devuelve true, el filtro principal también va a devolver siempre true. Tomando en cuenta lo dicho, vamos a cambiar la muestra del código inicial:

CTimes times = new CTimes();
CTimeFilter time1 = new CTimeFilter(gmt,8,12);
CTimeFilter time2 = new CTimeFilter(gmt,13,17);
CTimeFilter time0 = new CTimeFilter(gmt,0,0);
time0.Reverse();
time0.AddFilter(GetPointer(time1));
time0.AddFilter(GetPointer(time2));
times.Add(GetPointer(time0));

Después de estas modificaciones, el objeto de los filtros temporales contiene sólo un puntero al array, time0. time0, en su lugar, contiene dos subfiltros: time1 y time2, que inicialmente se encontraban en el contenedor de los filtros temporales. time0 tiene los mismos parámetros para la hora inicial y la hora final, y por eso siempre devuelve true. Ahora vamos a llamar al método Reverse, para que time0 siempre devuelva false. De esta manera, vamos a obligarle a comprobar si hay excepciones para la evaluación inicial (usando los subfiltros). Gráficamente, la tabla temporal tiene el siguiente aspecto:


Representación gráfica del filtro temporal principal

El puntero time0 se encuentra en el contenedor de los filtros temporales. Tomando en cuenta la ilustración de arriba, eso se comprueba en primer lugar. Dado que time0 siempre devuelve false, van a comprobarse sus subfiltros. En primer lugar, se comprueba si la hora se encuentra entre las 8:00 y las 12:00. Si no es así, se comprueba si se encuentra entre las 13:00 y las 17:00. Si alguna de estas verificaciones devuelve true, entonces time0 también devolverá true (de lo contrario, devolverá false). Por eso, si la hora se encuentra en uno de los intervalos indicados, finalmente seré devuelto true.

Se puede utilizar más de dos subfiltros, en este caso, seguiremos el mismo algoritmo. No obstante, los subfiltros de los subfiltros, seguramente, no serán necesarios, ya que las combinaciones del filtro del trading intradía pueden representarse a través de dos niveles.

Ejemplo

Como ejemplo, vamos a modificar el EA del artículo anterior. Incluiremos todos los filtros temporales que hemos discutido en este artículo. Empezaremos con la inclusión del archivo de cabecera para CTimesBase, ya que eso será suficiente para añadir todas las clases de los filtros temporales al EA.

#include "MQLx\Base\OrderManager\OrderManagerBase.mqh"
#include "MQLx\Base\Signal\SignalsBase.mqh"
#include "MQLx\Base\Time\TimesBase.mqh" //added include line
#include <Indicators\Custom.mqh>

Contenedores de los filtros temporales

CTimes *time_filters;

Crearemos la instancia CTimes en OnInit y la guardaremos en este puntero:

time_filters = new CTimes();

Nosotros vamos a aplicar la filtración temporal para este EA solamente cuando entramos en nuevas operaciones, evitando las salidas de ellas. Para conseguir eso, antes de que el EA abra la transacción, se realiza una verificación adicional, ¿se permite al EA entran en la operación en este momento?

if(signals.CheckOpenLong())
  {
   close_last();
   if (time_filters.Evaluate(TimeCurrent()))
   {
      //Print("Entering buy trade..");            
      money_manager.Selected(0);
      order_manager.TradeOpen(Symbol(),ORDER_TYPE_BUY,symbol_info.Ask());
   }
  }
else if(signals.CheckOpenShort())
  {
   close_last();
   if (time_filters.Evaluate(TimeCurrent()))
   {
      //Print("Entering sell trade..");
      money_manager.Selected(1);
      order_manager.TradeOpen(Symbol(),ORDER_TYPE_SELL,symbol_info.Bid());
   }
  }

Como se muestra en el código de arriba, la salida de la última posición se invoca antes de la comprobación de la hora y antes de la apertura real de la nueva transacción. Por tanto, los filtros temporales se aplican sólo al abrir la transacción, y no al cerrarla.

Para la filtración temporal usando un intervalo de las fechas, nosotros vamos a dar los siguientes parámetros de entrada: la fecha inicial y la fecha final. Estos parámetros tienen el tipo datetime. Además, necesitaremos los parámetros que permiten al usuario activar o desactivar esta función:

input bool time_range_enabled = true;
input datetime time_range_start = 0;
input datetime time_range_end = 0;

El valor predefinido 0 para estos parámetros significa que al principio ellos se dirigen a la hora UNIX. Para evitar un error ocasional durante el uso de los valores predefinidos, vamos a introducir una condición adicional: el filtro temporal se crea solamente cuando la hora de finalización es mayor que 0 y mayor que la hora inicial. Esto está codificado en la función OnInit del Asesor Experto:

if (time_range_enabled && time_range_end>0 && time_range_end>time_range_start)
 {
     CTimeRange *timerange = new CTimeRange(time_range_start,time_range_end);
     time_filters.Add(GetPointer(timerange));
 }    

Además, en el fragmento del código de arriba se muestra como el puntero al objeto se añade al contenedor del filtro temporal.

Para el filtro temporal por días de trading nos harán falta 7 diferentes parámetros, cada uno de los cuales representará uno de los días de la semana. Además, vamos a introducir el parámetro para activar/desactivar esta función:

input bool time_days_enabled = true;
input bool sunday_enabled = false;
input bool monday_enabled = true;
input bool tuesday_enabled = true;
input bool wednesday_enabled = true;
input bool thursday_enabled = true;
input bool friday_enabled = false;
input bool saturday_enabled = false;

En la función OnInit, también creamos la nueva instancia del filtro temporal y la añadimos al contenedor si la función está activada:

if (time_days_enabled)
 {
    CTimeDays *timedays = new CTimeDays(sunday_enabled,monday_enabled,tuesday_enabled,wednesday_enabled,thursday_enabled,friday_enabled,saturday_enabled);
    time_filters.Add(GetPointer(timedays));
 }

Para el temporizador, declaramos sólo un parámetro que representa el tiempo total del filtro hasta la expiración, en adición al parámetro de activación o desactivación de la función.

input bool timer_enabled= true;
input int timer_minutes = 10080;

Igualmente como en los filtros ya descritos, creamos la nueva instancia de CTimer y la añadimos al contenedor si la función está activada:

if(timer_enabled)
  {
   CTimer *timer=new CTimer(timer_minutes*60);
   timer.TimeStart(TimeCurrent());
   time_filters.Add(GetPointer(timer));
  }

La filtración por la hora intradía es un poco más compleja. Vamos a demostrar las posibilidades de la filtración temporal en el EA, basada en tres escenarios diferentes:

  1. cuando el inicio y la finalización caen en el mismo día;
  2. cuando el inicio y la finalización caen en días diferentes;
  3. varias instancias de CTimeFilter

Los escenarios №1 y №2 se puede demostrar usando el mismo conjunto de parámetros. Si la hora inicial es menor que la hora final (escenario №1), simplemente invertimos los valores, y obtenemos el escenario №2.

Para el escenario №3, necesitamos dos o más instancias, preferentemente con valores diferentes. Por tanto, necesitaremos por lo menos dos conjuntos de la hora inicial y final dentro del período de 24 horas. Para conseguir eso, primero declaramos una enumeración personalizada con tres configuraciones posibles: desactivado, escenario №1/№2 y escenario №3.

enum ENUM_INTRADAY_SET 
  {
   INTRADAY_SET_NONE=0,
   INTRADAY_SET_1,
   INTRADAY_SET_2
  };

Luego, declaramos los parámetros:

input ENUM_INTRADAY_SET time_intraday_set=INTRADAY_SET_1;
input int time_intraday_gmt=0;
// 1st set
input int intraday1_hour_start=8;
input int intraday1_minute_start=0;
input int intraday1_hour_end=17;
input int intraday1_minute_end=0;
// 2nd set
input int intraday2_hour1_start=8;
input int intraday2_minute1_start=0;
input int intraday2_hour1_end=12;
input int intraday2_minute1_end=0;
// 3rd set
input int intraday2_hour2_start=13;
input int intraday2_minute2_start=0;
input int intraday2_hour2_end=17;
input int intraday2_minute2_end=0;

Para inicializar este filtro temporal, usamos el operador switch. Si time_intraday_set está configurado en INTRADAY_SET_1, inicializamos una instancia de CTimeFilter y usamos el primer conjunto de parámetros. Si la configuración es para INTRADAY_SET_2, creamos dos instancias diferentes de CTimeFilter usando el segundo y el tercer conjunto de parámetros:
switch(time_intraday_set)
  {
   case INTRADAY_SET_1:
     {
      CTimeFilter *timefilter=new CTimeFilter(time_intraday_gmt,intraday1_hour_start,intraday1_hour_end,intraday1_minute_start,intraday1_minute_end);
      time_filters.Add(timefilter);
      break;
     }
   case INTRADAY_SET_2:
     {
      CTimeFilter *timefilter=new CTimeFilter(0,0,0);
      timefilter.Reverse(true);
      CTimeFilter *sub1 = new CTimeFilter(time_intraday_gmt,intraday2_hour1_start,intraday2_hour1_end,intraday2_minute1_start,intraday2_minute1_end);
      CTimeFilter *sub2 = new CTimeFilter(time_intraday_gmt,intraday2_hour2_start,intraday2_hour2_end,intraday2_minute2_start,intraday2_minute2_end);
      timefilter.AddFilter(sub1);
      timefilter.AddFilter(sub2);
      time_filters.Add(timefilter);
      break;
     }
   default: break;
  }

Pues bien, hemos escrito el código para la creación de todas las clases de los filtros temporales. Ahora inicializamos los contenedores de los filtros temporales, CTimes. Primero, asignamos el puntero al gestor de los símbolos (no es necesario en este ejemplo, pero puede ser necesario si los filtros temporales tienen que ser extendidos), luego, comprobamos sus configuraciones:

time_filters.Init(GetPointer(symbol_manager));
if(!time_filters.Validate())
  {
   Print("one or more time filters failed validation");
   return INIT_FAILED;
  }

Ahora, vamos a pasar a los resultados de la simulación del EA. Las pruebas han sido realizadas usando el Probador de estrategias a base de los datos del enero de 2017.

Los resultados de las pruebas del EA con la filtración por el intervalo del tiempo se encuentra en el archivo adjunto al artículo (tester_time_range.html). En esta prueba, el intervalo de tiempo se empieza con el comienzo del año 2017 y se termina el primer viernes del mes, es decir el 6 de enero de 2017. O sea, podemos decir que el filtro funciona cuando el EA ya no puede abrir las operaciones después de la fecha final del intervalo temporal. Aquí tenemos la captura de pantalla de la última transacción:

time days last trade

La última transacción de la prueba fue abierta el 6 de enero a las 15:00, encontrándose dentro del intervalo temporal determinado para el EA. Obsérvese que la transacción permanece abierta hasta la siguiente semana, lo que es aceptable ya que los filtros temporales funcionan solamente para la entrada en el mercado, pero no para la salida de él. La línea vertical punteada representa la última vela de la semana.

Para la filtración por días, hay que activar el parámetro correspondiente (poner true). Además, la filtración por el intervalo temporal queda activada pero el parámetro Friday (que reglamenta el trabajo el viernes) está desactivado. La prueba anterior demostró que la última operación fue abierta el 6 de enero, el viernes. De esta manera, si vemos que esta operación ya no puede ser abierta en el Probador, podemos confirmar que este filtro también funciona.

El resultado de la prueba se muestra en el archivo adjunto al artículo (tester_time_days.html). Aquí tenemos la captura de pantalla de la última transacción:

time days last trade


Como podemos ver, la última transacción fue realizada el 5 de enero, y no el día 6 de enero, como en la ilustración anterior. En esta nueva configuración, la penúltima transacción se convierte en la última. Su punto de salida coincide con la segunda línea vertical, que también es el punto de entrada de la última transacción para la prueba anterior porque el EA siempre tiene una posición abierta.

Para el filtro temporal, como fue mostrado antes, usábamos el constructor alternativo de CTimer, que recibía sólo un argumento. Luego, se guarda en el miembro de la clase m_total, que representa el número de segundos durante el cual el filtro devuelve true hasta el tiempo de expiración. Puesto que este tiempo se expresa en segundos, tenemos que multiplicar los parámetros de entrada por 60, y entonces, los valores también van a guardarse en términos de segundos. 10080 es el número de minutos establecido en el EA por defecto. Equivale a una semana. De esta manera, si juntamos el primer filtro temporal con éste, el resultado de la prueba con el uso del primer filtro tiene que ser idéntico a los resultados de este filtro. Es verdad que los resultado de la prueba coinciden. Se muestra al final del artículo (tester_timer.html).

Para terminar la filtración temporal CTimeFilter, hay tres casos diferentes. Tenemos que probar cada uno de ellos.

Puesto que el EA siempre mantiene una posición en cualquier momento, tiene que cerrar las transacciones anteriores antes de abrir una nueva, para cerrarla y abrir una nueva, y así constantemente. La excepción será la situación cuando uno o más filtros temporales devuelven false. Por tanto, si el trading durante el período intradía ha sido interrumpido, entonces los filtros temporales han impedido que el EA entre en la operación. Los resultados completos de la prueba sin los filtros temporales se muestran en el archivo adjundo al artículo (tester_full.html).

Para el escenario №1 por la filtración intradía descrita en este artículo, ponemos la hora inicial a las 08:00, y la hora final, a las 17:00. En la prueba completa, la primera transacción fue abierta directamente al principio de la simulación, que fue a las 00:00. Eso sale fuera de los marcos temporales establecidos por el primer escenario. De acuerdo con estas configuraciones, se espera que el EA no acepte esta operación y en vez de ella, abrirá la siguiente ya dentro de los límites del filtro temporal. Va a considerarse como la primera transacción aplicando el filtro. El resultado de la simulación aplicando estas configuraciones se adjunta al final del artículo (tester_time_hour1.html), y la captura de pantalla de su primera operación se muestra más abajo:


time hour trades 3

Como se esperaba, el EA no empezó a tradear el principio de la prueba. En vez de eso, esperó a que la operación entrase en el intervalo intradía definido. La línea vertical punteada representa el inicio de la prueba donde se ubicaría la primera transacción si el filtro no fuera aplicado.

Para el segundo escenario, simplemente alternamos la hora inicial y final del filtro temporal, y como resultado obtenemos el inicio a las 17:00, y el final, a las 8:00. En la prueba completa, sin la aplicación de los filtros, podemos ver la primera operación que no entra en el intervalo intradía el 3 de enero a las 10:00. Las 10:00 es más que las 08:00, y menos que las 17:00. Por eso, la transacción en esta vela no entra en nuestro intervalo intradía. El resultado de la simulación también se adjunta al final del artículo (tester_time_hour2.html), y la captura de pantalla de la operación se muestra más abajo:


time hour2 trades

Como podemos ver en la captura de pantalla, el EA cerró la orden anterior pero no abrió una nueva. La línea vertical punteada representa el inicio de una nueva sesión de trading. El EA abre la primera transacción de la sesión intradía dentro de tres velas a partir de su comienzo.

Para el tercer escenario, modificamos el EA de tal manera que utilice las configuraciones del primer escenario, excluyendo la hora de la comida. Por tanto, en la vela de las 12:00 no se ejecuta ninguna operación, y el EA vuelve a tradear con el inicio de la vela de las 13:00. En la prueba completa sin el uso de los filtros, vemos la instancia que abre la operación a las 12:00 el 9 de enero. Puesto que la operación cae a las 12:00, el EA no va a entrar en ella usando estas configuraciones. El resultado de la prueba con estas configuraciones se adjunta al final del artículo (tester_time_hour3.html), la imagen con esta transacción se muestra a continuación:

time hour3 trades

Como se puede ver en la imagen, en la vela de las 12:00 el EA cierra la operación actual buy, pero no entra en la siguiente operación. El EA se queda hibernado durante una hora, como se esperaba. Abre la siguiente posición de compra sólo a las 16:00, dentro de tres horas después de la comida, y una hora antes del fin de la sesión comercial. Es la hora más temprana cuando el EA pudo entrar en el mercado a base de la señal de reversa.

Conclusión

En este artículo hemos discutido la implementación de diferentes métodos de la filtración temporal en el Asesor Experto multiplataforma. El artículo abarca diferentes filtros temporales, así como los modos de su combinación con el uso de los contenedores de los filtros que permiten activar y desactivarlos en el EA, dependiendo de una determinada configuración de tiempo.