English Русский 中文 Deutsch 日本語 Português
Diagramas horizontales en los gráficos de MеtaTrader 5

Diagramas horizontales en los gráficos de MеtaTrader 5

MetaTrader 5Ejemplos | 6 marzo 2019, 06:57
1 789 0
Andrei Novichkov
Andrei Novichkov

Introducción

El desarrollador a veces se enfrenta a las tareas relacionadas con el dibujado de los diagramas horizontales en el gráfico del terminal, aunque eso no ocurre con frecuencia. ¿De qué tipo de tareas se trata? Se trata de los indicadores de distribución de los volúmenes para un determinado período. Además, son las tares de distribución del precio, diferentes versiones de la profundidad del mercado (Market Depth), etc. También se encuentran las situaciones más exóticas de la distribución de los valores de los indicadores personalizados (o estándar). En cualquier caso, estas tareas tienen en común lo siguiente: creación, colocación en el gráfico, escalado, desplazamiento y eliminación de diagramas. Vamos a remarcar algunos momentos:

  1. Puede haber varios diagramas (habitualmente, es así).
  2. La mayoría de los diagramas que nos interesan son columnas.
  3. Las columnas del histograma tienen la posición horizontal.

Es un ejemplo bastante bien conocido de este tipo de diagramas:



Aquí tenemos otro ejemplo. Los mismos diagramas dibujadas con otras primitivas gráficas.


En este caso, es un indicador de distribución de volúmenes de ticks por días. Este ejemplo muestra bien claro qué tipo de tareas tenía que realizar el desarrollador:

  • Crear varios objetos gráficos, atribuyéndoles los nombres únicos y colocándolos en el gráfico.
  • Escalar y desplazar los objetos si es necesario.
  • Eliminarlos del gráfico al finalizar el trabajo del indicador.

Vamos a recordar otra vez que hay varios diagramas, lo que en adelante nos dará la oportunidad de hablar de un array de diagramas.

Otro ejemplo clave:

En este caso, observamos una distribución más complicada de los mismos volúmenes. A pesar de que el diagrama parece como un todo único, se compone de tres diagramas diferentes que se ubican en el mismo lugar, a saber:

  • «Volúmenes de tick de venta»
  • «Volúmenes de tick de compra»
  • «Volúmenes de tick sumarios»

Podemos preguntar, ¿si existen maneras más simples de visualizar los datos mostrados? ¿si se puede hacer eso sin manejar tanta cantidad de primitivas gráficas? Pues sí, podemos encontrar estas maneras y discutir su eficacia. No obstante, es más fácil resolver completamente el conjunto de tareas que han sido expuestas al principio del artículo a través de los ejemplos de diagramas horizontales.

Planteamiento del problema

Marcamos dos partes de la implementación de lo planteado:

  1. Como todas las primitivas gráficas tienen las coordenadas de anclaje en forma de la hora y el precio, es obvio que de alguna manera hay que obtener los arrays de anclas que permitan posicionar los diagramas en el gráfico.
  2. Es necesario visualizar los diagramas y gestionarlos usando los arrays obtenidos en la primera etapa.

Vamos a formular las principales formas del anclaje de objetos gráficos usando el ejemplo de nuestro caso. En las primeras capturas de pantalla, se muestra una disposición, tal vez, más común de los diagramas horizontales. Es un anclaje al inicio de un determinado período (en nuestro caso, al inicio del día).

Desde luego, la lista de las opciones del anclaje no se agota con eso. Otra opción es el anclaje al borde izquierdo/derecho de la ventana del terminal. Esta opción puede aplicarse, por ejemplo, cuando el diagrama cubre un período muy grande y su comienzo se encuentra fuera de la parte visible de la ventana. Entonces, se puede usar el anclaje al borde izquierdo de la ventana.

Otra opción es cuando el diagrama se ancla al período actual, pero el desarrollador no quiere tener excesivas construcciones en el área de trabajo del gráfico. En este caso, se puede usar el borde derecho de la ventana del terminal. En cualquier caso, una de las coordenadas temporales de las primitivas gráficas, que forman el diagrama, será la misma. Otra coordenada va a calcularse, determinando la «longitud horizontal de la columna» del diagrama.

En cuanto al anclaje de precio de las primitivas gráficas, todo es mucho más simple. Un determinado segmento de precios se divide en intervalos con un paso constante. Por ejemplo, se puede aceptar un segmento de precios original por cien por cien, y el paso igual a diez por cien. Entonces, obtendremos los diagramas con un número constante de «columnas horizontales», lo que llevará a un fuerte redondeo de los resultados en algunas ocasiones. Por tanto, en este artículo, vamos a aplicar un esquema más eficaz que será considerado un poco más tarde.
De lo expuesto, se puede concluir que el array de anclajes según el precio puede ser innecesario en este caso. Para calcular el i-ésimo anclaje, se puede usar esta fórmula simple: 

i-ésimo anclaje de precio = inicio del segmento de precios del diagrama + i * paso de la división del segmento.

Cabe mencionar que para los ejemplos ya demostrados con volúmenes de ticks, el intervalo de precios en el que se puede construir los diagramas es el intervalo entre Low y High del período considerado.

Además, es necesario detallar la cuestión de la visualización de diagramas. Pues bien, ya hemos obtenido los arrays con anclajes para los diagramas según el siguiente principio: un diagrama, un conjunto de arrays para el anclaje. Otra observación evidente es que los gráficos muy a menudo incluyen los diagramas construidos usando las primitivas gráficas iguales, del mismo color y estilo. Se tiene en cuenta, por ejemplo, los diagramas en la captura 1 y 2. Mientras que en la tercera captura, todos los diagramas son distintos. Se diferencian por el color y por la «dirección». Un diagrama tiene direccionadas su columnas de derecha a izquierda, otros dos, de izquierda a derecha. Sería lógico combinar los diagramas del «mismo tipo» -como en dos primeras capturas- en un array, y encargar su control a algún administrador. Con el fin de estandarizar la tarea aún más, hay que seguir el siguiente principio:

  • Crear, para cada conjunto de diagramas del «mismo tipo», su propio administrador, incluso si el conjunto va a contener un diagrama. Así, para los diagramas de dos primeras capturas, será creado un array de tres diagramas (como mínimo) bajo el control de un administrador; y para los diagramas de la tercera captura, habría que crear tres arrays con un diagrama en cada uno de ellos y tres administradores (uno para cada array).

De esta manera, obtenemos un esquema de diseño. Sin embargo, aquí hay un momento importante. Hay que recordar que los diagramas horizontales dibujan una cierta distribución en el gráfico, que tiene que ver con los volúmenes de ticks, precio, etc. Por tanto, los métodos y principios para obtener los arrays originales para anclar las primitivas gráficas, pueden ser absolutamente diferentes, y no hace falta intentar crear un archivo de la biblioteca para esta parte de la tarea.

En particular, al diseñar un indicador de instrucción de la distribución de los volúmenes de ticks por días, se aplicará otro enfoque, más eficaz. En este artículo, Usted puede encontrar una manera más. En otras palabras, la primera parte de la tarea planteada va a resolverse cada vez de una manera diferente. Al contrario, la segunda parte de la tarea (es decir, la creación y el manejo de este array con la ayuda de un administrador, o varios agrupamientos «administrador-array de diagramas») será prácticamente la misma en todos los casos. Eso permite crear un archivo de biblioteca para ser incluido en todos los proyectos, donde es necesario crear los diagramas horizontales.


Hemos llegado a la última cuestión. Aquí, siempre se dice de algunas primitivas gráficas. Pues, es el momento para decidir exactamente de qué van a componerse los diagramas. Los diagramas van a constar de los segmentos de unas líneas horizontales o de los rectángulos. Son dos opciones más orgánicas que serán usadas.


En este punto, se puede considerar el planteamiento de la tarea por finalizada y proceder directamente al código.

Constantes y parámetros de entrada

Es bastante evidente que muchos parámetros mencionados de futuros diagramas van a almacenarse usando los tipos enumerados.

Posicionamiento de diagramas:

enum HD_POSITION
{
        HD_LEFT  = -1,
        HD_RIGHT =  1,
        HD_CNDLE =  2 
};

Hay tres opciones para colocar diagramas: anclando al borde izquierdo del terminal (HD_LEFT), al borde derecho (HD_RIGHT) y a la vela (HD_CNDLE), o velas. En tres capturas del inicio del artículo, los diagramas se ubican usando HD_CNDLE. En dos primeras, se colocan con el anclaje a las velas al principio de ciertos períodos (inicio del día), en la tercera, a una vela ubicada en el comienzo del día actual.

Apariencia del diagrama (apariencia de primitivas gráficas):

enum HD_STYLE 
{
        HD_LINE      = OBJ_HLINE,        
        HD_RECTANGLE = OBJ_RECTANGLE,    
};

Hay dos versiones de la apariencia: secciones de líneas horizontales (HD_LINE) y rectángulos (HD_RECTANGLE). En la primera y la tercera captura del inicio del artículo, los diagramas se componen de las primitivas HD_LINE, y en la segunda, de HD_RECTANGLE.

La dirección de las «columnas horizontales» de diagramas:

enum HD_DIRECT 
{
   HD_LEFTRIGHT = -1,
   HD_RIGHTLEFT =  1 
};

En la tercera captura, el diagrama compuesta de los segmentos de color rojo está dibujado como HD_RIGHTLEFT, otros dos, como HD_LEFTRIGHT.

El último tipo está relacionado con el número de niveles del diagrama. Antes hemos mencionado de paso que, para calcular el número de los niveles, va a usarse un método mejor que una simple división del intervalo de precios por un número de niveles especificado. Es muy importante observar que este tipo enumerado se refiere a la primera parte del Plantea miento de la tarea, por tanto, no será incluido en el archivo de la biblioteca final.

Vamos a describir el método aplicado. Es bastante simple y se reduce al redondeo del nivel de precios hasta un diez próximo o a un cien. Por tanto, el tamaño del paso de niveles de precios también va a ser igual a diez o a cien. Usando este enfoque, el número de niveles de precios será variable. Para los que quieren obtener la precisión máxima de cálculos (junto con el aumento del consumo de recursos), se deja el método HD_MIN sin redondeo:

enum HD_ZOOM {
   HD_MIN    = 0,  //1
   HD_MIDDLE = 1,  //10
   HD_BIG    = 2   //100
}; 

Por defecto, va a aplicarse el método HD_MIDDLE.

Vamos a ver un ejemplo. Como objeto del desarrollo, usamos un indicador de estudio que dibuja la distribución de volúmenes de ticks en el gráfico. El indicador semejante ha sido ejemplificado en dos primeras capturas de pantalla al principio del artículo.

Continuamos con el bloque de parámetros de entrada:

input HD_STYLE        hdStyle      = HD_LINE;
input int             hdHorSize    = 20;
input color           hdColor      = clrDeepSkyBlue;
input int             hdWidth      = 2;
input ENUM_TIMEFRAMES TargetPeriod = PERIOD_D1;
input ENUM_TIMEFRAMES SourcePeriod = PERIOD_M1;
input HD_ZOOM         hdStep       = HD_MIDDLE;   
input int             MaxHDcount   = 5;
input int             iTimer       = 1;   

Inmediatamente surge la pregunta, ¿por qué no hay ningún parámetro responsable de esta posición? La respuesta es evidente. Para el indicador necesario, puede ser aplicado solamente un método de colocación, HD_CNDLE Por eso, se puede no especificarlo.

La finalidad del parámetro HD_STYLE es bastante evidente y no requiere aclaraciones adicionales.

  • El parámetro hdHorSize desempeña un papel muy importante. Determina el tamaño máximo de la «columna horizontal» del diagrama en las velas. En este caso, la «columna horizontal» más grande no puede exceder veinte velas. Está claro que cuanto más grande sea este parámetro, más preciso será el diagrama. Hay una limitación: si el tamaño de este parámetro va a ser grande, los diagramas empezarán a sobreponerse uno encima de otro.
  • Los parámetros hdColor y hdWidth se refieren a la apariencia de diagramas, a saber: el color y el grosor de las líneas.
  • El parámetro TargetPeriod incluye el marco temporal (timeframe) analizado. En este caso, el indicador va a mostrar la distribución de los volúmenes de ticks por día.
  • El parámetro SourcePeriod contiene el timeframe desde el cual se usan los datos originales para construir una distribución. En este caso, se utiliza el timeframe de minuto. Hay que tener cuidado a la hora de usar este parámetro. Si el timeframe analizado es mensual, los cálculos pueden demorar.
  • El parámetro hdStep incluye el parámetro del redondeo de niveles temporales. Ya hemos hablado de este parámetro y en qué influye su elección.
  • El parámetro MaxHDcount contiene el número máximo de diagramas en el gráfico. Es necesario recordar que cada diagrama se compone de varias primitivas gráficas, y un número excesivo de diagramas puede ralentizar el funcionamiento del terminal.
  • El parámetro iTimer contiene la frecuencia de la activación del temporizador. Cuando se dispara, se verifica la creación de nuevas velas y se realizan las acciones necesarias. Podríamos colocar aquí el resultado de la llamada PeriodSeconds(SourcePeriod). Sin embargo, por defecto, es un segundo, lo que ha sido hecho para detectar el momento de aparición de velas nuevas con más precisión.

Inicialización

En esta fase, hay que crear el objeto «administrador para gestionar los diamagramas». Puesto que los diagramas serán del mismo tipo, necesitaremos un solo administrador. Como la clase del propio administrador todavía no está escrita, simplemente recordamos que se crea aquí, es decir, en el manejador (handle) OnInit(). Aquí mismo, se crean pero de se dibujan dos diagramas:

  1. Diagrama responsable de la distribución de volúmenes tick para el período actual. Este diagrama va a redibujarse periódicamente.
  2. El diagrama que va a dibujar la distribución de volúmenes de ticks usando el historial. Estas distribuciones no van a redibujarse. Por eso, después del dibujado, el diagrama va a «olvidar» de ellas, pasando el control al terminal.

Este esquema representa el enfoque más eficaz mencionado al principio del artículo. Así, el indicador no va a intentar gestionar las primitivas gráficas de los diagramas, ya que su apariencia no se cambia.

Luego, se realiza la inicialización de las variables para los cálculos subsiguientes de los niveles de precios con el paso. Estas variables son dos, siendo derivadas de Digit() y Point():

   hdDigit = Digits() - (int)hdStep; 
   switch (hdStep)
    {
      case HD_MIN:
         hdPoint =       Point();
         break;
      case HD_MIDDLE:
         hdPoint = 10 *  Point();
         break;     
      case HD_BIG:
         hdPoint = 100 * Point();
         break;      
      default:
         return (INIT_FAILED);
    }

En el mismo manejador, se realizan algunas acciones secundarias y el arranque del temporizador.


Cálculos principales

La próxima tarea se divide en dos etapas:

  1. El cálculo de datos necesarios y el dibujado el número necesario de diagramas, salvo el actual. Estos diagramas no van a cambiarse más, y basta con visualizarlos una vez.
  2. El cálculo de datos necesarios y el dibujado del diagrama para el período, incluyendo el actual. Los cálculos de este punto tendrán que ser repetidos periódicamente. Se puede hacerlo en el manejador OnTimer()

Vamos a realizar una parte en el manejador OnCalculate(), por ahora en la forma del pseudo-código.

int OnCalculate(...)
  {
   if (prev_calculated == 0 || rates_total > prev_calculated + 1) {
   }else {
      if (!bCreateHis) 
       {
         int br = 1;
         while (br < MaxHDcount) {
           {
            if(Calculate for bar "br") 
                 {
                  sdata.bRemovePrev = false;
                  Print("Send data to the new Diagramm");
                 }

           }
         ChartRedraw();
         bCreateHis = true;
      }
   }  
   return(rates_total);
  }  

Aquí, se realizan los cálculos necesarios y el dibujado del número necesario de diagramas, excepto el actual. Para eso, se ejecutan cíclicamente los cálculos para cada barra del timeframe TargetPeriod, desde el primero y hasta MaxHDcount. Si los cálculos son exitosos, en el mismo ciclo, se da un comando al administrador para dibujar el diagrama, pasando nuevos datos al administrador. Cuando se finaliza el ciclo, el gráfico se redibuja y se coloca una bandera que indica que ya no hace falta ejecutar esta parte del trabajo. Ahora, el terminal gestiona los diagramas.

Los trabajos de la creación y del redibujado del diagrama que incluye el período actual se realizan en el manehador OnTimer(). El pseudo-código para este manejador no se muestra debido a la simplicidad y claridad evidente de la tarea:

  1. Esperamos a que aparezca una vela nueva en el timeframe SourcePeriod.
  2. Realizamos los cálculos necesarios.
  3. Enviamos los datos al diagrama en el período actual con el fin de crear nuevas primitivas y el redibujado.
Volveremos un poco más tarde a la función en la que se realizan los cálculos principales para una determinada barra del timeframe TargetPeriod.

Los demás manejadores y funciones del indicador no son tan interesantes, siendo disponibles en el código adjunto. Ahora ha llegado el momento para describir las clases responsables de la creación, dibujado y gestión de los diagramas.

Clase de la gestión de diagramas

Vamos a comenzar con el administrador de la gestión de diagramas horizontales. Será la clase que no contiene las primitivas gráficas, pero gestiona el array de otras clases que contienen estas primitivas. Y puesto que todos los diagramas en un administrador son del mismo tipo (como ya se ha mencionado antes), muchas propiedades de estos diagramas serán iguales. Por tanto, no tiene sentido guardar el mismo conjunto de propiedades en cada diagrama, sino vale la pena colocar un único conjunto de propiedades en el administrador. Llamamos la clase del administrador "CHDiags" y procedemos a la escritura del código:

  1.  Los campos cerrados de la clase CHDiags que contienen, entre otras cosas, un conjunto de propiedades igual para todos los diagramas controlados por este administrador:
private:    
                HD_POSITION m_position;  
                HD_STYLE    m_style;     
                HD_DIRECT   m_dir;       
                int         m_iHorSize;     
                color       m_cColor;    
                int         m_iWidth;     
                int         m_id;
                int         m_imCount;       
                long        m_chart;    
                datetime    m_dtVis; 
   static const string      m_BaseName;  
                CHDiagDraw* m_pHdArray[];

Опишем данный набор:

  • m_position,  m_style,  m_dir — Estos tres parámetros describen el anclaje y la apariencia de los diagramas, ya han sido mencionados.
  • m_iHorSize — Es el tamaño horizontal máximo posible del diagrama. Este parámetro también ya ha sido mencionado.
  • m_cColor и  m_iWidth — Color del diagrama y el grosor de las líneas.
Los campos arriba descritos representan las propiedades uniformes para todos los diagramas controlados por el administrador.
  • m_id — Identificador único del administrador. Dado que puede haber más de un administrador, es necesario que tenga un identificador único. Será necesario para formar nombres único de los objetos.
  • m_chart — Identificador del gráfico en el que se muestran los diagramas. Por defecto, el valor de este campo es igual a cero (gráfico actual).
  • m_imCount — Número máximo de diagramas en el gráfico. Finalmente, el número de diagramas en el gráfico va a determinarse por este campo y por los siguientes.
  • m_dtVis — no hay que crear diagramas a la izquierda de esta marca.
  • m_BaseName — Un parámetro sumamente importante que define el nombre «base». Todos los elementos del diagrama, así como los propios diagrames, deben tener nombres únicos para una creación exitosa. Los nombres se dan basándose en este nombre «base».
Todos los campos en cuestión están disponibles a través de la función tipo GetXXXX()
  • m_pHdArray[] — Array con los punteros a los objetos que contiene los diagramas separados. Este campo no es una propiedad y no tiene funciones GetXXXX().

Las funciones tipo SetXXXX() no están previstas para las funciones. Todos ellos (salvo m_BaseName) se establecen en el contructor de la clase. Otra excepción es el campo m_dtVis. Se establece en el constructor por el parámetro tipo bool que tiene el siguiente significado:

  • Visualizar los diagramas sólo en las velas visibles en la ventana del terminal. Eso se hace para no cargar el terminal con la visualización de los diagramas que se encuentran a la izquierda del borde izquierdo del terminal. Por defecto, es true.

Después de la creación del administrador, se muestra crear objetos - diagramas. Eso hace el método de la clase CHDiags:

int CHDiags::AddHDiag(datetime dtCreatedIn)

El método devuelve el índice del objeto creado de la clase CHDiagDraw en el array m_pHdArray менеджерdel administrador, o -1 en caso del error. Como parámetro, el anclaje temporal del inicio del diagrama se pasa al método dtCreatedIn. Por ejemplo, para el indicador analizado, aquí será pasada la hora de apertura de la vela diaria. Si el anclaje de tiempo no se usa (las velas están ancladas a las bandas de la ventana el terminal), aquí hay que pasar TimeCurrent() En este caso, si el diagrama se ubica a la izquierda de la marca temporal del campo m_dtVis, el objeto no será creado. El siguiente código muestra claramente cómo funciona el método:

int CHDiags::AddHDiag(datetime dtCreatedIn) {
   if(dtCreatedIn < m_dtVis ) return (-1);
   int iSize = ArraySize(m_pHdArray);
   if (iSize >= m_imCount) return (-1);
   if (ArrayResize(m_pHdArray,iSize+1) == -1) return (-1);
   m_pHdArray[iSize] = new CHDiagDraw(GetPointer(this) );
   if (m_pHdArray[iSize] == NULL) {
      return (-1);
   }
   return (iSize);
}//AddHDiag()

Como se puede ver, el método realiza algunas comprobaciones, aumenta el tamaño del array para almacenar los diagramas, crea el objeto necesario pasándole el puntero al propio administrador, con el fin de acceder posteriormente a las propiedades.

El administrador también contiene otros métodos que permiten interactuar con el diagrama, pero no directamente, sino exclusivamente a través del administrador de diagramas:

   bool        RemoveDiag(const string& dname);
   void        RemoveContext(int index, bool bRemovePrev);
   int         SetData(const HDDATA& hddata, int index); 
  1. El primer método (como se ve de su nombre) elimina completamente el diagrama del administrador y del gráfico, usando el nombre del diagrama como parámetro. En este momento, es una opción reservada.
  2. El segundo método elimina sólo las primitivas gráficas que componen el diagrama. El diagrama se elimina del gráfico, pero está presente en el administrador, siendo «vacío». El valor de la bandera bRemovePrev se explica a continuación.
  3. El tercer método transfiere al diagrama una estructura con los datos originales para crear las primitivas gráficas y dibujar los diagramas. En éste y en los métodos anteriores, se usa el índice del diagrama en el array m_pHdArray del administrador como parámetro.

El último método que merece ser mencionado de paso es el método de la clase CHDiags:

void        Align();

El método se invoca si los diagramas se crean pegados a la banda izquierda/derecha de la ventana del terminal. Entonces, este método se invoca en el manejador OnChartEvent del evento CHARTEVENT_CHART_CHANGE , devolviendo los diagramas en sus lugares anteriores.

Otros métodos de la clase del administrador de de diagramas CHDiags son secundarios y están disponibles en el archivo adjunto.

Clase del dibujado y gestión de las primitivas gráficas de los diagramas

Llamaremos esta clase "CHDiagDraw" y la heredamos de CObject. En el constructor de la clase, obtenemos el puntero al administrador de la gestión (lo guardamos en el campo m_pProp). Aquí, también se define el nombre único del diagramas.

Luego, tenemos que implementar el método Type():

int CHDiagDraw::Type() const
  {
   switch (m_pProp.GetHDStyle() ) {
      case HD_RECTANGLE:
         return (OBJ_RECTANGLE);
      case HD_LINE:
         return (OBJ_TREND);
   }
   return (0);
  }

El tipo del diagramase se muestra igual a los tipos de las primitivas gráficas utilizadas, lo cual parece bastante lógico.

Los cálculos principales en la clase CHDiagDraw se realizan usando el método que se invoca por el método SetData del administrador:

int       SetData(const HDDATA& hddata);

La tarea del método consiste en determinar los tamaños del diagrama y crear el número necesario de primitivas en un determinado lugar del gráfico. Para eso, una referencia a la instancia de la estructura se pasa al método en el punto de la llamada:

struct HDDATA
 {
   double   pcur[];
   double   prmax; 
   double   prmin;   
   int      prsize;
   double   vcur[];
   datetime dtLastTime;
   bool     bRemovePrev;
 };

Vamos a describir los campos de esta estructura con más detalladamente:

  • pcur[] — Array de los niveles de precios del diagrama. Para las primitivas gráficas creadas , es el array de los anclajes de precios.
  • prmax — El valor máximo por la horizontal del diagrama. En este caso, es el valor máximo del volumen de ticks negociado en un determinado nivel.
  • prmin — Parámetro reservado.
  • prsize — Número de los niveles del diagrama. En otras palabras, es el número de las primitivas que van a componer el diagrama.
  • vcur[] — Array de los valores que determina el «tamaño horizontal de las columnas» del diagrama. En este caso, el array contiene los volúmenes de ticks negociados en los niveles correspondientes del array pcur[]. El tamaño de los arrays pcur y vcur tiene que coincidir y ser igual a prsize.
  • dtLastTime — Ubicación del diagrama. Para las primitivas gráficas es el anclaje por la hora. Este campo va a tener una prioridad más alta que el argumento del método AddHDiag del administrador.
  • bRemovePrev — Si esta bandera es true, entonces, con cada actualización de los datos, el diagrama va a redibujarse por completo con la eliminación de las primitivas gráficas anteriores. Si la bandera es false, el diagrama «se negará» a gestionar las primitivas gráficas antiguas y va a dibujar un diagrama nuevo sin eliminar las primitivas anteriores, como si «olvidara» de ellas.

Vamos a mostrar el código del método SetData por completo debido a su importancia:

int CHDiagDraw::SetData(const HDDATA &hddata) 
  {
   RemoveContext(hddata.bRemovePrev);
   if(hddata.prmax == 0.0 || hddata.prsize == 0) return (0);
   double dZoom=NormalizeDouble(hddata.prmax/m_pProp.GetHDHorSize(),Digits());
   if(dZoom==0.0) dZoom=1;
   ArrayResize(m_hItem,hddata.prsize);
   m_hItemCount=hddata.prsize;
   int iTo,t;
   datetime dtTo;

   string n;
   double dl=hddata.pcur[0],dh=0;
   

   GetBorders(hddata);
   for(int i=0; i<hddata.prsize; i++) 
     {
      if (hddata.vcur[i] == 0) continue;
      t=(int)MathCeil(hddata.vcur[i]/dZoom);
      switch(m_pProp.GetHDPosition()) 
        {
         case HD_LEFT:
         case HD_RIGHT:
            iTo=m_iFrom+m_pProp.GetHDPosition()*t;
            dtTo=m_pProp.GetBarTime(iTo);
            break;
         case HD_CNDLE:
            iTo   = m_iFrom + m_pProp.GetHDDirect() * t;
            dtTo  = m_pProp.GetBarTime(iTo);
            break;
         default:
            return (-1);
        }//switch (m_pProp.m_position)
      n=CHDiags::GetUnicObjNameByPart(m_pProp.GetChartID(),m_hname,m_iNameBase);
      m_iNameBase++;
      bool b=false;
      switch(m_pProp.GetHDStyle()) 
        {
         case HD_LINE:
            b=CHDiags::ObjectCreateRay(m_pProp.GetChartID(),n,dtTo,hddata.pcur[i],m_dtFrom,hddata.pcur[i]);
            break;
         case HD_RECTANGLE:
            if(dl!=hddata.pcur[i]) dl=dh;
            dh=(i == hddata.prsize-1) ? hddata.pcur[i] :(hddata.pcur[i]+hddata.pcur[i+1])/2;
            b = ObjectCreate(m_pProp.GetChartID(),n,OBJ_RECTANGLE,0,dtTo,dl,m_dtFrom,dh);
            break;
        }//switch(m_pProp.m_style)
      if(!b) 
        {
         Print("ERROR while creating graphic item: ",n);
         return (-1);
           } else {
         m_hItem[i]=n;
         ObjectSetInteger(m_pProp.GetChartID(), n, OBJPROP_COLOR, m_pProp.GetHDColor() );
         ObjectSetInteger(m_pProp.GetChartID(), n, OBJPROP_WIDTH, m_pProp.GeHDWidth() );
         ObjectSetInteger(m_pProp.GetChartID(), n, OBJPROP_SELECTABLE, false);
         ObjectSetInteger(m_pProp.GetChartID(), n, OBJPROP_BACK, true);
        }//if (!ObjectCreateRay(n, dtTo, hddata.pcur[i], m_dtFrom, hddata.pcur[i]) )    
     }// for (int i = 0; i < l; i++)      
   return (hddata.prsize);
  }//int CHDiagDraw::SetData(const HDDATA& hddata)

Lo primero que hace este método es limpiar y calcular la escala. Hay una longitud máxima de las «columnas horizontales» del diagrama y hay un tamaño horizontal máximo del diagrama en velas. Se puede obtener un coeficiente de proporcionalidad. No es difícil de notar que, como resultado, el redondeo será forzoso, porque el tamaño del diagrama «en velas» es un número entero.

Luego, se prepara un array para almacenar los nombres de las primitivas. Se calculan los parámetro adicionales del anclaje del diagrama, es decir, el número de la vela y el anclaje temporal en el método GetBorders. Después de eso, en el ciclo, se determina el segundo anclaje temporal del diagrama. De esta manera, ya tenemos todos los anclajes para crear la primitiva gráfica. Obtenemos los nombres únicos de las primitivas y las creamos consecutivamente usando los parámetro obtenidos. Guardamos los nombres de las primitivas en el array. Ajustamos las propiedades de las primitivas. Aquí, se termina el trabajo, el diagrama está creado. El método devuelve el número de los niveles del diagrama.

Probablemente, el método parezca demasiado largo, y sea posible sacar el código de la creación y del dibujado de las primitivas en un método protegido separado. Sin embargo, ambas partes del método parecen organicamente compatibles, la segunda parte sirve de una continuación clara de la primera. Esta consideración, así como la falta de deseo de crear varias llamada adicionales a un método nuevo con un número grande de argumentos, provocó que el método SetData había creado de esta forma.

Otras funciones de la clase CHDiagDraw llevan un carácter secundario y sirven para la integración con el administrador.

Cabe destacar que el usuario no llama a ninguno de los métodos de la clase CHDiagDraw directamente, pero actúa exclusivamente a través del administrador de diagramas horizontales.

El código entero arriba escrito de la clase del administrador de los diagramas horizontales, clase del dibujado de los diagramas, estructuras y enumeraciones está disponible en el archivo adjunto HDiagsE.mqh.

Ahora, se puede volver al código del EA y analizar más detalladamente su contenido, sin usar el pseudo-código.

Volviendo al indicador

Declaramos dos objetos y las variables en el contexto global:

CHDiags    *pHd;
int         iCurr, iCurr0;
HDDATA      sdata;

Es un puntero al administrador de diagramas, índices de diagramas y la estructura para transmitir los datos al diagrama.

Creamos el administrador y ambos diagramas en OnInit() usando los parámetros de entrada del indicador:

   pHd         = new CHDiags(HD_CNDLE, hdStyle, HD_LEFTRIGHT, hdHorSize, hdColor, hdWidth, 0, MaxHDcount);
   if(pHd      == NULL) return (INIT_FAILED);
   iCurr       = pHd.AddHDiag(TimeCurrent() );
   if(iCurr  == -1) return (INIT_FAILED); 
   iCurr0       = pHd.AddHDiag(TimeCurrent() );
   if(iCurr0  == -1) return (INIT_FAILED);    

Esta será la apariencia de la parte del manejador OnCalculate para la cual antes era necesario usar el pseudo-código:

        {
         int br=1;
         while(br<MaxHDcount) 
           {
            if(PrepareForBar(br++,sdata)) 
              {
               sdata.bRemovePrev = false;
               if(iCurr!=-1) 
                 {
                  Print(br-1," diag level: ",pHd.SetData(sdata,iCurr));
                 }
              }
           }
         ChartRedraw();
         bCreateHis=true;
        }

Nos queda considerar la función que rellena la estructura tipo HDDATA con datos, para la barra seleccionada en el timeframe para el cual se construye la enumeración  (TargetPeriod):

bool PrepareForBar(int bar, HDDATA& hdta) {

   hdta.prmax  = hdta.prmin  = hdta.prsize  = 0;
   int iSCount;
   datetime dtStart, dtEnd;
   dtEnd = (bar == 0)? TimeCurrent() : iTime(Symbol(), TargetPeriod, bar - 1);
   hdta.dtLastTime = dtStart = iTime(Symbol(), TargetPeriod, bar);
   
   hdta.prmax = iHigh(Symbol(), TargetPeriod, bar);
   if(hdta.prmax == 0) return (false);
   hdta.prmax      = (int)MathCeil(NormalizeDouble(hdta.prmax, hdDigit) / hdPoint );
   
   hdta.prmin = iLow(Symbol(), TargetPeriod, bar);
   if(hdta.prmin == 0) return (false);
   hdta.prmin      = (int)MathCeil(NormalizeDouble(hdta.prmin, hdDigit) / hdPoint );

   iSCount = CopyRates(Symbol(), SourcePeriod, dtStart, dtEnd, source);
   if (iSCount < 1) return (false);
   
   hdta.prsize = (int)hdta.prmax - (int)hdta.prmin + 10;
   
   ArrayResize(hdta.pcur,  hdta.prsize);
   ArrayResize(hdta.vcur,  hdta.prsize);
   ArrayInitialize(hdta.pcur, 0);
   ArrayInitialize(hdta.vcur, 0);
   
   double avTick;
   int i, delta;
   hdta.prmax = 0;
   
   for (i = 0; i < hdta.prsize; i++) hdta.pcur[i] = (hdta.prmin + i) * hdPoint;
   int rs = 0;
   for (i = 1; i < iSCount; i++) {
      if (source[i].tick_volume == 0.0) continue;
      if (!MqlRatesRound(source[i], (int)hdta.prmin) ) continue;
      delta = (int)(source[i].high - source[i].low);
      if (delta == 0) delta = 1;
      avTick = (double)(source[i].tick_volume / delta);
      int j;
      for (j = (int)source[i].low; j <= (int)(source[i].low) + delta; j++) {
         if (j >= hdta.prsize) {
            Print("Internal ERROR. Wait for next source period or switch timeframe");
            return false;
         }
         hdta.vcur[j] += avTick;
         if (hdta.vcur[j] > hdta.prmax) hdta.prmax = (int)hdta.vcur[j];
      }//for (int j = (int)source[i].low; j <= (int)(source[i].low) + delta; j++)   
      if (j > rs) rs = j; //real size
   }//for (int i = 1; i < iSCount; i++)
   hdta.prsize = rs + 1;
   return (true);
}  

Primero, el método averigua el período necesario para realizar los cálculo. Aquí, también se calculan los límites del rango de precios para los cálculos tomando en cuenta la división posterior en los niveles para el dibujado.

Luego, se lee MqlRates desde el timeframe que ha sido tomado por la fuente de los datos para el período que acaba de ser determinado. Para cada estructura MqlRates obtenida, se considera que el volumen de ticks tick_volume está distribuido uniformemente dentro del rango de low a high de la estructura. Sabiendo los límites del rango de precio para el futuro diagrama entero, esta distribución del volumen de ticks se posiciona en el diagrama. De esta manera, se forma el array de la distribución de volúmenes de ticks en el período deseado.


Los cálculos se terminan con la determinación del tamaño real del array de la distribución de los volúmenes de ticks y, por consiguiente, del número de las primitivas gráficas en el futuro diagrama.

Aquí, los cálculos se terminan, los campos de la estructura para el diagrama están rellenos y están listos para ser transmitidos a través del método SetData(...).

El esquema general del trabajo con las clases descritas es el siguiente:

  1. Se conecta HDiagsE.mqh.
  2. Se crea un objeto, es decir, el administrador para cada grupo de los diagramas del «mismo tipo».
  3. Se crean los diagramas a través de la llamada al método del administrador AddHDiag. El método devuelve su índice en el array conocido por el administrador.
  4. El diagrama se limpia de los datos no actuales usando la llamada el método del administrador. Los nuevos datos se transfieren para el diagrama llamando al método del administrador SetData transmitiéndole la estructura tipo HDDATA con los datos. La parte receptora se responsabiliza del llenado correcto de los campos de esta estructura.
  5. Si es necesario, se puede pegar los diagramas al borde izquierdo o derecho de la ventana del terminal a través de la llamada al método del administrador Align.
  6. Todos los diagramas se eliminan en el destructor de la clase del administrador CHDiags.

El código completo del indicador se encuentra en el archivo adjunto VolChart.mq5, mientras que el archivo de biblioteca se encuentra en el archivo HDiagsE.mqh.

Sobre los nombres

Prácticamente, todos los objetos relacionados con los diagramas horizontales tienen los nombres. Ellos se forman jerárquicamente de la siguiente manera:

  1. El administrador tiene el campo privado m_BaseName que determina el «nombre base». Todos los demás se comienzan con este nombre.
  2. Cuando se crea un objeto-administrador, se le asigna un identificador único. El código de la llamada se responsabiliza de la exclusividad de este parámetro. El campo m_BaseName y este identificador forma el nombre del administrador.
  3. Cuando se crea un objeto-diagrama, también recibe un nombre único basado en el nombre del administrador.
  4. Finalmente, las primitivas gráficas creadas en el objeto-diagrama reciben sus nombres únicos a base del nombre del objeto-diagrama del propietarios de las primitivas.

El esquema usado permite filtrar fácilmente los objetos necesarios y gestionarlos.

Otro indicador

Vamos a modificar el indicador ya diseñado en el indicador que dibuja la distribución de volúmenes de ticks en forma de un diagrama pegado al borde derecho de la ventana del terminal:


Para eso, ejecutamos unas alteraciones mínimas en el código:

  • Hay que modificar una parte del código de inicialización
       pHd         = new CHDiags(HD_RIGHT, hdStyle, HD_RIGHTLEFT, hdHorSize, hdColor, hdWidth, 0, MaxHDcount);
       if(pHd      == NULL) return (INIT_FAILED);
       iCurr       = pHd.AddHDiag(TimeCurrent() );
       if(iCurr    == -1) return (INIT_FAILED); 
    
  • Eliminar el código entero del manejador OnCalculate. 
  • Añadir el manejador OnChartEvent
    void OnChartEvent(const int id,
                      const long &lparam,
                      const double &dparam,
                      const string &sparam)
      {
          switch (id) {
             case CHARTEVENT_CHART_CHANGE:
                pHd.Align();
                break;
            default:
                break;    
          }//switch (id)
      }  
    
Eso es todo. El nuevo indicador está hecho y está funcionando. Su código completo se encuentra en el archivo adjunto VolChart1.mq5.

Conclusiones

Pues, hemos obtenido la posibilidad de crear los diagramas horizontales mediante la inclusión de un solo archivo. Ahora, la tarea del desarrollador es preparar los datos para la construcción, y este tema tiene un papel secundario en este artículo. El código del archivo de biblioteca supone un posible desarrollo debido a la presencia de los métodos reservados. También es posible añadir otras primitivas gráficas para el dibujado.

No hay que olvidar que los indicadores adjuntos están destinadas para los propósitos de demostración y aprendizaje, no pudiendo ser aplicados en el trading real. En particular, se debe prestar atención en el hecho de que no tiene que haber artefactos en el timeframe que se usa como la fuente de datos.

Programas y archivos usados en el artículo:

 # Nombre
Tipo
 Descripción
1 VolChart.mq5 Indicador
Indicador de distribución de volúmenes de ticks.
2
HDiagsE.mqh Archivo de biblioteca
El archivo de biblioteca con el administrador de los diagramas horizontales y un diagrama horizontal.
3
VolChart1.mq5
Indicador
Indicador de distribución de volúmenes de ticks pegado a la banda derecha de la ventana del terminal.


Traducción del ruso hecha por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/ru/articles/4907

Archivos adjuntos |
HDiagsE.mqh (36.66 KB)
VolChart.mq5 (12.65 KB)
VolChart1.mq5 (12.21 KB)
Cómo crear y testear personalmente los instrumentos de la Bolsa de Moscú en MetaTrader 5 Cómo crear y testear personalmente los instrumentos de la Bolsa de Moscú en MetaTrader 5
En este artículo, se describe cómo se puede crear su propio símbolo de un instrumento de la bolsa de valores usando el lenguaje MQL5. En particular, se puede utilizar las cotizaciones bursátiles del sitio web popular «Finam.ru». Otra opción considerada es la posibilidad de trabajar con un formato aleatorio de los archivos de texto usados para crear un símbolo personalizado. Por esa razón, podemos trabajar con cualquier instrumento financiero y fuente de datos. Después de crear un símbolo personalizado, podemos usar todas las posibilidades del Simulador de Estrategias de MetaTrader 5 para testear los algoritmos comerciales para los instrumentos bursátiles.
Aplicando la teoría de probabilidades usando el trading con gaps Aplicando la teoría de probabilidades usando el trading con gaps
Aplicamos los métodos de la teoría de probabilidades y estadística matemática en el proceso del desarrollo y el testeo de estrategias comerciales. Buscamos un valor óptimo para los riesgos de la transacción usando las diferencias entre el precio y el paseo aleatorio. Ha sido demostrado que si los precios se comportan como un paseo aleatorio sin desviación (falta de una tendencia con dirección), el trading rentable es imposible.
Desarrollando una utilidad para la selección y navegación de instrumentos en los lenguajes MQL5 y MQL4 Desarrollando una utilidad para la selección y navegación de instrumentos en los lenguajes MQL5 y MQL4
Para el tráder avanzado no es un secreto que la mayor parte del tiempo que ocupa el comercio no se invierte en la apertura o acompañamiento de transacciones. Lo que más tiempo ocupa es la selección de instrumentos y la búsqueda de puntos de entrada. En este artículo trataremos de escribir un asesor que simplifique la búsqueda de puntos de entrada en los instrumentos ofrecidos por nuestro bróker.
Patrones de reversión: Testeando el patrón "Cabeza-Hombros" Patrones de reversión: Testeando el patrón "Cabeza-Hombros"
El presente artículo es una continuación lógica del artículo anterior «Patrones de viraje: poniendo a prueba el patrón Pico/Valle doble». Ahora vamos a considerar otro patrón de reversión bastante bien conocido, llamado «Cabeza- Hombros», compararemos la eficacia del trading de ambos patrones e intentaremos combinar el trading con estos dos patrones en un sistema comercial único.