Construir un analizador de espectro

Victor | 24 enero, 2014


Introducción

Este artículo pretende que sus lectores se familiaricen con una posible variante del uso de los objetos gráficos en el lenguaje MQL5. Analiza un indicador que implementa un panel para la gestión de un analizador de espectro simple usando objetos gráficos.

A este artículo se adjuntan tres archivos:

  1. SpecAnalyzer.mq5 – el indicador que se describe en el artículo.
  2. SpecAnalyzer.mqh – el archivo include para SpecAnalyzer.mq5.
  3. SAInpData.mq5 – el indicador usado para la organización del acceso a los datos externos.

Para cargar normalmente el indicador SpecAnalyzer debemos ubicar los tres archivos en la carpeta \MQL5\Indicators del terminal de cliente. A continuación debemos compilar los archivos SpecAnalyzer.mq5 y SAInpData.mq5. El indicador está pensado para ser cargado en la ventana principal del gráfico. Una vez que se ha cargado el indicador, cambian los parámetros que se visualizan en esta ventana y, cuando se elimina, todos los objetos gráficos se borran de la ventana. Por ello, debemos cargar el indicador en una ventana especial separada creada al efecto, para evitar así cambiar accidentalmente el modo de visualización de las ventanas existentes del terminal.

Teniendo en cuenta el hecho de que el artículo no contiene todo el código fuente del indicador, se recomienda tener el código abierto en los archivos adjuntos mientras se lee el artículo.

El indicador descrito no pretende ser una aplicación ya terminada. Es solo un ejemplo del uso de objetos gráficos del lenguaje. Aquellos que estén interesados pueden actualizar y optimizar el código mostrado en el artículo por sí mismos.


Coordenadas

En MQL5 hay dos formas de especificar las coordenadas al dibujar objetos gráficos. Para algunos objetos las coordenadas se especifican como un número de píxeles de un punto determinado de la ventana, mientras que para otros las coordenadas se especifican como el precio y los valores de tiempo del gráfico mostrados en la ventana.

Por ejemplo, para ubicar dicho objeto como "etiqueta" o "botón" en un gráfico, debemos especificar sus coordenadas como la distancia en píxeles desde una de las esquinas de la ventana del gráfico. A través de esta forma de trabajar con los objetos se mantiene su posición sin que influyan las propiedades actuales de la ventana y la escala del gráfico mostrada en ella. Incluso si cambia el tamaño de la ventana, estos objetos mantienen su posición, uniéndose unos a otros. Si movemos el gráfico en la ventana usando el botón izquierdo del ratón, estos objetos mantienen su posición con relación al punto de anclaje elegido de la ventana.

El otro grupo de objetos implica la unión al gráfico en la ventana en lugar de las coordenadas de esta. Estos objetos son "Trend Line", "Rectangle", etc. Al crear y colocar estos objetos, las coordenadas se especifican como valor del tiempo y el precio de un gráfico mostrado en la ventana. Con esta forma de especificar las coordenadas, los objetos cambian su posición con relación a la ventana del gráfico y con relación a los demás cuando la escala del gráfico se cambia o cuando se hace un desplazamiento sobre ellas. A medida que aparecen nuevas barras en el gráfico, los objetos van cambiando también su posición, pero el eje de tiempo se desplaza a la izquierda en el periodo de tiempo.

En el indicador SpecAnalyzer ambos tipos de objetos gráficos se usan simultáneamente para crear el panel de control del analizador de espectro. Para que los objetos unidos al gráfico no se muevan con relación a la ventana, establecemos el modo fijo de visualización del gráfico a lo largo del eje vertical y el modo correspondiente a la escala mínima posible de visualización a lo largo de la escala horizontal del gráfico. Además de esto, el mínimo de la escala vertical se establece a 0,0 y para el eje horizontal establecemos el modo sin cambiar la barra cero del borde derecho y sin desplazamiento automático al borde derecho del gráfico. Por tanto, el punto de coordenadas que coincide con la barra cero y el valor 0,0 del gráfico parece estar en la esquina inferior derecha y con la escala fija puede usarse un punto de anclaje para objetos como "Trend Line" y "Rectangle". En dicho punto, si establecemos la misma esquina inferior derecha como punto de anclaje de objetos como "label" y "button", entonces ambos tipos de objetos se unirán definitivamente entre sí.

Todas las propiedades del gráfico necesarias se establecen en la función SetOwnProperty() en el archivo SpecAnalyzer.mqh.

void GRaphChart::SetOwnProperty(void)
  {
  ChartSetInteger(m_chart_id,CHART_FOREGROUND,1);
  ChartSetInteger(m_chart_id,CHART_SHIFT,0);
  ChartSetInteger(m_chart_id,CHART_AUTOSCROLL,0);
  ChartSetInteger(m_chart_id,CHART_AUTOSCROLL,1);
  ChartSetInteger(m_chart_id,CHART_SCALE,0);
  ChartSetInteger(m_chart_id,CHART_SCALEFIX_11,1);
  ChartSetInteger(m_chart_id,CHART_SHOW_OHLC,0);
  ChartSetInteger(m_chart_id,CHART_SHOW_BID_LINE,0);
  ChartSetInteger(m_chart_id,CHART_SHOW_ASK_LINE,0);
  ChartSetInteger(m_chart_id,CHART_SHOW_LAST_LINE,0);
  ChartSetInteger(m_chart_id,CHART_SHOW_PERIOD_SEP,0);
  ChartSetInteger(m_chart_id,CHART_SHOW_GRID,0);
  ChartSetInteger(m_chart_id,CHART_SHOW_VOLUMES,CHART_VOLUME_HIDE);
  ChartSetInteger(m_chart_id,CHART_SHOW_OBJECT_DESCR,0);
  ChartSetInteger(m_chart_id,CHART_SHOW_TRADE_LEVELS,0);
  ChartSetInteger(m_chart_id,CHART_COLOR_BACKGROUND,Black);
  ChartSetInteger(m_chart_id,CHART_COLOR_FOREGROUND,Black);
  ChartSetDouble(m_chart_id,CHART_FIXED_MIN,0.0);
  }

Además de establecer las propiedades necesarias del gráfico para proporcionar la unión de los objetos gráficos, esta función establece las propiedades que permiten asignar un color y limitar la visualización de algunos elementos del gráfico.

Como el indicador cambia las propiedades del gráfico mientras trabaja, debemos guardar la configuración previa del gráfico cuando cargamos el indicador y la restauramos cuando los descarguemos. La librería estándar de MQL5 incluye las funciones virtuales Save() y Load() de la clase CChart, específicas para este propósito. Estas funciones están pensadas para guardar las propiedades de un objeto de la clase CChart en un archivo y restaurar esas propiedades a partir del archivo creado. Para cambiar el conjunto de propiedades guardadas y evitar usar operaciones de archivo al guardar las propiedades del gráfico, se pasan por alto las funciones Save() y Load() de la clase CChart cuando se crea la nueva clase GRaphChart (véase el archivo SpecAnalyzer.mqh). 

class GRaphChart : public CChart
  {
  protected:
    struct ChartPropertyes
      {
      double shift_size;
      double fixed_max;
      double fixed_min;
      double points_per_bar;
      long   mode;
      bool   foreground;
      bool   shift;
      bool   autoscroll;
      long   scale;
      bool   scalefix;
      bool   scalefix_11;
      bool   scale_pt_per_bar;
      bool   show_ohls;
      bool   show_bid_line;
      bool   show_ask_line;
      bool   show_last_line;
      bool   show_period_sep;
      bool   show_grid;
      long   show_volumes;
      bool   show_object_descr;
      bool   show_trade_levels;
      long   color_background;
      long   color_foreground;
      long   color_grid;
      long   color_volume;
      long   color_chart_up;
      long   color_chart_down;
      long   color_chart_line;
      long   color_candle_bull;
      long   color_candle_bear;
      long   color_bid;
      long   color_ask;
      long   color_last;
      long   color_stop_level;
      string ch_comment;
      };
      ChartPropertyes ChProp;
  
  public:
                   GRaphChart();
                  ~GRaphChart();
                   
         void      SetOwnProperty();
  virtual void      Save();
  virtual void      Load();
  };

La clase básica de GRaphChart es CChart de la librería estándar de MQL5. la clase GRaphChart contiene la descripción de la estructura de ChartPropertyes y la creación del objeto ChProp destinado a almacenar las propiedades del gráfico en la memoria en lugar de en un archivo a medida que se implementa en la clase básica. La función Save() rellena la estructura ChProp con datos según las propiedades actuales del gráfico y la función Load() restablece las propiedades guardadas anteriormente.

La función Save() es llamada en el constructor de la clase GRaphChart y la función Load() es llamada en su destructor. Por esta razón, el guardado y la recuperación del estado previo del gráfico se realiza automáticamente al crear y borrar el objeto de la clase GRaphChart. La propiedad SetOwnProperty() mencionada anteriormente también es llamada en el constructor de la clase.

//---------------------------------------------------- Constructor GRaphChart --
GRaphChart::GRaphChart()
  {
  m_chart_id=ChartID();
  Save();                                // Keep a original chart settings
  SetOwnProperty();                             // Set own chart settings
  }
//----------------------------------------------------- Destructor GRaphChart --
GRaphChart::~GRaphChart()
  {
  Load();                             // Restore a previous chart settings
  m_chart_id=-1;
  }

Vamos a ver el uso de la clase GRaphChart con un ejemplo sencillo. Para hacer esto vamos a crear un indicador personalizado en MetaEditor y lo llamaremos Test. Incluimos el archivo cabecera SpecAnalyzer.mqh en el código del indicador y creamos un objeto de la clase GRaphChart añadiendo dos líneas.

//+------------------------------------------------------------------+
//|                                                         Test.mq5 |
//|                        Copyright 2010, MetaQuotes Software Corp. |
//|                                              https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "2010, MetaQuotes Software Corp."
#property link      "https://www.mql5.com"
#property version   "1.00"
#property indicator_chart_window

#include "SpecAnalyzer.mqh"    // <----- Including header file 

GRaphChart  MainChart; // <----- Creating object of the class GRaphChart

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- indicator buffers mapping
//---
   return(0);
  }
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime& time[],
                const double& open[],
                const double& high[],
                const double& low[],
                const double& close[],
                const long& tick_volume[],
                const long& volume[],
                const int& spread[])
  {
//---
//--- return value of prev_calculated for next call
   return(rates_total);
  }
//+------------------------------------------------------------------+

Para compilar con éxito el código anterior, el archivo SpecAnalyzer.mqh debe ubicarse en la carpeta \MQL5\Indicators del terminal de cliente.

Si creamos un gráfico en el terminal de cliente e intentamos cargar nuestro ejemplo en él, se cambiarán las propiedades del gráfico y veremos solo una ventana vacía preparada para mostrar los objetos gráficos en ella. Cuando retiramos nuestro indicador de prueba del gráfico, se restablece su apariencia inicial con la llegada de un nuevo tick.

Vamos a volver al indicador SpecAnalyzer. Al principio del indicador (véase el archivo SpecAnalyzer.mq5), se realiza la creación del objeto MainChart de la clase GRaphCHart, que da lugar a la carga del indicador y al guardado de las propiedades del gráfico.

                 button. button. button.
GRaphChart MainChart; // Create MainChart object
                 button. button. button.

Al descargar el indicador, el objeto MainChart finaliza automáticamente y se restablecen las propiedades iniciales en el destructor de la clase.


Panel de control

La apariencia del panel de control en el indicador SpecAnalyzer se determina por los objetos gráficos situados en la ventana. La clase AllGrObject unifica todas las funciones necesarias para crear e interactuar con ellas. Véase el archivo SpecAnalyzer.mqh.

class AllGrObject : public CChart
  {
  protected:
    long      m_chart_id;                                    // chart identifier
    
  public:
              AllGrObject();
             ~AllGrObject();
                   
    void      AddLabel(string name,int fsize,string font,
                             color col,int x,int y,string text="");
    void      AddButton(string name,int xsize,int ysize,color bgcol,
                        int fsize,string font,color col,int x,int y,
                        string text="",int state=0);
    void      AddEdit(string name,int xsize,int ysize,color bgcol,int fsize,
                      string font,color col,int x,int y,string text="");
    void      AddTrendLine(string name,color col,int style=0,int width=1);
    void      AddArrowline(string name,color col,int style=0,int width=1);
    void      AddRectangle(string name,color col,int style=0,int width=1);
    void      MoveGrObject(string name,int x1,int y1,int x2,int y2);
    void      SetButtonProp(string name,bool state,color col);
    long      GetRowNumber(string name);
    void      LabelTxt(string name,string str);
    
  };

Las funciones de la clase cuyos nombres comienzan con Add tienen por finalidad la creación de objetos gráficos. Por ejemplo, AddButton () crea el objeto "Button".

En la aplicación, las coordenadas para todos los objetos gráficos se establecen como la distancia en píxeles desde la esquina inferior derecha del gráfico. Para los objetos “Trend Line”, “Arrowed Line” y “Rectangle” debemos transformar estas coordenadas en valores de tiempo y precio. Dicha transformación se realiza en la función MoveGrObject() antes de asignar las coordenadas a un objeto. Un pixel horizontal representa una barra y uno vertical representa un punto.

void AllGrObject::MoveGrObject(string name,int x1,int y1,int x2,int y2)
  {
  datetime t1[1],t2[1];
  long typ;
  
  typ=ObjectGetInteger(m_chart_id,name,OBJPROP_TYPE);
  if(typ==OBJ_TREND||typ==OBJ_ARROWED_LINE||typ==OBJ_RECTANGLE)
    {
    CopyTime(_Symbol,_Period,x1,1,t1);
    CopyTime(_Symbol,_Period,x2,1,t2);
    ObjectSetInteger(m_chart_id,name,OBJPROP_TIME,0,t1[0]);
    ObjectSetDouble(m_chart_id,name,OBJPROP_PRICE,0,_Point*y1);
    ObjectSetInteger(m_chart_id,name,OBJPROP_TIME,1,t2[0]);
    ObjectSetDouble(m_chart_id,name,OBJPROP_PRICE,1,_Point*y2);
    }
  }

Todos los objetos gráficos se crean una sola vez en el indicador cuando se llama a gr_object_create() desde la función OnInit() del indicador. Véase el archivo SpecAnalyzer.mq5. Para todos los objetos, excepto “Trend Line”, “Arrowed Line” y “Rectangle”, las coordenadas se establecen de inmediato. Para objetos como “Trend Line”, “Arrowed Line” y “Rectangle” las coordenadas se establecen llamando a la función gr_object_coordinate() que utiliza la función MoveGrObject() mencionada anteriormente y que transforma el modo de direccionado. 

void gr_object_coordinate()
  {
  GObj.MoveGrObject("Trdline1",48,150,48,360);
  GObj.MoveGrObject("Trdline2",176,150,176,360);
  GObj.MoveGrObject("Trdline3",304,150,304,360);
  GObj.MoveGrObject("Trdline4",432,150,432,360);
  GObj.MoveGrObject("Trdline5",42,350,560,350);
  GObj.MoveGrObject("Trdline6",42,300,560,300);
  GObj.MoveGrObject("Trdline7",42,250,560,250);
  GObj.MoveGrObject("Trdline8",42,200,560,200);
  GObj.MoveGrObject("Arrline1",560,150,28,150);
  GObj.MoveGrObject("Arrline2",560,150,560,370);
  GObj.MoveGrObject("Rect1",0,1,208,110);
  GObj.MoveGrObject("Rect2",208,1,416,110);
  GObj.MoveGrObject("Rect3",416,1,624,110);
  GObj.MoveGrObject("Rect4",0,1,624,400);
  GObj.MoveGrObject("Rect5",20,10,195,80);
  }

La llamada de la función gr_object_coordinate() se incluye en la función OnCalculate() del indicador. Proporciona un nuevo cálculo correcto de las coordenadas cuando se forma una nueva barra en el gráfico, ya que la función es llamada con cada llegada de un nuevo tick.

Los botones del panel del indicador se dividen en tres grupos. El primer grupo está formado por cuatro botones situados a la izquierda y permiten seleccionar un modo de visualización del resultado de estimar el espectro de la secuencia de entrada por el indicador. Hay cuatro modos de visualización (según el número de botones):

  1. Amplitud/línea - visualiza el módulo de la transformada de Fourier a escala lineal a lo largo del eje Y.
  2. Amplitud/dB - visualiza el módulo de la transformada de Fourier a escala logarítmica a lo largo del eje Y.
  3. Potencia/línea - visualiza el cuadrado del módulo de la transformada de Fourier a escala lineal a lo largo del eje Y.
  4. Potencia/dB- visualiza el cuadrado del módulo de la transformada de Fourier a escala logarítmica a lo largo del eje Y.

Para procesar los clics en los botones de este grupo se incluye el siguiente código en la función OnChartEvent() del indicador: 

  if(id==CHARTEVENT_OBJECT_CLICK)                       // Click on the gr. object
    {
    if(sparam=="Butt1")                                   // Click on the button
      {
      GObj.SetButtonProp("Butt1",1,Chocolate);
      GObj.SetButtonProp("Butt2",0,SlateGray);
      GObj.SetButtonProp("Butt3",0,SlateGray);
      GObj.SetButtonProp("Butt4",0,SlateGray);
      YPowerFlag=0;YdBFlag=0;
      }
    if(sparam=="Butt2")                                   // Click on the button
      {
      GObj.SetButtonProp("Butt1",0,SlateGray);
      GObj.SetButtonProp("Butt2",1,Chocolate);
      GObj.SetButtonProp("Butt3",0,SlateGray);
      GObj.SetButtonProp("Butt4",0,SlateGray);
      YPowerFlag=0;YdBFlag=1;
      }
    if(sparam=="Butt3")                                   // Click on the button
      {
      GObj.SetButtonProp("Butt1",0,SlateGray);
      GObj.SetButtonProp("Butt2",0,SlateGray);
      GObj.SetButtonProp("Butt3",1,Chocolate);
      GObj.SetButtonProp("Butt4",0,SlateGray);
      YPowerFlag=1;YdBFlag=0;
      }
    if(sparam=="Butt4")                                   // Click on the button
      {
      GObj.SetButtonProp("Butt1",0,SlateGray);
      GObj.SetButtonProp("Butt2",0,SlateGray);
      GObj.SetButtonProp("Butt3",0,SlateGray);
      GObj.SetButtonProp("Butt4",1,Chocolate);
      YPowerFlag=1;YdBFlag=1;
      }

Cuando se detecta un clic en uno de los botones, el estado de los demás botones cambia a no pulsado, lo que evita pulsar simultáneamente varios botones en un grupo. Al mismo tiempo, se establecen los valores correspondientes para los flags YPowerFlag y YdBFlag que determinan el modo actual de visualización.

El segundo grupo, que consta de cuatro botones, ofrece la posibilidad de seleccionar una fuente de datos de entrada. Esta pueden ser datos externos obtenidos mediante la llamada al indicador SAInpData.mq5 o tres secuencias de prueba generadas por la propia aplicación. El último grupo de botones incluye dos botones usados para el desplazamiento de la lista en el campo de entrada de información de texto. La gestión de los clics en todos estos botones se realiza en la función OnChartEvent() del indicador de la misma forma que con los botones del primer grupo. Véase el archivo SpecAnalyzer.mq5.

Vamos a ver un ejemplo del uso de la clase AllGrObject usando el indicador de prueba creado anteriormente Test.mq5. Para hacer esto, añadimos varias líneas a su código fuente e incluimos las funciones gr_object_create() and gr_object_coordinate() mencionadas antes del archivo SpecAnalyzer.mq5.

//+------------------------------------------------------------------+
//|                                                         Test.mq5 |
//|                        Copyright 2010, MetaQuotes Software Corp. |
//|                                              https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "2010, MetaQuotes Software Corp."
#property link      "https://www.mql5.com"
#property version   "1.00"
#property indicator_chart_window

#include "SpecAnalyzer.mqh" 

GRaphChart  MainChart;

AllGrObject GObj;        // <----- Creating object of the class AllGrObject

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- indicator buffers mapping
//---

  gr_object_create();          // <----- creating graphical objects

   return(0);
  }
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime& time[],
                const double& open[],
                const double& high[],
                const double& low[],
                const double& close[],
                const long& tick_volume[],
                const long& volume[],
                const int& spread[])
  {
//---

  MainChart.SetOwnProperty();    // <----- restoring current properties of the chart
  gr_object_coordinate();     // <----- setting coordinates for the graphical objects


//--- return value of prev_calculated for next call
   return(rates_total);
  }

//----------------------------------------------- Create all graphical objects --
void gr_object_create()
  {
  GObj.AddLabel("Title",10,"Arial",DarkGray,256,367,"Spectrum Analyzer");
  GObj.AddLabel("Label1",9,"Arial",LightSteelBlue,557,128,"0");
  GObj.AddLabel("Label2",9,"Arial",LightSteelBlue,422,128,"128");
  GObj.AddLabel("Label3",9,"Arial",LightSteelBlue,294,128,"256");
  GObj.AddLabel("Label4",9,"Arial",LightSteelBlue,166,128,"384");
  GObj.AddLabel("Label5",9,"Arial",LightSteelBlue,40,128,"512");
  GObj.AddLabel("Label6",9,"Arial",LightSteelBlue,28,156,"N");
  GObj.AddLabel("Label7",9,"Arial",LightSteelBlue,569,141,"0.00");
  GObj.AddLabel("Label8",9,"Arial",LightSteelBlue,569,191,"0.25");
  GObj.AddLabel("Label9",9,"Arial",LightSteelBlue,569,241,"0.50");
  GObj.AddLabel("Label10",9,"Arial",LightSteelBlue,569,291,"0.75");
  GObj.AddLabel("Label11",9,"Arial",LightSteelBlue,569,341,"1.00");
  GObj.AddLabel("Label12",9,"Arial",LightSteelBlue,569,358,"U");
  GObj.AddLabel("Label13",9,"Arial",DarkGray,490,86,"Y-axis Mode");
  GObj.AddLabel("Label14",9,"Arial",DarkGray,285,86,"Input Data");
  GObj.AddLabel("Label15",9,"Arial",DarkGray,75,86,"Level");
  GObj.AddLabel("Label16",9,"Arial",DarkGray,185,86,"N");
  GObj.AddLabel("Label17",8,"Courier",DarkOliveGreen,64,64);
  GObj.AddLabel("Label18",8,"Courier",DarkOliveGreen,64,51);
  GObj.AddLabel("Label19",8,"Courier",DarkOliveGreen,64,38);
  GObj.AddLabel("Label20",8,"Courier",DarkOliveGreen,64,25);
  GObj.AddLabel("Label21",8,"Courier",DarkOliveGreen,64,12);
  GObj.AddButton("Butt1",185,18,C'32,32,32',8,"Arial",SlateGray,612,79,"Amplitude (line)",0);
  GObj.AddButton("Butt2",185,18,C'32,32,32',8,"Arial",Chocolate,612,61,"Amplitude (log)",1);
  GObj.AddButton("Butt3",185,18,C'32,32,32',8,"Arial",SlateGray,612,43,"Power (line)",0);
  GObj.AddButton("Butt4",185,18,C'32,32,32',8,"Arial",SlateGray,612,25,"Power (log)",0);
  GObj.AddButton("Butt5",185,18,C'32,32,32',8,"Arial",SlateGray,403,79,"External Data",0);
  GObj.AddButton("Butt6",185,18,C'32,32,32',8,"Arial",SlateGray,403,61,"Test 1. SMA(3)",0);
  GObj.AddButton("Butt7",185,18,C'32,32,32',8,"Arial",Chocolate,403,43,"Test 2. SMA(32)",1);
  GObj.AddButton("Butt8",185,18,C'32,32,32',8,"Arial",SlateGray,403,25,"Test 3. LWMA(12)",0);
  GObj.AddButton("Butt9",14,34,C'32,32,32',8,"Wingdings",SlateGray,36,78,"\x0431",0);
  GObj.AddButton("Butt10",14,34,C'32,32,32',8,"Wingdings",SlateGray,36,44,"\x0432",0);
  GObj.AddEdit("Edit1",35,18,Black,9,"Arial",SlateGray,180,102);
  GObj.AddTrendLine("Trdline1",C'32,32,32');
  GObj.AddTrendLine("Trdline2",C'32,32,32');
  GObj.AddTrendLine("Trdline3",C'32,32,32');
  GObj.AddTrendLine("Trdline4",C'32,32,32');
  GObj.AddTrendLine("Trdline5",C'32,32,32');
  GObj.AddTrendLine("Trdline6",C'32,32,32');
  GObj.AddTrendLine("Trdline7",C'32,32,32');
  GObj.AddTrendLine("Trdline8",C'32,32,32');
  GObj.AddArrowline("Arrline1",LightSteelBlue);
  GObj.AddArrowline("Arrline2",LightSteelBlue);
  GObj.AddRectangle("Rect1",C'72,72,72');
  GObj.AddRectangle("Rect2",C'72,72,72');
  GObj.AddRectangle("Rect3",C'72,72,72');
  GObj.AddRectangle("Rect4",DarkGray);
  GObj.AddRectangle("Rect5",C'72,72,72');
  }
//---------- Set object coordinate for Trend Line, Arroved Line and Rectangle --
void gr_object_coordinate()
  {
  GObj.MoveGrObject("Trdline1",48,150,48,360);
  GObj.MoveGrObject("Trdline2",176,150,176,360);
  GObj.MoveGrObject("Trdline3",304,150,304,360);
  GObj.MoveGrObject("Trdline4",432,150,432,360);
  GObj.MoveGrObject("Trdline5",42,350,560,350);
  GObj.MoveGrObject("Trdline6",42,300,560,300);
  GObj.MoveGrObject("Trdline7",42,250,560,250);
  GObj.MoveGrObject("Trdline8",42,200,560,200);
  GObj.MoveGrObject("Arrline1",560,150,28,150);
  GObj.MoveGrObject("Arrline2",560,150,560,370);
  GObj.MoveGrObject("Rect1",0,1,208,110);
  GObj.MoveGrObject("Rect2",208,1,416,110);
  GObj.MoveGrObject("Rect3",416,1,624,110);
  GObj.MoveGrObject("Rect4",0,1,624,400);
  GObj.MoveGrObject("Rect5",20,10,195,80);
  }
//+------------------------------------------------------------------+

Para proporcionar acceso a las funciones de la clase AllGrObject creamos el objeto GObj de esta clase. La función OnInit() del indicador incluye la llamada de la función gr_object_create(), que crea todos los objetos gráficos necesarios que determinan la aparición y funcionalidad del panel de control del indicador.

En la función OnCalculate añadimos las llamadas de las funciones MainChart.SetOwnProperty() y gr_object_coordinate() y de esta forma las propiedades del gráfico y las coordenadas de los objetos dibujados en él se restablecerán con la llegada de cada nuevo tick. Dicho restablecimiento es necesario cuando se forma una nueva barra en el gráfico inicial o se mueve el gráfico usando el botón izquierdo del ratón o cuando un usuario cambia las propiedades del gráfico. Después de compilar y cargar este ejemplo de prueba, veremos las interfaces del panel de control. Véase fig. 1.

Fig. 1. Interfaz del panel de control. 

Fig. 1. Interfaz del panel de control.

 Para mostrarle visualmente la colocación del panel de control con relación al gráfico, habilito la visualización de la escala del gráfico en el ejemplo anterior. Véase la fig. 2.

 Fig. 2. La escala del gráfico.

Fig. 2. La escala del gráfico. 

El analizador de espectro

El analizador de un espectro en el indicador se realiza por la graduación 1024 de la secuencia de entrada. El análisis de espectro se realiza usando el algoritmo de la transformada de Fourier rápida (FFT). La función que implementa el algoritmo FFT se toma de las publicaciones de la web www.mql4.com. Para los cálculos usamos la función FFT de secuencia de tiempo real de entrada. Su código se encuentra en el archivo SpecAnalyzer.mqh. Todas las acciones requeridas para el análisis de espectro se implementan en la función fft_calc(). 

void fft_calc()
  {
  int i,k;

  realfastfouriertransform(InpData,ArraySize(InpData),false);          // FFT
  for(i=1;i<ArraySize(Spectrum);i++)
    {
    k=i*2;
    Spectrum[i]=InpData[k]*InpData[k]+InpData[k+1]*InpData[k+1];    
    }
  Spectrum[0]=0.0;                             // Clear constant component level
  }

Cuando se llama a la función fft_calc(), la matriz InpData[] debe contener una secuencia de entrada para ser analizada. Después de ejecutar realfastfouriertransform(), el resultado del FFT se ubicará en esa matriz. Además, el cuadrado del módulo se calcula para cada armónico de la parte real e imaginaria de las estimaciones del espectro. Los resultados se escriben en la matriz Spectrum[]. El índice del elemento en el matriz Spectrum[] representa el número del armónico. Como el valor calculado del componente de la constante no se usa en el indicador, el valor cero siempre se asigna al elemento Spectrum[0] de la matriz.

Dependiendo del valor de la variable InputSource, la matriz InpData[] puede rellenarse con las secuencias de prueba o con los datos obtenidos de un indicador externo. Los datos de entrada se forman en la función get_input_data().

void get_input_data()
  {
  int i;
  
  ArrayInitialize(InpData,0.0);
  switch(InputSource)
    {
    case 0:                                                    // External Data
      if(ExtHandle!=INVALID_HANDLE)
        CopyBuffer(ExtHandle,0,0,ArraySize(InpData),InpData);
      break;
    case 1:                                                    // Test 1. SMA(3)
      for(i=0;i<3;i++)InpData[i]=1;
      break;
    case 2:                                                   // Test 2. SMA(32)
      for(i=0;i<32;i++)InpData[i]=1;
      break;
    case 3:                                                  // Test 3. LWMA(12)
      for(i=0;i<12;i++)InpData[i]=12-i;
      break;
    }
  }

Si el valor de InputSource es igual a cero, serán copiados 1024 valores en la matriz de entrada InpData[] del buffer cero del indicador SAInpData.mq5. Los datos para el análisis pueden generarse en el indicador o a través de la llamada a otros indicadores del mismo. Para proporcionar acceso al indicador SAInpData.mq5 se añaden las líneas siguientes a la función OnInit(), que determina el valor de la variable ExtHandle.

int OnInit() 
 {
 button. button. button.

 ExtHandle=iCustom(NULL,0,"SAInpData");  // External indicator Handle

 return(0);
 }

El indicador SAInpData.mq5 debe colocarse en el directorio \MQL5\Indicators del terminal de cliente. El indicador \MQL5\Indicators adjunto a este artículo como ejemplo pasa una secuencia aleatoria al analizador. Para conseguir que SAInpData.mq5 pase otra secuencia al analizador, cambiamos el código fuente.

Como secuencias de prueba para las funciones get_input_data(), se generan las características de impulso de las medias móviles SMA(3), SMA(32) y LWMA(12). Teniendo en cuenta que la transformada de Fourier de la característica de un impulso de un filtro se corresponde con las características de amplitud-frecuencia de ese filtro, podemos observar las características de amplitud-frecuencia de las medias móviles si las seleccionamos como secuencias de prueba.

Para normalizar el resultado de la estimación del espectro almacenado en la matriz Spectrum[] y prepararlo para su visualización en el modo especificado, se usa la función norm_and_draw(). Véase el archivo SpecAnalyzer.mq5. Dependiendo del modo de visualización elegido esta función reemplaza la marca de texto del eje Y.

El resultado de la estimación del espectro de la secuencia de entrada se muestra no solo en forma gráfica sino también en forma de texto. Para representar los resultados en forma de texto, se crean cinco objetos gráficos del tipo "label" que corresponden a cinco líneas de texto representadas en pantalla. La función list_levels() realiza el llenado de estas líneas con información. 

void list_levels()
  {
  int m;
  string str;
  
  if(YdBFlag==0) str="%3u    %.5f";                     // If Y-axis mode = Line
  else str="%3u  %6.1f dB";                               // If Y-axis mode = dB
  m=ArraySize(ListArray)-5;
  if(ListPointer<1)ListPointer=1;
  if(ListPointer>m)ListPointer=m;
  GObj.LabelTxt("Label17",StringFormat(str,ListPointer,ListArray[ListPointer]));
  GObj.LabelTxt("Label18",StringFormat(str,ListPointer+1,ListArray[ListPointer+1]));
  GObj.LabelTxt("Label19",StringFormat(str,ListPointer+2,ListArray[ListPointer+2]));
  GObj.LabelTxt("Label20",StringFormat(str,ListPointer+3,ListArray[ListPointer+3]));
  GObj.LabelTxt("Label21",StringFormat(str,ListPointer+4,ListArray[ListPointer+4]));
  }

Las líneas muestran los valores de los niveles de la matriz ListArray[] formateada usando la función StringFormat(). Según el modo de visualización actual, esta matriz se rellena con información dentro de la función norm_and_draw(). Véase el archivo StringFormatSpecAnalyzer.mq5. La información de la matriz ListArray[] se muestra empezando por el índice de la matriz igual al valor almacenado en la variable ListPointer. Puede cambiar el valor de la variable ListPointer y, de esta forma, el índice de inicio de las líneas a mostrar pulsando los botones situados a la derecha del campo de salida o especificando el índice necesario en el campo de entrada. Los eventos relacionados con la pulsación de estos botones y con las modificaciones de acabado en el campo de entrada se controlan en la función OnChartEvent() del indicador. Véase el archivo SpecAnalyzer.mq5.

La apariencia del indicador SpecAnalyzer se muestra en la figura de abajo.

 Fig. 3. Apariencia del indicador SpecAnalyzer.

Fig. 3. Apariencia del indicador SpecAnalyzer.  

Conclusión

Como ya se ha mencionado, el indicador es solo un prototipo de un analizador de espectro completo. En el artículo se usa como ejemplo de la utilización de objetos gráficos. Para crear un indicador más complejo probablemente necesitará mejorar su apariencia, implementar una interfaz más funcional y mejorar el algoritmo de la estimación del espectro.

Puede mejorar significativamente la interfaz del indicador usando el objeto gráfico "Bitmap" para su decoración, creando una imagen para su panel de control frontal en un editor gráfico y usarlo como sustrato en el que se mostrarán los elementos del control. Dicho enfoque puede usarse para crear indicadores con aspectos intercambiables.

Bibliografía

  1. Yukio Sato. Introduction to Signal Management.
  2. L. Rabiner, B. Gold. Theory and Application of Digital Signal Processing.
  3. S.L. Marple, Jr. Digital Spectral Analysis with Applications.

Archivos

  1. SpecAnalyzer.mq5 – el indicador que se describe en el artículo.
  2. SpecAnalyzer.mqh – el archivo include para SpecAnalyzer.mq5.
  3. SAInpData.mq5 – el indicador usado para la organización de un acceso a los datos externos.