Modos de visualización de gráficos

Cuatro propiedades de la enumeración ENUM_CHART_PROPERTY_INTEGER describen los modos de visualización de los gráficos. Todas estas propiedades están disponibles para la lectura a través de ChartGetInteger, y para el registro a través de ChartSetInteger, que permite cambiar la apariencia del gráfico.

Identificador

Descripción

Tipo de valor

CHART_MODE

Tipo de gráfico (velas, barras o líneas)

ENUM_CHART_MODE

CHART_FOREGROUND

Gráfico de precios en primer plano

bool

CHART_SHIFT

Modo de sangría del gráfico de precios desde el borde derecho

bool

CHART_AUTOSCROLL

Desplazamiento automático al borde derecho del gráfico

bool

Hay una enumeración especial ENUM_CHART_MODE para el modo CHART_MODE en MQL5. Sus elementos se muestran en la siguiente tabla.

Identificador

Descripción

Valor

CHART_BARS

Visualización en forma de barras

0

CHART_CANDLES

Visualización en forma de velas japonesas

1

CHART_LINE

Visualización como una línea trazada a precios de cierre

2

Implementemos el script ChartMode.mq5, que monitorizará el estado de los modos e imprimirá mensajes en el registro cuando se detecten cambios. Dado que los algoritmos de procesamiento de propiedades son de carácter general, los colocaremos en un archivo de encabezado independiente ChartModeMonitor.mqh, que luego conectaremos a diferentes pruebas.

Vamos a sentar las bases en una clase abstracta ChartModeMonitorInterface: proporciona métodos get- y set- sobrecargados para todos los tipos. Las clases derivadas tendrán que comprobar directamente las propiedades en la medida necesaria anulando el método virtual snapshot.

class ChartModeMonitorInterface
{
public:
   long get(const ENUM_CHART_PROPERTY_INTEGER propertyconst int window = 0)
   {
      return ChartGetInteger(0propertywindow);
   }
   double get(const ENUM_CHART_PROPERTY_DOUBLE propertyconst int window = 0)
   {
      return ChartGetDouble(0propertywindow);
   }
   string get(const ENUM_CHART_PROPERTY_STRING property)
   {
      return ChartGetString(0property);
   }
   bool set(const ENUM_CHART_PROPERTY_INTEGER propertyconst long valueconst int window = 0)
   {
      return ChartSetInteger(0propertywindowvalue);
   }
   bool set(const ENUM_CHART_PROPERTY_DOUBLE propertyconst double value)
   {
      return ChartSetDouble(0propertyvalue);
   }
   bool set(const ENUM_CHART_PROPERTY_STRING propertyconst string value)
   {
      return ChartSetString(0propertyvalue);
   }
   
   virtual void snapshot() = 0;
   virtual void print() { };
   virtual void backup() { }
   virtual void restore() { }
};

La clase también tiene métodos reservados: print, por ejemplo, para enviar a un registro, backup para guardar el estado actual, y restore para recuperarlo. Se declaran no abstractos, sino con una implementación vacía, ya que son opcionales.

Tiene sentido definir ciertas clases para propiedades de diferentes tipos como una única plantilla heredada de ChartModeMonitorInterface y que acepte tipos paramétricos de valor (T) y enumeración (E). Por ejemplo, para las propiedades de número entero, tendría que configurar T=long y E=ENUM_CHART_PROPERTY_INTEGER.

El objeto contiene el array data para almacenar pares [clave,valor] con todas las propiedades solicitadas. Tiene un tipo genérico MapArray<K,V>, que introdujimos anteriormente para el indicador IndUnityPercent en el capítulo Indicadores multidivisa y de marco temporal múltiple. Su peculiaridad radica en que, además del acceso habitual a los elementos del array por números, se puede utilizar el direccionamiento por clave.

Para rellenar el array se pasa un array de números enteros al constructor, mientras que primero se comprueba que los enteros cumplen los identificadores de la enumeración E dada mediante el método detect. Todas las propiedades correctas se leen inmediatamente a través de la llamada get, y los valores resultantes se almacenan en el mapa junto con sus identificadores.

#include <MQL5Book/MapArray.mqh>
   
template<typename T,typename E>
class ChartModeMonitorBasepublic ChartModeMonitorInterface
{
protected:
   MapArray<E,Tdata// array-map of pairs [property, value]
   
   // the method checks if the passed constant is an enumeration element,
   // and if it is, then add it to the map array
   bool detect(const int v)
   {
      ResetLastError();
      EnumToString((E)v); // resulting string is not used
      if(_LastError == 0// it only matters if there is an error or not
      {
         data.put((E)vget((E)v));
         return true;
      }
      return false;
   }
 
public:
   ChartModeMonitorBase(int &flags[])
   {
      for(int i = 0i < ArraySize(flags); ++i)
      {
         detect(flags[i]);
      }
   }
   
   virtual void snapshot() override
   {
      MapArray<E,Ttemp;
      // collect the current state of all properties
      for(int i = 0i < data.getSize(); ++i)
      {
         temp.put(data.getKey(i), get(data.getKey(i)));
      }
      
      // compare with previous state, display differences
      for(int i = 0i < data.getSize(); ++i)
      {
         if(data[i] != temp[i])
         {
            Print(EnumToString(data.getKey(i)), " "data[i], " -> "temp[i]);
         }
      }
      
      // save for next comparison
      data = temp;
   }
   ...
};

El método snapshot itera a través de todos los elementos del array y solicita el valor de cada propiedad. Como queremos detectar cambios, los nuevos datos se almacenan primero en un array de mapas temporal temp. A continuación, los arrays data y temp se comparan elemento por elemento y, por cada diferencia, se muestra un mensaje con el nombre de la propiedad, su valor antiguo y su nuevo valor. En este ejemplo simplificado sólo se utiliza el diario. No obstante, si es necesario, el programa puede llamar a algunas funciones de la aplicación que adaptan el comportamiento al entorno.

Los métodos print, backup y restore se implementan de la forma más sencilla posible.

template<typename T,typename E>
class ChartModeMonitorBasepublic ChartModeMonitorInterface
{
protected:
   ...
   MapArray<E,Tstore// backup
public:
   ...
   virtual void print() override
   {
      data.print();
   }
   virtual void backup() override
   {
      store = data;
   }
   
   virtual void restore() override
   {
      data = store;
      // restore chart properties
      for(int i = 0i < data.getSize(); ++i)
      {
         set(data.getKey(i), data[i]);
      }
   }

Una combinación de métodos backup/restore permite guardar el estado del gráfico antes de iniciar experimentos con él y, una vez finalizado el script de prueba, restaurarlo todo como estaba.

Por último, la última clase del archivo ChartModeMonitor.mqh es ChartModeMonitor. Combina tres instancias de ChartModeMonitorBase, creadas para las combinaciones disponibles de tipos de propiedad. Tienen un array de punteros m a la interfaz base ChartModeMonitorInterface. La propia clase también deriva de ella.

#include <MQL5Book/AutoPtr.mqh>
   
#define CALL_ALL(A,Mfor(int i = 0size = ArraySize(A); i < size; ++iA[i][].M
   
class ChartModeMonitorpublic ChartModeMonitorInterface
{
   AutoPtr<ChartModeMonitorInterfacem[3];
   
public:
   ChartModeMonitor(int &flags[])
   {
      m[0] = new ChartModeMonitorBase<long,ENUM_CHART_PROPERTY_INTEGER>(flags);
      m[1] = new ChartModeMonitorBase<double,ENUM_CHART_PROPERTY_DOUBLE>(flags);
      m[2] = new ChartModeMonitorBase<string,ENUM_CHART_PROPERTY_STRING>(flags);
   }
   
   virtual void snapshot() override
   {
      CALL_ALL(msnapshot());
   }
   
   virtual void print() override
   {
      CALL_ALL(mprint());
   }
   
   virtual void backup() override
   {
      CALL_ALL(mbackup());
   }
   
   virtual void restore() override
   {
      CALL_ALL(mrestore());
   }
};

Para simplificar el código se utiliza aquí la macro CALL_ALL, que llama al método especificado para todos los objetos del array, y lo hace teniendo en cuenta el operador sobrecargado [] de la clase AutoPtr (se utiliza para desreferenciar un puntero inteligente y obtener un puntero directo al objeto «protegido»).

El destructor suele encargarse de liberar los objetos, pero en este caso se decidió utilizar el array AutoPtr (esta clase se abordó en la sección Plantillas de tipos de objeto). Esto garantiza la eliminación automática de los objetos dinámicos cuando el array m se libera normalmente.

En el archivo ChartModeMonitorFull.mqh se proporciona una versión más completa del monitor con soporte para números de subventana.

Basándose en la clase ChartModeMonitor, puede implementar fácilmente la secuencia de comandos prevista ChartMode.mq5. Su tarea consiste en comprobar el estado de un determinado conjunto de propiedades cada medio segundo. Ahora estamos utilizando aquí un bucle infinito y Sleep, pero pronto vamos a aprender a reaccionar a los eventos en los gráficos de una manera diferente: debido a las notificaciones de la terminal.

#include <MQL5Book/ChartModeMonitor.mqh>
   
void OnStart()
{
   int flags[] =
   {
      CHART_MODECHART_FOREGROUNDCHART_SHIFTCHART_AUTOSCROLL
   };
   ChartModeMonitor m(flags);
   Print("Initial state:");
   m.print();
   m.backup();
   
   while(!IsStopped())
   {
      m.snapshot();
      Sleep(500);
   }
   m.restore();
}

Ejecute el script en cualquier gráfico e intente cambiar los modos utilizando los botones de herramientas. De esta forma puede acceder a todos los elementos excepto a CHART_FOREGROUND, que puede cambiarse desde el cuadro de diálogo de propiedades (la pestaña Common, bandera Chart on top).

Botones de la barra de herramientas para cambiar de modo de gráfico

Botones de la barra de herramientas para cambiar de modo de gráfico

Por ejemplo, el siguiente registro se creó cambiando la visualización de velas a barras, de barras a líneas y de nuevo a velas, y después activando la sangría y el desplazamiento automático hasta el principio.

Initial state:
    [key] [value]
[0]     0       1
[1]     1       0
[2]     2       0
[3]     4       0
CHART_MODE 1 -> 0
CHART_MODE 0 -> 2
CHART_MODE 2 -> 1
CHART_SHIFT 0 -> 1
CHART_AUTOSCROLL 0 -> 1

Un ejemplo más práctico del uso de la propiedad CHART_MODE es una versión mejorada del indicador IndSubChart.mq5 (ya hablamos de su versión simplificada IndSubChartSimple.mq5 en la sección Indicadores multidivisa y de marco temporal múltiple). El indicador está diseñado para mostrar cotizaciones de un símbolo ajeno en una subventana, y antes teníamos que solicitar al usuario un método de visualización (velas, barras o líneas) a través de un parámetro de entrada. Ahora el parámetro ya no es necesario porque podemos cambiar automáticamente el indicador al modo que se utiliza en la ventana principal.

El modo actual se almacena en la variable global mode y se asigna primero durante la inicialización.

ENUM_CHART_MODE mode = 0;
   
int OnInit()
{
   ...
   mode = (ENUM_CHART_MODE)ChartGetInteger(0CHART_MODE);
   ...
}

La detección de un nuevo modo se realiza mejor en un manejador de eventos especialmente diseñado OnChartEvent, que estudiaremos en un capítuloaparte. En esta etapa, es importante saber que con cualquier cambio en el gráfico, el programa MQL puede recibir notificaciones del terminal si el código describe una función con este prototipo predefinido (nombre y lista de parámetros). En concreto, su primer parámetro contiene un identificador de evento que describe su significado. Seguimos interesados en el gráfico en sí, por lo que comprobamos si eventId es igual a CHARTEVENT_CHART_CHANGE. Esto es necesario porque el manejador también es capaz de rastrear objetos gráficos, teclado, ratón y mensajes arbitrarios del usuario.

void OnChartEvent(const int eventId,
                 // parameters not used here
                  const long &, const double &, const string &)
{
   if(eventId == CHARTEVENT_CHART_CHANGE)
   {
      const ENUM_CHART_MODE newmode = (ENUM_CHART_MODE)ChartGetInteger(0CHART_MODE);
      if(mode != newmode)
      {
         const ENUM_CHART_MODE oldmode = mode;
         mode = newmode;
         // change buffer bindings and rendering type on the go
         InitPlot(0InitBuffers(mode), Mode2Style(mode));
         // TODO: we will auto-adjust colors later
         // SetPlotColors(0, mode);
         if(oldmode == CHART_LINE || newmode == CHART_LINE)
         {
            // switching to or from CHART_LINE mode requires updating the entire chart,
            // because the number of buffers changes
            Print("Refresh");
            ChartSetSymbolPeriod(0_Symbol_Period);
         }
         else
         {
           // when switching between candles and bars, it is enough
           // just redraw the chart in a new manner,
           // because data doesn't change (previous 4 buffers with values)
            Print("Redraw");
            ChartRedraw();
         }
      }
   }
}

Puede probar el nuevo indicador usted mismo ejecutándolo en el gráfico y cambiando los métodos de dibujo.

Estas no son todas las mejoras introducidas en IndSubChart.mq5. Un poco más adelante, en la sección sobre colores del gráfico, mostraremos el ajuste automático de los gráficos al esquema de colores del gráfico.