Horario de verano (local)

Para determinar si los relojes locales se cambian al horario de verano, MQL5 proporciona la función TimeDaylightSavings, que toma la configuración de su sistema operativo.

Determinar el horario de verano en un servidor no es tan fácil. Para ello, es necesario aplicar el análisis MQL5 de cotizaciones, eventos de calendario económico o una hora de rollover/swap en el historial de trading de la cuenta. En el siguiente ejemplo mostraremos una de las opciones.

int TimeDaylightSavings()

La función devuelve la corrección en segundos si se ha aplicado el horario de verano. El horario de invierno es estándar para cada zona horaria, por lo que la corrección para este periodo es cero. En forma condicional, la fórmula para obtener la corrección puede escribirse del siguiente modo:

TimeDaylightSavings() = TimeLocal winter() - TimeLocal summer()

Por ejemplo, si la zona horaria estándar (winter) es igual a UTC+3 (es decir, la hora de la zona está 3 horas por delante de UTC), durante la transición al horario de verano (summer) añadimos 1 hora y obtenemos UTC+4. Donde TimeDaylightSavings devolverá -3600.

Un ejemplo de uso de la función se ofrece en el script TimeSummer.mq5, que también sugiere una de las posibles formas empíricas de identificar el modo apropiado en el servidor.

void OnStart()
{
   PRTF(TimeLocal());          // local time of the terminal
   PRTF(TimeCurrent());        // last known server time
   PRTF(TimeTradeServer());    // estimated server time
   PRTF(TimeGMT());            // GMT time (calculation from local via time zone shift)
   PRTF(TimeGMTOffset());      // time zone shift compare to GMT, in seconds
   PRTF(TimeDaylightSavings());// correction for summer time in seconds
   ...

En primer lugar, vamos a mostrar todos los tipos de tiempo y su corrección proporcionada por MQL5 (las funciones TimeGMT y TimeGMTOffset se presentarán en la siguiente sección sobre Hora universal, pero su significado ya debería estar claro en general por la descripción anterior).

Se supone que el script se ejecuta en días de trading. Las entradas del registro corresponderán a la configuración de su ordenador y del servidor del broker.

TimeLocal()=2021.09.09 22:06:17 / ok
TimeCurrent()=2021.09.09 22:06:10 / ok
TimeTradeServer()=2021.09.09 22:06:17 / ok
TimeGMT()=2021.09.09 19:06:17 / ok
TimeGMTOffset()=-10800 / ok
TimeDaylightSavings()=0 / ok

En este caso, la zona horaria del cliente está a 3 horas de GMT (UTC+3), no hay ajuste por horario de verano.

Ahora echemos un vistazo al servidor. Basándonos en el valor de la función TimeCurrent podemos determinar la hora actual del servidor, pero no su zona horaria estándar, ya que esta hora puede implicar la transición al horario de verano (MQL5 no proporciona información sobre si se utiliza en absoluto y si está actualmente activada).

Para determinar la zona horaria real del servidor y el horario de verano, utilizaremos el hecho de que la traducción de la hora del servidor afecta a las cotizaciones. Como la mayoría de los métodos empíricos para resolver problemas, éste puede no dar resultados completamente correctos en determinadas circunstancias. Si la comparación con otras fuentes muestra discrepancias, debe elegirse otro método.

El mercado Forex abre el domingo a las 22:00 UT (lo que corresponde al inicio de las operaciones matutinas en la región Asia-Pacífico) y cierra el viernes a las 22:00 (el cierre de las operaciones en América). Esto significa que en los servidores de la zona UTC+2 (Europa oriental), las primeras barras aparecerán exactamente a las 0 horas 0 minutos del lunes. Según la hora de Europa central, que corresponde a UTC+1, la semana de trading comienza a las 23:00 del domingo.

Una vez calculadas las estadísticas del desplazamiento intradiario de la primera barra H1 después de cada pausa de fin de semana, obtendremos una estimación de la zona horaria del servidor. Por supuesto, para ello es mejor utilizar el instrumento más líquido de Forex, que es EURUSD.

Si en las estadísticas de un periodo anual se encuentran dos desplazamientos intradiarios máximos y estos están situados uno al lado del otro, significará que el broker está cambiando al horario de verano y viceversa.

Tenga en cuenta que los periodos de verano e invierno no son iguales. Así, al cambiar al horario de verano a principios de marzo y volver al de invierno a principios de noviembre, tenemos unos 8 meses de horario de verano. Esto afectará a la proporción de máximos en las estadísticas.

Al disponer de dos husos horarios podemos determinar fácilmente cuál de ellos está activo en ese momento y, de ese modo, averiguar la presencia o ausencia de una corrección para el horario de verano.

Al cambiar los relojes al horario de verano, la zona horaria del broker cambiará de UTC+2 a UTC+3, lo que desplazará el comienzo de la semana de las 22:00 a las 21:00. Esto afectará a la estructura de las barras H1: en el gráfico veremos tres barras el domingo por la tarde en lugar de dos.

Cambio de horas del horario de invierno (UTC+2) al de verano (UTC+3) en el gráfico EURUSD H1

Cambio de horas del horario de invierno (UTC+2) al de verano (UTC+3) en el gráfico EURUSD H1

Para ello, disponemos de una función independiente, ServerTimeZone. La llamada a la función integrada CopyTime se encarga de obtener cotizaciones, o marcas de tiempo de barras, para ser más precisos (estudiaremos esta función en la sección sobre acceso a series temporales).

ServerTime ServerTimeZone(const string symbol = NULL)
{
  const int year = 365 * 24 * 60 * 60;
  datetime array[];
  if(PRTF(CopyTime(symbolPERIOD_H1TimeCurrent() - yearTimeCurrent(), array)) > 0)
  {
     // here we get about 6000 bars in the array
     const int n = ArraySize(array);
     PrintFormat("Got %d H1 bars, ~%d days"nn / 24);
     // (-V-) loop through H1 bars
     ...
  }
}

La función CopyTime recibe como parámetros el instrumento de trabajo, el marco temporal H1 y el intervalo de fechas del último año. El valor NULL en lugar del instrumento significa el símbolo del gráfico actual en el que se colocará el script, por lo que se recomienda seleccionar la ventana con EURUSD. La constante PERIOD_H1 corresponde a H1, como se puede adivinar. Ya estamos familiarizados con la función TimeCurrent: devolverá la hora actual, la última conocida, del servidor. Y si le restamos el número de segundos de un año, que se coloca en la variable year, obtendremos la fecha y la hora de hace exactamente un año. Los resultados entrarán en array.

Para calcular las estadísticas sobre cuántas veces se ha iniciado una semana por medio de una barra a una hora determinada, reservamos el array hours[24]. El cálculo se realizará en un bucle mediante el array resultante, es decir, por barras desde el pasado hasta el presente. En cada iteración, la hora de inicio de la semana que se está visualizando se almacenará en la variable current. Cuando finalice el bucle, la zona horaria actual del servidor permanecerá en current, ya que la semana actual se procesará en último lugar.

     // (-v-) cycle through H1 bars
     int hours[24] = {};
     int current = 0;
     for(int i = 0i < n; ++i)
     {
     // (-V-) processing of the i-th bar H1
        ...
     }
     
     Print("Week opening hours stats:");
     ArrayPrint(hours);

Dentro del bucle de días utilizaremos la clase datetime del archivo de encabezado MQL5Book/DateTime.mqh (véase Fecha y hora).

        // (-v-) processing the i-th bar H1
        // find the day of the week of the bar
        const ENUM_DAY_OF_WEEK weekday = TimeDayOfWeek(array[i]);
        // skip all days except Sunday and Monday
        if(weekday > MONDAYcontinue;
        // analyze the first bar H1 of the next trading week
        // find the hour of the first bar after the weekend
        current = _TimeHour();
        // calculate open hours statistics
        hours[current]++;
        
        // skip next 2 days
        // (because the statistics for the beginning of this week have already been updated)
        i += 48;

El algoritmo propuesto no es óptimo, pero no requiere comprender los detalles técnicos de la organización de las series temporales, que aún desconocemos.

Algunas semanas no tienen formato (comienzan después de las vacaciones). Si esta situación se produce en la última semana, la variable current contendrá un desplazamiento inusual. Esto se puede comprobar por estadística: para la hora resultante habrá un número muy pequeño de «inicios» registrados de la semana. En el script de prueba, en este caso, simplemente se muestra un mensaje en el registro. En la práctica, debe aclarar el inicio estándar de una o dos semanas anteriores.

     // (-V-) cycle through H1 bars
     ...
     if(hours[current] <= 52 / 4)
     {
        // TODO: check for previous weeks
        Print("Extraordinary week detected");
     }

Si el broker no cambia al horario de verano, las estadísticas tendrán un máximo, que incluirá todas o casi todas las semanas. Si el broker practica un cambio de huso horario, habrá dos máximos en las estadísticas.

     // find the most frequent time shift
     int max = ArrayMaximum(hours);
     // then check if there is another regular shift
     hours[max] = 0;
     int sub = ArrayMaximum(hours);

Tenemos que determinar hasta qué punto es significativo el segundo extremo (es decir, diferente de los días festivos aleatorios que podrían desplazar el inicio de la semana). Para ello, evaluamos las estadísticas de un trimestre del año (52 semanas / 4). Si se supera este límite, el broker admite el horario de verano.

     int DST = 0;
     if(hours[sub] > 52 / 4)
     {
        // basically, DST is supported 
        if(current == max || current == sub)
        {
           if(current == MathMin(maxsub))
              DST =fabs(max -sub); // DST is enabled now
        }
     }

Si el desfase en el inicio de la semana actual (en la variable actual) coincide con uno de los dos extremos principales, entonces la semana actual se inició normalmente, y se puede utilizar para sacar una conclusión sobre la zona horaria (esta condición de protección es necesaria porque no tenemos ninguna corrección para las semanas no estándar y en su lugar sólo se emite una advertencia).

Ahora todo está listo para formar la respuesta de nuestra función: la zona horaria del servidor y el signo del horario de verano activado.

 current +=2 +DST;// +2 to get offset from UTC
     current %= 24;
 // timezones are always in the range [UTC-12,UTC+12]
     if(current > 12current = current - 24;

Puesto que tenemos dos características para devolver desde una función (current y DST), y además de eso, podemos decirle al código llamado si el broker utiliza el horario de verano para empezar (incluso si ahora es invierno), tiene sentido declarar una estructura especial ServerTime con todos los campos requeridos.

struct ServerTime
{
 intoffsetGMT;      // timezone in seconds relative to UTC/GMT
 intoffsetDST;      // DST correction in seconds (included in offsetGMT)
 boolsupportDST;    // DST correction detected in quotes in principle
 stringdescription// result description
};

A continuación, en la función ServerTimeZone, podemos rellenar y devolver dicha estructura como resultado del trabajo.

     ServerTime st = {};
     st.description = StringFormat("Server time offset: UTC%+d, including DST%+d"currentDST);
     st.offsetGMT = -current * 3600;
     st.offsetDST = -DST * 3600;
     return st;

Si por alguna razón la función no puede obtener cotizaciones, devolveremos una estructura vacía.

ServerTime ServerTimeZone(const string symbol = NULL)
{
  const int year = 365 * 24 * 60 * 60;
  datetime array[];
  if(PRTF(CopyTime(symbolPERIOD_H1TimeCurrent() - yearTimeCurrent(), array)) > 0)
  {
     ...
     return st;
  }
  ServerTime empty = {-INT_MAX, -INT_MAXfalse};
  return empty;
}

Comprobemos la nueva función en acción, para lo cual añadimos en OnStart las siguientes instrucciones:

   ...
   ServerTime st = ServerTimeZone();
   Print(st.description);
   Print("ServerGMTOffset: "st.offsetGMT);
   Print("ServerTimeDaylightSavings: "st.offsetDST);
}

Veamos los posibles resultados:

CopyTime(symbol,PERIOD_H1,TimeCurrent()-year,TimeCurrent(),array)=6207 / ok
Got 6207 H1 bars, ~258 days
Week opening hours stats:
52  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0
Server time offset: UTC+2, including DST+0
ServerGMTOffset: -7200
ServerTimeDaylightSavings: 0

Según las estadísticas recopiladas de las barras H1, la semana para este broker se inicia estrictamente a las 00:00 del lunes. Así, la zona horaria real es igual a UTC+2, y no hay corrección para el horario de verano, es decir, la hora del servidor debe coincidir con EET (UTC+2). Sin embargo, en la práctica, como vimos en la primera parte del registro, la hora en el servidor difiere de GMT en 3 horas.

Aquí podemos suponer que nos encontramos con un servidor que funciona todo el año en verano. En ese caso, la función ServerTimeZone no podrá distinguir la corrección de la hora adicional en la «zona horaria»: como resultado, el modo DST será igual a cero, y la hora GMT calculada a partir de las cotizaciones del servidor se desplazará hacia la derecha una hora con respecto a la real. O bien, nuestra suposición inicial de que las cotizaciones empiezan a llegar a las 22:00 del domingo no se corresponde con el modo en que funciona este servidor. Estas cuestiones deben aclararse con el servicio de asistencia del broker.