Expert Advisor Multiplataforma: Filtros de Tempo

Enrico Lambino | 28 agosto, 2017


Índice

  1. Introdução
  2. Objetivos
  3. Classe Base
  4. Classes e Tipos de Filtro de Tempo
  5. Container de Filtros de Tempo
  6. Sub-Filtros (CTimeFilter)
  7. Exemplo
  8. Conclusão

Introdução

A filtragem de tempo é usada quando um certo período de tempo é definido, necessitando que o Expert Advisor verifique se um determinado momento corresponde ao período de tempo definido. Certos recursos podem ser ativados ou desativados quando a condição for satisfeita ou não. Isso é muito útil quando uma determinada característica de um Expert Advisor está definido para funcionar em horários específicos (periodicamente, ou funcionar em todos os momentos com algumas exceções). Abaixo estão alguns exemplos em que a filtragem de tempo pode ser aplicada:

  1. Evitando certos períodos de tempo (por exemplo, períodos de lateralização do mercado ou de alta volatilidade)
  2. Definir uma "expiração" para uma ordem a mercado ou posição (encerrando-se no horário de expiração)
  3. Encerrar posições no fim da semana de negociação

Estas são algumas das características mais comuns que os traders usam, embora existam outras variações.

Objetivos

  • Compreenda e aplique os métodos mais comuns na filtragem de tempo
  • Permita que os Expert Advisors usem facilmente os múltiplos filtros de tempo
  • Ser compatível com a MQL4 e MQL5

Classe Base

A classe chamada CTime servirá como classe base para outros objetos de filtro de tempo discutidos para o nosso Expert Advisor. A definição da classe CTimeBase (onde CTime é baseada) é exibida no seguinte trecho de código:

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;}
   //--- Inicialização
   virtual bool      Init(CSymbolManager*,CEventAggregator*);
   virtual CObject *GetContainer(void);
   virtual void      SetContainer(CObject*);
   virtual bool      Validate(void);
   //--- setters e getters
   bool              Active(void) const;
   void              Active(const bool);
   bool              Reverse(void);
   void              Reverse(const bool);
   //--- verificação
   virtual bool      Evaluate(datetime)=0;
  };

A classe base possui 3 membros de tipos de dados primitivos. m_active é usado para habilitar ou desativar o objeto de classe. m_reverse é usado para reverter a saída do objeto de classe (retornar true se a saída original for false ou retornar false se a saída original for true). m_time_start é usado para referir à criação da intenção da classe, seja se foi criada durante a OnInit ou posterior na execução do Expert Advisor.

Classes e Tipos de Filtro de Tempo

Filtragem de tempo por um Determinado Intervalo de Tempo

Este é o método mais simples de filtragem de tempo. Para verificar o horário usando este método, é necessário apenas das datas de início e fim, e se o horário definido cair entre essas datas, a saída retorna true. Caso contrário, a saída retorna false.

Esse método é implementado como CTimeRange. O código a seguir mostra a definição para a CTimeRangeBase, a partir da qual o CTimeRange se baseia:

class CTimeRangeBase : public CTime
  {
protected:
   datetime          m_begin;
   datetime          m_end;
public:
                     CTimeRangeBase(void);
                     CTimeRangeBase(datetime,datetime);
                    ~CTimeRangeBase(void);
   //--- inicialização de                    datetime,datetime
   virtual bool      Set(datetime,datetime);
   virtual bool      Validate(void);
   //--- setters e getters
   datetime          Begin(void) const;
   void              Begin(const datetime);
   datetime          End(void) const;
   void              End(const datetime);
   //--- processando
   virtual bool      Evaluate(datetime);
  };

No construtor da classe, o horário de início e término devem ser especificados. O horário real a ser comparado com esses dois valores é definido na chamada do método de classe Evaluate. Se o horário ajustado não for definido ou for zero, o método usa a hora atual no momento da chamada:

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

Filtragem de Tempo pelo Dia da Semana

A filtragem pelo dia da semana é um dos métodos mais simples e mais comuns de filtragem de tempo. É comum usar esse filtro de tempo para limitar ou permitir algumas funções do Expert Advisor em determinados dias da semana.

Agora, essa classe específica pode ser implementada de várias maneiras. Um método é fornecer uma função personalizada para TimeDayOfWeek, que está disponível na MQL4, mas não na MQL5. Outro método é converter o horário que será verificado na estrutura MqlDateTime, e depois verificar se o parâmetro day_of_week está definido nos sinalizadores previamente definidos. O último método é selecionado e é recomendado, pois nos permite colocar todos os métodos de classe utilizáveis ​​dentro da classe base.

Este método é representado em nosso Expert Advisor como CTimeDays. O código a seguir mostra a definição da CTimeDaysBase, a partir da qual a CTimeDays se baseia:

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);
   //--- Inicialização                    
   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);
   //--- setters e getters
   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);
  };

Conforme exibido na definição, a classe possui apenas um membro da classe do tipo long. Este é o membro que a classe usará ao definir sinalizadores para os dias em que ele deve retornar true quando for avaliado (7 dias da semana). Está implícito que nós vamos utilizar operadores bitwise, por isso, nós também temos que declarar uma enumeração personalizada cujos membros representam cada um dos 7 dias:

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

Os sinalizadores dos dias da semana são definidos (ou não definidos) usando o método Set. Por conveniência, este método é chamado em um dos construtores de sua classe como uma medida para evitar uma definição acidental da instância da classe sem primeiro definir os sinalizadores.

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

Os sinalizadores também podem ser configurados individualmente. Isso é útil quando é necessário modificar apenas uma única sinalização, em vez de chamar a função Set que define os sinalizadores durante todos os 7 dias (o que pode ser propenso a erros em algumas situações). O seguinte trecho de código mostra o método chamado Monday (segunda-feira), que é usado para definir/resetar o segundo dia dos sinalizadores. Os métodos setters e getters para os outros dias também são codificados da mesma forma.

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

Com os métodos para definir os sinalizadores implementados, o nosso próximo método lida com a avaliação atual do filtro, ou seja, verificar se um determinado momento corresponde a um determinado dia da semana ou não:

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 discutido brevemente anteriormente, o método primeiro obtém um parâmetro do tipo datetime. Se o argumento não estiver na chamada do método, o método usará a hora atual. Em seguida, ele transforma esse tempo no formato MqlDateTime e obtém o membro day_of_week, que é avaliado em relação ao valor atual do único membro da classe (m_day_flags).

Este método é frequentemente usado para satisfazer os requisitos dos traders, como "sem negociação às sextas", ou mesmo nos "domingos" quando suas corretoras estão ativas em dias específicos que o trader considera indesejáveis ​​para negociação.

Usando um Timer

Outro método de filtragem de tempo é o uso de um timer. Em um timer, a hora atual é comparada com um certo ponto no passado. Se o tempo ainda estiver dentro da expiração após a avaliação, ele deve retornar true, caso contrário, false. Este método é representado pela classe CTimer. O seguinte trecho mostra o código para a definição da CTimerBase, da qual a CTimer se baseia:

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);
   //--- Inicialização
   virtual bool      Set(const uint,const uint,const uint,const uint,const uint,const uint);
   virtual bool      Validate(void);
   //--- getters e setters
   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);
   //--- processando   
   virtual bool      Elapsed(void) const;
   virtual bool      Evaluate(datetime);
   virtual void      RecalculateTotal(void);
  };

Os argumentos do construtor são usados ​​para construir o tempo total decorrido ou a expiração da hora de início, que é armazenada no membro da classe m_total. Por conveniência, nós vamos declarar constantes com base no número de segundos para determinados períodos de tempo, de um ano até um minuto:

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

O construtor da classe requer o tempo de expiração do timer, expresso de anos 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);
  }

Alternativamente, nós podemos construir uma instância da classe usando o valor preferido para m_total como um único argumento:

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

Chamando o método Evaluate da classe, logo após instanciação da classe, resultaria no método comparando m_total ao início do horário em UNIX, a partir do qual o tipo de dados se baseia. Assim, antes de chamar o método Evaluate, é necessário definir a hora de início desejada (a menos que a hora de início definida seja 1 de janeiro de 1970 (meia-noite UTC/GMT). A seguir é exibido os métodos setter e getter para o membro da classe m_time_start, usando a sobrecarga do método TimeStart:

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

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

O método Evaluate desta classe é bem simples: Ele obtém a diferença entre a hora de início e o tempo decorrido como argumento do método (geralmente, o horário atual). Este é o tempo decorrido, e se o tempo decorrido exceder o tempo total permitido (m_total), o método retorna 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;
  }

Esse método de filtragem de tempo é usado de várias maneiras, como definir um período máximo (expiração) para determinados recursos de um Expert Advisor e definir a "expiração" de uma ordem a mercado ou posição (semelhante à negociação de opções binárias). Este filtro de tempo é praticamente o mesmo que usar o evento do timer (que é compatível tanto com a MQL4 quanto com a MQL5), mas como somente um evento de timer pode ser configurado usando esta função de evento, o CTimer pode ser necessário somente se os timers extras forem realmente necessários para o Expert Advisor.

Filtragem Usando um Cronograma de Tempo Intradiário

A filtragem usando um cronograma de tempo intradiário é uma das mais populares usadas pelos traders. O filtro usa um cronograma de 24 horas e o Expert Advisor seleciona (por meio de seus parâmetros) determinados horários pelos quais o Expert Advisor pode executar uma operação (geralmente, negociando com base nos sinais de entrada). Esse método de filtragem é representado pela classe CTimeFilter. O código a seguir exibe a definição para a CTimeFilterBase, da qual a CTimeFilter se baseia.

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

A classe tem dois membros do tipo MqlDateTime, e um membro da classe CArrayObj. As duas estruturas são usadas para conter o intervalo dentro de um período de 24 horas, enquanto objeto membro é usado para armazenar seus sub-filtros.

Através do construtor de objeto de classe, nós obtemos o horário de início e término em horas, minutos e segundos. Esses valores são eventualmente armazenados nos membros da classe m_filter_start e m_filter_end através do método Set da classe. O parâmetro gmt é usado para contabilizar o deslocamento GMT da corretora (fuso horário).

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

Em seguida, nós procedemos com o método Evaluate da classe. Na inicialização, os dados nos dois parâmetros da MqlDateTime são expressos somente em termos de horas, minutos e segundos em um período de 24 horas. Ele não contém outros dados, como o ano e o mês. Para comparar as horas de início e término com o tempo indicado (ou a hora atual, se o argumento do método for padrão). Existem pelo menos dois métodos para fazer isso:

  1. expresse o tempo indicado em termos de horas, minutos e segundos e, em seguida, compare estes com os parâmetros da estrutura.
  2. atualize os parâmetros da estrutura ausente usando a hora atual, converta as estruturas em UNIX (tipo datetime), e compare-os com o horário indicado.

O segundo método foi escolhido para ser implementado no método Evaluate, que é mostrado abaixo:

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;
   /*
     outras tarefas aqui
   */
  }

A comparação é exclusiva do horário de término. Isso significa que, se usarmos esta classe para que um Expert Advisor negocie somente entre as 08:00 e as 17:00, o Expert Advisor pode começar a operar já às 08:00, logo no início dessa vela, mas ele poderá negociar até às 17:00, o que significa que ele pode realizar a sua última negociação até às 13:59.

Uma vez que as estruturas não contêm dados maiores do que as horas, os dados ausentes precisarão ser recuperados do horário atual (ou do horário indicado). No entanto, alguns ajustes são necessários quando os horários de início e término estão dentro de um período de 24 horas, mas não pertencem ao mesmo dia. No exemplo acima, 08:00 são 8:00 da manhã enquanto que 17:00 são 5:00 PM. Neste caso, ambos os horários se encontram no mesmo dia. No entanto, suponha que trocamos os dois, com a hora de início às 5:00 da tarde e o horário final às 8:00 da manhã. Se o horário de início for superior ao horário final, significa que o intervalo de tempo se estende até o dia seguinte. Assim, o tempo de término não está no mesmo dia da hora de início. A situação é então uma das duas:
  1. A hora de início é a partir do dia atual (ou o dia do horário indiciado), o horário final é no dia seguinte.
  2. A hora de início é do dia anterior (o dia anterior ao horário indicado), o horário final é do dia atual (ou o dia do horário indicado).

O ajuste necessário dependerá da hora atual ou do horário indicado. Suponhamos que tenhamos uma horário indicando (ou hora atual) as 5:01 PM (17:01). Neste caso, o horário de início é no mesmo dia que o horário indicado. Aqui, nós temos certeza de que o horário final pertence ao dia seguinte. Por outro lado, se tivermos 01:00 ou 1:00 da manhã como o horário indicado, a horário indicado é no mesmo dia que o horário de término, enquanto que o horário de início é de ontem. Portanto, as estruturas MqlDateTime calculadas anteriormente devem ser ajustadas da seguinte maneira:

  1. Se o horário de início estiver no mesmo dia que o horário indicado, adicione 1 dia ao horário final.
  2. Se o horário de término estiver no mesmo dia que o horário indicado, subtraia 1 dia do horário de início.

Estes só se aplicam quando o horário de início e o horário de término não pertencem ao mesmo dia, que é quando o horário de início é maior do que o horário final no filtro. Os ajustes são implementados dentro do método Evaluate da seguinte maneira:

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

A variável de retorno é inicialmente definida como true, de modo que a verificação real do filtro de tempo dependerá se o horário indicado corresponde ao horário de início e o horário de término. O cálculo no método Evaluate garante que o horário de início sempre seja menor ou igual ao horário final. Se o horário de início for igual ao horário final (igual em termos de horas, minutos e segundos), o método continuará a ser true. Por exemplo, se o horário de início for as 05:00 e o horário de término também for as 05:00, o filtro trataria isso como se as duas vezes não correspondessem ao mesmo dia, caso em que o filtro abrange todo o período de 24 horas.

Container de Filtros de Tempo

Semelhante a outros objetos de classe discutidos nesta série, os filtros de tempo também teriam um container onde seus ponteiros são armazenados. Isso permitiria que a avaliação seja realizada chamando o método Evaluate deste container. Se o método Evaluate de todos os filtros de tempo retornar true (não há objeções no que diz respeito à filtragem de tempo), esse container também deve retornar true. Isso é implementado pela classe CTimes. O código a seguir mostra a definição da CTimesBase, em que a CTimes se baseia:

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;}
   //--- inicialização
   virtual bool      Init(CSymbolManager*,CEventAggregator*);
   virtual CObject *GetContainer(void);
   virtual void      SetContainer(CObject*);
   virtual bool      Validate(void) const;
   //--- ativação e desativação
   bool              Active(void) const;
   void              Active(const bool);
   int               Selected(void);
   //--- verificação
   virtual bool      Evaluate(datetime) const;
   //--- recuperação
   virtual bool      CreateElement(const int);
  };

Sub-Filtros (CTimeFilter)

O método Evaluate do container de filtros de tempo requer que, para que ele retorne true, todos os seus membros principais devem retornar o mesmo também. Embora a maioria dos objetos de filtro de tempo não exijam mais do que uma instância no mesmo Expert Advisor, uma exceção a isso é a CTimeFilter, que é, aliás, o filtro de tempo mais usado de todos. Considere o seguinte código:

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

Vamos assumir que o filtro de tempo é usado para a negociação (entrada). Nesse caso, o container de filtros de tempo contém apenas um ponteiro em seu array de ponteiro dinâmico. Sob esta configuração, quando o método Evaluate de horário é chamado, o resultado final dependerá se o tempo indicado corresponde entre as 08:00 e as 17:00.

Agora, considere o caso quando, ao invés de negociar das 8:00 da manhã às 5:00 da tarde, o Expert Advisor foi configurado para ignorar o horário do almoço. Ou seja, ele negocia das 8:00 da manhã ao 12:00 PM e entre às 13:00 até as 17:00 da tarde. Agora, a linha de tempo não é mais contínua, mas em vez disso ela é dividida em duas. O programador pode ser tentado a alterar o código inicial usando duas instâncias da CTimeFilter em vez de apenas uma:

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

O código acima não funcionará corretamente. Ele sempre retornará false desde que o container de filtros de tempo requer todas as instâncias primárias de filtro de tempo para retornar true. Na configuração acima quando alguém retorna true, o outro retorna false e vice-versa. A situação é ainda mais complicada se houver mais de 2 filtros de tempo envolvidos. Usando esta configuração, a única maneira de funcionar corretamente é quando um filtro está ativo e o resto está desativado.

A solução seria garantir sempre que o container de filtros de tempo só deve armazenar no máximo um ponteiro para a CTimeFilter. Se for necessário mais de um instância da CTimeFilter, ele deve ser adicionado como um sub-filtro de outra instância da CTimeFilter. Os ponteiros para os sub-filtros são armazenados dentro de um membro da classe CTimeFilter, m_time_filters, e os ponteiros são adicionados através do método AddFilter. O código para avaliar os sub-filtros pode ser encontrado dentro do método Evaluate da classe, e é exibido abaixo:

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

Este código só é executado se o filtro principal retornar false ou seja, um método de último recurso para ver se existe uma exceção à avaliação inicial. Se pelo menos um sub-filtro retornar true, o filtro principal sempre retornará true. Com isso, nós modificamos o exemplo do código anterior da seguinte maneira:

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));
Dadas essas alterações, o objeto de filtros de tempo contém apenas um ponteiro em seu array, que é o time0. time0, por outro lado, tem dois sub-filtros, time1 e time2, que estavam originalmente no container de filtros de tempo. time0 tem os mesmos parâmetros para os horários de início e fim e, portanto, sempre retornará true. Nós chamamos o método Reverse para que time0 sempre retorne false, forçando-o a verificar se há exceções à avaliação inicial (através de seus sub-filtros). Quando representado graficamente, nós podemos ver o cronograma da seguinte maneira:


Representação Gráfica do Filtro de Tempo Principal e dos Sub-Filtros

Um ponteiro para o time0 pode ser encontrado dentro do container de filtros de tempo. Dada a ilustração acima, isso seria avaliado primeiro. Como o time0 sempre retorna false, ele verificaria seus sub-filtros. Primeiro, ele verificaria se o horário está entre as 8:00 e as 12:00. Se não estiver, então verificaria se o horário indicado está entre as 13:00 e 17:00. Se qualquer um desses retornar true, então o time0 também retornará verdadeiro (caso contrário, false). Portanto, a linha de tempo final retornaria true se o horário indicado for entre as 8:00 e 12:00, ou 13:00 e 17:00. Mais do que dois sub-filtros são possíveis, e ainda seguiria a mesma regra exibida acima. No entanto, os sub-filtros de sub-filtros provavelmente não são necessários, pois as combinações de filtros de tempo intradiário podem ser representadas somente em dois níveis.

Exemplo

Como exemplo, nós modificaremos o exemplo do Expert Advisor do artigo anterior. Neste Expert Advisor, nós incluiremos todos os filtros de tempo que foram discutidos no artigo. Nós começamos por incluir o arquivo de cabeçalho para a CTimesBase, pois isso é suficiente para o nosso Expert Advisor incluir todas as classes de filtro de tempo.

#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>

Em seguida, nós declaramos um ponteiro global para o container de filtros de tempo:

CTimes *time_filters;

Sob a OnInit, nós criamos uma instância da CTimes e depois armazenamos ela neste ponteiro:

time_filters = new CTimes();

Para este Expert Advisor, nós aplicaremos a filtragem de tempo para a entrada de novos negócios apenas, não com a saída. Para conseguir isso, antes que o Expert Advisor entre em uma negociação, é realizado uma verificação extra para ver se o EA está realmente autorizado a abrir uma posição naquele momento:

if(signals.CheckOpenLong())
  {
   close_last();
   if (time_filters.Evaluate(TimeCurrent()))
   {
      //Print("Abrindo uma posição de compra..");            
      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("Abrindo uma posição de venda..");
      money_manager.Selected(1);
      order_manager.TradeOpen(Symbol(),ORDER_TYPE_SELL,symbol_info.Bid());
   }
  }

Conforme exibido no código acima, a saída da última posição é chamada antes para verificação do horário e da entrada atual da nova operação e, portanto, os filtros de tempo só se aplicam à entrada e não à saída dos negócios existentes.

Para a filtragem de tempo usando um intervalo de datas, nós forneceremos os parâmetros de entrada para a data de início e fim. Esses parâmetros são do tipo datetime. Nós também fornecemos parâmetros que permitirão ao usuário ativar ou desativar esse recurso:

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

Um valor padrão de zero para ambos os parâmetros significa que estes se referem ao início do tempo UNIX. Para evitar erros acidentais na utilização dos valores padrão, nós forneceremos uma medida extra para que o filtro de tempo seja criado somente quando o tempo de término for maior que zero e que o tempo de término seja maior que a hora de início. Isso será codificado na função OnInit do EA:

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

No código acima também é encontrado a adição real do ponteiro do objeto no container de filtros de tempo.

Para a filtragem de tempo por dia de negociação, nós fornecemos 7 parâmetros diferentes, cada um representando um dia específico dentro da semana. Nós também fornecemos um parâmetro para ativar ou desativar esta função:

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;

Sob a função OnInit, nós também criamos uma nova instância do filtro de tempo e adicionamos ela também ao container se o recurso estiver habilitado:

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 o timer, nós declaramos apenas um parâmetro que é o tempo total para o filtro antes da expiração, além do parâmetro para ativar ou desativar a função:

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

Semelhante aos filtros anteriores discutidos anteriormente, nós criamos uma nova instância da CTimer e adicionamos o ponteiro ao container se o recurso estiver habilitado:

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

A filtragem do horário intradiário é um pouco mais complexa, pois nós queremos demonstrar a capacidade do EA na filtragem de tempo com base nos seguintes cenários:

  1. filtragem de tempo quando os horários de início e término se enquadram no mesmo dia
  2. filtragem de tempo quando os horários de início e término não se enquadram no mesmo dia
  3. várias instâncias da CTimeFilter

Os cenários #1 e #2 podem ser demonstrados usando o mesmo conjunto de parâmetros. Se o horário de início for inferior à hora final (cenário #1), nós simplesmente alteramos os valores dos dois, e nós obteríamos o cenário #2. Para o #3, no entanto, seria exigido duas ou mais instâncias, de preferência com valores diferentes e, portanto, nós precisaríamos de pelo menos dois horários de início e término em um período de 24 horas. Para conseguir isso, primeiro, nós declaramos uma enumeração personalizada com três configurações possíveis: desativado, cenário #1/# 2 e cenário #3:

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

Em seguida, nós declaramos os parâmetros da seguinte forma:

input ENUM_INTRADAY_SET time_intraday_set=INTRADAY_SET_1;
input int time_intraday_gmt=0;
// 1º conjunto
input int intraday1_hour_start=8;
input int intraday1_minute_start=0;
input int intraday1_hour_end=17;
input int intraday1_minute_end=0;
// 2º conjunto
input int intraday2_hour1_start=8;
input int intraday2_minute1_start=0;
input int intraday2_hour1_end=12;
input int intraday2_minute1_end=0;
// 3º conjunto
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 de tempo, nós usamos a instrução switch. Se o time_intraday_set estiver configurado para INTRADAY_SET_1, inicializaremos uma única instância da CTimeFilter usando o primeiro conjunto de parâmetros. Por outro lado, se a configuração for INTRADAY_SET_2, nós criamos duas instâncias diferentes da CTimeFilter usando os 2º e 3º conjuntos 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;
  }

Após todo o código para a instanciação das classes de filtro de tempo, nós inicializamos o container de filtros de tempo, CTimes. Primeiro, nós atribuímos um ponteiro ao gerenciador de símbolos (não é necessário neste exemplo, mas pode ser necessário no caso dos filtros de tempo precisarem ser estendidos) e depois verificar suas configurações:

time_filters.Init(GetPointer(symbol_manager));
if(!time_filters.Validate())
  {
   Print("Um ou mais filtros de tempo falharam na validação");
   return INIT_FAILED;
  }

Agora, vamos proceder aos resultados do teste do EA. Os testes foram realizados utilizando o testador de estratégia para todo o mês de janeiro de 2017.

O resultado do teste para executar o Expert Advisor com a filtragem por intervalo de tempo ativada pode ser encontrada na parte inferior deste artigo (tester_time_range.html). Sob este teste, o intervalo de tempo começa no início de 2017 e termina na primeira sexta-feira do mês, 06 de janeiro de 2017. Assim, nós podemos dizer que o filtro funciona quando o Expert Advisor já não entra em mais nenhuma operação após a data de término. Uma imagem da última negociação é exibida abaixo:

dia da última negociação

O último negócio do teste foi registrado em 01/06 as 15:00, que está dentro do limite estabelecido no Expert Advisor. Observe que o negócio permaneceu aberto até a próxima semana, o que ainda é aceitável desde que os filtros de tempo se aplique somente na entrada. A linha vertical pontilhada representa a última vela da semana.

Para a filtragem dos dias, o parâmetro de filtragem por dia é definido como true. Além disso, a filtragem por intervalo de datas permanece ativada, mas com o parâmetro Friday desativado. A última negociação do teste anterior mostra que o último negócio registrado foi em 01/06 (sexta-feira). Assim, se nós vemos que esse negócio já não está mais no teste, nós podemos confirmar que este filtro de tempo específico está funcionando. O resultado do teste também é exibido na parte inferior deste artigo (tester_time_days.html). Uma imagem do último negócio é exibido abaixo:

dia da última negociação


Conforme mostrado na imagem, o último negócio foi gerado em 01/05, contra o negócio em 01/06 na configuração anterior. Sob esta nova configuração, o penúltimo negócio é agora o último do teste. Seu ponto de saída coincide com a segunda linha vertical, que também é o ponto de entrada do último negócio do teste anterior, uma vez que o EA está preparado para manter uma posição (compra ou venda) aberta em todos os momentos.

Para o filtro do timer, como é exibido anteriormente, nós utilizamos o construtor alternativo da CTimer, que só aceita um único argumento. Isso é armazenado no membro da classe m_total que representa o número de segundos que o filtro retornará true antes da expiração. Uma vez que ela é expressa em segundos, nós temos que multiplicar o parâmetro de entrada por 60 para que o valor armazenado seja em termos de segundos. 10080 é a quantidade padrão de minutos para o EA, que é equivalente a 1 semana. Assim, se combinarmos o primeiro filtro de tempo com este filtro, o resultado do teste usando o primeiro filtro deve ser idêntico ao resultado desse filtro. O resultado do teste é realmente idêntico ao primeiro, e é fornecido no final deste artigo (tester_timer.html).

Para o filtro de tempo final, que é a CTimeFilter, ele possui três casos diferentes, assim, nós precisamos testar cada um deles também. Uma vez que o EA sempre mantém uma posição a todo momento, ele é obrigado a fechar a operação anterior e abrir uma nova, apenas para fechá-la novamente e abrir uma nova, e assim por diante. A exceção a isso é quando um ou mais filtros de tempo retornam false. Assim, se uma negociação desaparecer dentro de um período intradiário, o EA foi impedido pelos filtros de tempo a entrarem em uma negociação. O resultado do teste completo sem filtros de tempo é fornecido no final deste artigo (tester_full.html).

Para o cenário #1 na filtragem de tempo intradiária mencionada neste artigo, nós definimos o horário de início às 08:00 e o horário final às 17:00. No teste completo, a primeira negociação foi registrada no início do teste, que cai no primeiro horário (00:00). Isso está fora dos limites definidos para o primeiro cenário. Dadas essas configurações, espera-se que o EA não faça essa negociação, mas que faça a próxima, correspondendo ao filtro de tempo como a primeira negociação com os filtros aplicados. O resultado do teste através desta configuração é fornecido no final deste artigo (tester_time_hour1.html), e uma imagem de sua primeira operação é exibida abaixo:


Negociação do horário 3

Como esperado, o EA não aceitou a negociação no início do teste. Em vez disso, ele esperou até que o intervalo intradiário estabelecido seja alcançado. A linha vertical pontilhada representa o início do teste, onde a primeira operação do teste completo (sem filtros) pode ser encontrado.

Para o segundo cenário, nós simplesmente alternamos as horas de início e término para o filtro, resultando na hora de início as 17:00 e no final as 08:00. No teste completo (sem filtros), nós podemos encontrar o primeiro negócio que não se enquadra na faixa intradiária em 01/03 10:00. 10:00 é maior do que 08:00 e menor que 17:00, e por isso nós estamos certos de que a negociação nesta vela está além da faixa intradiária. O resultado do teste através desta configuração é fornecido no final deste artigo (tester_time_hour2.html) e uma imagem da negociação é exibida abaixo:


Negociações pelo horário 2

Como nós podemos ver a partir da imagem, o EA fechou a ordem anterior, mas não abriu uma nova. A linha vertical pontilhada representa o início da nova sessão. O EA abriu o primeiro negócio para a sessão intradiária 3 velas após o início dessa sessão.

Para o terceiro cenário, nós configuramos o EA para usar as configurações do primeiro cenário, com exceção da hora do almoço. Assim, na vela das 12:00, nenhum negócio deve ser colocado, e o EA deve retomar a negociação no início da vela das 13:00. No teste completo (sem filtros), nós podemos encontrar uma instância onde ele abriu uma negociação em 01/09 12:00. Uma vez que o negócio cai sob as 12:00, dada a configuração do EA para este cenário, nós esperamos que este comércio não seja colocado pelo EA. O resultado do teste através esta configuração é fornecido no final deste artigo (tester_time_hour3.html), e uma imagem da operação é exibida abaixo:


Negociações do horário 3

Conforme mostrado na imagem, na vela das 12:00, o EA fechou a posição de compra existente, mas não entrou em outra negociação. A EA entrou em estado de espera de uma hora, como esperado. Ele só entrou em outra posição (compra) às 16:00, 3 horas após a sessão da tarde e 1 hora antes do final dessa sessão, mas esta é a primeira vez que o EA pode entrar em uma negociação com base na próxima inversão do sinal.

Conclusão

Neste artigo, nós discutimos uma implementação de vários métodos de filtragem de tempo em um Expert Advisor multiplataforma. O artigo abordou vários filtros de tempo, bem como sobre como esses filtros de tempo podem ser combinados através de um container de filtros de tempo para que certos recursos possam ser ativados ou desativados em um Expert Advisor, dependendo de uma determinada configuração de tempo.