Gestor de evento "Nueva barra"

Konstantin Gruzdev | 11 marzo, 2014

Introducción

Los autores de indicadores y expertos siempre han estado interesados en escribir el código de forma compacta en términos de tiempo de ejecución. Puede abordar este problema desde diferentes ángulos. A partir de este amplio tema, en este artículo vamos a examinar un problema que aparentemente ya se ha resuelto: comprobar una nueva barra. Esta es una forma muy popular de limitar los bucles de cálculo, ya que todos los cálculos y operaciones de trading se realizan en una sola vez durante la generación de una nueva barra en el gráfico. Por tanto, veremos a continuación:  

Formas de detectar nuevas barras

Ahora, hay algunas soluciones aceptables sobre cómo detectar una nueva barra. Por ejemplo, pueden encontrare en los artículos Limitaciones y verificaciones en asesores expertos, principios del cálculo económico de indicadores o aquí. Por cierto, recomiendo que se estudien estos materiales. Será más sencillo saber de lo que estoy hablando.  

Estos materiales utilizan el principio de seguir el tiempo de apertura de la barra no finalizada actualmente. Esta es una forma muy sencilla y fiable. Hay otras formas de detectar una nueva barra.

Por ejemplo, en indicadores personalizados para esta finalidad puede usar dos parámetros de entrada de la función OnCalculate(): rates_total y prev_calculated. Limitación de este método: es básicamente el hecho de que solo puede usarse para detectar una nueva barra en el gráfico actual y solo en indicadores. Si quiere encontrar una nueva barra en otro periodo o símbolo, es necesario usar técnicas adicionales.

O, por ejemplo, puede intentar capturar una nueva barra en su primer tick, cuando Tick Volumen = 1, o cuando todos los precios de la barra son iguales: Open = High = Low = Close. Estos métodos pueden usarse para pruebas, pero en el trading real fallan a menudo. Esto se debe a que, a veces, el momento entre el primer y el segundo tick no es suficiente para capturar la barra generada. Esto se puede apreciar, especialmente, con un fuerte movimiento del mercado o cuando la calidad de la conexión a internet es pobre.  

Hay una forma de detectar una nueva barra que se basa en la función TimeCurrent(). Por cierto, es una buena forma si necesita detectar una nueva barra para el gráfico actual. La usaremos al final de este artículo.

Bueno, también puede preguntárselo a un vecino: "¿Oiga, hay una nueva barra?" Me pregunto qué responderá. Bueno, de acuerdo, vamos a abandonar la opción del principio de seguir el tiempo de apertura de la barra no finalizada actual para detectar una nueva. Su simplicidad y fiabilidad está verdaderamente probada. 

Punto de inicio

En los materiales mencionados anteriormente las cosas no pintan mal para la detección de una nueva barra. Pero...  

Para comprender qué significa "pero" aquí, como punto de inicio (o prototipo) usaremos una función simple y que funciona bien para detectar una nueva barra del artículo Limitaciones y verificaciones en asesores expertos Aquí está:

//+------------------------------------------------------------------+
//| Returns true if a new bar has appeared for a symbol/period pair  |
//+------------------------------------------------------------------+
bool isNewBar()
  {
//--- memorize the time of opening of the last bar in the static variable
   static datetime last_time=0;
//--- current time
   datetime lastbar_time=SeriesInfoInteger(Symbol(),Period(),SERIES_LASTBAR_DATE);

//--- if it is the first call of the function
   if(last_time==0)
     {
      //--- set the time and exit
      last_time=lastbar_time;
      return(false);
     }

//--- if the time differs.
   if(last_time!=lastbar_time)
     {
      //--- memorize the time and return true
      last_time=lastbar_time;
      return(true);
     }
//--- if we passed to this line, then the bar is not new; return false
   return(false);
  }

Esta función prototipo está actualmente funcionando y tiene todo el derecho a hacerlo. Pero... 

Análisis de la función prototipo

He copiado esta función en el código fuente de mi (por supuesto) mayor y mejor de los mejores asesor experto. No funcionó. Empecé a investigar. Esta es mi opinión sobre esta función.

Encabezado de la función. Vamos a mirar todo consecuentemente. Vamos a empezar con el encabezado de la función:

bool isNewBar()

Me gusta el encabezado de la función, es muy simple, intuitivo y no es necesario trabajar con parámetros pasados. Estará bien usarlo en este formulario en el futuro.

Restricción del número de llamadas. A continuación del encabezado se encuentra la primera declaración que inicializa la variable estática:

//---  memorize the time of opening of the last bar in the static variable
   static datetime last_time=0;

Todo parece estar muy bien. Pero...

El problema es que usamos una variable estática. Los temas de ayuda nos dicen: Los temas de ayuda nos dicen:

Las variables estáticas existen desde el momento de la ejecución del programa y se inicializan solo una vez antes de llamar a la función especializada OnInit(). Si no se especifican los valores iniciales, las variables de la clase de almacenamiento estáticas toman valores iniciales cero. 

Las variables locales declaradas con la palabra clave estática conservan sus valores a lo largo del periodo de vida de la función. Con cada función siguiente, dichas variables contienen los valores que tenía durante la llamada previa.

Si llamamos a esta función prototipo desde un lugar, ya tenemos lo que necesitamos. Pero si queremos usar esta función, por ejemplo, de nuevo en otro lugar en el mismo bucle de cálculo, siempre devolverá "falso", lo que significa que no hay barra. Y esto no siempre será verdad. La variable estática, en este caso, impone un límite artificial al número llamadas a la función prototipo.

Una cuestión de universalidad. La siguiente declaración en la función prototipo es como sigue:

//--- current time
   datetime lastbar_time=SeriesInfoInteger(Symbol(),Period(),SERIES_LASTBAR_DATE);

Es lógico que para obtener el tiempo de apertura de la última barra no finalizada se use la función SeriesInfoInteger() con el modificador SERIES_LASTBAR_DATE.

Nuestra función prototipo isNewBar() fue concebida originalmente como simple y por defecto utiliza el instrumento de trading y el periodo del gráfico actual, Esto es aceptable si queremos hacer un seguimiento a la nueva barra solo en el gráfico actual. Pero ¿qué hacer si usamos el periodo y el instrumento no solo para el gráfico actual? Además, ¿y si tenemos un gráfico complejo? Por ejemplo, ¿y si decidimos trazar Renko o Kagi?

Su falta puede limitarnos considerablemente. Luego vamos a ver cómo arreglarlo.  

Gestión de errores Vamos a ver la función SeriesInfoInteger(). ¿Qué cree que nos devolverá si la ejecutamos cuando el gráfico no ha sido creado aún? Dicha situación puede ocurrir, por ejemplo, si hemos adjuntado nuestro asesor experto o indicador a un gráfico y hemos decidido cambiar el periodo o el símbolo, o cuando reiniciamos el terminal. Y ¿qué pasará durante la actualización de las series de tiempo? Dicho sea de paso, ya existe este aviso en los temas de ayuda:

Disponibilidad de datos

La presencia de datos en formato HCC o incluso en formato HC listo para usarse no siempre quiere decir una absoluta disponibilidad de estos datos para que se muestren en un gráfico o para que sean usados en programas MQL5.

Al acceder a los datos sobre precios o a los valores del indicador de un programa MQL5, recuerde que su disponibilidad en un momento concreto o a partir de un cierto momento no está garantizada. Esto se debe al hecho de que para ahorrar recursos del sistema, en MetaTrader 5 no se guarda la copia completa de los datos necesarios para un programa mql5, solo se proporcionan el acceso directo a la base de datos del terminal.

El historial de precios para todos los periodos de tiempo se construye a partir de datos comunes con formato HCC, y cualquier actualización de datos de un servidor conlleva la actualización de datos de todos los periodos de tiempo y el recálculo de los indicadores. Debido a esto, puede denegarse el acceso a los datos, incluso si estos datos estaban disponibles hace un momento.

Por tanto, ¿qué devolverá esta función? Para evitar esta incertidumbre necesitamos de alguna forma empezar a capturar los errores de consulta de tiempo de apertura de la última barra no finalizada.  

Posibilidad de inicialización. Sigamos adelante. Consideremos las siguientes declaraciones de nuestra función prototipo:

//--- if it is the first call of the function
   if(last_time==0)
     {
      //--- set the time and exit
      last_time=lastbar_time;
      return(false);
     }

Aquí todo está bien. Sin embargo, hay un matiz. ¿Ha observado la declaración anterior de la ayuda: Las variables estáticas existen desde el momento de la ejecución del programa y se inicializan solo una vez antes de llamar a la función especializada OnInit()? ¿Y si necesitamos más tiempo para inicializar la variable last_time? De forma más precisa, ¿qué hacer si quieres crear artificialmente una situación de la primera llamada? ¿o alguna otra situación? Es fácil hacer preguntas cuando sabes las respuestas. Pero hablaremos más de eso más tarde.

Número de barras. A continuación, nuestra función prototipo tendrá el siguiente código:

//--- if the time differs
   if(last_time!=lastbar_time)
     {
      //--- memorize the time and return true
      last_time=lastbar_time;
      return(true);
     }

Puede ver que un programador como yo puede hacer que el operador "if" "sorprenda" al terminal de cliente y al probador de estrategia. El hecho es que, lógicamente, el pasado siempre es menos que el presente. Eso es last_time < lastbar_time. Debido a un error accidental del programa tengo la máquina del tiempo, o más exactamente, ha ocurrido lo opuesto: lastbar_time < last_time. ¡Qué sorpresa! En general, dicha paradoja temporal es fácil de detectar y hacer que se muestre en pantalla un mensaje de error.

Pero toda nube tiene un recubrimiento plateado. Mientras miraba a mi "máquina del tiempo", me he dado cuenta de que entre las llamadas de isNewBar() no solo puede aparecer una barra. Cuanto menor sea el periodo del gráfico, mayor es la probabilidad de que aparezcan varias barras entre las llamadas a la función. Pueden ser varias las razones de esto: comenzando por el largo tiempo de procesado y terminando por una pérdida temporal de conexión con el servidor. La oportunidad para no solo recibir la señal de una nueva barra sino también el número de barras será realmente útil.

Nuestra función prototipo termina así:

//--- if we passed to this line, then the bar is not new; return false
   return(false);

Sí, si hemos pasado a esta línea, la barra no es nueva.

Crear una nueva función isNewBar() 

Aquí comienza algo interesante. Solucionaremos las debilidades encontradas. Ya sabe, fue un poco humilde llamar a la sección "Crear una nueva función isNewBar()". Vamos a hacer algo más sólido.

Comenzaremos por deshacernos de las restricciones en el número de llamadas a la función.

Lo primero que se me viene a la mente es que puede usar funciones con el mismo nombre que isNewBar() a partir del artículo Los principios del cálculo económico de indicadores o de aquí isNewBar. Es decir, para incluir matrices que almacenen múltiples valores last_time en el cuerpo de la función, poner contadores de las llamadas de la función isNewBar() de diferentes lugares, y demás. Por supuesto, estas son todas versiones funcionales y pueden implementarse. Pero imagine que estamos escribiendo un asesor experto multimoneda para trabajar sobre 12 pares de divisas. ¿Será posible considerar tantos matices sin llegar a confundirnos?

¿Qué debemos hacer? ¡La respuesta está aquí!

La belleza de la programación orientada a objeto está en que un objeto o una instancia de cierta clase, puede "vivir sus propia vida" de forma independiente a otras instancias de la misma clase. Vamos a comenzar a crear una clase CisNewBar, de forma que seremos capaces de producir instancias de esta clase en cualquier lugar de nuestro asesor experto o indicador cualquier número de veces. Y dejaremos que cada instancia "viva su propia vida".

Debemos empezar por esto:

class CisNewBar 
  {
   protected:
      datetime          m_lastbar_time;   // Time of opening last bar
      
   public:
      void              CisNewBar();      // CisNewBar constructor
      //--- Methods of detecting new bars:
      bool              isNewBar();       // First type of request for new bar
  };  

bool CisNewBar::isNewBar()
  {
   //--- here is the definition of static variable

   //--- here will be the rest of method's code   
   ...

   //--- if we've reached this line, then the bar is not new; return false
   return(false);
  }

Lo que antes fue la función isNewBar(), es ahora el método. Observe que ahora no hay variable estática last_time, en su lugar tenemos la variable de clase protegida m_lastbar_time. Si hemos dejado la variable estática en el método isNewBar(), entonces todos nuestros esfuerzos no habrían servido de nada, ya que tendríamos los mismos problemas de antes con la función isNewBar(). Estas son características propias de las variables estáticas.

Y ahora, el tiempo de la última barra se almacenará en la variable protegida m_lastbar_time de la clase, y para cada instancia de clase se asignará memoria para esta variable De esta forma podemos eliminar la restricción del número de llamadas que estaban en la función prototipo. Podemos llamar al método isNewBar() en diferentes lugares en nuestro programa MQL todas las veces que queramos, creando una instancia de clase para cada lugar.

Esto es algo con lo que hemos tenido éxito. vamos a trabajar ahora con la universalidad. Antes de añadir algo a nuestra nueva clase, me gustaría mostrarle una idea divertida.

Vamos a razonar. ¿Qué queremos? Queremos obtener la señal de la nueva barra. ¿Cómo queremos hacer esto? Si el tiempo de apertura de la barra no finalizada actual en el último tick (o en el último momento) es mayor que el tiempo de apertura de la barra no finalizada actual en el tick previo (en el momento previo), se forma la nueva barra. Una frase compleja, pero es correcta. El límite es que necesitamos comparar el tiempo. Por tanto, he decidido que sería lógico pasar el tiempo de apertura de la barra no finalizada actual newbar_time al método isNewBar(). Entonces, el encabezado del método será de la siguiente forma:

bool isNewBar(datetime newbar_time)

No pregunte aún de dónde obtendremos newbar_time, asuma que ya lo conocemos. Veremos esto más tarde.  

Por cierto, al pasar el tiempo al método isNewBar(), obtenemos una herramienta muy flexible para detectar una nueva barra. Podremos cubrir todos los periodos gráficos estándar con todo tipo de herramientas de trading. Ha ocurrido, por lo que no dependemos del nombre del símbolo ni del tamaño del período.  

También podemos usar gráficos no estándar. Por ejemplo, si está trazando gráficos de velas de tick o Renko p Kagi, su tiempo de apertura de la barra nunca coincide prácticamente con el tiempo de los períodos de los gráficos estándar. En este caso nuestra función será indispensable.

Bueno, ahora ya hemos cumplido con la versatilidad. Vamos a complementar nuestra clase CisNewBar según nuestra idea:

class CisNewBar 
  {
   protected:
      datetime          m_lastbar_time;   // Time of opening last bar
      uint              m_retcode;        // Result code of detecting new bar
      int               m_new_bars;       // Number of new bars
      string            m_comment;        // Comment of execution
      
   public:
      void              CisNewBar();      // CisNewBar constructor
      //--- Methods of detecting new bars:
      bool              isNewBar(datetime new_Time); // First type of request for new bar
  };
   
//+------------------------------------------------------------------+
//| First type of request for new bar                     |
//| INPUT:  newbar_time - time of opening (hypothetically) new bar   |
//| OUTPUT: true   - if new bar(s) has(ve) appeared                  |
//|         false  - if there is no new bar or in case of error      |
//| REMARK: no.                                                      |
//+------------------------------------------------------------------+
bool CisNewBar::isNewBar(datetime newbar_time)
  {
   //--- Initialization of protected variables
   m_new_bars = 0;      // Number of new bars
   m_retcode  = 0;      // Result code of detecting new bar: 0 - no error
   m_comment  =__FUNCTION__+" Successful check for new bar";
   //---
   
   //--- Just to be sure, check: is the time of (hypothetically) new bar m_newbar_time less than time of last bar m_lastbar_time? 
   if(m_lastbar_time>newbar_time)
     { // If new bar is older than last bar, print error message
      m_comment=__FUNCTION__+" Synchronization error: time of previous bar "+TimeToString(m_lastbar_time)+
                                                  ", time of new bar request "+TimeToString(newbar_time);
      m_retcode=-1;     // Result code of detecting new bar: return -1 - synchronization error
      return(false);
     }
   //---
        
   //--- if it's the first call
   if(m_lastbar_time==0)
     {  
      m_lastbar_time=newbar_time; //--- set time of last bar and exit
      m_comment   =__FUNCTION__+" Initialization of lastbar_time = "+TimeToString(m_lastbar_time);
      return(false);
     }   
   //---

   //--- Check for new bar:
   if(m_lastbar_time<newbar_time)       
     { 
      m_new_bars=1;               // Number of new bars
      m_lastbar_time=newbar_time; // remember time of last bar
      return(true);
     }
   //---
   
   //--- if we've reached this line, then the bar is not new; return false
   return(false);
  }

Al observar el código fuente de nuestra clase, podemos darnos cuenta de que hemos tenido en cuenta el seguimiento de los errores en tiempo de ejecución, y que hemos introducido una variable que almacena el número de nuevas barras.

Todo está bien, pero nuestro método universal isNewBar (datetime newbar_time) contiene un gran inconveniente. Esta desventaja consiste en que siempre tenemos que preocuparnos de calcular el tiempo de newbar_time (hipotéticamente) en el código fuente de nuestro experto o indicador.  

Por suerte, en algunos casos podemos simplificarlo asignando esta función al nuevo método adicional de nuestra clase. Para periodos y símbolos estándar en nuestra función prototipo, esto puede hacerse usando la segunda versión de la función SeriesInfoInteger() con el modificador SERIES_LASTBAR_DATE, y en todos los demás casos, usando el método genérico. Esto es lo que tengo:

//+------------------------------------------------------------------+
//| Second type of request for new bar                               |
//| INPUT:  no.                                                      |
//| OUTPUT: m_new_bars - Number of new bars                          |
//| REMARK: no.                                                      |
//+------------------------------------------------------------------+
int CisNewBar::isNewBar()
  {
   datetime newbar_time;
   datetime lastbar_time=m_lastbar_time;
      
   //--- Request time of opening last bar:
   ResetLastError(); // Set value of predefined variable _LastError as 0
   if(!SeriesInfoInteger(m_symbol,m_period,SERIES_LASTBAR_DATE,newbar_time))
     { // If request has failed, print error message:
      m_retcode=GetLastError();  // Result code of detecting new bar: write value of variable _LastError
      m_comment=__FUNCTION__+" Error when getting time of last bar opening: "+IntegerToString(m_retcode);
      return(0);
     }
   //---
   
   //---Next use first type of request for new bar, to complete analysis:
   if(!isNewBar(newbar_time)) return(0);
   
   //---Correct number of new bars:
   m_new_bars=Bars(m_symbol,m_period,lastbar_time,newbar_time)-1;
   
   //--- If we've reached this line - then there is(are) new bar(s), return their number:
   return(m_new_bars);
  }

¿Qué tenemos ahora? Ahora, para periodos estándar no necesitamos preocuparnos por determinar el tiempo de apertura de la última barra no finalizada. Hemos abordado nuestra función prototipo con su simple llamada y sin las desventajas que tenía. Incluso tenemos ventajas adicionales, incluyendo códigos de error, comentarios de tiempo de ejecución y número de barras nuevas.   

¿Queda algo? Sí. Queda el último momento, la inicialización. Para esto usaremos el constructor de la clase y varios métodos Set. Nuestro constructor de clase es el siguiente:  

//+------------------------------------------------------------------+
//| CisNewBar constructor.                                           |
//| INPUT:  no.                                                      |
//| OUTPUT: no.                                                      |
//| REMARK: no.                                                      |
//+------------------------------------------------------------------+
void CisNewBar::CisNewBar()
  {
   m_retcode=0;         // Result code of detecting new bar
   m_lastbar_time=0;    // Time of opening last bar
   m_new_bars=0;        // Number of new bars
   m_comment="";        // Comment of execution
   m_symbol=Symbol();   // Symbol name, by default - symbol of current chart
   m_period=Period();   // Chart period, by default - period of current chart
  }

Y los métodos Set son estos:

//--- Methods of initializing protected data:
void              SetLastBarTime(datetime lastbar_time){m_lastbar_time=lastbar_time;                            }
void              SetSymbol(string symbol)             {m_symbol=(symbol==NULL || symbol=="")?Symbol():symbol;  }
void              SetPeriod(ENUM_TIMEFRAMES period)    {m_period=(period==PERIOD_CURRENT)?Period():period;      }

Gracias al constructor de la clase, no necesitamos prestar atención a la inicialización del símbolo y periodo del gráfico actual. Como en la función prototipo, se usarán por defecto. Pero si necesitamos usar otro símbolo o periodo del gráfico, podemos usar para esto nuestros métodos Set que hemos creado. Además, usando podemos crear la situación de la SetLastBarTime(datetime lastbar_time) "primera llamada".

En conclusión, vamos a crear varios métodos Get para obtener datos de nuestra clase en asesores expertos e indicadores: 

      //--- Methods of access to protected data:
uint              GetRetCode()     const  {return(m_retcode);     }  // Result code of detecting new bar 
datetime          GetLastBarTime() const  {return(m_lastbar_time);}  // Time of opening last bar
int               GetNewBars()     const  {return(m_new_bars);    }  // Number of new bars
string            GetComment()     const  {return(m_comment);     }  // Comment of execution
string            GetSymbol()      const  {return(m_symbol);      }  // Symbol name
ENUM_TIMEFRAMES   GetPeriod()      const  {return(m_period);      }  // Chart period

Ahora podemos obtener toda la información necesario en nuestros programas mql5. Por ahora, podemos hacer una parada en nuestro proceso de creación de la clase CisNewBar.

Todo el código fuente para nuestra clase está en el archivo adjunto Lib CisNewBar.mqh

Ejemplos de uso de la clase CisNewBar

Le propongo considerar los ejemplos de uso de nuestra clase para ver todas las sutilezas de lo que hemos creado. Quizás puede que no solo haya ventajas, sino también desventajas.

Ejemplo 1. Para empezar, vamos a crear un asesor experto absolutamente idéntico para la función isNewBar a partir del artículo limitaciones y verificaciones en asesores expertos:

//+------------------------------------------------------------------+
//|                                               Example1NewBar.mq5 |
//|                                            Copyright 2010, Lizar |
//|                                               Lizar-2010@mail.ru |
//+------------------------------------------------------------------+
#property copyright "Copyright 2010, Lizar"
#property link      "Lizar-2010@mail.ru"
#property version   "1.00"

#include <Lib CisNewBar.mqh>

CisNewBar current_chart; // instance of the CisNewBar class: current chart

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   if(current_chart.isNewBar()>0)
     {     
      PrintFormat("New bar: %s",TimeToString(TimeCurrent(),TIME_SECONDS));
     }
  }

Vamos a ejecutar ambos asesores expertos en gráficos con el mismo par y periodo. Vamos a ver qué tenemos:


En primer lugar, ambos asesores expertos informan sobre la nueva barra de forma sincronizada. Luego se quedan en silencio y cuatro minutos más tarde informan de que hay una nueva barra (este tiempo se marca como 1). De acuerdo, acabo de desconectar de internet por unos minutos y he decidido ver qué pasa. A pesar del hecho de que se han formado algunas barras, no hemos recibido esta información. En nuestro nuevo asesor experto podemos corregir esta desventaja ya que nuestro método isNewBar() permite hacer tal cosa.

Luego he cambiado el periodo del gráfico a M2. La reacción de los asesores expertos fue distinta. CheckLastBar empezó a informar sobre una nueva barra cada 2 minutos, y Example1NewBar nos informa de nuevas barras cada minuto, como si el periodo no hubiera cambiado (marcado como 2).

El hecho de que nuestra instancia current_chart ha sido inicializada por el constructor de la clase cuando el asesor experto se adjuntó al gráfico. Cuando cambiamos el periodo del asesor experto que ya ha sido adjuntado al gráfico, el constructor de la clase no se inicia, y el asesor experto continua trabajando con el periodo M1. Esto nos dice que nuestra instancia de clase vive su propia vida y no se ve afectada por cambios en el entorno, Esto puede ser tanto una ventaja como una desventaja, todo depende de las tareas.  

Para que nuestro asesor experto actúe como CheckLastBar, tenemos que inicializar las variables de clase protegidas m_symbol y m_period en la función OnInit(). Vamos a hacerlo.

Ejemplo 2. Vamos a introducir algunas cosas más a nuestro asesor experto y, de nuevo, vamos a comparar su rendimiento frente a CheckLastBar. El código fuente del asesor experto se adjunta como archivo Example2NewBar.mq5. Vamos a ejecutar ambos asesores expertos en gráficos con el mismo par y periodo. Vamos a crear los mismos obstáculos para ellos que la última vez. Vamos a ver qué tenemos:


Al igual que en la última ocasión, los asesores expertos primero informaron sobre una nueva barra de forma sincronizada. Luego me desconecté de internet durante unos minutos... Luego lo encendí. Nuestro nuevo asesor experto no solo ha informado sobre una nueva barra, sino también sobre cuantas han aparecido (marcadas como 1). Para la mayoría de indicadores y expertos, este número significa el número de barras no calculadas. De esta forma, tenemos una buena base para los algoritmos de recálculo rentables.  

Luego he cambiado el periodo del gráfico a M2. A diferencia del Ejemplo 1, el asesor experto funciona de forma sincronizada (marcado como 2). La inicialización de las variables de clase protegidas m_symbol y m_period en la función OnInit() ¡ha sido de ayuda! Al cambiar el símbolo (marcado como 3), los asesores expertos también funcionan de la misma forma. 

Ejemplo 3. En nuestra clase CisNewBar hemos permitido la posibilidad de hacer un seguimiento de los errores. Puede ocurrir que el asesor experto esté diseñado de forma que no sea necesario hacer un seguimiento de los errores. Bueno, entonces no usamos esta posibilidad. Intentaremos crear, artificialmente, una situación donde el error sea posible, e intentaremos capturarlo. Para ello complementaremos ligeramente el código fuente del asesor experto (el archivo Example3NewBar.mq5).

¿Qué voy a hacer? Como siempre, ejecutaré Example3NewBar en los gráficos de un minuto. Luego, empezaré a cambiar los instrumentos de los gráficos con la esperanza de que surja una situación en la que el terminal no tenga tiempo de construir series de tiempo antes de la solicitud del asesor experto. En general, torturaré al terminal de cliente y veré qué ocurre...  

Después de varios intentos, nuestro asesor experto captura un error:

 

Ahora podemos decir con confianza que podemos capturar errores de tiempo de ejecución. Como gestionarlos es cuestión de gustos. Observe que hemos seguido este error cuatro veces. Cuando la descarga se ha completado y se ha formado el gráfico, el asesor experto ha sugerido que hemos perdido solo una barra.

Por cierto, aquellos que miraron en el código fuente del asesor experto puede que se hayan dado cuenta de que tiene sentido comprobar los errores cuando el método isNewBar() devuelve un valor menor o igual a cero.

Aviso: Si durante este experimento, empieza a cambiar el periodo del gráfico, entonces cuando este cambio vaya de menos a más obtendrá un error de sincronización. Esto ocurre porque el momento de apertura de la barra (por ejemplo) de H1 es anterior a M1 en 59 casos. Para evitar este error al cambiar el periodo del gráfico, necesitamos inicializar adecuadamente la variable m_lastbar_time en la función OnInit() con el método SetLastBarTime (datetime lastbar_time).

Ejemplo 4. En este ejemplo vamos a complicar la tarea del asesor experto. Tomamos tres pares de divisas: EURUSD en M1, GBPUSD en M1 y USDJPY en M2. El gráfico con el primer par será el actual y en él observaremos una nueva barra. A través del segundo par, calcularemos el número de barras formadas después del inicio del asesor experto. Contaremos hasta que el primer par de la señal de que ahí hay una nueva barra. Y en el tercer par realizaremos constantemente (cuando aparece una barra en EURUSD) la inicialización de la variable de clase protegida m_lastbar_time. El código fuente del asesor experto se adjunta como archivo Example4NewBar.mq5.

Al crear este ejemplo quiero descubrir cómo funcionará nuestra clase CisNewBar en modo multidivisa. Bueno, lo empiezo... Esto es lo que tengo:


Los resultados generan preguntas. Añadiré más leña al fuego y ejecutaré este intervalos de tiempo en el Probador de Estrategias. Resultados del probador de estrategias:


Ahora puede jugar al juego "encuentre las diez diferencias". Además de las rarezas del funcionamiento del asesor experto en una cuenta demo, es obvio que hay diferencias entre una cuenta demo y el probador de estrategias, y son claramente visibles. Una comparación similar con el mismo enfoque no solo revelará las desventajas del asesor experto, sino que también permitirá eliminarlas. Quizás, no voy a analizar por qué ocurrió, cómo ocurrió y qué debe ser corregido en el asesor experto.  

Ejemplo 5. En los ejemplos nunca hemos usado explícitamente el método más universal para detectar una nueva barra: isNewBar(datetime newbar_time). Para hacer esto, tomaré la vela del tick del artículo Crear indicadores de tick en MQL5 y añadiré un buffer para almacenar el tiempo de apertura de la barra (archivo TickColorCandles v2.00.mq5). Escribirá un asesor experto muy corto que nos dirá el momento de la nueva vela de tick (archivo Example5NewBar.mq5):

#property copyright "Copyright 2010, Lizar"
#property link      "Lizar-2010@mail.ru"
#property version   "1.00"

#include <Lib CisNewBar.mqh>

CisNewBar newbar_ind; // instance of the CisNewBar class: detect new tick candlestick
int HandleIndicator;  // indicator handle
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Get indicator handle:
   HandleIndicator=iCustom(_Symbol,_Period,"TickColorCandles v2.00",16,0,""); 
   if(HandleIndicator==INVALID_HANDLE)
     {
      Alert(" Error when creating indicator handle, error code: ",GetLastError());
      Print(" Incorrect initialization of Expert Advisor. Trade is not allowed.");
      return(1);
     }

//--- Attach indicator to chart:  
   if(!ChartIndicatorAdd(ChartID(),1,HandleIndicator))
     {
      Alert(" Error when attaching indicator to chart, error code: ",GetLastError());
      return(1);
     }
//--- If you passed until here, initialization was successful
   Print(" Successful initialization of Expert Advisor. Trade is allowed.");
   return(0);
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   double iTime[1];

//--- Get time of opening last unfinished tick candlestick:
   if(CopyBuffer(HandleIndicator,5,0,1,iTime)<=0)
     {
      Print(" Failed to get time value of indicator. "+
            "\nNext attempt to get indicator values will be made on the next tick.",GetLastError());
      return;
     }
//--- Detect the next tick candlestick:
   if(newbar_ind.isNewBar((datetime)iTime[0]))
     {
      PrintFormat("New bar. Tiempo de apertura: % de tiempo del último tick: %s",
                  TimeToString((datetime)iTime[0],TIME_SECONDS),
                  TimeToString(TimeCurrent(),TIME_SECONDS));
     }
  }

Seguro que se ha dado cuenta de cómo he obtenido el momento de apertura de la vela de tick. Muy fácil, ¿verdad? Puse el indicador y el asesor experto en sus carpetas, lo compilé y ejecuté el asesor expertor. Funciona y estos son los resultados:  

 

Gestor de evento "Nueva barra"


Llegando al final de este artículo, me gustaría compartir otra idea. En el foro (en ruso) había una idea según la cual estaría bien tener un controlador de evento "nueva barra" estándar. Puede que quizás alguna vez los desarrolladores hayan caído en esto, pero puede que no. Pero la belleza de MQL5 es que es posible implementar las mejores ideas de forma elegante y simple.

Si quiere tener un controlador de evento "nueva barra" (o NewBar) ¡vamos a crearlo! Especialmente, ahora que podemos capturar este evento ahora con facilidad usando nuestra clase. Así es como sería nuestro experto (con el nuevo controlador de evento NewBar OnNewBar()):

#property copyright "Copyright 2010, Lizar"
#property link      "Lizar-2010@mail.ru"
#property version   "1.00"

#include "OnNewBar.mqh" // Here is the secret of launching the "new bar" event handler

//+------------------------------------------------------------------+
//| New bar event handler function                                   |
//+------------------------------------------------------------------+
void OnNewBar()
  {
   PrintFormat("New bar: %s",TimeToString(TimeCurrent(),TIME_SECONDS));
  }

Es muy bonito. Nuestro asesor experto es muy sencillo. Este controlador imprime el string de la nueva barra. Eso es todo lo que hace. Para comprender cómo seguir al evento NewBar y cómo ejecutar el controlador, necesita mirar en el archivo OnNewBar.mqh:

#property copyright "Copyright 2010, Lizar"
#property link      "Lizar@mail.ru"

#include <Lib CisNewBar.mqh>
CisNewBar current_chart; // instance of the CisNewBar class: current chart

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   int period_seconds=PeriodSeconds(_Period);                     // Number of seconds in current chart period
   datetime new_time=TimeCurrent()/period_seconds*period_seconds; // Time of bar opening on current chart
   if(current_chart.isNewBar(new_time)) OnNewBar();               // When new bar appears - launch the NewBar event handler
  }

Como puede ver no hay nada complicado tampoco. Pero hay un par de momentos sobre los que me gustaría llamar su atención.

Primero. Como habrá notado, utilizo la función TimeCurrent() para calcular el tiempo de apertura de la barra y uso el primer método para comprobar el evento NewBar de nuestra clase. Es algo añadido que está muy bien. Esto se basa en el hecho de que tal método no requiere procesar ningún error como ocurre al usar SeriesInfoInteger() con el modificador SERIES_LASTBAR_DATE. Para nosotros es importante, ya que nuestro controlador OnNewBar() debe ser tan fiable como sea posible.

Segundo. Usar la función TimeCurrent() para calcular el tiempo de apertura de la barra es la forma más rápida. Usar la función SeriesInfoInteger() aún sin control de error para la misma finalidad es una forma más lenta.

El resultado de nuestro controlador:

   

Conclusión

Durante la presentación del material hemos hecho un buen análisis de las formas de detectar una nueva barra. Hemos visto las ventajas y desventajas de los métodos existentes para detectar una nueva barra. Basándonos en los que hemos obtenido, hemos creado la clase CisNewBar, permitiendo capturar la "nueva barra" incluso en casi cualquier tarea, sin constes adicionales de programación. Al mismo tiempo, nos hemos desecho de la mayoría de los inconvenientes de las soluciones anteriores.    

Estos ejemplos nos han ayudado a comprender las ventajas y desventajas de los métodos que hemos creado. Es necesario prestar especial atención, en términos de trabajo correcto, al modo multidivisa. Debe realizar un análisis en profundidad de las ineficiencias identificadas y desarrollar formas de solventarlas.

El controlador de evento "nueva barra" que hemos creado solo es apto para asesores expertos de una sola moneda. Pero hemos aprendido a usar la forma más rápida y fiable para esta finalidad. Ahora puede seguir y crear un controlador de evento NewBar multidivisa. Pero este es un tema para otro artículo.