Combinar salida a ventanas principal y auxiliar

Volvamos al problema de mostrar gráficos de un indicador en la ventana principal y en una subventana, ya que nos lo encontramos al desarrollar el ejemplo de UseDemoAllSimple.mq5. Ahí descubrimos que los indicadores concebidos para una ventana independiente no son adecuados para la visualización en el gráfico principal, y los indicadores para la ventana principal no tienen ventanas adicionales. Existen varios enfoques alternativos:

  • Implementar un indicador padre para una ventana separada y mostrar gráficos allí y usarlo en la ventana principal para mostrar datos del tipo objetos gráficos. Esto es malo, porque los datos de los objetos no se pueden leer de la misma manera que los de una serie temporal, y muchos objetos consumen recursos adicionales.
  • Desarrolle su propio panel virtual (clase) para la ventana principal y, con la escala correcta, represente allí las series temporales que deben mostrarse en la subventana.
  • Utilice varios indicadores, al menos uno para la ventana principal y otro para la subventana, e intercambie datos entre ellos a través de la memoria compartida (se requiere DLL), recursos o base de datos.
  • Duplicar los cálculos (utilizar código fuente común) en los indicadores de la ventana principal y de la subventana.

Presentaremos una de las soluciones que va más allá de un único programa MQL: necesitamos un indicador adicional con la propiedad indicator_separate_window. En realidad ya lo tenemos, puesto que creamos su parte calculada solicitando un manejador. Sólo tenemos que mostrarlo de alguna manera en una subventana separada.

En la nueva versión (completa) de UseDemoAll.mq5, analizaremos los metadatos del indicador cuya creación se solicita en el elemento de enumeración IndicatorType correspondiente. Recordemos que, entre otras cosas, allí se codifica la ventana de trabajo de cada tipo de indicador integrado. Cuando un indicador requiera una ventana separada, crearemos una usando funciones especiales de MQL5, que todavía tenemos que descubrir.

No hay forma de obtener información sobre la ventana de trabajo de los indicadores personalizados. Por lo tanto, vamos a añadir la variable de entrada IndicatorCustomSubwindow, en la que el usuario puede especificar que se requiere una subventana.

input bool IndicatorCustomSubwindow = false// Custom Indicator Subwindow

En OnInit, ocultamos los búferes destinados a la subventana.

int OnInit()
{
   ...
   const bool subwindow = (IND_WINDOW(IndicatorSelector) > 0)
      || (IndicatorSelector == iCustom_ && IndicatorCustomSubwindow);
   for(int i = 0i < BUF_NUM; ++i)
   {
      ...
      PlotIndexSetInteger(iPLOT_DRAW_TYPE,
         i < n && !subwindow ? DrawType : DRAW_NONE);
   }
   ...

Después de esta configuración, tendremos que utilizar un par de funciones que se aplican al trabajo no sólo con indicadores, sino también con gráficos. Los estudiaremos en detalle en el capítulo correspondiente, mientras que en la sección anterior se presenta una visión general introductoria.

Una de las funciones ChartIndicatorAdd permite añadir el indicador especificado por el manejador a la ventana, y no sólo a la parte principal, sino también a la subventana. Hablaremos de los identificadores de gráficos y de la numeración de ventanas en el capítulo sobre gráficos, y por ahora es suficiente saber que la siguiente llamada a la función ChartIndicatorAdd añade un indicador con el handle al gráfico actual, a una nueva subventana.

 inthandle = ...// get indicator handle, iCustom or IndicatorCreate
 
                    // set the current chart (0)
                    // |
                    // |     set the window number to the current number of windows
                    // |                          |
                    // |                          | passing the descriptor
                    // |                          |                       |
                    // v                          v                       v
   ChartIndicatorAdd(  0, (int)ChartGetInteger(0CHART_WINDOWS_TOTAL), handle); 

Conociendo esta posibilidad, podemos pensar en llamar a ChartIndicatorAdd y pasarle el manejador de un indicador subordinado ya preparado.

La segunda función que necesitamos es ChartIndicatorName. Devuelve el nombre corto del indicador por su manejador. Este nombre corresponde a la propiedad INDICATOR_SHORTNAME establecida en el código del indicador y puede diferir del nombre del archivo. El nombre deberá limpiarse a sí mismo, es decir, eliminar el indicador auxiliar y su subventana, después de eliminar o reconfigurar el indicador padre.

string subTitle = "";
   
int OnInit()
{
   ...
   if(subwindow)
   {
      // show a new indicator in the subwindow
      const int w = (int)ChartGetInteger(0CHART_WINDOWS_TOTAL);
      ChartIndicatorAdd(0wHandle);
      // save the name to remove the indicator in OnDeinit
      subTitle = ChartIndicatorName(0w0);
   }
   ...
}

En el manejador OnDeinit, utilizamos la función guardada subTitle para llamar a otra función que estudiaremos más adelante: ChartIndicatorDelete. Esta elimina del gráfico el indicador con el nombre especificado en el último argumento.

void OnDeinit(const int)
{
   Print(__FUNCSIG__, (StringLen(subTitle) > 0 ? " deleting " + subTitle : ""));
   if(StringLen(subTitle) > 0)
   {
      ChartIndicatorDelete(0, (int)ChartGetInteger(0CHART_WINDOWS_TOTAL) - 1,
         subTitle);
   }
}

Aquí se supone que sólo nuestro indicador funciona en el gráfico, y sólo en una única instancia. En un caso más general, todas las subventanas deberían analizarse para su correcta eliminación, pero esto requeriría algunas funciones más de las que se presentarán en el capítulo sobre gráficos, por lo que, de momento, nos limitaremos a una versión sencilla.

Si ahora ejecutamos UseDemoAll y seleccionamos un indicador marcado con un asterisco (es decir, el que requiere una subventana) de la lista, por ejemplo, RSI, veremos el resultado esperado: RSI en una ventana aparte.

RSI en la subventana creada por el indicador UseDemoAll

RSI en la subventana creada por el indicador UseDemoAll