Building a Spectrum Analyzer

Victor | 1 March, 2011


Introduction

This article is intended to get its readers acquainted with a possible variant of using graphical objects of the MQL5 language. It analyses an indicator, which implements a contol panel of a simple spectrum analyzer using the graphical objects.

Three files are attached to the article:

  1. SpecAnalyzer.mq5 – the indicator described in this article.
  2. SpecAnalyzer.mqh – the include file for SpecAnalyzer.mq5.
  3. SAInpData.mq5 – the indicator used for organization of an access to external data.

To normally load the indicator SpecAnalyzer, you should place all three files to the folder \MQL5\Indicators of the client terminal. Then you should compile the files SpecAnalyzer.mq5 and SAInpData.mq5. The indicator is intended to be loaded in the main chart window. Once the indicator is loaded, it changes the displaying parameters of this window, and when it is removed all the graphic objects are deleted from the window. That is why you should load the indicator in a special separate window created for it, to avoid accidental changing of the displaying mode of existing windows of the terminal.

Considering the fact that the article doesn't contain the full source code of the indicator, it's recommended to have the code in the attached files open while reading the article.

The indicator described in the article doesn't pretend to be a ready-made application. It's just an example of using graphical objects of the language. The ones, who are interested, can upgrade and optimize the code shown in the article on their own.

Coordinates

Two ways can be used in MQL5 to specify coordinates when drawing graphical objects. For some objects the coordinates are specified as a number of pixels from a specified point of the window; and for others the coordinates are specified as the price and time values of the chart displayed in the window.

For example, to place such object as “Label” or “Button” on a chart, you should specify their coordinates as the distance in pixels from one of the corners of the chart window. With this way of addressing objects keep their position regardless of current properties of the window and the scale of chart displayed in it. Even if the window size changes, such objects keep their position and binding to each other. If you move the chart in the window using the left mouse button, those objects keep their position relatively to the chosen anchor point of the window.

The other group of objects implies binding to a chart in the window instead of the window coordinates. Those objects are “Trend Line”, “Rectangle”, etc. When creating and placing such objects, the coordinates are specified as a time and price value of a chart displayed in the window. With this mode of specifying coordinates, the objects change their position relatively to the chart window and to each other when the chart scale is changed or when it is scrolled. As a new bar appears on the chart, the objects also change their position, because the time axis moves to the left on the timeframe size.

In the indicator SpecAnalyzer, both types of the graphical objects are used simultaneously for creating the control panel of the spectrum analyzer. For objects bound to the chart not to move relatively to the window, we set fixed mode of displaying of chart along the vertical axis and the mode corresponding with the minimum possible scale of displaying along the horizontal scale of the chart. In addition to it, the minimum of the vertical scale is set to 0.0; and for the horizontal axis we set the mode without shift of the zero bar from the right edge and without automatic scrolling to the right edge of the chart. Therefore, the coordinate point that matches the zero bar and 0.0 value of the chart appears to be in the lower right corner, and with the fixed scale it can be used an anchor point for the objects like “Trend Line” and “Rectangle”. At that, if we set the same lower right corner as the anchor point of the objects like “Label” or “Button”, then both types of objects will be definitely bound to each other.

All the necessary properties of the chart are set in the function SetOwnProperty() in the file 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);
  }

In addition to setting the necessary properties of the chart for providing the binding of graphical objects, this function sets the properties that allow assigning a color and restricting the displaying of some elements of the chart.

Since the indicator SpecAnalyzer changes the chart properties while working, we should provide saving previous settings of the chart at loading of the indicator and restoring of settings at unloading it. The standard library of MQL5 includes the special virtual functions for that purpose - Save() and Load() of the class CChart. These functions are intended for saving the properties of an object of the CChart class in a file and restoring those properties from the created file. To change the set of saved properties and avoid using file operations when saving properties of a chart, the virtual functions Save() and Load() of the CChart class are overridden at creating the new class GRaphChart (see the file 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();
  };

The base class of GRaphChart is CChart of the standard library of MQL5. The class GRaphChart contains the description of the ChartPropertyes structure and creation of the ChProp object intended for storing the chart properties in the memory instead of a file as is implemented in the base class. The Save() function fills the structure ChProp with data according to the current properties of the chart, and the Load() function restores previously save properties from it.

The function Save() is called in the constructor of the GRaphChart class, and the function Load() is called in its destructor. That's why the saving and restoring of the previous state of the chart is performed automatically when creating and deleting the object of the GRaphChart class. The SetOwnProperty() mentioned above is also called in the class constructor.

//---------------------------------------------------- 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;
  }

Let's demonstrate the use of the class GRaphChart with a simple example. To do it, let's create new custom indicator in MetaEditor and name it Test. Include the header file SpecAnalyzer.mqh in the code of the indicator and create an object of the GRaphChart class by adding two lines.

//+------------------------------------------------------------------+
//|                                                         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);
  }
//+------------------------------------------------------------------+

For successful compilation of the above code, the SpecAnalyzer.mqh file must be placed in the folder \MQL5\Indicators of the client terminal.

If you create a chart in the client terminal and try to load our test example in it, then the chart properties will be changed and you'll see only an empty window prepared to displaying of graphical objects in it. As you remove our test indicator from the chart, its initial appearance will be restored with arriving of a new tick.

Let's return to the indicator SpecAnalyzer. In the beginning of the indicator (see the file SpecAnalyzer.mq5), the creation of the object MainChart of the class GRaphChart is performed, what leads to loading of the indicator and saving the chart properties.

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

When unloading the indicator, the object MainChart is automatically terminated, at that the initial properties of the chart are restored in the class destructor. 

Control Panel

The appearance of the control panel in the indicator SpecAnalyzer is determined by graphical objects placed in the window. The AllGrObject class unites all the functions necessary for creating and interacting with them; see the file 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);
    
  };

Functions of the class, whose names start with Add, are intended for creating the graphical objects. For example, AddButton() creates the object “Button”.

In the application, the coordinates for all the graphical objects are set as the distance in pixels from the lower right corner of the chart. For the objects “Trend Line”, “Arrowed Line” and “Rectangle” we should transform such coordinates into the time and price values. Such transformation is performed in the MoveGrObject() function before assigning coordinates to an object. One horizontal pixel corresponds to one bar, and one vertical pixel corresponds to one point.

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);
    }
  }

All the graphical objects are created only once in the indicator, it is done when calling gr_object_create() from the function OnInit() of the indicator; see the file SpecAnalyzer.mq5. For all the objects, except “Trend Line”, “Arrowed Line” and “Rectangle”, coordinates are set right away. For objects like “Trend Line”, “Arrowed Line” and “Rectangle” coordinates are set by calling the gr_object_coordinate() function that uses the MoveGrObject() function mentioned above, which transforms the mode of addressing. 

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);
  }

The call of the function gr_object_coordinate() is included in the OnCalculate() function of the indicator. It provides a correct recalculation of coordinates when a new bar is formed on the chart, since the function is called at every coming of a new tick.

Buttons on the panel of the indicator are divided into three groups. The first group consists of four buttons located on the left; they allow selecting a mode of displaying the result of estimating the spectrum of input sequence by the indicator. Four modes of displaying are supported (according to the number of buttons):

  1. Amplitude/Line - displaying the module of Fourier transform at a linear scale along the Y axis.
  2. Amplitude/dB - displaying the module of Fourier transform at a logarithmic scale along the Y axis.
  3. Power/Line - displaying the square of module of Fourier transform at a linear scale along the Y axis.
  4. Power/dB - displaying the square of module of Fourier transform at a logarithmic scale along the Y axis.

To process clicking on the buttons of this group, the following code is included in the OnChartEvent() function of the indicator: 

  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;
      }

When a click on one of the buttons is detected, the state of other buttons is changed to unpressed, what prevents simultaneous pressing of several buttons in one group. At the same time, the corresponding values are set for the flags YPowerFlag and YdBFlag that determine the current mode of displaying.

The second group that consists of four buttons provides a possibility to select a source of input data. That can be external data obtained through calling the SAInpData.mq5 indicator or three test sequences generated by the application itself. The last group of buttons includes two buttons used for scrolling the list in the field of inputting text information. Handling of clicking all those buttons is also performed in the OnChartEvent() function of the indicator just the same as the buttons of the first group; see the file SpecAnalyzer.mq5.

Let's show an example of using the class AllGrObject using the previously created test indicator Test.mq5. To do it, add several lines to its source code and include the functions gr_object_create() and gr_object_coordinate() mentioned earlier from the file 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);
  }
//+------------------------------------------------------------------+

To provide access to the functions of the AllGrObject class create the GObj object of this class. In the OnInit() function of the indicator include the call of the gr_object_create() function, which creates all the necessary graphical objects that determine the appearance and functionality of the indicator control panel.

In the function OnCalculate add calls of the functions MainChart.SetOwnProperty() and gr_object_coordinate(); thus, the chart properties and the coordinates of objects drawn on it will be restored with coming of every new tick. Such restoring is required when a new bar is formed at the initial chart or the chart is moved using the left mouse button or when a user changes the chart properties. After we compile and load this test example, we'll see the interfaces of the control panel; see fig. 1.

Fig. 1. Interface of the control panel. 

Fig. 1. Interface of the control panel.

 In order to visually demonstrate you the placement of the control panel relatively to the chart, enable displaying of the chart scale in the example above; see fig. 2.

 Fig. 2. The chart scale.

Fig. 2. The chart scale. 

The Spectrum Analyzer

The analysis of a spectrum in the indicator is performed by 1024 graduation of the input sequence. The spectrum analysis is performed using the fast Fourier transformation algorithm. The function that implements the FFT algorithm is taken from the publications of the www.mql4.com website. For the calculations we use the FFT function of input real time sequence; its code is placed in the file SpecAnalyzer.mqh. All the actions required for the spectrum analysis are implemented in the fft_calc() function. 

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
  }

When the fft_calc() function is called, the InpData[] array must contain an input sequence to be analyzed. After the realfastfouriertransform() is executed, the result of FFT will be placed to that array. Further, the square of modulo is calculated for each harmonic from the real and imaginary part of the estimates of the spectrum; the result is written to the array Spectrum[]. The element index in the Spectrum[] array corresponds with the harmonic number. Since the calculated value of the constant component is not used in the indicator, the zero value is always assigned to the Spectrum[0] element of the array.

Depending on the value of the variable InputSource, the array InpData[] can be filled either with test sequences or with data obtained from an external indicator. Input data is formed in the get_input_data() function.

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;
    }
  }

If the value of InputSource is equal to zero, 1024 values will be copied to the input array InpData[] from the zero buffer of the SAInpData.mq5 indicator. Data for the analysis can be formed either in the indicator itself, or through calling other indicators from it. To provide access to the indicator SAInpData.mq5, the following line is added to the OnInit() function, it determines the value of the ExtHandle variable.

int OnInit() 
 {
 button. button. button.

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

 return(0);
 }

The indicator SAInpData.mq5 must be placed in the directory \MQL5\Indicators of the client terminal. The indicator SAInpData.mq5 attached to this article as an example passes a random sequence to the analyzer. To make SAInpData.mq5 pass another sequence to the analyzer, change its source code.

As a test sequences for the functions get_input_data(), the impulse characteristics of the SMA(3), SMA(32) and LWMA(12) moving averages are generated. Taking into consideration that the Fourier transformation of an impulse characteristic of a filter corresponds to the amplitude-frequency characteristics of that filter, we can observe the amplitude-frequency characteristics of moving averages if we select them as the test sequences.

For normalizing the result of spectrum estimation that is stored in the Spectrum[] array and preparing it to displaying in the specified mode, the norm_and_draw() function is used; see the file SpecAnalyzer.mq5. Depending on the chosen mode of displaying, this function replaces the text marking of the Y axis.

The result of the spectrum estimation of the input sequence is displayed not only in the graphical form, but in the text form too. For representation of results in the text form, five graphical objects of the “Label” type are created; they correspond to five displayed text lines. The function list_levels() performs filling of these lines with information. 

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]));
  }

The lines display the values of levels from the array ListArray[] formatted using the StringFormat() function. According to the current mode of displaying, this array is filled with information inside the norm_and_draw() function; see the file SpecAnalyzer.mq5. The information of the array ListArray[] is displayed starting from the array index equal to the value stored in the ListPointer variable. You can change the value of the ListPointer variable, and thus, the starting index of lines to be displayed, by pressing the buttons located to the right of the output field or by specifying the necessary index in the entry field. The events related to pressing those buttons and finishing modifications on the entry field are handled in the OnChartEvent() function of the indicator; see the file SpecAnalyzer.mq5.

The appearance of the indicator SpecAnalyzer is shown in the figure below.

 Fig. 3. The appearance of the indicator SpecAnalyzer.

Fig. 3. The appearance of the indicator SpecAnalyzer.  

Conclusion

As already mentioned, the indicator SpecAnalyzer.mq5 is only a prototype of a complete spectrum analyzer; in the article it is used as an example of using the graphical objects. To create a thorough indicator, you will probably need to improve its appearance, implement a more functional interface and improve the algorithm of spectrum estimation.

You can significantly improve the indicator interface by using the graphical object “Bitmap” for its decoration, creating an image for its front control panel in a graphical editor and using it as an underlayer, on which the elements of controlling will be displayed. Such approach can be used for creating indicators with changeable skins.

Literature

  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.

Files

  1. SpecAnalyzer.mq5 – the indicator described in this article.
  2. SpecAnalyzer.mqh – the include file for SpecAnalyzer.mq5.
  3. SAInpData.mq5 – the indicator used for organization of an access to external data.