English Русский 中文 Español Deutsch 日本語 한국어 Français Italiano Türkçe
Fundamentos básicos da programação MQL5: Tempo

Fundamentos básicos da programação MQL5: Tempo

MetaTrader 5Exemplos | 19 março 2014, 16:24
20 954 0
Dmitry Fedoseev
Dmitry Fedoseev

Conteúdo


Introdução

A linguagem MQL5 oferece uma gama de funções simples para trabalhar com o tempo e não será difícil familiarizar-se com elas. A variedade de tarefas que requerem o uso de data e hora é muito pequena. As principais tarefas são:

  • A realização de determinadas ações em um determinado ponto de tempo (Figura 1). Essas poderão ser ações realizadas na mesma hora a cada dia ou a cada hora do dia e a cada dia da semana de forma semanal ou simplesmente realizadas em uma data e hora determinada.

    Figura 1. Ponto de tempo.
    Figura 1. Ponto de tempo.

  • Para habilitar ou desabilitar determinadas ações dentro de um intervalo de tempo determinado (sessão de tempo). Isso pode incluir uma sessão de tempo dentro do dia (todos os dias, de um ponto de tempo a outro), habilitando/desabilitando determinadas ações em determinados dias da semana, sessões de tempo de um determinado horário da semana a outro em outro dia da semana e apenas ações que ocorrem dentro de um período de tempo e data específicos.

    Figura 2. Período de tempo.
    Figura 2. Período de tempo.

Na prática, o uso do tempo é muito complexo. Há dificuldades associados às peculiaridades da medição de tempo e ao ambiente em que os Expert Advisors e os indicadores operam:

  • Barras perdidas no gráfico devido à ausência de mudanças de preço. Isto é particularmente óbvio em períodos de tempo mais curtos: M1, M5 e até mesmo M15. Barras perdidas também podem ser observadas em períodos de tempo mais longos.

  • As cotações de alguns centros de negociações incluem barras de domingo que, na realidade, deveriam pertencer à segunda-feira.

  • Finais de semana. O dia da semana que precede segunda-feira é sexta-feira e não domingo. A sexta-feira é seguida da segunda-feira, e não do sábado.

  • Além das barras de domingo, alguns centros de negociações fornecem cotações continuamente, incluindo todo o final de semana. A atividade de preços, embora muito baixa em relação aos dias da semana, está presente durante todo o final de semana.

  • Diferença de fusos horários entre o servidor de negociação e o computador local (computador do negociador e terminal de negociação). O horário do servidor em diferentes centros de negociação poderá variar.

  • Horário de verão.

O artigo iniciará com uma teoria geral sobre o tempo. Em seguida, procederemos a uma análise das funções MQL5 para trabalhar com tempo e, simultaneamente, consideraremos algumas técnicas de programação, completando com o gerenciamento de problemas práticos.

O artigo é muito longo, de forma que os programadores iniciantes que apenas recentemente começaram a explorar MQL5 dificilmente conseguirão entender tudo de uma só vez. Será melhor dedicar pelo menos três dias para isso.


Peculiaridades da medição de tempo

Vamos sair um pouco do assunto e falar sobre astronomia. É um fato conhecido que a Terra gira ao redor do Sol, enquanto gira ao mesmo tempo ao redor do seu eixo. O eixo da Terra é levemente inclinado em relação a sua órbita ao redor do Sol. O tempo requerido para uma volta completa da Terra em seu eixo em coordenadas astronômicas (celestes, globais) é chamado de dia astronômico ou sideral.

O dia sideral não tem relevância para as pessoas comuns na Terra (ao contrário dos astrônomos). O mais importante é a alternância entre dia e noite. O tempo requerido para um ciclo noite-dia é chamado de dia solar. Observando o sistema solar de cima do Polo Norte da Terra (Figura 3), você poderá ver que a terra gira ao redor do seu eixo e em torno do Sol no sentido anti-horário. Assim, para completar uma volta ao redor do seu eixo em relação ao Sol, a Terra precisa girar um pouco mais que 360 graus. Consequentemente, um dia solar é um pouco mais longo que um dia sideral.

Figura 3. Direção da rotação da Terra sobre o seu eixo e ao redor do Sol (conforme visto de cima do Polo Norte da Terra).
Figura 3. Direção da rotação da Terra sobre o seu eixo e ao redor do Sol (conforme visto de cima do Polo Norte da Terra).

Por conveniência e precisão, o dia solar é utilizado como base das medições de tempo. Um dia solar é dividido em 24 partes de uma hora que duram 60 minutos, etc. Um dia sideral tem 23 horas, 56 minutos e 4 segundos de duração. Uma leve inclinação no eixo da Terra em relação ao seu plano de órbita resulta em mudanças de estações perceptíveis pelos seres vivos na Terra.

O número de dias solares em um ano não é inteiro. Na realidade, é um pouco mais - 365 dias e 6 horas. É por essa razão que o calendário é ajustado periodicamente, uma vez a cada quatro anos (no ano múltiplo de 4) através da adição de mais um dia, 29 de fevereiro (ano bissexto). Entretanto, esse ajuste não é completamente preciso (um pouco de tempo extra é adicionado), de forma que alguns anos, embora sejam múltiplos de 4, não são bissextos. Nenhum ajuste no calendário ocorre em anos que terminam em "00" (um múltiplo de 100). Mas isso não é tudo.

Se um ano é um múltiplo de 100 e 400 ao mesmo tempo, esse ano é considerado bissexto e o calendário deve ser ajustado. O ano 1900 é um múltiplo de 4 e de 100, mas não é múltiplo de 400, razão pela quão não é um ano bissexto. O ano 2000 é múltiplo de 4, 100 e 400, o que o torna um ano bissexto. O próximo ano que é múltiplo de 4 e 100 é o ano 2100. Entretanto, como ele não é múltiplo de 400, não será um ano bissexto. Pode-se concluir que os leitores deste artigo vivem na era em que todo ano que é múltiplo de 4 é bissexto. O próximo ano que é múltiplo de 100 e ao mesmo tempo é um ano bissexto é o ano 2400.


Fusos horários

A Terra gira sobre o seu eixo, resultando na alternância entre dia e noite. Poderá ser dia ou noite, ou melhor, qualquer hora do dia ao mesmo tempo em diferentes locais da Terra. Porque um dia tem 24 horas, a circunferência da Terra está dividida em 24 seções de 15 graus, chamadas de fusos horários. Por conveniência, os limites dos fusos horários nem sempre seguem as linhas de longitude, mas, em vez disso, são traçados de acordo com os limites da divisão territorial administrativa: fronteiras de países, regiões, etc.

O ponto de referência é o meridiano primário, chamado Meridiano de Greenwich. Ele passa sobre o distrito de Londres chamado de Greenwich, razão pela qual recebe o seu nome. A Hora Média de Greenwich se estende 7,5 graus em cada lado do Meridiano de Greenwich. Há 12 fusos horários a leste da Hora Média de Greenwich (de +1 a +12) e 12 fusos horários a oeste da Hora Média de Greenwich (de -1 a -12). Na realidade, os fusos horários -12 e +12 são apenas 7,5 graus de largura em vez de 15. Os fusos horários -12 e+12 estão localizados à direita e à esquerda do meridiano 180, a chamada Linha Internacional de Data.

Suponha que, quando é meio-dia em Greenwich (12:00), é 00:00, ou seja, o começo do dia no fuso horário -12 e 24:00, ou seja, 00:00 do próximo dia no fuso horário +12. O mesmo se aplica a qualquer outra hora - enquanto que o tempo no relógio é idêntico, as datas no calendário são diferentes. O fuso horário -12, na realidade, não é utilizado e é substituído pelo +12. O mesmo acontece para o fuso horário -11, que é substituído pelo fuso horário +13. Isso provavelmente está relacionado às peculiaridades das relações econômicas: por exemplo, o Estado de Samoa, localizado no fuso horário +13 possui fortes relações econômicas com o Japão, de forma que um horário mais próximo ao do Japão é visto como mais conveniente.

Além disso, há fusos horários não usuais, como -04:30, +05:45, etc. Os curiosos podem encontrar a lista de todos os fusos horários disponíveis nas configurações de hora do Windows.


Horário de verão

Muitos países do mundo adiantam os seus relógios em uma hora como prática de horário de verão em uma tentativa de explorar com mais eficiência a luz do dia e economizar energia. Aproximadamente 80 países do mundo observam o horário de verão, enquanto outros não fazem isso. Alguns países que praticam o horário de verão em larga escala têm algumas de suas partes que não participam da prática (incluindo os EUA). O horário de verão é observado em países importantes, economicamente desenvolvidos, e em regiões: quase todos os países europeus (incluindo Alemanha, Reino Unido, Suíça), Estados Unidos (Nova Iorque, Chicago), Austrália (Sydney) e Nova Zelândia (Wellington). O Japão não pratica o horário de verão. Em 27 de março de 2011, a Rússia adiantou os seus relógios em uma hora pela última vez e nunca reverteu para o horário padrão em outubro. Desde então, a Rússia está oficialmente fora da prática do horário de verão.

O procedimento de mudar para o horário de verão varia de país a país. Nos Estados Unidos, a mudança ocorre às 02:00 do horário local no segundo domingo de março e o relógio é revertido às 02:00 no primeiro domingo de novembro. Na Europa, os relógios passam para o horário de verão às 02:00 do último domingo de março e retornam ao horário padrão às 03:00 do último domingo de outubro. Em vez de ser estabelecido para o horário local, as mudanças ocorrem todas de uma só vez em todos os países europeus: quando são 02:00 horas em Londres, 03:00 em Berlim, etc., de acordo com os fusos horários. O relógio retorna ao tempo padrão quando são 03:00 horas em Londres, 04:00 em Berlim, etc.

Austrália e Nova Zelândia estão localizadas no Hemisfério Sul em que o verão inicia quando o inverno chega ao Hemisfério Norte. Consequentemente, a Austrália muda os relógios para o horário de verão no primeiro domingo de outubro e reverte para o horário padrão no primeiro domingo de abril. É difícil ser mais preciso em relação ao horário de verão na Austrália, visto que as datas de início e fim nem sempre coincidem nas diferentes regiões do país. Na Nova Zelândia, a mudança para o horário de verão ocorre às 02:00 horas do último domingo de setembro e é revertida às 03:00 do primeiro domingo do primeiro domingo de abril.


Padrão de horário

Conforme mencionado anteriormente, o dia solar é utilizado como base para as medições de tempo e a Hora Média de Greenwich é utilizada como padrão de horário com base no qual a hora é determinada em todos os outros fusos horários. A Hora Média de Greenwich é geralmente abreviada como GMT (Greenwich Mean Time).

Entretanto, visto que foi estabelecido que a rotação da Terra é um pouco não uniforme, um relógio atômico foi utilizado para medir o tempo e o Tempo Universal Coordenado, UTC (Coordinated Universal Time), tornou-se o novo padrão de horário. Atualmente, o UTC serve como padrão de horário primário para todo o mundo, fundamentando as medições de tempo em todos os fusos horários, com os ajustes necessários realizados para o horário de verão. O UTC não está sujeito ao horário de verão.

Devido ao fato de que o GMT baseado no Sol não está completamente alinhado ao UTC, que é baseado em relógios atômicos, há uma diferença entre UTC e GMT, que corresponde a cerca de 1 segundo a cada 500 dias. Em razão disso, um ajuste de um segundo é feito de vez em quando no dia 30 de junho ou 31 de dezembro.


Formatos de data e hora

Os formatos de data variam de país a país. Na Rússia, por exemplo, é habitual escrever primeiro o dia, seguido pelo mês e ano. Os números nas datas são separados por pontos, por exemplo 01.12.2012 - 1 de dezembro de 2012. Nos Estados Unidos, o formato de data é mês/dia/ano, em que os números nas datas são separados por uma barra "/". Além de pontos e barras "/", alguns padrões de formato poderão utilizar um travessão "-" para separar os números na data. Na escrita de horário, as horas, os minutos e os segundos são separados por dois pontos ":", por exemplo 12:15:30 - 12 horas, 15 minutos e 30 segundos.

Há uma forma simples de especificar o formato de data e hora. Por exemplo, "dd.mm.aaaa" significa que primeiramente escrevemos o dia (o dia do mês que consiste em dois dígitos; se o dia é um número de 1 a 9, adicionamos 0 ao início), em seguida, o mês (precisa consistir em dois dígitos) e o ano, que consiste em quatro dígitos. "d-m-aa" significa que o dia (que pode ser um número de um dígito) é escrito primeiro, seguido pelo mês (permite-se número de um dígito) e ano, que consiste em dois dígitos. Por exemplo, 1/12/12 - 1 de dezembro de 2012. Os valores de dia, mês e ano são separados por um travessão "-".

O tempo é separado da data através de um espaço. O formato de tempo utiliza "h" para horas, "m" para minutos e "s" para segundos, ao mesmo tempo que especifica o número requerido de dígitos. Por exemplo, "hh:mi:ss" significa que primeiramente escrevemos as horas (0 deve ser adicionado antes de valores de 1 a 9), em seguida, minutos (são necessários 2 dígitos) e segundos (2 dígitos), em que os valores de horas, minutos e segundos são separados pelo sinal de dois pontos.

O formato de data e hora que é considerado o mais preciso do ponto de vista de um programados é "aaaa.mm.dd hh:mi:ss". Ao organizar strings com datas escritas utilizando esta notação, elas são facilmente dispostas em ordem cronológica. Suponha que você armazene informações em arquivos de texto todos os dias e os mantenha na mesma pasta. Se você nomear os seus arquivos utilizando esse formado, os arquivos na pasta estarão convenientemente organizados e dispostos em ordem.

Agora que concluímos a parte teórica, vamos partir para a implementação.


Horário em MQL5

Em MQL5, o tempo é medido em segundos transcorridos desde o início da era Unix, que iniciou em 1 de janeiro de 1970. Para armazenar tempo, utilizamos variáveis do tipo datetime. O valor mínimo de uma variável do tipo datetime é 0 (correspondente à data do início da era), enquanto que o valor máximo é 32 535 244 799 (correspondente a 23:59:59 de 31 de dezembro de 3000).


Determinando a hora atual do servidor

Para determinar a hora atual, utilizamos a função TimeCurrent(). Ela retorna a última hora conhecida do servidor:

datetime tm=TimeCurrent();
//--- output result
Alert(tm);

A mesma hora do servidor é utilizada para especificar a hora das barras no gráfico. A última hora conhecida do servidor é a hora da última mudança no preço de qualquer um dos símbolos abertos na janela Market Watch. Se há apenas EURUSD na janela Market Watch, a função TimeCurrent() retornará a última mudança de preço de EURUSD. Porque a janela Market Watch, como regra, exibe um número considerável de símbolos, a função basicamente retorna a hora atual do servidor. Entretanto, porque os preços não são alterados no final de semana, o valor retornado pela função será muito diferente da hora atual do servidor.

Se você precisa encontrar a hora do servidor através de um símbolo determinado (hora da última mudança no preço), você poderá utilizar a função SymbolInfoInteger() com o identificador SYMBOL_TIME:

datetime tm=(datetime)SymbolInfoInteger(_Symbol,SYMBOL_TIME);
//--- output result
Alert(tm);


Determinando a hora atual do servidor

A hora local (exibida pelo relógio do computador do usuário) é determinada pela função TimeLocal():

datetime tm=TimeLocal();
//--- output result
Alert(tm);

Na prática, ao programar EAs e indicadores, na maioria das vezes você utiliza a hora do servidor. A hora local é útil para alertas e anotações do diário: é mais conveniente para um usuário comparar a hora de uma mensagem ou anotação com a hora exibida pelo relógio do computador para ver há quanto tempo esta mensagem ou anotação foi registrada. Entretanto, o terminal MetaTrader 5 automaticamente adiciona uma impressão de hora às mensagens e entradas do diário que são transferidas com o uso das funções Alert() e Print(). Assim, a necessidade de utilizar a função TimeLocal() pode ser muito rara.


Saída de hora

Por favor, note que o valor da variável tm no código acima é transferido com o uso da função Alert(). Dito isso, o valor é exibido em um formato que é fácil de ler, por exemplo "2012.12.05 22:31:57". Isso se deve ao fato de que a função Alert() converte os argumentos passados a ela para o tipo string (o mesmo acontece quando se usa a função Print() e Comment() e quando se transfere para arquivos de texto e csv). Ao gerar uma mensagem de texto que contém o valor de uma variável do tipo datetime, é sua responsabilidade realizar a conversão do tipo. Converta para o tipo string se você precisa obter o tempo formatado ou para o tipo long seguido pelo tipo string se o que você precisa é um valor numérico:

datetime tm=TimeCurrent();
//--- output result
Alert("Formatted: "+(string)tm+", in seconds: "+(string)(long)tm);

Porque os limites de valor das variáveis do tipo long e ulong cobrem o limite de valores da variável do tipo datetime, elas também podem ser utilizadas para armazenar tempo, mas, nesse caso, para transferir o tempo formatado, você precisa converter o tipo long para o tipo datetime e então para o tipo string ou somente para o tipo string se você deseja transferir um valor numérico:

long tm=TimeCurrent();
//--- output result
Alert("Formatted: "+(string)(datetime)tm+", in seconds: "+(string)tm);


Formatação de hora

A formatação de hora foi abordada no artigo intitulado "Fundamentos básicos da programação MQL5: Strings" na seção dedicada a converter diversas variáveis para uma string. Vamos destacar brevemente os principais pontos aqui. Além de converter tipos, a MQL5 oferece uma função que permite que você especifique o formato de data e hora ao convertê-las para uma string - a função TimeToString():

datetime tm=TimeCurrent();
string str1="Date and time with minutes: "+TimeToString(tm);
string str2="Date only: "+TimeToString(tm,TIME_DATE);
string str3="Time with minutes only: "+TimeToString(tm,TIME_MINUTES);
string str4="Time with seconds only: "+TimeToString(tm,TIME_SECONDS);
string str5="Date and time with seconds: "+TimeToString(tm,TIME_DATE|TIME_SECONDS);
//--- output results
Alert(str1);
Alert(str2);
Alert(str3);
Alert(str4);
Alert(str5);

A função TimeToString() pode ser aplicada a variáveis do tipo datetime, bem como variáveis do tipo long e ulong e alguns outros tipos de variáveis inteiras, mas elas não devem ser utilizadas para armazenar hora.

Ao formatar uma mensagem de texto utilizando a função StringFormat(), você precisa cuidar da conversão de tipo.

  • Ao armazenar hora em uma variável do tipo datetime:

    datetime tm=TimeCurrent();
    //--- generating a string
    string str=StringFormat("Formatted: %s, in seconds: %I64i",(string)tm,tm);
    //--- output result
    Alert(str);
  • Ao armazenar hora em uma variável do tipo long:

    long tm=TimeCurrent();
    //--- generating a string
    string str=StringFormat("Formatted: %s, in seconds: %I64i",(string)(datetime)tm,tm);
    //--- output result
    Alert(str);
  • Ou utilizando a função TimeToString():

    datetime tm=TimeCurrent();
    //--- generating a string
    string str=StringFormat("Date: %s",TimeToString(tm,TIME_DATE));
    //--- output result
    Alert(str);


Conversão de hora para números. Adição e subtração de horas

Para converter uma data formatada (string) para um número (número de segundos transcorridos desde o início da era), utiliza-se a função StringToTime():

datetime tm=StringToTime("2012.12.05 22:31:57");
//--- output result
Alert((string)(long)tm);

Como resultado do código acima, a hora de saída em segundos será "1354746717" o que corresponde a seguinte data: "2012.12.05 22:31:57".

Quando a hora é representada como número, diversas operações com ele serão mais fáceis e convenientes, por exemplo, você pode encontrar a data e hora no passado ou no futuro. Porque o tempo é medido em segundos, você deve adicionar um período de tempo expresso em segundos. Sabendo que um minuto é igual a 60 segundos e que uma hora é 60 minutos ou 3600 segundos, não será muito difícil fazer o cálculo da duração de qualquer período de tempo.

Subtraia ou adicione uma hora (3600 segundos) em relação ao tempo atual e você obterá o tempo há uma hora ou o que será daqui a uma hora:

datetime tm=TimeCurrent();
datetime ltm=tm-3600;
datetime ftm=tm+3600;
//--- output result
Alert("Current: "+(string)tm+", an hour ago: "+(string)ltm+", in an hour: "+(string)ftm);

A data transferida à função StringToTime() não precisa ser completa. Além disso, você pode transferir a data sem a hora ou a hora sem a data. Se você transferir a data sem a hora, a função retornará o valor 00:00:00 para a data especificada:

datetime tm=StringToTime("2012.12.05");
//--- output result
Alert(tm);

Se você transferir apenas a hora, a função retornará o valor correspondente à hora especificada da data atual:

datetime tm=StringToTime("22:31:57");
//--- output result
Alert((string)tm);

A hora também pode ser transferida sem os segundos. Se você transferir a data, o único componente de horário que pode ser transferido é horas. Isso raramente será requerido na prática. Entretanto, se você tiver curiosidade, sinta-se livre para fazer testes.


Componentes de data e hora

Para determinar os valores dos componentes individuais de data e hora (ano, mês, data, etc), utilizamos a função TimeToStruct() e a estrutura MqlDateTime. A estrutura é transferida à função por referência. Após executar a função, a estrutura será preenchida com os valores dos componentes de data transferidos a ela:

datetime    tm=TimeCurrent();
MqlDateTime stm;
TimeToStruct(tm,stm);
//--- output date components
Alert("Year: "        +(string)stm.year);
Alert("Month: "      +(string)stm.mon);
Alert("Day: "      +(string)stm.day);
Alert("Hour: "        +(string)stm.hour);
Alert("Minute: "     +(string)stm.min);
Alert("Second: "    +(string)stm.sec);
Alert("Day of the week: "+(string)stm.day_of_week);
Alert("Day of the year: "  +(string)stm.day_of_year);

Perceba que, além dos componentes de data, a estrutura também contém dois campos adicionais: dia da semana (o campo day_of_week) e dia do ano (day_of_year). Os dias da semana são contados a partir de 0 (0 - domingo, 1 - segunda-feira, etc.). Os dias do ano também são contados a partir de 0. Os outros valores seguem a ordem de contagem que é geralmente aceita (os meses, assim como os dias, são contados a partir de 1).

Há outra forma de chamar a função TimeCurrent(). Uma estrutura do tipo MqlDateTime é transferida à função por referência. Após a execução da função, a estrutura será preenchida com os componentes da data atual:

MqlDateTime stm;
datetime tm=TimeCurrent(stm);
//--- output date components
Alert("Year: "        +(string)stm.year);
Alert("Month: "      +(string)stm.mon);
Alert("Day: "      +(string)stm.day);
Alert("Hour: "        +(string)stm.hour);
Alert("Minute: "     +(string)stm.min);
Alert("Second: "    +(string)stm.sec);
Alert("Day of the week: "+(string)stm.day_of_week);
Alert("Day of the year: "  +(string)stm.day_of_year);

A função TimeLocal() também pode ser chamada dessa forma.


Geração de data a partir de seus componentes

Você também pode transformar inversamente a estrutura MqlDateTime para o tipo datetime. Para isso, utilizamos a função StructToTime().

Vamos determinar a hora que era exatamente há um mês. O número de dias em um mês varia. Há meses com 30 ou 31 dias e fevereiro pode ter 28 ou 29 dias. O método de adição e subtração de hora que abordamos acima, então, não é bem apropriado. Dessa forma, quebraremos as datas até os seus componentes, diminuiremos o valor do mês em 1 e, se o valor do mês é 1, vamos configurá-lo para 12 e diminuir o valor do ano em 1.

datetime tm=TimeCurrent();
MqlDateTime stm;
TimeToStruct(tm,stm);
if(stm.mon==1)
  {
   stm.mon=12;
   stm.year--;
  }
else
  {
   stm.mon--;
  }
datetime ltm=StructToTime(stm);
//--- output result
Alert("Current: "+(string)tm+", a month ago: "+(string)ltm);


Determinação de hora de barra

Durante o desenvolvimento de um indicador, o MetaEditor cria automaticamente uma das duas versões da função OnCalculate().

Versão 1:

int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[])
  {
   return(rates_total);
  }

Versão 2:

int OnCalculate(const int rates_total,
                const int prev_calculated,
                const int begin,
                const double &price[])
  {
   return(rates_total);
  }

Os parâmetros da primeira versão da função incluem o array time[] cujos elementos contêm a hora de todas as barras.

Ao utilizar a segunda versão, bem como em todas as vezes durante a programação de EAs e organização do acesso dos indicadores para a hora das barras em outros períodos de tempo, utilizamos a função CopyTime(). Essa função existe em três funções. Em todas as versões, os dois primeiros parâmetros de função definem o símbolo e o período de tempo do gráfico em que a hora da barra está determinada. O último parâmetro define um array que armazena os valores retornados e os dois parâmetros do meio dependem da versão da função utilizada.

CopyTime - Versão 1. Você precisa especificar o índice da barra e o número de elementos a serem copiados:

//--- variables for function parameters
int start = 0; // bar index
int count = 1; // number of bars
datetime tm[]; // array storing the returned bar time
//--- copy time 
CopyTime(_Symbol,PERIOD_D1,start,count,tm);
//--- output result
Alert(tm[0]);

Esse exemplo demonstra a implementação da cópia da hora da última barra no período de tempo D1. A versão da função que utilizamos depende do tipo de parâmetro transferido a ela. O exemplo acima exibe o uso de variáveis de tipo int, o que significa que precisamos obter o tempo através do número da barra para o número de barras especificado.

Quando se utiliza a função CopyTime() as barras são contadas a partir de zero, da direita para a esquerda. Um array dinâmico utilizado para os valores obtidos (o último parâmetro) é dimensionado para o tamanho requerido pela própria função CopyTime(). Você também pode utilizar um array estático, mas nesse caso, o tamanho do array deve corresponder estritamente ao número de elementos solicitados (o valor do 4º parâmetro).

É importante compreender a ordem dos elementos no array retornado quando a hora é obtida de diversas barras ao mesmo tempo. Enquanto que as barras do gráfico são contadas da direita para a esquerda, começando com a barra especificada, os elementos do array são dispostos da esquerda para a direita:

//--- variables for function parameters
int start = 0; // bar index
int count = 2; // number of bars
datetime tm[]; // array storing the returned bar time
//--- copy time 
CopyTime(_Symbol,PERIOD_D1,start,count,tm);
//--- output result
Alert("Today: "+(string)tm[1]+", yesterday: "+(string)tm[0]);

Como resultado desse código, a hora da barra de ontem será armazenada no elemento 0 do array tm, enquanto que o 1º elemento conterá a hora da barra de hoje.

Em alguns casos, poderá parecer mais conveniente ter a hora no array organizada na mesma ordem que a ordem de contagem de barras no gráfico. E aqui, a função ArraySetAsSeries() pode solucionar o problema:

//--- variables for function parameters
int start = 0; // bar index
int count = 2; // number of bars
datetime tm[]; // array storing the returned bar time
ArraySetAsSeries(tm,true); // specify that the array will be arranged in reverse order
//--- copy time 
CopyTime(_Symbol,PERIOD_D1,start,count,tm);
//--- output result
Alert("Today: "+(string)tm[0]+", yesterday: "+(string)tm[1]);

Agora, a hora da barra de hoje está no elemento com índice 0 e a hora da barra de ontem está no elemento com índice 1.

CopyTime - Versão 2. Aqui, ao chamar a função CopyTime(), precisamos especificar a hora da barra a partir da qual a cópia inicia e o número de barras a serem copiadas. Esta versão será apropriada para determinar a hora de um período de tempo maior que contém uma barra de um período de tempo menor:

//--- get the time of the last bar on M5
int m5_start=0; 
int m5_count=1;
datetime m5_tm[];
CopyTime(_Symbol,PERIOD_M5,m5_start,m5_count,m5_tm);
//--- determine the bar time on H1 that contains the bar on M5
int h1_count=1;
datetime h1_tm[];
CopyTime(_Symbol,PERIOD_H1,m5_tm[0],h1_count,h1_tm);
//--- output result
Alert("The bar on М5 with the time "+(string)m5_tm[0]+" is contained in the bar on H1 with the time "+(string)h1_tm[0]);

Isso torna-se mais complicado se você precisa determinar a hora da barra em um período de tempo menor quando o início da barra está em um período de tempo maior. Uma barra com a mesma hora que a hora da barra em um período de tempo maior poderá não ser encontrada em um período de tempo menor. Nesse caso, obteremos a hora da última barra em um período de tempo menor que está contida na última barra do período de tempo maior. Então, precisamos determinar o número da barra de um período de tempo menor e obter a hora da próxima barra.

Abaixo, segue a implementação do que foi apresentado acima, na forma de uma função pronta para uso:

bool LowerTFFirstBarTime(string aSymbol,
                         ENUM_TIMEFRAMES aLowerTF,
                         datetime aUpperTFBarTime,
                         datetime& aLowerTFFirstBarTime)
  {
   datetime tm[];
//--- determine the bar time on a lower time frame corresponding to the bar time on a higher time frame 
   if(CopyTime(aSymbol,aLowerTF,aUpperTFBarTime,1,tm)==-1)
     {
      return(false);
     }
   if(tm[0]<aUpperTFBarTime)
     {
      //--- we got the time of the preceding bar
      datetime tm2[];
      //--- determine the time of the last bar on a lower time frame
      if(CopyTime(aSymbol,aLowerTF,0,1,tm2)==-1)
        {
         return(false);
        }
      if(tm[0]<tm2[0])
        {
         //--- there is a bar following the bar of a lower time frame  that precedes the occurrence of the bar on a higher time frame
         int start=Bars(aSymbol,aLowerTF,tm[0],tm2[0])-2;
         //--- the Bars() function returns the number of bars from the bar with time tm[0] to
         //--- the bar with time tm2[0]; since we need to determine the index of the bar following 
         //--- the bar with time tm2[2], subtract 2
         if(CopyTime(aSymbol,aLowerTF,start,1,tm)==-1)
           {
            return(false);
           }
        }
      else
        {
         //--- there is no bar of a lower time frame contained in the bar on a higher time frame
         aLowerTFFirstBarTime=0;
         return(true);
        }
     }
//--- assign the obtained value to the variable 
   aLowerTFFirstBarTime=tm[0];
   return(true);
  }

Parâmetros da função:

  • aSymbol - símbolo;
  • aLowerTF - período de tempo menor;
  • aUpperTFBarTime - hora da barra em um período de tempo maior;
  • aLowerTFFirstBarTime - valor retornado de um período de tempo menor.

Ao longo de todo o código, a função verifica se o chamado da função CopyTime() é exitoso e, em caso de erro, a função retorna falso. A hora da barra é retornada por referência através do parâmetro aLowerTFFirstBarTime.

Um exemplo do uso da função é apresentado abaixo:

//--- time of the bar on the higher time frame H1
   datetime utftm=StringToTime("2012.12.10 15:00");
//--- variable for the returned value
   datetime val;
//--- function call
   if(LowerTFFirstBarTime(_Symbol,PERIOD_M5,utftm,val))
     {
      //--- output result in case of successful function operation
      Alert("val = "+(string)val);
     }
   else
     {
      //--- in case of an error, terminate the operation of the function from which the LowerTFFirstBarTime() function is called
      Alert("Error copying the time");
      return;
     }

Uma situação em que não há nenhuma barra em um período de tempo menor contida em uma barra de um período de tempo maior poderá surgir caso a função receber a hora de uma barra não existente de um período de tempo maior. Nesse caso, a função retorna verdadeiro e o valor da hora 0 é escrito na variável aLowerTFFirstBarTime. Se existe uma barra de um período de tempo maior, sempre haverá pelo menos uma barra correspondente em cada um dos períodos de tempo menores.

Encontrar a hora da última barra em um período de tempo menor que está contido em uma barra de um período de tempo maior é um pouco mais fácil. Calculamos a hora da próxima barra em um período de tempo maior e utilizamos o valor obtido para determinar a hora da barra correspondente em um período de tempo menor. Se a hora correspondente é igual à hora calculada de um período de tempo maior, precisamos determinar a hora da barra precedente em um período de tempo menor. Se a hora resultante é menor, então obtivemos a hora correta.

Abaixo, segue a implementação do que foi apresentado acima, na forma de uma função pronta para uso:

bool LowerTFLastBarTime(string aSymbol,
                        ENUM_TIMEFRAMES aUpperTF,
                        ENUM_TIMEFRAMES aLowerTF,
                        datetime aUpperTFBarTime,
                        datetime& aLowerTFFirstBarTime)
  {
//--- time of the next bar on a higher time frame
   datetime NextBarTime=aUpperTFBarTime+PeriodSeconds(aUpperTF);
   datetime tm[];
   if(CopyTime(aSymbol,aLowerTF,NextBarTime,1,tm)==-1)
     {
      return(false);
     }
   if(tm[0]==NextBarTime)
     {
      //--- There is a bar on a lower time frame corresponding to the time of the next bar on a higher time frame.
      //--- Determine the time of the last bar on a lower time frame
      datetime tm2[];
      if(CopyTime(aSymbol,aLowerTF,0,1,tm2)==-1)
        {
         return(false);
        }
      //--- determine the preceding bar index on a lower time frame
      int start=Bars(aSymbol,aLowerTF,tm[0],tm2[0]);
      //--- determine the time of this bar
      if(CopyTime(aSymbol,aLowerTF,start,1,tm)==-1)
        {
         return(false);
        }
     }
//--- assign the obtain value to the variable 
   aLowerTFFirstBarTime=tm[0];
   return(true);
  }

Parâmetros da função:

  • aSymbol - símbolo;
  • aUpperTF - período de tempo maior;
  • aLowerTF - período de tempo menor;
  • aUpperTFBarTime - hora da barra em um período de tempo maior;
  • aLowerTFFirstBarTime - valor retornado de um período de tempo menor.

Ao longo de todo o código, a função verifica se o chamado da função CopyTime() é exitoso e, em caso de erro, a função retorna falso. A hora da barra é retornada por referência através do parâmetro aLowerTFFirstBarTime.

Um exemplo do uso da função é apresentado abaixo:

//--- time of the bar on the higher time frame H1
datetime utftm=StringToTime("2012.12.10 15:00");
//--- variable for the returned value
datetime val;
//--- function call
if(LowerTFLastBarTime(_Symbol,PERIOD_H1,PERIOD_M5,utftm,val))
  {
//--- output result in case of successful function operation
   Alert("val = "+(string)val);
  }
else
  {
//--- in case of an error, terminate the operation of the function from which the LowerTFFirstBarTime() function is called
   Alert("Error copying the time");
   return;
  }

Atenção! Supõe-se que a hora transferida à função seja a hora exata de um período de tempo maior. Se a hora exata da barra é desconhecida e apenas sabemos a hora de um determinado tick dessa barra em um período de tempo menor contido na barra de um período de tempo maior, a normalização de tempo será requerida. Os períodos de tempo utilizados no terminal MetaTrader 5 dividem o dia em um número inteiro de barras. Determinamos o número da barra desde o início da era e multiplicamos ele pelo comprimento da barra em segundos.

datetime BarTimeNormalize(datetime aTime,ENUM_TIMEFRAMES aTimeFrame)
  {
   int BarLength=PeriodSeconds(aTimeFrame);
   return(BarLength*(aTime/BarLength));
  }

Parâmetros da função:

  • aTime - hora;
  • aTimeFrame - período de tempo.

Um exemplo do uso da função é apresentado abaixo:

//--- the time to be normalized
datetime tm=StringToTime("2012.12.10 15:25");
//--- function call
datetime tm1=BarTimeNormalize(tm,PERIOD_H1);
//--- output result
Alert(tm1);

Entretanto, isso não é apenas o método de normalização de tempo, mas também ainda outro método para determinar a hora de um período de tempo maior pela hora de um período de tempo menor.

CopyTime - Versão 3. Nesse caso, ao chamar a função CopyTime(), especificamos o intervalo de tempo em relação às barras cujas horas precisam ser copiadas. Esse método permite obtermos facilmente todas as barras de um período de tempo menor contidas na barra de um período de tempo maior:

//--- time of the bar start on H1
datetime TimeStart=StringToTime("2012.12.10 15:00");
//--- the estimated time of the last bar on
//--- M5
datetime TimeStop=TimeStart+PeriodSeconds(PERIOD_H1)-PeriodSeconds(PERIOD_M5);
//--- copy time
datetime tm[];
CopyTime(_Symbol,PERIOD_M5,TimeStart,TimeStop,tm);
//--- output result 
Alert("Bars copied: "+(string)ArraySize(tm)+", first bar: "+(string)tm[0]+", last bar: "+(string)tm[ArraySize(tm)-1]);


Determinação de hora de início do dia e tempo transcorrido desde o início do dia

A forma mais óbvia de determinar a hora de início do dia através de uma hora determinada é separar a hora em seus componentes, zerar as horas, minutos e segundos e adicioná-los novamente. Entretanto, há uma forma mais fácil. Um dia corresponde a 86400 segundos. Precisamos obter um número inteiro como resultado da divisão da hora pelo número de segundos de um dia e multiplicá-lo pelo número de segundos de um dia:

datetime tm=TimeCurrent();
tm=(tm/86400)*86400;
//--- output result
Alert("Day start time: "+(string)tm);

Atenção! Isso só é eficaz para variáveis inteiras. Se variáveis do tipo double e float aparecerem em qualquer um de seus cálculos, você precisa cortar a parte fracionária utilizando a função MathFloor():

MathFloor(tm/86400)*86400

Após a multiplicação, os valores devem ser normalizados com a utilização da função NormalizeDouble(). Porque o valor resultante deve ser um número inteiro, você pode utilizar a função de arredondamento, MathRound():

MathRound(MathFloor(tm/86400)*86400)

Ao utilizar variáveis inteiras, a sobra é eliminada automaticamente. Ao lidar com hora, dificilmente haverá necessidade de variáveis do tipo double ou float e o uso delas muito provavelmente indicará uma abordagem fundamentalmente errada.

Para determinar o número de segundos transcorridos desde o início do dia, apenas precisamos obter o resultado da divisão da hora por 86400:

datetime tm=TimeCurrent();
long seconds=tm%86400;
//--- output result
Alert("Time elapsed since the day start: "+(string)seconds+" sec.");

Métodos similares podem ser utilizados para converter o tempo obtido em segundos para horas, minutos e segundos. Implementação como uma função:

int TimeFromDayStart(datetime aTime,int &aH,int &aM,int &aS)
  {
//--- Number of seconds elapsed since the day start (aTime%86400),
//--- divided by the number of seconds in an hour is the number of hours
   aH=(int)((aTime%86400)/3600);
//--- Number of seconds elapsed since the last hour (aTime%3600),
//--- divided by the number of seconds in a minute is the number of minutes 
   aM=(int)((aTime%3600)/60);
//--- Number of seconds elapsed since the last minute 
   aS=(int)(aTime%60);
//--- Number of seconds since the day start
   return(int(aTime%86400));
  }

O primeiro parâmetro transferido é a hora. Outros parâmetros são utilizados para os valores retornados: aH - horas, aM - minutos, aS - segundos. A própria função retorna o número total de segundos desde o início do dia. Vamos verificar a função:

datetime tm=TimeCurrent();
int t,h,m,s;
t=TimeFromDayStart(tm,h,m,s);
//--- output result
Alert("Time elapsed since the day start ",t," s, which makes ",h," h, ",m," m, ",s," s ");

Você também pode calcular o número do dia a ser utilizado nos indicadores para determinação da barra do início do dia:

bool NewDay=(time[i]/86400)!=(time[i-1]/86400);

Presume-se que as barras estejam indexadas da esquerda para a direita, em que time[i] é a hora da barra atual e time[i-1] é a hora da barra anterior.


Determinação de hora de início da semana e tempo transcorrido desde o início da semana

Determinar a hora de início da semana é um pouco mais complexo que determinar o início do dia. Embora o número de dias da semana seja constante e possamos calcular a furação de uma semana em segundos (604800 segundos), não é suficiente simplesmente calcular o número inteiro de semanas que passaram desde o início da era e multiplicá-lo pela duração de semanas.

Ocorre que, enquanto que na maioria dos países a semana inicia na segunda-feira, em outros países (como EUA, Canadá, Israel e outros), ela inicia no domingo. Porém, como lembramos, a era da medição de tempo inicia na quinta-feira. Se a quinta-feira fosse o primeiro dia da semana, aqueles cálculos simples seriam o suficiente.

Por conveniência, consideraremos as peculiaridades da determinação do início da semana através do exemplo do primeiro dia da era correspondendo ao valor 0. Precisamos encontrar o valor que, ao ser adicionado ao tempo, alteraria o primeiro dia da era (1970.01.01 00:00), contado a partir de zero, até o quarto dia. Ou seja, precisamos adicionar a duração de quatro dias. Se a semana inicia na segunda-feira, quinta-feira deve ser o quarto dia, então, precisamos adicionar a duração de três dias. Entretanto, se a semana inicia no domingo, quinta-feira deve ser o quinto dia, então precisamos adicionar a duração de quatro dias.

Vamos escrever a função para calcular o número de uma semana:

long WeekNum(datetime aTime,bool aStartsOnMonday=false)
  {
//--- if the week starts on Sunday, add the duration of 4 days (Wednesday+Tuesday+Monday+Sunday),
//    if it starts on Monday, add 3 days (Wednesday, Tuesday, Monday)
   if(aStartsOnMonday)
     {
      aTime+=259200; // duration of three days (86400*3)
     }
   else
     {
      aTime+=345600; // duration of four days (86400*4)  
     }
   return(aTime/604800);
  }

Essa função pode ser útil em indicadores para determinar a primeira barra da nova semana:

bool NewWeek=WeekNum(time[i])!=WeekNum(time[i-1]);

Presume-se que as barras estejam indexadas da esquerda para a direita, em que time[i] é a hora da barra atual e time[i-1] é a hora da barra anterior.

Agora, podemos calcular a hora de início da semana. Visto que, para calcular o número da semana, presumimos que a era inicia três (ou quatro) dias antes, agora precisamos realizar uma correção de tempo inversa:

long WeekStartTime(datetime aTime,bool aStartsOnMonday=false)
  {
   long tmp=aTime;
   long Corrector;
   if(aStartsOnMonday)
     {
      Corrector=259200; // duration of three days (86400*3)
     }
   else
     {
      Corrector=345600; // duration of four days (86400*4)
     }
   tmp+=Corrector;
   tmp=(tmp/604800)*604800;
   tmp-=Corrector;
   return(tmp);
  }  

A função retorna um valor do tipo long porque o valor para a primeira semana poderá ser negativo (três a quatro dias antes do início da era). O segundo parâmetro da função pode determinar se a função inicia no domingo ou na segunda-feira.

Agora que temos a hora de início da semana, podemos calcular o número de segundos transcorridos desde o início da semana:

long SecondsFromWeekStart(datetime aTime,bool aStartsOnMonday=false)
  {
   return(aTime-WeekStartTime(aTime,aStartsOnMonday));
  }

Os segundos podem ser convertidos em dias, horas, minutos e segundos. Embora não tenha sido difícil calcular horas, minutos e segundos desde o início do dia, neste caso será mais fácil utilizar a função TimeToStruct():

long sfws=SecondsFromWeekStart(TimeCurrent());
MqlDateTime stm;
TimeToStruct(sfws,stm);
stm.day--;
Alert("Time elapsed since the week start "+(string)stm.day+" d, "+(string)stm.hour+" h, "+(string)stm.min+" m, "+(string)stm.sec+" s");

Por favor, perceba que o valor stm.day é diminuído em 1: Os dias do mês são contados a partir de 1, visto que precisamos determinar o número de dias inteiros. Alguns de vocês podem considerar as funções desta seção inúteis a partir do ponto de vista prático, mas a compreensão de vocês a respeito dessas funções será de grande valor como experiência ao se trabalhar com o tempo.


Determinação do número da semana desde uma determinada data, início do ano ou início do mês

Observando-se os campos da estrutura MqlDateTime, em particular o campo day_of_year, sente-se vontade de criar funções que determinarão o número da semana desde o início do ano ou início do mês. É melhor escrever uma função geral para determinar o número da semana desde uma determinada data. O princípio da operação da função é similar ao da função utilizada para determinar o número da semana desde o início da era:

long WeekNumFromDate(datetime aTime,datetime aStartTime,bool aStartsOnMonday=false)
  {
   long Time,StartTime,Corrector;
   MqlDateTime stm;
   Time=aTime;
   StartTime=aStartTime;
//--- determine the beginning of the reference epoch
   StartTime=(StartTime/86400)*86400;
//--- determine the time that elapsed since the beginning of the reference epoch
   Time-=StartTime;
//--- determine the day of the week of the beginning of the reference epoch
   TimeToStruct(StartTime,stm);
//--- if the week starts on Monday, numbers of days of the week are decreased by 1,
//    and the day with number 0  becomes a day with number 6
   if(aStartsOnMonday)
     {
      if(stm.day_of_week==0)
        {
         stm.day_of_week=6;
        }
      else
        {
         stm.day_of_week--;
        }
     }
//--- calculate the value of the time corrector 
   Corrector=86400*stm.day_of_week;
//--- time correction
   Time+=Corrector;
//--- calculate and return the number of the week
   return(Time/604800);
  }

Com base nessa função, vamos escrever duas funções para determinar o número da semana desde o início do ano e desde o início do mês. Para fazer isso, primeiramente precisamos determinar a hora do início do ano e a hora do início do mês. Divida a hora em seus componentes, ajuste o valor de alguns campos e converta os componentes novamente para a notação de hora.

  • A função para determinação da hora do início do ano:

    datetime YearStartTime(datetime aTime)
          {
           MqlDateTime stm;
           TimeToStruct(aTime,stm);
           stm.day=1;
           stm.mon=1;
           stm.hour=0;
           stm.min=0;
           stm.sec=0;
           return(StructToTime(stm));
          }
  • A função para determinação da hora do início do mês:

    datetime MonthStartTime(datetime aTime)
          {
           MqlDateTime stm;
           TimeToStruct(aTime,stm);
           stm.day=1;
           stm.hour=0;
           stm.min=0;
           stm.sec=0;
           return(StructToTime(stm));
          }

Agora, seguem as funções para determinação do número da semana desde o início do ano e desde o início do mês.

  • Desde o início do ano:

    long WeekNumYear(datetime aTime,bool aStartsOnMonday=false)
          {
           return(WeekNumFromDate(aTime,YearStartTime(aTime),aStartsOnMonday));
          }
  • Desde o início do mês:

    long WeekNumMonth(datetime aTime,bool aStartsOnMonday=false)
          {
           return(WeekNumFromDate(aTime,MonthStartTime(aTime),aStartsOnMonday));
          }

Finalmente, passaremos a tarefas puramente práticas.


Criação de um conjunto de ferramentas experimental

Como já mencionado, há centros de negociação cujas cotações incluem barras de domingo e que fornecem cotações continuamente ao longo de todo o final de semana. Devemos nos certificar de que as funções necessárias operam adequadamente em todos os casos. É claro que podemos encontrar os centros de negociação adequados na Internet e testar a operação das funções utilizando cotações em contas de demonstração. Entretanto, além de procurar um centro de negociação adequado, teremos que procurar os locais apropriados no gráfico para executar os testes requeridos.

Vamos criar a nossa própria área de teste para testar as funções. Sextas-feiras, finais de semana e segundas-feiras são de especial interesse. Criaremos um array contendo a hora das barras de sexta-feira, segunda-feira e finais de semana, conforme necessário. Haverá um total de quatro opções:

  1. Sem barras de final de semana.
  2. Algumas barras no final de domingo, por exemplo, 4.
  3. Cotações contínuas no final de semana.
  4. Barras de sábado, mas nenhuma barra de domingo.

Para evitar que o array fique muito grande, utilizaremos o período de tempo H1. O tamanho máximo do array será 96 elementos (24 barras por dia durante 4 dias) e o próprio array caberá no gráfico quando representado através de objetos gráficos. Então, obteremos algo como um buffer de indicador com hora e faremos a iteração sobre o array em um ciclo, de forma similar a primeira execução da função OnCalculate() ao iniciar um indicador. Assim, seremos capazes de visualizar a operação do indicador.

Essa ferramenta é implementada na forma de um script anexado a este artigo (o arquivo sTestArea.mq5). A preparação é feita na função OnStart() do script. A variável Variant no início do código da função permite que você selecione uma das quatro opções listadas acima. Abaixo da função OnStart(), você pode ver a função LikeOnCalculate() similar à função OnCalculate() dos indicadores. Essa função tem dois parâmetros: rates_total - número de barras e time[] - array com hora de barras.

Além disso, continuamos trabalhando nesta função como se estivéssemos escrevendo um indicador. Você pode configurar um marcador a partir da função chamando a função SetMarker(). Os parâmetros transferidos para a função SetMarker() são: índice de barra, índice de buffer (linha em que o marcador é exibido) e cor do marcador.

A Figura 4 mostra os resultados do desempenho do script, com a variável Variant igual a 2 e duas linhas de marcadores estabelecidos sob cada barra (as barras são marcadas com as impressões de hora relevantes). A cor estabelecida para todos os elementos do gráfico é invisível.

Figura 4. O desempenho do script sTestArea.mq5.
Figura 4. O desempenho do script sTestArea.mq5.

As impressões de hora da barra são coloridas dependendo do dia da semana: Sexta-feira - vermelho, sábado - magenta, domingo - verde, segunda-feira - azul. Agora, podemos continuar escrevendo diversas funções que requerem uma abordagem especial para as barras de final de semana, sendo capazes de monitorar visualmente o trabalho delas.


Indicador Pivot - Opção 1

Primeiramente, vamos tentar criar um indicador Pivot simples. Para calcular a linha Pivot, precisamos saber o preço de encerramento de ontem, bem como os preços máximo e mínimo de ontem. O valor do indicador é calculado como a média desses três valores. Identificaremos novas altas e baixas durante o dia, calcularemos o valor Pivot no início do novo dia e, em seguida, representaremos e exibiremos o nível ao longo do dia.

Apresentaremos duas versões da operação do indicador:

  1. O Pivot é calculado a cada novo dia (presume-se que não há barras de final de semana). Se houver barras de final de semana, as barras de sábado e domingo serão tratadas de forma diferente.
  2. As barras de sábado pertencerão à sexta-feira, enquanto que as barras de domingo pertencerão à segunda-feira (isso se aplica a casos em que as cotações são fornecidas continuamente ao longo do final de semana e quando há somente barras de domingo). Aqui, você deve levar em consideração que poderá não haver qualquer barra no final de semana.

Na primeira versão, é suficiente apenas determinar o início do novo dia. Transferiremos a hora atual (aTimeCur) e a hora anterior (aTimePre) à função, calcularemos os números de dias desde o início da era e, se eles não forem correspondentes, concluiremos que um novo dia começou:

bool NewDay1(datetime aTimeCur,datetime aTimePre)
  {
   return((aTimeCur/86400)!=(aTimePre/86400));
  }

A segunda versão. Se o sábado começou, o início do dia deve ser ignorado. Se o domingo começou, definiremos o início do dia (isso é naturalmente apenas outro dia). Se a segunda-feira começou após o domingo, pule o início do dia. Se a segunda-feira foi precedida por qualquer outro dia da semana, por exemplo, sábado ou sexta-feira, defina o início do dia. O que obtemos é uma função como a seguinte:

bool NewDay2(datetime aTimeCur,datetime aTimePre)
  {
   MqlDateTime stm;
//--- new day
   if(NewDay1(aTimeCur,aTimePre))
     {
      TimeToStruct(aTimeCur,stm);
      switch(stm.day_of_week)
        {
         case 6: // Saturday
            return(false);
            break;
         case 0: // Sunday
            return(true);
            break;
         case 1: // Monday
            TimeToStruct(aTimePre,stm);
            if(stm.day_of_week!=0)
              { // preceded by any day of the week other than Sunday
               return(true);
              }
            else
              {
               return(false);
              }
            break;
         default: // any other day of the week
            return(true);
        }
     }
   return(false);
  }

E aqui está a função geral, dependendo da versão:

bool NewDay(datetime aTimeCur,datetime aTimePre,int aVariant=1)
  {
   switch(aVariant)
     {
      case 1:
         return(NewDay1(aTimeCur,aTimePre));
         break;
      case 2:
         return(NewDay2(aTimeCur,aTimePre));
         break;
     }
   return(false);
  }

Vamos testar a operação da função utilizando a ferramenta "sTestArea" (o arquivo sTestArea_Pivot1.mq5 anexo; o início do dia está marcado na cor marrom). Você precisará executar oito testes: 2 versões de função para quatro opções de geração de barra. Após certificar-se de que as funções operam adequadamente, podemos começar a desenvolver um indicador com segurança. Entretanto, porque o desenvolvimento de indicadores não é o foco deste artigo, anexamos aqui um indicador já pronto (o arquivo Pivot1.mq5), com a parte mais difícil do seu desenvolvimento considerada em detalhes.


Determinação de sessão de tempo

Precisamos permitir que o Expert Advisor negocie dentro de um período de tempo especificado durante o dia, todos os dias no mesmo intervalo. Especificamos as horas e minutos do início da sessão de negociação, bem como as horas e minutos do término da sessão. Horas e minutos especificados separadamente, em vez de variáveis de string com o horário especificado como "14:00", permitirá realizarmos otimizações no Strategy Tester se a função for utilizada no Expert Advisor.

Para determinar a sessão de tempo, faça o seguinte:

  1. Calcule o tempo em segundos desde o início do dia para o ponto inicial de tempo e faça o mesmo para o ponto final de tempo.
  2. Calcule a hora atual em segundos desde o início do dia.
  3. Compare a hora atual com a hora de início e final.

Não é impossível que uma sessão de negociação inicie em um dia e termine em outro, ou seja, quando uma sessão de negociação passa da meia-noite, caso em que a hora de término calculada desde o início do dia acaba sendo menor que a hora de início. Assim, precisamos fazer duas verificações. A função que obtemos é a seguinte:

bool TimeSession(int aStartHour,int aStartMinute,int aStopHour,int aStopMinute,datetime aTimeCur)
  {
//--- session start time
   int StartTime=3600*aStartHour+60*aStartMinute;
//--- session end time
   int StopTime=3600*aStopHour+60*aStopMinute;
//--- current time in seconds since the day start
   aTimeCur=aTimeCur%86400;
   if(StopTime<StartTime)
     {
      //--- going past midnight
      if(aTimeCur>=StartTime || aTimeCur<StopTime)
        {
         return(true);
        }
     }
   else
     {
      //--- within one day
      if(aTimeCur>=StartTime && aTimeCur<StopTime)
        {
         return(true);
        }
     }
   return(false);
  }

Caso a sessão passe da meia-noite, a hora atual deve ser maior ou igual à hora de início da sessão OU menor que a hora de término da sessão. Se a sessão ocorre durante o dia, a hora atual deve ser maior ou igual à hora de início E menor que a hora de término.

Um indicador criado para testar a operação da função pode ser encontrado anexado ao final do artigo (o arquivo Session.mq5). Como qualquer outro indicador do aplicativo, ele pode ser utilizado não apenas para teste, mas também para outras finalidades práticas.


Determinação de um ponto de tempo durante o dia

Uma simples verificação da igualdade do tempo especificado não funcionará adequadamente, pois os ticks não ocorrem em intervalos regulares e poderá haver atrasos de vários segundos a vários minutos. É muito provável que simplesmente não haja qualquer tick com a hora especificada no mercado. Precisamos verificar a interseção de uma impressão de hora determinada.

A hora atual deve ser igual ou maior à hora especificada, enquanto que a hora anterior deve ser inferior à hora especificada. Porque se requer a determinação de um ponto de tempo dentro do dia, precisamos converter a hora atual (bem como a hora anterior) para segundos desde o início do dia. De forma similar, os parâmetros determinados da hora (hora e minutos) devem ser convertidos para segundos. Poderá muito bem acontecer que a hora anterior caia no dia anterior, ou seja, ela será maior que a hora atual, quando convertida para segundos desde o início do dia. Nesse caso, procederemos de forma igual a quando determinamos a sessão de tempo - faremos duas verificações.

A função que obtemos é a seguinte:

bool TimeCross(int aHour,int aMinute,datetime aTimeCur,datetime aTimePre)
  {
//--- specified time since the day start
   datetime PointTime=aHour*3600+aMinute*60;
//--- current time since the day start
   aTimeCur=aTimeCur%86400;
//--- previous time since the day start
   aTimePre=aTimePre%86400;
   if(aTimeCur<aTimePre)
     {
      //--- going past midnight
      if(aTimeCur>=PointTime || aTimePre<PointTime)
        {
         return(true);
        }
     }
   else
     {
      if(aTimeCur>=PointTime && aTimePre<PointTime)
        {
         return(true);
        }
     }
   return(false);
  }

Há um indicador criado com base nessa função (o arquivo TimePoint.mq5 anexado ao artigo).


Indicador Pivot - Opção 2

Agora que aprendemos a determinar um ponto de tempo, vamos sofisticar o indicador Pivot. Em vez do 00:00 comum, agora o dia iniciará em qualquer horário determinado. Vamos chamá-lo de dia definido pelo usuário. Para determinar o início de um dia definido pelo usuário, utilizaremos a função TimeCross() descrita anteriormente. Devido as diferentes opções de geração de barra aos finais de semana, alguns dias terão que ser omitidos. É difícil planejar todas as regras de verificação agora, então vamos dar um passo de cada vez. O que é importante é ter algo para começar e ter opções de como proceder. Nós temos um script de teste, sTestArea.mq5, de forma que a solução correta pode ser encontrada até mesmo de forma experimental.

O caso "Sem barras de final de semana" é o mais simples: um novo dia inicia na interseção de uma determinada impressão de hora pela hora.

Caso haja apenas algumas barras no fim do domingo, a função TimeCross() definirá a primeira barra de domingo como início do dia, considerando quaisquer parâmetros da função. Presume-se que não há cotações nos finais de semana (as barras de domingo pertencem a segunda-feira), então o domingo deve ser ignorado. Se uma determinada hora cai em algum lugar no meio de uma série de barras de domingo, ela também deverá ser ignorada visto que o início do novo dia já foi registrado na sexta-feira.

Cotações contínuas no final de semana: Se o início de um dia definido pelo usuário cai no meio de um dia civil (Figura 5),

Figura 5. Um dia definido pelo usuário inicia no meio de um dia civil. Sexta-feira - vermelho, sábado - magenta, domingo - verde, segunda-feira - azul.
Figura 5. Um dia definido pelo usuário inicia no meio de um dia civil.
Sexta-feira - vermelho, sábado - magenta, domingo - verde, segunda-feira - azul.

uma metade do sábado pode ser tratada como sexta-feira e uma metade de domingo pode ser tratada como segunda-feira. Mas há algumas barras do meio de sábado ao meio de domingo que não pertencem a local algum. É claro que podemos dividir o intervalo de sábado a domingo em partes iguais e tratar uma metade como sexta-feira e a outra metade como segunda-feira. Isso complicaria significativamente um indicador muito simples, mesmo que as cotações de final de semana não sejam tão importantes.

A solução mais razoável será considerar todas as barras de sábado e domingo como um dia definido pelo usuários que dura de sexta a segunda-feira. Isso significa que os dias definidos pelo usuário que iniciam no sábado e no domingo são pulados.

A função que obtemos é a seguinte:

bool NewCustomDay(int aHour,int aMinute,datetime aTimeCur,datetime aTimePre)
  {
   MqlDateTime stm;
   if(TimeCross(aHour,aMinute,aTimeCur,aTimePre))
     {
      TimeToStruct(aTimeCur,stm);
      if(stm.day_of_week==0 || stm.day_of_week==6)
        {
         return(false);
        }
      else
        {
         return(true);
        }
     }
   return(false);
  }

Há um indicador criado com base nessa função (o arquivo Pivot2.mq5 anexado ao artigo).


Determinação de dias de negociação da semana

Permitir que um Expert Advisor negocie apenas em determinados dias é muito simples. Dividimos a hora em seus componentes utilizando a função TimeToStruct() e declaramos as variáveis do tipo bool para cada um dos dias da semana nos parâmetros do Expert Advisor. Dependendo do dia da semana, a função retorna o valor da variável correspondente.

Isso pode ser feito de uma forma melhor. Ao iniciar um Expert Advisor ou um indicador, preencha um array com valores de variáveis que permitem ou não a negociação em determinados dias. Em seguida, verifique o valor do elemento do array correspondente ao dia da semana. Obtemos duas funções: uma é chamada durante a inicialização, enquanto que a outra é chamada conforme for necessário.

Variáveis:

input bool Sunday   =true; // Sunday
input bool Monday   =true; // Monday
input bool Tuesday  =true; // Tuesday 
input bool Wednesday=true; // Wednesday
input bool Thursday =true; // Thursday
input bool Friday   =true; // Friday
input bool Saturday =true; // Saturday

bool WeekDays[7];

A função de inicialização:

void WeekDays_Init()
  {
   WeekDays[0]=Sunday;
   WeekDays[1]=Monday;
   WeekDays[2]=Tuesday;
   WeekDays[3]=Wednesday;
   WeekDays[4]=Thursday;
   WeekDays[5]=Friday;
   WeekDays[6]=Saturday;
  }

A função principal:

bool WeekDays_Check(datetime aTime)
  {
   MqlDateTime stm;
   TimeToStruct(aTime,stm);
   return(WeekDays[stm.day_of_week]);
  }

Um indicador criado com base nessa função pode ser encontrado anexado ao final do artigo (o arquivo TradeWeekDays.mq5).


Determinação de tempo de negociação da semana

Precisamos determinar as sessões de negociação a partir de um determinado horário de um dia da semana até um horário de outro dia da semana. Essa função é similar à função TimeSession(), sendo que a única diferença é que os cálculos são baseados no tempo transcorrido desde o início da semana. A função que obtemos é a seguinte:

bool WeekSession(int aStartDay,int aStartHour,int aStartMinute,int aStopDay,int aStopHour,int aStopMinute,datetime aTimeCur)
  {
//--- session start time since the week start
   int StartTime=aStartDay*86400+3600*aStartHour+60*aStartMinute;
//--- session end time since the week start
   int StopTime=aStopDay*86400+3600*aStopHour+60*aStopMinute;
//--- current time in seconds since the week start
   long TimeCur=SecondsFromWeekStart(aTimeCur,false);
   if(StopTime<StartTime)
     {
      //--- passing the turn of the week
      if(TimeCur>=StartTime || TimeCur<StopTime)
        {
         return(true);
        }
     }
   else
     {
      //--- within one week
      if(TimeCur>=StartTime && TimeCur<StopTime)
        {
         return(true);
        }
     }
   return(false);
  }

Um indicador criado com base nessa função pode ser encontrado anexado ao final do artigo (o arquivo SessionWeek.mq5).

Consideramos praticamente todas as tarefas mais comuns relacionadas ao tempo e analisamos as técnicas relevantes de programação e as funções MQL5 padrão requeridas para solucioná-las.


Funções MQL5 adicionais

Há mais algumas funções MQL5 para trabalhar com tempo: TimeTradeServer(), TimeGMT(), TimeDaylightSavings() e TimeGMTOffset(). A principal peculiaridade dessas funções é que elas são utilizadas no relógio e configurações de tempo do computador de um usuário.

A função TimeTradeServer(). Foi mencionado anteriormente no artigo que a função TimeCurrent() exibirá a hora errado aos finais de semana (a hora da última mudança de preço na sexta-feira). A função TimeTradeServer() calcula a hora correta do servidor:

datetime tm=TimeTradeServer();
//--- output result
Alert(tm);

A função TimeGMT(). A função calcula a hora GMT com base nos valores do relógio e configurações de hora de um computador do usuário: fuso horário e horário de verão:

datetime tm=TimeGMT();
//--- output result
Alert(tm);

Para ser mais precisa, a função retorna a hora UTC.

A função TimeDaylightSavings(). A função retorna o valor de correção para o horário de verão a partir das configurações do computador do usuário.

int val=TimeDaylightSavings();
//--- output result
Alert(val);

Para obter a hora sem o valor de correção para o horário de verão, você deve adicionar o valor de correção à hora local.

A função TimeGMTOffset(). A função permite que você obtenha o fuso horário do computador de um usuário. O valor é retornado em segundos para ser adicionado à hora local para obter a hora GMT.

int val=TimeGMTOffset();
//--- output result
Alert(val);

A hora no computador de um usuário será TimeGMT()-TimeGMTOffset()-TimeDaylightSavings():

datetime tm1=TimeLocal();
datetime tm2=TimeGMT()-TimeGMTOffset()-TimeDaylightSavings();
//--- output result
Alert(tm1==tm2);


Mais algumas funções úteis para lidar com o tempo

Função para determinar o ano bissexto

bool LeapYear(datetime aTime)
  {
   MqlDateTime stm;
   TimeToStruct(aTime,stm);
//--- a multiple of 4 
   if(stm.year%4==0)
     {
      //--- a multiple of 100
      if(stm.year%100==0)
        {
         //--- a multiple of 400
         if(stm.year%400==0)
           {
            return(true);
           }
        }
      //--- not a multiple of 100 
      else
        {
         return(true);
        }
     }
   return(false);
  }

O princípio da determinação do ano bissexto está descrito acima na seção Peculiaridades da medição de tempo.

Função para determinar o número de dias em um mês

int DaysInMonth(datetime aTime)
  {
   MqlDateTime stm;
   TimeToStruct(aTime,stm);
   if(stm.mon==2)
     {
      //--- February
      if(LeapYear(aTime))
        {
         //--- February in a leap year 
         return(29);
        }
      else
        {
         //--- February in a non-leap year 
         return(28);
        }
     }
   else
     {
      //--- other months
      return(31-((stm.mon-1)%7)%2);
     }
  }

A função verifica se o ano é bissexto para retornar o valor adequado de 28 ou 29 dias para fevereiro e calcula o número de dias dos outros meses. O número de dias dos primeiros 7 meses é alternado da seguinte forma: 31, 30, 31, 30, etc., bem como o número de dias dos 5 meses restantes. Assim, a função calcula o resto da divisão por 7. Em seguida, realizamos a verificação de paridade ímpar e a correção obtida é subtraída de 31.


Peculiaridades da operação da função de tempo no Strategy Tester

O Strategy Tester gera o seu próprio fluxo de cotações e os valores da função TimeCurrent() correspondem ao fluxo de cotações do Strategy Tester. Os valores da função TimeTradeServer() correspondem aos valores de TimeCurrent(). De forma similar, os valores da função TimeLocal() correspondem aos valores de TimeCurrent(). A função TimeCurrent() no Strategy Tester não leva em consideração os fusos horários e a correção de horário de verão. A operação dos Expert Advisors se baseia em mudanças de preço, de forma que, se o seu Expert Advisor precisa lidar com hora, utilize a função TimeCurrent(). Isso permite que você teste o seu Expert Advisor com segurança no Strategy Tester.

As funções TimeGMT(), TimeDaylightSavings() e TimeGMTOffset() operam exclusivamente com base nas configurações atuais do computador de um usuário (as alterações entre horário de verão e horário padrão não são simuladas no Strategy Tester). Se, ao testar um Expert Advisor, você precisas simular alterações entre horário de verão e horário padrão (caso isso seja realmente necessário), você deverá fazer isso por conta própria. Isso requererá informações sobre as datas e horas exatas para mudar o relógio, bem como uma análise cuidadosa.

Uma solução para este problema vai muito além da abrangência de um único artigo e não é considerada aqui. Se um Expert Advisor trabalha dentro das horas de sessão da Europa ou dos Estados Unidos, enquanto que o centro de negociação observa o horário de verão, não haverá discrepância entre a hora do servidor e a hora do evento, ao contrário da sessão da Ásia (o Japão não observa o horário de verão e a Austrália altera os seus relógios para o horário de verão em novembro).


Conclusão

O artigo abordou as funções MQL5 padrão para trabalhar com horário. Ele apresentou as técnicas de programação utilizadas para gerenciamento de tarefas relacionadas a horário. O artigo também demonstrou a criação de diversos indicadores e de algumas funções úteis, fornecendo uma descrição detalhada dos seus princípios de operação.

Todas as funções padrão para trabalhar com horário podem ser classificadas em diversas categorias:

  1. TimeCurrent() e TimeLocal() são as principais funções que são utilizadas para determinar a hora atual.
  2. TimeToString(), StringToTime(), TimeToStruct() e StructToTime() são funções de processamento de hora.
  3. CopyTime() é uma função para trabalhar com hora de barra.
  4. TimeTradeServer(), TimeGMT(), TimeDaylightSavings() e TimeGMTOffset() são funções que dependem das configurações do computador do usuário.


Arquivos anexos

  • sTestArea.mq5 - um script para teste de funções de hora complexas.
  • sTestArea_Pivot1.mq5 - o script sTestArea.mq5 utilizado para teste das funções de hora do indicador Pivot1.mq5.
  • Pivot1.mq5 - um indicador Pivot que utiliza dias padrão (a função NewDay ).
  • Session.mq5 - indicador da sessão de negociação do dia (a função TimeSession).
  • TimePoint.mq5 - um indicador de um determinado ponto de tempo (a função TimeCross).
  • Pivot2.mq5 - um indicador Pivot que utiliza dias definidos pelo usuário (a função NewCustomDay ).
  • TradeWeekDays.mq5 - um indicador de dias de negociação da semana (a função WeekDays_Check ).
  • SessionWeek.mq5 - indicador da sessão de negociação da semana (a função WeekSession).
  • TimeFunctions.mqh - todas as funções de horário apresentadas neste artigo, em um arquivo único.

Traduzido do russo pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/ru/articles/599

Arquivos anexados |
stestarea.mq5 (4.96 KB)
pivot1.mq5 (4.5 KB)
session.mq5 (3.51 KB)
timepoint.mq5 (3.32 KB)
pivot2.mq5 (4.14 KB)
tradeweekdays.mq5 (3.59 KB)
sessionweek.mq5 (5.08 KB)
timefuncions.mqh (20.52 KB)
Cálculo de características integrais das emissões de indicador Cálculo de características integrais das emissões de indicador
Emissões do indicador são uma área pouco estudada da pesquisa de mercado. Isso se deve principalmente à dificuldade de análise que é causada pelo processamento de arrays muito grandes de dados de tempo variável. A análise gráfica existente é um recurso muito intensivo e, portanto, tem provocado o desenvolvimento de um algoritmo parcimonioso que usa série temporal de emissões. Este artigo demonstra como a análise visual (imagem intuitiva) pode ser substituída pelo estudo de características de emissões integral. Isso pode ser de interesse para ambos negociantes e desenvolvedores de sistemas de negociação automatizados.
Como testar um robô de negociação antes da compra Como testar um robô de negociação antes da compra
A compra de um robô de negociação no Mercado MQL5 apresenta uma vantagem distinta em relação a todas as outras opções similares - um sistema automatizado oferecido pode ser inteiramente testado diretamente no terminal MetaTrader 5. Antes da compra, um Expert Advisor pode e deve ser cuidadosamente executado em todos os modos não favoráveis no Strategy Tester integrado para que você entenda completamente o sistema.
Informações gerais sobre os sinais de negociação para MetaTrader 4 e MetaTrader 5 Informações gerais sobre os sinais de negociação para MetaTrader 4 e MetaTrader 5
Sinais de negociação MetaTrader 4 / MetaTrader 5 é um serviço que permite que os negociadores copiem as operações de um provedor de sinais. O nosso objetivo foi desenvolver o novo serviço amplamente utilizado protegendo os assinantes e livrando eles de custos desnecessários.
Fundamentos básicos da programação MQL5: strings Fundamentos básicos da programação MQL5: strings
O artigo cobre tudo que você pode fazer com strings no MQL5. Deve ser de interesse principalmente para programadores MQL5 novatos, enquanto os desenvolvedores mais experientes terão uma boa oportunidade para resumir e sistematizar seu conhecimento.