Construindo um Analisador de Espectro

Victor | 7 fevereiro, 2014

Introdução

Este artigo é destinado a familiarizar seus leitores com uma possível variável de uso de objetos gráficos da linguagem MQL5. Ele analisa um indicador que implementa um painel de controle de um simples analisador de espectro usando objetos gráficos.

Três arquivos estão anexos ao artigo:

  1. SpecAnalyzer.mq5 – o indicador descrito neste arquivo.
  2. SpecAnalyzer.mqh – o arquivo incluso para SpecAnalyzer.mq5.
  3. SAInpData.mq5 – o indicador usado para organização de um acesso a um dado externo.

Para carregar normalmente o indicador SpecAnalyzer, você deve colocar todos os três arquivos na pasta \MQL5\Indicators do terminal do cliente. Depois, você deve compilar os arquivos SpecAnalyzer.mq5 e SAInpData.mq5. O indicador é destinado a ser carregado na janela do gráfico principal. Uma vez o indicador seja carregado, ele muda os parâmetros de exibição desta janela e, quando é removido, todos os objetos gráficos são excluídos da janela. Este é o motivo pelo qual você deve carregar o indicador em uma janela separada especial criada para isso, para evitar a mudança acidental do modo de exibição das janelas existentes do terminal.

Considerando o fato de que o artigo não contém o código-fonte completo do indicador, é recomendado ter o código nos arquivos anexos aberto durante a leitura do artigo.

O indicador descrito no artigo não pretende ser uma aplicação pronta. É apenas um exemplo do uso de objetos gráficos da linguagem. Para os que estejam interessados, é possível eles mesmos fazerem o upgrade e otimizar o código mostrado no artigo.

Coordenadas

Duas formas podem ser usadas no MQL5 para especificar coordenadas ao desenhar objetos gráficos. Para alguns objetos as coordenadas são especificadas como um número de pixels de um ponto específico da janela; e para outros as coordenadas são especificadas como valores de tempo e preço do gráfico exibido na janela.

Por exemplo, para posicionar um objeto como “Etiqueta” ou “Botão” em um gráfico, você deve especificar suas coordenadas como a distância em pixels de um dos cantos da janela do gráfico. Com este jeito de abordar os objetos, mantém-se sua posição independente das propriedades da janela atuais e a escala do gráfico exibida nele. Mesmo se o tamanho da janela mudar, tais objetos mantém suas posições e são ligados um ao outro. Se você mover o gráfico na janela usando o botão esquerdo do mouse, estes objetos mantém sua posição relativamente ao ponto de fixação escolhido da janela.

O outro grupo de objetos implica a ligação ao gráfico na janela em vez das coordenadas da janela. Estes objetos são “Linha de tendência”, “Retângulo”, etc. Ao criar e posicionar estes objetos, as coordenadas são especificadas como valor de preço e tempo de um gráfico exibido na janela. Com este modo de especificação de coordenadas, os objetos mudam suas posições relativamente à janela do gráfico e um ao outro quando a escala do gráfico é alterada ou quando é rolado. Com o aparecimento de uma nova barra no gráfico, os objetos também mudam sua posição, porque o eixo de tempo move-se para esquerda no tamanho do período de tempo.

No indicador SpecAnalyzer, ambos os tipos de objetos gráficos são usados simultaneamente para criar o painel de controle do analisador de espectro. Para objetos ligados ao gráfico que não se movem relativamente à janela, definimos o modo fixo de exibição do gráfico junto com o eixo vertical e o modo correspondente com a escala mínima possível de exibição junto com a escala horizontal do gráfico. Além disso, o mínimo da escala vertical é definido para 0,0; e para o eixo horizontal definimos o modo sem a troca da barra zero da extremidade direita e sem o rolamento automático para a extremidade direita do gráfico. Assim, o ponto da coordenada que corresponde a barra zero e valor 0.0 do gráfico parece estar no canto direito inferior, e com a escala fixa, pode ser usado um ponto de fixação para objetos como “Linha de tendência” e “Retângulo”. Nisso, se definirmos o mesmo canto direito inferior como ponto de fixação de objetos como “Etiqueta” ou “Botão”, então ambos os tipos de objetos serão definitivamente ligados um ao outro.

Todas as propriedades do gráfico necessárias são definidas na função SetOwnProperty() no arquivo 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);
  }

Além de definir as propriedades necessárias do gráfico fornecendo a ligação dos objetos gráficos, esta função define as propriedades que permitem atribuir uma cor e restringir a exibição de alguns elementos do gráfico.

Devido ao indicador SpecAnalyzer mudar as propriedades do gráfico enquanto trabalha, devemos providenciar o salvamento das configurações anteriores do gráfico no carregamento do indicador e a restauração das configurações ao descarregá-lo. A biblioteca padrão do MQL5 inclui as funções virtuais especiais para este propósito - Save() e Load() da classe CChart. Estas funções são destinadas a salvar as propriedades de um objeto da classe CChart em um arquivo e restaurar estas propriedades a partir do arquivo criado. Para alterar o conjunto de propriedades salvas e evitar usar as operações do arquivo ao salvar as propriedades de um gráfico, as funções virtuais Save() e Load() da classe CChart são sobrepostas na criação da nova classe GRaphChart (veja o arquivo 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();
  };

A classe base de GRaphChart é CChart da biblioteca padrão do MQL5. A classe GRaphChart contém a descrição da estrutura ChartProperties e a criação do objeto ChProp destinado ao armazenamento das propriedades do gráfico na memória em vez de um arquivo como é implementado na classe base. A função Save() preenche a estrutura ChProp com dados de acordo com as propriedades atuais do gráfico, e a função Load() restaura as propriedades salvas anteriormente a ela.

A função Save() é chamada no construtor da classe CRaphChart e a função Load() é chamada em seu construtor. Este é o motivo pelo qual o salvamento e a restauração do estado anterior do gráfico são realizados automaticamente ao criar e excluir o objeto da classe GRaphChart. A função SetOwnProperty() mencionada acima também é chamada no construtor de classe.

//---------------------------------------------------- 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 demonstrar o uso da classe GRaphChart com um exemplo simples. Para isso, vamos criar um novo indicador personalizado no MetaEditor e nomeá-lo como Teste. Inclua o arquivo do cabeçalho SpecAnalyzer.mqh no código do indicador é crie um objeto da classe GRaphChart adicionando duas linhas.

//+------------------------------------------------------------------+
//|                                                         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 a compilação bem sucedida do código acima, o arquivo SpecAnalyzer.mqh deve ser colocado na pasta \MQL5\Indicators do terminal do cliente.

Se você criar um gráfico no terminal do cliente e tentar carregar nosso exemplo de teste nele, então as propriedades do gráfico serão alteradas e você verá apenas uma janela vazia preparada para exibição de objetos gráficos nela. Conforme você remover nosso indicador de teste do gráfico, sua aparência inicial será restaurada com a chegada de um novo tick.

Vamos retornar ao SpecAnalyzer. No começo do indicador (veja o arquivo SpecAnalyzer.mq5), a criação do objeto MainChart da classe GRaphChart é realizada, o que leva ao carregamento do indicador e o salvamento das propriedades do gráfico.

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

Ao descarregar o indicador, o objeto MainChart é automaticamente finalizado, nele as propriedades do gráfico são restauradas no destrutor de classe.

Painel de controle

A aparência do painel de controle no indicador SpecAnalyzer é determinada pelos objetos gráficos colocados na janela. A classe AllGrObject reúne todas as funções necessárias para a criação e interação com elas; veja o arquivo 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);
    
  };

As funções da classe, cujos nomes começam com Add, são destinadas a criação de objetos gráficos. Por exemplo, AddButton() cria o objeto “Button”.

Na aplicação, as coordenadas para todos os objetos gráficos são definidas como a distância em pixels a partir do canto direito inferior do gráfico. Para os objetos “Linha de tendência”, “Linha em seta” e “Retângulo” devemos transformar tais coordenadas em valores de preço e tempo. Tal transformação é realizada na função MoveGrObject() antes de atribuir as coordenadas a um objeto. Um pixel horizontal corresponde a uma barra, e um pixel vertical corresponde a um ponto.

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 os objetos gráficos são criados apenas uma vez no indicador, isso é feito ao chamar gr_object_create() da função OnInit() do indicador, veja o arquivo SpecAnalyzer.mq5. Para todos os objetos, exceto “Linha de tendência”, “Linha em seta” e “Retângulo”, as coordenadas são definidas imediatamente. Para objetos como “Linha de tendência”, “Linha em seta” e “Retângulo” as coordenadas são definidas chamando a função gr_object_coordinate() que utiliza a função MoveGrObject() mencionada acima, o que transforma o modo de abordagem.

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

A chamada da função gr_object_coordinate() é inclusa na função OnCalculate() do indicador. Ela fornece um recálculo correto das coordenadas quando uma nova barra é formada no gráfico, devido a função ser chamada a cada chegada de um novo tick.

Os botões no painel do indicador são divididos em três grupos. O primeiro grupo consiste em quatro botões localizados à esquerda, eles permitem selecionar um modo de exibição do resultado da estimativa do espectro de sequência de entrada pelo indicador. São suportados quatro modos de exibição (de acordo com o número de botões):

  1. Amplitude/Linha - exibindo o módulo de transformação Fourier na escala linear ao longo do eixo Y.
  2. Amplitude/dB - exibindo o módulo de transformação Fourier na escala logarítmica ao longo do eixo Y.
  3. Potência/Linha - exibindo o quadrado do módulo de transformação Fourier na escala linear ao longo do eixo Y.
  4. Potência/dB - exibindo o quadrado do módulo de transformação Fourier na escala logarítmica ao longo do eixo Y.

Para processar o clique nos botões deste grupo, o seguinte código é incluso na função OnChartEvet() do 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;
      }

Quando detectado o click em um dos botões, o estado dos outros botões é alterado para não pressionado, o que evita o pressionamento simultâneo de vários botões em um grupo. Ao mesmo tempo, os valores correspondentes são definidos para as marcações YPowerFlag e YdBFlag e que determinam o modo atual de exibição.

O segundo grupo que consiste em quatro botões fornece a possibilidade de selecionar uma fonte de dados de entrada. Isso pode ser os dados externos obtidos através da chamada do indicador SAInpData.mq5 ou três sequências de teste geradas pela aplicação em si. O último grupo de botões inclui dois botões usados para rolar a lista no campo de entrada de informações de texto. A manipulação do click de todos estes botões é também realizada na função OnChartEvent() do indicador, exatamente igual aos botões do primeiro grupo; veja o arquivo SpecAnalyzer.mq5.

Vamos mostrar um exemplo de uso da classe AllGrObject usando o indicador de teste criado anteriormente Test.mq5. Para isso, adicione diversas linhas em seu código-fonte e inclua as funções gr_object_create() e gr_object_coordinate() mencionadas anteriormente do arquivo 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 fornecer acesso a essas funções da classe AllGrObject, crie o objeto GObj desta classe. Na função OnInit() do indicador, inclua a chamada da função gr_object_create(), que cria todos os objetos gráficos que determinam a aparências e funcionalidade do painel de controle do indicador.

Na função OnCalculate, adicione chamadas das funções MainChart.SetOwnProperty() e gr_object_coordinate(); assim, as propriedades do gráfico e as coordenadas dos objetos desenhadas nele serão restauradas com a chegada de cada novo tick. Tal restauração é necessária quando uma nova barra é formada no gráfico inicial ou o gráfico é movido usando o botão esquerdo do mouse ou quando um usuário muda as propriedades do gráfico. Após compilarmos e carregarmos este exemplo de teste, veremos as interfaces do painel de controle; veja fig. 1.

Fig. 1. Interface do painel de controle.

Fig. 1. Interface do painel de controle

Para demonstrar visualmente a você o posicionamento do painel de controle relativamente ao gráfico, ative a exibição da escala de gráfico no exemplo acima; veja fig. 2.

Fig. 2. A escala do gráfico.

Fig. 2. A escala do gráfico

O analisador de espectro

A análise de um espectro no indicador é realizada pela graduação 1024 da sequência de entrada. A análise de espectro é realizada usando o algorítimo transformação de Fourir rápida. A função que implementa o algorítimo FFT é obtida de publicações do site www.mql4.com. Para os cálculos, usamos a função FFT de sequência de tempo real de entrada; seu código está posicionado no arquivo SpecAnalyzer.mqh. Todas as ações necessárias para a análise de espectro são implementadas na função 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
  }

Quando a função fft_calc() é chamada, o array InpData[] deve conter uma sequência de entrada a ser analisada. Após a função realfastfouriertransform() ser executada, o resultado de FFT é colocado no array. Adiante, o quadrado do módulo é calculado para cada harmônico a partir da parte real e imaginária das estimativas de espectro; o resultado é escrito no array Spectrum[]. O índice de elemento no array Spectrum[] corresponde ao número harmônico. Devido ao valor calculado do componente constante não ser usado no indicador, o valor zero é sempre atribuído para o elemento Spectrum[0] do array.

Dependendo do valor da variável InputSource, o array InpData[] pode ser preenchido tanto com sequências de teste como com dados obtidos de um indicador externo. Os dados de entrada são formados na função 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;
    }
  }

Se o valor de InputSource for igual a zero, 1024 valores serão copiados para o array de entrada InpData[] a partir do buffer zero do indicador SAInpData.mq5; os dados para análise podem ser realizados tanto no indicador em si, ou através da chamada dos indicadores a partir dele. Para fornecer acesso ao indicador SAInpData.mq5, a seguinte linha é adicionado à função OnInit(), ela determina o valor da variável ExtHandle.

int OnInit() 
 {
 button. button. button.

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

 return(0);
 }

O indicador SAInpData.mq5 deve ser posicionado no diretório \MQL5\Indicators do terminal do cliente. O indicador SAInpData.mq5, anexo a este artigo como exemplo, passa uma sequência aleatória para o analisador. Para fazer o SAInpData.mq5 passar outra sequência para o analisador, mude seu código-fonte.

Nas sequências de teste para as funções get_input_data(), são geradas as características de impulso das médias móveis SMA(3), SMA(32) e LWMA(12). Levando em consideração que a transformação Fourier de uma característica de impulso de um filtro corresponde às características de frequência de amplitude deste filtro, podemos observar as características de frequência de amplitude das médias móveis se as selecionarmos como sequências de teste.

Para normalizar o resultado da estimativa de espectro que é armazenada no array Spectrum[] e o preparando para exibição no modo especificado, é usada a função norm_and_draw(); veja o arquivo SpecAnalyzer.mq5. Dependendo do modo escolhido de exibição, esta função substitui a marcação de texto do eixo Y.

O resultado da estimativa de espectro da sequência de entrada é exibido não apenas na forma gráfica, mas na forma de texto também. Para a representação dos resultados na forma de texto, cinco objetos gráficos do tipo “Etiqueta” são criados; eles correspondem a cinco linhas de texto exibidas. A função list_levels() realiza o preenchimento destas linhas com informações.

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

As linhas exibem os valores dos níveis do array ListArray[] formatado usando a função StringFormat(). De acordo com o modo atual de exibição, este array é preenchido com informações dentro da função norm_and_draw(); veja o arquivo SpecAnalyzer.mq5. As informações do array ListArray[] são exibidas começando a partir do índice de array igual ao valor armazenado na variável ListPointer. Você pode mudar o calor da variável ListPointer e, assim, o índice inicial de linhas a ser exibido, pressionando os botões localizados à direita do campo de saída ou especificando o índice necessário no campo de entrada. Os eventos relacionados com o pressionamento destes botões e finalização das modificações no campo de entrada são manipulados na função OnChartEvent() do indicador; veja o arquivo SpecAnalyzer.mq5.

A aparência do indicador SpecAnalyzer é mostrada na figura abaixo:

Fig. 3. A aparência do indicador SpecAnalyzer.

Fig. 3. A aparência do indicador SpecAnalyzer

Conclusão

Como já mencionado, o indicador SpecAnalyzer.mq5 é apenas um protótipo de um analisador de espectro completo; no artigo é usado como exemplo de uso de objetos gráficos. Para criar um indicador, você provavelmente precisará melhorar sua aparência, implementar uma interface mais funcional e melhorar o algorítimo de estimativa de espectro.

Você pode melhorar significativamente a interface do indicador usando o objeto gráfico "Bitmap" para decoração, criando uma imagem para seu painel de controle em um editor gráfico e usando como camada inferior, onde os elementos de controle serão exibidos. Tal abordagem pode ser usada para criar indicadores com skins que possam ser trocadas.

Literatura

  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.

Arquivos

  1. SpecAnalyzer.mq5 – o indicador descrito neste arquivo.
  2. SpecAnalyzer.mqh – o arquivo incluso para SpecAnalyzer.mq5.
  3. SAInpData.mq5 – o indicador usado para organização de um acesso a um dado externo.