Download MetaTrader 5

Criação de indicadores personalizados usando a classe CCanvas

20 junho 2017, 16:17
Alexander Fedosov
0
730

Conteúdo

Introdução

Uma parte integrante da negociação no terminal MetaTrader 5 tem a ver com os indicadores personalizados. Eles são empregados tanto ao utilizar sistemas de negociação automatizados quanto ao operar manualmente. Até agora quando se cria um indicador é possível definir o estilo de desenho e usar 18 tipos de construções gráficas. Mas o terminal não se limita a estes recursos gráficos. Para criar indicadores personalizados com intermináveis recursos de visualização, foi criada a biblioteca de Gráficos personalizados CCanvas. O propósito deste artigo é familiarizar usuários com as possibilidades desta biblioteca, fornecer exemplos de como usá-la, bem como desenvolver uma biblioteca separada de indicadores personalizados de vários tipos.


Construção de classe básica de gráfico personalizado

Como classe base é necessário escrever um conjunto de métodos que formam a base de qualquer objeto de gráfico personalizado e que, além disso, incluem um conjunto geral de propriedades. Para fazer isto, criamos no <diretório de dados>\MQL5\Include a pasta CustomGUI, e nela, o arquivo CanvasBase.mqh. Este arquivo conterá a classe básica CCanvasBase para todos os futuros tipos de gráfico personalizado.

//+------------------------------------------------------------------+
//|                                                  CCanvasBase.mqh |
//|                                Copyright 2017, Alexander Fedosov |
//|                           https://www.mql5.com/pt/users/alex2356 |
//+------------------------------------------------------------------+
#include <Canvas\Canvas.mqh>
//+------------------------------------------------------------------+
//| Classe base para criação de gráfico personalizado                |
//+------------------------------------------------------------------+
class CCanvasBase
  {
private:
   //--- Nome da área de desenho
   string            m_canvas_name;
   //--- Coordenadas da área de desenho
   int               m_x;
   int               m_y;
   //--- Tamanho da área de desenho
   int               m_x_size;
   int               m_y_size;
protected:
   CCanvas           m_canvas;
   //--- Cria um recurso gráfico para objeto
   bool              CreateCanvas(void);
   //--- Remove o recurso gráfico
   bool              DeleteCanvas(void);
public:
                     CCanvasBase(void);
                    ~CCanvasBase(void);
   //--- Define e retorna as coordenadas
   void              X(const int x)                         { m_x=x;                      }
   void              Y(const int y)                         { m_y=y;                      }
   int               X(void)                                { return(m_x);                }
   int               Y(void)                                { return(m_y);                }
   //--- Define e retorna os tamanhos
   void              XSize(const int x_size)                { m_x_size=x_size;            }
   void              YSize(const int y_size)                { m_y_size=y_size;            }
   int               XSize(void)                            { return(m_x_size);           }
   int               YSize(void)                            { return(m_y_size);           }
   //--- Define o nome do indicador durante a criação
   void              Name(const string canvas_name) { m_canvas_name=canvas_name;  }
  };
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CCanvasBase::CCanvasBase(void) : m_x(0),
                                 m_y(0),
                                 m_x_size(200),
                                 m_y_size(200)
  {
  }
//+------------------------------------------------------------------+
//| Destructor                                                       |
//+------------------------------------------------------------------+
CCanvasBase::~CCanvasBase(void)
  {
  }
//+------------------------------------------------------------------+
//| Cria um recurso gráfico para o objeto                            |
//+------------------------------------------------------------------+
bool CCanvasBase::CreateCanvas(void)
  {
   ObjectDelete(0,m_canvas_name);
   if(!m_canvas.CreateBitmapLabel(m_canvas_name,m_x,m_y,m_x_size,m_y_size,COLOR_FORMAT_ARGB_NORMALIZE))
      return(false);
   ObjectSetInteger(0,m_canvas_name,OBJPROP_CORNER,CORNER_LEFT_UPPER);
   ObjectSetInteger(0,m_canvas_name,OBJPROP_ANCHOR,ANCHOR_CENTER);
   ObjectSetInteger(0,m_canvas_name,OBJPROP_BACK,false);
   return(true);
  }
//+------------------------------------------------------------------+
//| Remove um recurso gráfico                                        |
//+------------------------------------------------------------------+
bool CCanvasBase::DeleteCanvas()
  {
   return(ObjectDelete(0,m_canvas_name)?true:false);
  }
//+------------------------------------------------------------------+

Como você pode ver, além das coordenadas iniciais do objeto, do seu nome e do tamanho, entre os métodos estabelecidos há métodos para criação e exclusão do recurso gráfico, isto é, uma espécie de base ou área de desenho em que serão renderizados elementos do gráfico personalizado. Mais tarde, durante a construção inicial de quaisquer objetos gráficos serão usados os objetos CreateCanvas() e DeleteCanvas() na ordem necessária. Portanto, as variáveis utilizadas nestes métodos devem ser inicializadas como é feito no construtor.


Classe de indicador circular simples CCircleSimple

Para compreender os princípios de plotagem de gráficos personalizados, vamos passar do simples ao complexo. Criamos um indicador circular simples com borda, valores numéricos e descrição. A figura 1 mostra a estrutura de elementos de que se compõe.

  • Borda. Uma espécie de contorno bem demarcado.
  • Fundo. O espaço em que estarão os elementos de texto.
  • Valor. Elemento de texto que exibe um valor numérico do indicador.
  • Descrição. Elemento de texto para descrever o indicador (nome, período ou outras informações diferenciadoras).


Figura 1 Estrutura básica de indicador circular simples

Na pasta criada anteriormente CustomGUI, criaremos outra pasta Indicator. Nela serão localizadas todas as classes de indicadores futuros. Criamos o primeiro deles com o nome CircleSimple.mqh. Primeiro, conectamos o arquivo criado anteriormente CanvaseBase.mqh com classe base para todos os tipos de objetos gráficos, e fazemos a classe CCanvaseBase básica para a atual, a fim de todos os métodos desta classe se tornarem disponíveis para a atual CCircleSimple.

//+------------------------------------------------------------------+
//|                                                 CircleSimple.mqh |
//|                                Copyright 2017, Alexander Fedosov |
//|                           https://www.mql5.com/pt/users/alex2356 |
//+------------------------------------------------------------------+
#include "..\CanvasBase.mqh"
//+------------------------------------------------------------------+
//| Indicador circular com valores numéricos e descrição             |
//+------------------------------------------------------------------+
class CCircleSimple : public CCanvasBase

Para, nos arquivos dos Experts e indicadores, ao utilizar objetos gráficos não reescrevê-los manualmente de cada vez, criamos na pasta CustomGUI o arquivo CustomGUI.mqh em que serão recolhidas todas integrações de classes da pasta Indicators. Assim, teremos que conectar apenas esse arquivo, para obter acesso a todas as classes da biblioteca. Agora conectamos o atual:

//+------------------------------------------------------------------+
//|                                                    CustomGUI.mqh |
//|                                Copyright 2017, Alexander Fedosov |
//|                           https://www.mql5.com/pt/users/alex2356 |
//+------------------------------------------------------------------+
#include "Indicators\CircleSimple.mqh"

Ao implementar a classe de indicador circular simples é necessário considerar um conjunto de propriedades e métodos que possam permitir ajustar quanto possível até um aparentemente simples modelo de indicador gráfico. A listagem abaixo mostra um desses conjuntos:

//+------------------------------------------------------------------+
//| Indicador circular com valores numéricos e descrição             |
//+------------------------------------------------------------------+
class CCircleSimple : public CCanvasBase
  {
private:
   //--- Cor de fundo 
   color             m_bg_color;
   //--- Cor de borda
   color             m_border_color;
   //--- Cor de texto de valor
   color             m_font_color;
   //--- Cor de texto de rótulo
   color             m_label_color;
   //--- Transparência
   uchar             m_transparency;
   //--- Espessura de borda
   int               m_border;
   //--- Tamanho de indicador
   int               m_radius;
   //--- Tamanho de fonte de valor
   int               m_font_size;
   //--- Tamanho de fonte de rótulo
   int               m_label_font_size;
   //---
   int               m_digits;
   //--- Rótulo
   string            m_label;
public:
                     CCircleSimple(void);
                    ~CCircleSimple(void);
   //--- Define e retorna a cor de fundo
   color             Color(void)                      { return(m_bg_color);            }
   void              Color(const color clr)           { m_bg_color=clr;                }
   //--- Define e retorna o tamanho
   int               Radius(void)                     { return(m_radius);              }
   void              Radius(const int r)              { m_radius=r;                    }
   //--- Define e retorna o tamanho de fonte de valor
   int               FontSize(void)                   { return(m_font_size);           }
   void              FontSize(const int fontsize)     { m_font_size=fontsize;          }
   //--- Define e retorna o tamanho de fonte de rótulo
   int               LabelSize(void)                  { return(m_label_font_size);     }
   void              LabelSize(const int fontsize)    { m_label_font_size=fontsize;    }
   //--- Define e retorna a cor de fonte de valor
   color             FontColor(void)                  { return(m_font_color);          }
   void              FontColor(const color fontcolor) { m_font_color=fontcolor;        }
   //--- Define e retorna a cor de fonte de rótulo
   color             LabelColor(void)                 { return(m_label_color);         }
   void              LabelColor(const color fontcolor){ m_label_color=fontcolor;       }
   //--- Define a cor e espessura de borda
   void              BorderColor(const color clr)     { m_border_color=clr;            }
   void              BorderSize(const int border)     { m_border=border;               }
   //--- Define e retorna a transparência
   uchar             Alpha(void)                      { return(m_transparency);        }
   void              Alpha(const uchar alpha)         { m_transparency=alpha;          }
   //--- Define e retorna o valor de rótulo
   string            Label(void)                      { return(m_label);               }
   void              Label(const string label)        { m_label=label;                 }
   //--- Cria o indicador
   void              Create(string name,int x,int y);
   //--- Remove o indicador
   void              Delete(string name);
   //--- Define e atualiza o valor de indicador
   void              NewValue(int value);
   void              NewValue(double value);
  };

A finalidade das variáveis e métodos em que são utilizados se faz evidente a partir da descrição. Passamos para os métodos que por si mesmos realizam a plotagem do indicador como apresentado na Figura 1. O primeiro método que consideramos é o CreateCanvas(). Como pode ser visto na listagem ele tem apenas três argumentos. Eu considerei os mais importantes. A adição excessiva de argumentos adicionais complica a implementação do método. Portanto, todas as outras propriedades foram enviadas para métodos separados. Devido a isto, todas as variáveis foram inicializadas no construtor da classe:

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CCircleSimple::CCircleSimple(void) : m_bg_color(clrAliceBlue),
                                     m_border_color(clrRoyalBlue),
                                     m_font_color(clrBlack),
                                     m_label_color(clrBlack),
                                     m_transparency(255),
                                     m_border(5),
                                     m_radius(40),
                                     m_font_size(17),
                                     m_label_font_size(20),
                                     m_digits(2),
                                     m_label("label")
  {
  }

Isto é conveniente porque, quando se cria um indicador desse tipo, basta criar a instância de sua classe e utilizar apenas um método CreateCanvas(). É claro que, antes da criação, você pode especificar apenas as propriedades que você deseja alterar. Ao criar objetos gráficos complexos usando a biblioteca CCanvas, deve ser entendido que os métodos que implementam primitivas são construídos sequencialmente e por camadas. Na verdade, como em qualquer área de desenho ou tela, o artista primeiro pinta o fundo, em seguida, desenha os objetos sobre ela - detalhes, etc. 

//+------------------------------------------------------------------+
//| Cria o indicador                                                 |
//+------------------------------------------------------------------+
void CCircleSimple::Create(string name,int x,int y)
  {
   int r=m_radius;
//--- Correção de posição de indicador relativa ao raio
   x=(x<r)?r:x;
   y=(y<r)?r:y;
   Name(name);
   X(x);
   Y(y);
   XSize(2*r+1);
   YSize(2*r+1);
   if(!CreateCanvas())
      Print("Error. Can not create Canvas.");
   if(m_border>0)
      m_canvas.FillCircle(r,r,r,ColorToARGB(m_border_color,m_transparency));
   m_canvas.FillCircle(r,r,r-m_border,ColorToARGB(m_bg_color,m_transparency));
//---
   m_canvas.FontSizeSet(m_font_size);
   m_canvas.TextOut(r,r,"0",ColorToARGB(m_font_color,m_transparency),TA_CENTER|TA_VCENTER);
//---
   m_canvas.FontSizeSet(m_label_font_size);
   m_canvas.TextOut(r,r+m_label_font_size,m_label,ColorToARGB(m_label_color,m_transparency),TA_CENTER|TA_VCENTER);
   m_canvas.Update();
  }

Não vamos entrar em detalhes sobre a implementação do método. Concentramo-nos apenas nos principais pontos:

  • Em primeiro lugar, corrigimos a posição do componente de gráfico sem relação ao tamanho real do indicador, de modo que uma parte fique fora do gráfico principal.
  • Em seguida, é usada a configuração e trabalho com os métodos de nome, tamanho, coordenadas. É criada a base a partir da classe básica CCanvasBase.
  • Na implementação da borda foi usado o princípio da superposição como descrito acima, nomeadamente, dois círculos criados: o primeiro (preenchido) e um segundo - raio que é inferior ao raio da primeira borda no valor de espessura.
  • Em cima desses objetos, foram criados os elementos de valor numérico e descrição.

Em seguida, consideramos o método NewValue(), ele implementa a exibição de atualização de valor numérico do indicador em tempo real:

//+------------------------------------------------------------------+
//| Define e atualiza o valor do indicador                           |
//+------------------------------------------------------------------+
void CCircleSimple::NewValue(int value)
  {
   int r=m_radius;
   m_canvas.FillCircle(r,r,r-m_border,ColorToARGB(m_bg_color,m_transparency));
//---
   m_canvas.FontSizeSet(m_font_size);
   m_canvas.TextOut(r,r,IntegerToString(value),ColorToARGB(m_font_color,m_transparency),TA_CENTER|TA_VCENTER);
//---
   m_canvas.FontSizeSet(m_label_font_size);
   m_canvas.TextOut(r,r+m_label_font_size,m_label,ColorToARGB(m_label_color,m_transparency),TA_CENTER|TA_VCENTER);
   m_canvas.Update();
  }

Para que o valor numérico do indicador seja atualizado corretamente, você precisa re-redesenhar três objetos (o fundo e os elementos de texto) da mesma maneira como durante a criação. Por tanto, no método NewValue() é redesenhado o fundo e, em seguida, os elementos de texto e descrição.

Assim, é hora de verificar a aplicação do indicador circular no terminal MetaTrader 5. Para fazer isso, criamos um indicador vazio, conetamos a ele o arquivo CustomGUI.mqh e criamos duas instâncias de classe CCircleSimple:

//+------------------------------------------------------------------+
//|                                              CustomIndicator.mq5 |
//|                                Copyright 2017, Alexander Fedosov |
//|                           https://www.mql5.com/pt/users/alex2356 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2017, Alexander Fedosov"
#property link      "https://www.mql5.com/pt/users/alex2356"
#property version   "1.00"
#property indicator_plots 0
#property indicator_chart_window

#include <CustomGUI\CustomGUI.mqh>
//---
CCircleSimple ind1,ind2;

Além disso, na inicialização do indicador, tomamos os indicadores padrão, cujos valores serão usados mais tarde nos indicadores circulares:

//---
int InpInd_Handle,InpInd_Handle1;
double adx[],rsi[];
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {

//---- obtenção do indicador identificador do indicador
   InpInd_Handle=iADX(Symbol(),PERIOD_CURRENT,10);
   InpInd_Handle1=iRSI(Symbol(),PERIOD_CURRENT,10,PRICE_CLOSE);
   if(InpInd_Handle==INVALID_HANDLE)
     {
      Print("Failed to get indicator handle");
      return(INIT_FAILED);
     }
   ArraySetAsSeries(adx,true);
   ArraySetAsSeries(rsi,true);

Definimos algumas propriedades dos indicadores circulares para demonstrar, e criamo-los:

//---
   ind1.Radius(60);
   ind1.Label("ADX");
   ind1.Color(clrWhiteSmoke);
   ind1.LabelColor(clrBlueViolet);
   ind1.Create("adx",100,100);
//---
   ind2.Radius(55);
   ind2.BorderSize(8);
   ind2.FontSize(22);
   ind2.LabelColor(clrCrimson);
   ind2.BorderColor(clrFireBrick);
   ind2.Label("RSI");
   ind2.Create("rsi",250,100);

Na parte de cálculo do indicador, resta apenas substituir os valores numéricos atuais dos indicadores padrão pelos criados:

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[])
  {
//---
   if(CopyBuffer(InpInd_Handle,0,0,2,adx)<=0 || 
      CopyBuffer(InpInd_Handle1,0,0,2,rsi)<=0
      )
      return(0);
   ind1.NewValue(adx[0]);
   ind2.NewValue(rsi[0]);
//--- return value of prev_calculated for next call
   return(rates_total);
  }

Para que o trabalho corra como devido, têm de se especificar os métodos de remoção de recursos gráficos na anulação da inicialização, isto faz com que, ao remover o indicador, os objetos gráficos sejam corretamento eliminados junto com ele.

//+------------------------------------------------------------------+
//| Custom indicator deinitialization function                       |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   ind1.Delete();
   ind2.Delete();
   ChartRedraw();
  }
//+------------------------------------------------------------------+

O resultado da operação é apresentado na Figura 2. Como pode ser observado, os indicadores diferem significativamente uns dos outros em muitos aspectos.

Figura 2. Exemplo de trabalho de indicadores circulares.

Classe de indicador circular com indicação de arco CCircleArc

Nós revisamos a classe que era a forma mais simples de realização de um indicador circular. Complicamos um pouco o problema e criamos uma classe que terá um elemento gráfico adicional, além de um elemento numérico - o arco. A estrutura dos elementos básicos do indicador com indicação de arco está representado na Figura 3.


Figura 3. Estrutura básica de um arco circular com indicação de arco.

Como você pode ver, esse tipo se diferencia pela adição do elemento Indicador de arco. O método perfeito para implementar a indicação de arco é aquele que desenha um setor preenchido de elipse, ele é chamado Pie(). De todas as sobrecargas deste método, decidi usar a seguinte:

void  Pie( 
   int         x,       // coordenada X do centro da elipse 
   int         y,       // coordenada Y do centro da elipse 
   int         rx,      // raio da elipse na coordenada X 
   int         ry,      // raio da elipse na coordenada Y 
   int         fi3,     // ângulo - da linha que parte do centro da elipse - que define o primeiro limite do arco 
   int         fi4,     // ângulo - da linha que parte do centro da elipse - que define o segundo limite do arco  
   const uint  clr,     // cor de linha 
   const uint  fill_clr // cor de preenchimento 
   );

Aqui o valor fi3, que define o primeiro limite do arco, será o início de nossa escala de arco, enquanto o valor fi4 será alterado dinamicamente dependendo do valor numérico transferido para nosso indicador. Criamos na pasta Indicators o arquivo CircleArc.mqh e imediatamente no arquivo CustomGUI.mqh adicionamos a seguinte linha:

#include "Indicators\CircleArc.mqh"

Assim, adicionamos a futura classe à lista geral de inclusões de todos os indicadores. Definimos o conjunto de propriedades e métodos comuns. Na verdade, eles não diferem dos métodos da classe anterior, mas vale a pena determos na enumeração ENUM_ORIENTATION. Ele tem dois valores — VERTICAL e HORIZONTAL — e determina a posição do primeiro arco da escala de arco. Se o valor é vertical, o arco começa com 90 graus, enquanto se é horizontal, a partir de zero. Indicação é contada no sentido anti-horário.

//+------------------------------------------------------------------+
//|                                                    CircleArc.mqh |
//|                                Copyright 2017, Alexander Fedosov |
//|                           https://www.mql5.com/pt/users/alex2356 |
//+------------------------------------------------------------------+
#include "..\CanvasBase.mqh"
//+------------------------------------------------------------------+
//| Indicador circular com valores numéricos e descrição de arco     |
//+------------------------------------------------------------------+
class CCircleArc : public CCanvasBase
  {
private:
   //--- Cor de fundo do indicador
   color             m_bg_color;
   //--- Cor de arco de indicador
   color             m_arc_color;
   //--- Cor de área de indicação 
   color             m_area_color;
   //--- Cor de descrição de indicador 
   color             m_label_color;
   //--- Cor de valor de indicador
   color             m_value_color;
   //--- Transparência indicador
   uchar             m_transparency;
   //--- Tamanho de indicador
   int               m_radius;
   //--- Espessura de escala de indicador 
   int               m_scale_width;
   //--- Tamanho de fonte de descrição
   int               m_label_font_size;
   //--- Tamanho de fonte de valor
   int               m_value_font_size;
   //--- Descrição de indicador
   string            m_label_value;
   //--- Tipo de orientação de escala de indicador
   ENUM_ORIENTATION  m_orientation;
public:
                     CCircleArc(void);
                    ~CCircleArc(void);
   //--- Define e retorna a cor de fundo do indicador
   color             BgColor(void)                                   { return(m_bg_color);            }
   void              BgColor(const color clr)                        { m_bg_color=clr;                }
   //--- Define e retorna a cor de arco do indicador
   color             ArcColor(void)                                  { return(m_arc_color);           }
   void              ArcColor(const color clr)                       { m_arc_color=clr;               }
   //--- Define e retorna a cor de área de indicação
   color             AreaColor(void)                                 { return(m_area_color);          }
   void              AreaColor(const color clr)                      { m_area_color=clr;              }
   //--- Define e retorna a cor de descrição do indicador 
   color             LabelColor(void)                                { return(m_label_color);         }
   void              LabelColor(const color clr)                     { m_label_color=clr;             }
   //--- Define e retorna a cor de valor do indicador
   color             ValueColor(void)                                { return(m_value_color);         }
   void              ValueColor(const color clr)                     { m_value_color=clr;             }
   //--- Define e retorna a transparência do indicador 
   uchar             Alpha(void)                                     { return(m_transparency);        }
   void              Alpha(const uchar trn)                          { m_transparency=trn;            }
   //--- Define e retorna o tamanho do indicador
   int               Radius(void)                                    { return(m_radius);              }
   void              Radius(const int r)                             { m_radius=r;                    }
   //--- Define e retorna a espessura de escala do indicador
   int               Width(void)                                     { return(m_scale_width);         }
   void              Width(const int w)                              { m_scale_width=w;               }
   //--- Define e retorna o tamanho de fonte da descrição
   int               LabelSize(void)                                 { return(m_label_font_size);     }
   void              LabelSize(const int sz)                         { m_label_font_size=sz;          }
   //--- Define e retorna o tamanho de fonte de valor
   int               ValueSize(void)                                 { return(m_value_font_size);     }
   void              ValueSize(const int sz)                         { m_value_font_size=sz;          }
   //--- Define e retorna a descrição do indicador
   string            LabelValue(void)                                { return(m_label_value);         }
   void              LabelValue(const string str)                    { m_label_value=str;             }
   //--- Define e retorna a o tipo de orientação da escala
   void              Orientation(const ENUM_ORIENTATION orietation)  { m_orientation=orietation;      }
   ENUM_ORIENTATION  Orientation(void)                               { return(m_orientation);         }
   //--- Cria o indicador
   void              Create(string name,int x,int y);
   //--- Remove o indicador
   void              Delete(void);
   //--- Define e atualiza o valor de indicador
   void              NewValue(double value,double maxvalue,int digits);
  };

Consideraremos mais em detalhe os métodos cuja implementação não está representada: Create() e NewValue(). Lembre-se que, ao criar objetos, eles se sobrepõem em camadas, de modo que a sequência para criá-los é a seguinte:

  1. Fundo do indicador de arco. Sob a forma de um círculo preenchido.
  2. Indicador de arco. Sob a forma de um sector preenchido.
  3. Fundo de exibição de informações de texto. Sob a forma de um círculo preenchido.
  4. Valor numérico do indicador e sua descrição.

Abaixo, na ordem estabelecida é representada a implementação:

//+------------------------------------------------------------------+
//| Cria o indicador                                                |
//+------------------------------------------------------------------+
void CCircleArc::Create(string name,int x,int y)
  {
   int r=m_radius;
   double a,b;
//--- Configuração de posição inicial do indicador
   a=(m_orientation==VERTICAL)?M_PI_2:0;
   b=(m_orientation==VERTICAL)?M_PI_2:0;
   b+=90*M_PI/180;
//--- Correção de posição de indicador relativa ao raio
   x=(x<r)?r:x;
   y=(y<r)?r:y;
   Name(name);
   X(x);
   Y(y);
   XSize(2*r+1);
   YSize(2*r+1);
   if(!CreateCanvas())
      Print("Error. Can not create Canvas.");
//---
   m_canvas.FillCircle(r,r,r,ColorToARGB(m_bg_color,m_transparency));
   m_canvas.Pie(r,r,XSize()/2,YSize()/2,a,b,ColorToARGB(m_arc_color,m_transparency),ColorToARGB(m_arc_color,m_transparency));
   m_canvas.FillCircle(r,r,r-m_scale_width,ColorToARGB(m_area_color,m_transparency));
//---
   m_canvas.FontSizeSet(m_value_font_size);
   m_canvas.TextOut(r,r,"0",ColorToARGB(m_value_color,m_transparency),TA_CENTER|TA_VCENTER);
//---
   m_canvas.FontSizeSet(m_label_font_size);
   m_canvas.TextOut(r,r+m_label_font_size,m_label_value,ColorToARGB(m_label_color,m_transparency),TA_CENTER|TA_VCENTER);
   m_canvas.Update();
  }

Aqui vale a pena prestar atenção ao fato de, ao definir os valores iniciais do primeiro e segundo limite do arco (ou seja, o início do indicador e seu valor atual), nós considerarmos sua orientação e o fato de o ângulo para o método Pie() ser em radianos. Como exemplo, por padrão, o valor atual correspondente à variável b é definido como 90, além disso, ele é convertido para radianos , caso contrário, a visualização será incorreta. O método NewValue() também é implementado de acordo com esses princípios:

//+------------------------------------------------------------------+
//| Define e atualiza o valor do indicador                    |
//+------------------------------------------------------------------+
void CCircleArc::NewValue(double value,double maxvalue,int digits=2)
  {
   int r=m_radius;
   double a,b,result;
//--- Verifica-se a saída para além dos limites do intervalo
   value=(value>maxvalue)?maxvalue:value;
   value=(value<0)?0:value;
//--- Configuração de posição inicial do indicador
   a=(m_orientation==VERTICAL)?M_PI_2:0;
   b=(m_orientation==VERTICAL)?M_PI_2:0;
//---
   result=value*(360.0/maxvalue);
   b+=result*M_PI/180;
   if(b>=2*M_PI)
      b=2*M_PI-0.02;
//---
   m_canvas.FillCircle(r,r,r,ColorToARGB(m_bg_color,m_transparency));
   m_canvas.Pie(r,r,XSize()/2,YSize()/2,a,b,ColorToARGB(m_arc_color,m_transparency),ColorToARGB(m_arc_color,m_transparency));
   m_canvas.FillCircle(r,r,r-m_scale_width,ColorToARGB(m_area_color,m_transparency));
//---
   m_canvas.FontSizeSet(m_value_font_size);
   m_canvas.TextOut(r,r,DoubleToString(value,digits),ColorToARGB(m_value_color,m_transparency),TA_CENTER|TA_VCENTER);
//---
   m_canvas.FontSizeSet(m_label_font_size);
   m_canvas.TextOut(r,r+m_label_font_size,m_label_value,ColorToARGB(m_label_color,m_transparency),TA_CENTER|TA_VCENTER);
   m_canvas.Update();
  }
//+------------------------------------------------------------------+

Depois de verificar e definir as posições iniciais, é recalculado o ângulo em radianos do segundo limite do arco, em seguida, todos os elementos do indicador são redesenhados com os novos valores. Como um exemplo de uso desse indicador, foi implementado um indicador simples de spread que muda a cor da escala de arco quando se atinge um valor de limite.

//+------------------------------------------------------------------+
//|                                                                  |
//|                                Copyright 2017, Alexander Fedosov |
//|                           https://www.mql5.com/pt/users/alex2356 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2017, Alexander Fedosov"
#property link      "https://www.mql5.com/pt/users/alex2356"
#property version   "1.00"
#property indicator_plots 0
#property indicator_chart_window

#include <CustomGUI\CustomGUI.mqh>
//+------------------------------------------------------------------+
//|  Parâmetros de entrada do indicador                              |
//+------------------------------------------------------------------+
input double         maxspr=12;                 // Valor máximo
input int            stepval=8;                 // Valor limite
input color          stepcolor=clrCrimson;      // Cor durante o valor limite
//---
CCircleArc arc;
double spr;
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   arc.Orientation(HORIZONTAL);
   arc.LabelValue("Spread");
   arc.Create("spread_ind",100,100);
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| 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[])
  {
   spr=double(SymbolInfoInteger(Symbol(),SYMBOL_SPREAD));
   if(spr>=stepval)
      arc.ArcColor(stepcolor);
   else
      arc.ArcColor(clrForestGreen);
//---
   arc.NewValue(spr,maxspr,0);
//--- return value of prev_calculated for next call
   return(rates_total);
  }
//+------------------------------------------------------------------+
//| Custom indicator deinitialization function                       |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   arc.Delete();
   ChartRedraw();
  }
//+------------------------------------------------------------------+

Como você pode ver a partir da listagem, a implementação usando a classe CCircleArc é bastante simples de criar e configurar. Vale a pena notar que a alteração e configuração de quaisquer propriedades devem ser estritamente antes da criação do indicador (método Create()), quer antes de usar o método NewValue(), porque é neles que acontece o redesenho dos elementos do indicador, e, consequentemente, o uso de propriedades alteradas. A Figura 4 mostra um exemplo do indicador de spread em andamento.

Figura 4. Exemplo de trabalho do indicador de arco com indicação de arco.


Classe de indicador circular com indicação secional de arco CCircleSection

Em contraste com a indicação de arco simples, a indicação de secional visualmente parece como se nela fossem aplicadas etiquetas que dividem valores igualmente espaçados. Ao construir o esquema para um indicador deste tipo, foi decidido fazer dez seções, além da adição de um novo elemento, isto é, a borda interior. A estrutura básica de uma indicação secional de arco é mostrada na Figura 5.


Figura 5 Estrutura básica de um indicador circular com indicação secional de arco.

O método de exibição da indicação de arco com seções fundamentalmente difere da classe anterior. A diferença é que na classe anterior havia um método descartável Pie() da biblioteca CCanvas, nele, quando se alterava o valor, eram alterados o valor e a posição do segundo arco do sector da elipse. Aqui, este método é aplicado dez vezes e sua posição é estática. Isto é, simplesmente, nesse indicador, há 10 setores da elipse preenchidos, dispostos em círculo. A indicação visual será a alteração de cor de determinados sectores.

Criamos, na pasta Indicators , o arquivo CircleSection.mqh, e imediatamente, como todos os anteriores, associamo-lo ao arquivo CustomUI.mqh. Agora, sua lista ficará assim:

//+------------------------------------------------------------------+
//|                                                    CustomGUI.mqh |
//|                                Copyright 2017, Alexander Fedosov |
//|                           https://www.mql5.com/pt/users/alex2356 |
//+------------------------------------------------------------------+
#include "Indicators\CircleSimple.mqh"
#include "Indicators\CircleArc.mqh"
#include "Indicators\CircleSection.mqh"

Como a apresentação de classes de indicadores é na ordem de complexidade de sua estrutura e funcionalidade, não vou dar suas propriedades e métodos comuns. Focamo-nos naqueles que adicionam funcionalidades adicionais ou têm uma implementação diferente. Portanto, nesta classe, a maior parte dos métodos é idêntica aos anteriores, mas foi adicionado o seguinte:

//+------------------------------------------------------------------+
//|                                                CircleRounded.mqh |
//|                                Copyright 2017, Alexander Fedosov |
//|                           https://www.mql5.com/pt/users/alex2356 |
//+------------------------------------------------------------------+
#include "..\CanvasBase.mqh"
//+--------------------------------------------------------------------------+
//| Indicador circular com valores numéricos e indicação secional circular   |
//+--------------------------------------------------------------------------+
class CCircleSection : public CCanvasBase
  {
private:
   //--- Cor de divisão ativa/inativa de escala
   color             m_scale_color_on;
   color             m_scale_color_off;
public:
   //--- Define a cor de divisão ativa/inativa de escala
   void              ScaleColorOn(const color clr)                   { m_scale_color_on=clr;          }
   void              ScaleColorOff(const color clr)                  { m_scale_color_off=clr;         }
   //--- Cria o indicador
   void              Create(string name,int x,int y);
   //--- Define e atualiza o valor de indicador
   void              NewValue(double value,double maxvalue,int digits);
  };

Os métodos Create() e NewValue() são abandonados devido às diferenças - quanto a implementação - em relação aos métodos anteriores. Como pode ser visto a partir da lista abaixo, após a correção de posição com respeito ao raio, aparece o bloco de geração de dez seções com ajuda do ciclo. Isso leva em conta a origem: a horizontal - de zero graus verticais - 90.

//+------------------------------------------------------------------+
//| Cria o indicador                                                 |
//+------------------------------------------------------------------+
void CCircleSection::Create(string name,int x,int y)
  {
   int r=m_radius;
   double a,b;
//--- Correção de posição de indicador relativa ao raio
   x=(x<r)?r:x;
   y=(y<r)?r:y;
   Name(name);
   X(x);
   Y(y);
   XSize(2*r+1);
   YSize(2*r+1);
   if(!CreateCanvas())
      Print("Error. Can not create Canvas.");
//--- Seções do indicador
   for(int i=0;i<10;i++)
     {
      if(m_orientation==HORIZONTAL)
        {
         a=36*i*M_PI/180;
         b=36*(i+1)*M_PI/180;
         if(a>2*M_PI)
            a-=2*M_PI;
         if(b>2*M_PI)
            b-=2*M_PI;
        }
      else
        {
         a=M_PI_2+36*i*M_PI/180;
         b=M_PI_2+36*(i+1)*M_PI/180;
         if(a>2*M_PI)
            a-=2*M_PI;
         if(b>2*M_PI)
            b-=2*M_PI;
        }
      m_canvas.Pie(r,r,XSize()/2,YSize()/2,a,b,ColorToARGB(m_bg_color,m_transparency),ColorToARGB(m_scale_color_off,m_transparency));
     }
//--- Borda e fundo
   m_canvas.FillCircle(r,r,XSize()/2-m_scale_width,ColorToARGB(m_border_color,m_transparency));
   m_canvas.FillCircle(r,r,XSize()/2-m_scale_width-m_border_size,ColorToARGB(m_area_color,m_transparency));
//--- Descrição
   m_canvas.FontSizeSet(m_label_font_size);
   m_canvas.TextOut(r,r+m_value_font_size,m_label_value,ColorToARGB(m_label_color,m_transparency),TA_CENTER|TA_VCENTER);
//--- Valor numérico
   m_canvas.FontSizeSet(m_value_font_size);
   m_canvas.TextOut(r,r,"0",ColorToARGB(m_value_color,m_transparency),TA_CENTER|TA_VCENTER);
   m_canvas.Update();
  }

Como já dissemos, além de alterar os valores numéricos, a exibição visual é apresentada como uma mudança na cor de seção. Além disso, a mudança deve ser consistente e se afastar dos valores atuais e máximos especificados. Assim, por exemplo, com um valor numérico de 20 e um máximo de 100, as cores mudam duas seções, mas com um máximo de 200, apenas um. Vamos considerar mais detalhadamente como isso é feito no método NewValue():

//+------------------------------------------------------------------+
//| Define e atualiza o valor do indicador                           |
//+------------------------------------------------------------------+
void CCircleSection::NewValue(double value,double maxvalue=100,int digits=2)
  {
//---
   int r=m_radius;
   double a,b;
   color clr;
//---
   for(int i=0;i<10;i++)
     {
      if(m_orientation==HORIZONTAL)
        {
         a=36*i*M_PI/180;
         b=36*(i+1)*M_PI/180;
         if(a>2*M_PI)
            a-=2*M_PI;
         if(b>2*M_PI)
            b-=2*M_PI;
        }
      else
        {
         a=M_PI_2+36*i*M_PI/180;
         b=M_PI_2+36*(i+1)*M_PI/180;
         if(a>2*M_PI)
            a-=2*M_PI;
         if(b>2*M_PI)
            b-=2*M_PI;
        }
      clr=(maxvalue/10*(i+1)<=value)?m_scale_color_on:m_scale_color_off;
      m_canvas.Pie(r,r,XSize()/2,YSize()/2,a,b,ColorToARGB(m_bg_color,m_transparency),ColorToARGB(clr,m_transparency));
     }
//---
   m_canvas.FillCircle(r,r,XSize()/2-m_scale_width,ColorToARGB(m_border_color,m_transparency));
   m_canvas.FillCircle(r,r,XSize()/2-m_scale_width-m_border_size,ColorToARGB(m_area_color,m_transparency));
//---
   m_canvas.FontSizeSet(m_label_font_size);
   m_canvas.TextOut(r,r+m_value_font_size,m_label_value,ColorToARGB(m_label_color,m_transparency),TA_CENTER|TA_VCENTER);
//---
   m_canvas.FontSizeSet(m_value_font_size);
   m_canvas.TextOut(r,r,DoubleToString(value,digits),ColorToARGB(m_value_color,m_transparency),TA_CENTER|TA_VCENTER);
   m_canvas.Update();
  }
//+------------------------------------------------------------------+

Como você pode ver a partir da lista acima, na implementação é bastante simples. É verificado o valor atual em relação ao máximo. Se ele for maior ou igual ao valor máximo dividido pelo número de seções e multiplicado pela iteração atual do ciclo, a cor da seção mudará de inativo para ativo.

Como exemplo de uso desta classe, realizei o indicador de drawdown, ele mostra a proporção entre o capital liquido e o balanço atual na conta. Assim, é possível monitorar visualmente o abaixamento na conta, em vez de calculá-lo por números no terminal. A listagem de implementação de esse indicador é apresentado a seguir.

//+------------------------------------------------------------------+
//|                                              CustomIndicator.mq5 |
//|                                Copyright 2017, Alexander Fedosov |
//|                           https://www.mql5.com/pt/users/alex2356 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2017, Alexander Fedosov"
#property link      "https://www.mql5.com/pt/users/alex2356"
#property version   "1.00"
#property indicator_plots 0
#property indicator_chart_window

#include <CustomGUI\CustomGUI.mqh>
//---
CCircleSection ind;
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   ind.Radius(70);
   ind.LabelValue("Drawdown");
   ind.Create("drawdown",150,150);
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| 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[])
  {
//---
   ind.NewValue(AccountInfoDouble(ACCOUNT_EQUITY),AccountInfoDouble(ACCOUNT_BALANCE));
//--- return value of prev_calculated for next call
   return(rates_total);
  }
//+------------------------------------------------------------------+
//| Custom indicator deinitialization function                       |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   ind.Delete();
   ChartRedraw();
  }
//+------------------------------------------------------------------+

Não se esqueça de usar o método Delete() em deinitialization para remover corretamente o indicador no gráfico.


Classe de gráfico linear CLineGraph

Para criar um gráfico de linha através de gráficos personalizados, primeiro é necessário determinar os elementos do gráfico, a saber:

  • Fundo do gráfico. Ele terá duas propriedades, isto é, o tamanho que também será o tamanho do objeto, e a cor.
  • Borda do gráfico. Tem apenas a propriedade cor. É constituída por dois retângulos a sombreados, e as coordenadas do último (ele também serve como fundo de gráfico) são deslocadas uma unidade, o que dá o efeito da borda.
  • Fundo do gráfico. Ele tem apenas a propriedade cor. 
  • Eixo do gráfico. Ele tem apenas a propriedade cor. É implementada da mesma forma como a borda do gráfico, isto é, a sobreposição de dois retângulos onde o topo é deslocado uma unidade com relação às coordenadas.
  • Divisão de eixo. Ele tem apenas a propriedade cor. Conjunto de linhas implementado com ajuda dos métodos LineHorizontal() para os eixos Y e LineVertical() Х.
  • Valor do eixo. Ele tem a propriedade cor e tamanho de fonte. TextOut().
  • Grade. Ele tem apenas a propriedade cor. Para implementação de grade foi selecionado o método LineAA(), uma vez que nele é possível definir o estilo de linha.
  • Gráfico. Ele tem apenas a propriedade cor. É constituído de elementos de linha e círculo preenchido - método FillCircle()

Na descrição dos elementos, são indicadas apenas as propriedades personalizadas. A Figura 6 mostra a estrutura de elementos de gráfico. 


Figura 6. Estrutura básica de um gráfico de linhas.

Primeiro, na pasta Indicators, criamos o arquivo LineGraph.mqh e conetamo-lo no arquivo CustomGUI.mqh:

//+------------------------------------------------------------------+
#include "Indicators\CircleSimple.mqh"
#include "Indicators\CircleArc.mqh"
#include "Indicators\CircleSection.mqh"
#include "Indicators\LineGraph.mqh"
//+------------------------------------------------------------------+

Depois, no arquivo LineGraph.mqh, criamos a classe CLineGraph e fazemos - como nas anteriores - básica para ela CCanvasBase. Definimos todas as propriedades e métodos descritos acima na estrutura de básica do gráfico de linhas. 

//+------------------------------------------------------------------+
//|                                                    LineGraph.mqh |
//|                                Copyright 2017, Alexander Fedosov |
//|                           https://www.mql5.com/pt/users/alex2356 |
//+------------------------------------------------------------------+
#include "..\CanvasBase.mqh"
//+------------------------------------------------------------------+
//| Gráfico de linhas                                                |
//+------------------------------------------------------------------+
class CLineGraph : public CCanvasBase
  {
private:
   //--- Cor de fundo do gráfico
   color             m_bg_color;
   //--- Cor de fundo do gráfico
   color             m_bg_graph_color;
   //--- Cor de borda do gráfico
   color             m_border_color;
   //--- Cor de valores de eixos do gráfico
   color             m_axis_color;
   //--- Cor de seções do gráfico
   color             m_grid_color;
   //--- Cor das marcas nos eixos do gráfico
   color             m_scale_color;
   //--- Cor das linhas do gráfico
   color             m_graph_color;
   //--- Tamanho do gráfico
   int               m_x_size;
   int               m_y_size;
   //--- Recuo do gráfico a partir da borda do fundo
   int               m_gap;
   //--- Tamanho de fonte dos valores nos eixos do gráfico
   int               m_font_size;
   //--- Matriz de valore do eixo de coordenadas(y)
   int               m_x[];
   //--- Valores máximo e mínimo do eixo y
   double            m_y_min;
   double            m_y_max;
   //--- Número de divisões do eixo y
   int               m_num_grid;
   //--- Transparência do gráfico
   uchar             m_transparency;
public:
                     CLineGraph(void);
                    ~CLineGraph(void);
   //--- Define e retorna a cor de fundo do gráfico
   color             BgColor(void)                                   { return(m_bg_color);                  }
   void              BgColor(const color clr)                        { m_bg_color=clr;                      }
   //--- Define e retorna a cor de fundo do gráfico
   color             BgGraphColor(void)                              { return(m_bg_graph_color);            }
   void              BgGraphColor(const color clr)                   { m_bg_graph_color=clr;                }
   //--- Define e retorna a cor de borda do gráfico
   color             BorderColor(void)                               { return(m_border_color);              }
   void              BorderColor(const color clr)                    { m_border_color=clr;                  }
   //--- Define e retorna a cor de valores de eixos do gráfico
   color             AxisColor(void)                                 { return(m_axis_color);                }
   void              AxisColor(const color clr)                      { m_axis_color=clr;                    }
   //--- Define e retorna a cor de grade do gráfico
   color             GridColor(void)                                 { return(m_grid_color);                }
   void              GridColor(const color clr)                      { m_grid_color=clr;                    }
   //--- Define e retorna a cor de divisões nos eixos do gráfico
   color             ScaleColor(void)                                { return(m_scale_color);               }
   void              ScaleColor(const color clr)                     { m_scale_color=clr;                   }
   //--- Define e retorna a cor de linhas do gráfico
   color             GraphColor(void)                                { return(m_graph_color);               }
   void              GraphColor(const color clr)                     { m_graph_color=clr;                   }
   //--- Define e retorna o tamanho do gráfico
   int               XGraphSize(void)                                { return(m_x_size);                    }
   void              XGraphSize(const int x_size)                    { m_x_size=x_size;                     }
   int               YGraphSize(void)                                { return(m_y_size);                    }
   void              YGraphSize(const int y_size)                    { m_y_size=y_size;                     }
   //--- Define e retorna o tamanho da fonte dos valores nos eixos do gráfico
   int               FontSize(void)                                  { return(m_font_size);                 }
   void              FontSize(const int fontzise)                    { m_font_size=fontzise;                }
   //--- Define e retorna o recuo do gráfico a partir da borda do fundo
   int               Gap(void)                                       { return(m_gap);                       }
   void              Gap(const int g)                                { m_gap=g;                             }
   //--- Definir e retorna os valores máximo e mínimo do eixo y
   double            YMin(void)                                      { return(m_y_min);                     }
   void              YMin(const double ymin)                         { m_y_min=ymin;                        }
   double            YMax(void)                                      { return(m_y_max);                     }
   void              YMax(const double ymax)                         { m_y_max=ymax;                        }
   //--- Define e retorna o número de divisões do eixo y
   int               NumGrid(void)                                   { return(m_num_grid);                  }
   void              NumGrid(const int num)                          { m_num_grid=num;                      }
   //--- Cria o gráfico
   void              Create(string name,int x,int y);
   //--- Remove o gráfico 
   void              Delete(void);
   //--- Define a matriz de dados de entrada
   void              SetArrayValue(double &data[]);
private:
   //---
   void              VerticalScale(double min,double max,int num_grid);
   void              HorizontalScale(int num_grid);
  };

Consideraremos em mais detalhes a implementação dos métodos não mencionados na lista acima. No método Create(), é criado o recurso gráfico, é colocado no gráfico do instrumento. Neste caso, também são usados dois métodos privados para ajustar as escalas vertical e horizontal: quer na base das definições inicializadas no construtor da classe, ou usando os métodos da classe antes de criar.

//+------------------------------------------------------------------+
//| Cria o gráfico                                                   |
//+------------------------------------------------------------------+
void CLineGraph::Create(string name,int x,int y)
  {
//--- Correção da posição do indicador relativa
   x=(x<m_x_size/2)?m_x_size/2:x;
   y=(y<m_y_size/2)?m_y_size/2:y;
   Name(name);
   X(x);
   Y(y);
   XSize(m_x_size);
   YSize(m_y_size);
   if(!CreateCanvas())
      Print("Error. Can not create Canvas.");
//--- Criação da borda do gráfico e do fundo
   m_canvas.FillRectangle(0,0,XSize(),YSize(),ColorToARGB(m_border_color,m_transparency));
   m_canvas.FillRectangle(1,1,XSize()-2,YSize()-2,ColorToARGB(m_bg_color,m_transparency));
//--- Criação dos eixos e do fundo do gráfico
   m_canvas.FillRectangle(m_gap-1,m_gap-1,XSize()-m_gap+1,YSize()-m_gap+1,ColorToARGB(m_border_color,m_transparency));
   m_canvas.FillRectangle(m_gap,m_gap,XSize()-m_gap,YSize()-m_gap,ColorToARGB(m_bg_graph_color,m_transparency));
//--- Criação de divisões de eixo e seus valores
   VerticalScale(m_y_min,m_y_max,m_num_grid);
   HorizontalScale(5);
   m_canvas.Update();
  }

Para exibir qualquer gráfico de linhas, deve ser utilizada como fundamento a matriz de dados, uma vez que no MetaTrader matrizes são muitas vezes utilizadas para copiar os valores a partir dos buffers de indicador. Por conseguinte, aquando da aplicação do método SetArrayValue() de exibição de gráficos, é usada como o fundamento a matriz de dados (é um argumento do método), onde no eixo X aparece os valores correspondentes ao tamanho da matriz, e no eixo Y - os valores. 

//+------------------------------------------------------------------+
//| Define a matriz de dados de entrada                              |
//+------------------------------------------------------------------+
void CLineGraph::SetArrayValue(double &data[])
  {
   int y0,y1;
//--- Criação da borda do gráfico e do fundo
   m_canvas.FillRectangle(0,0,XSize(),YSize(),ColorToARGB(m_border_color,m_transparency));
   m_canvas.FillRectangle(1,1,XSize()-2,YSize()-2,ColorToARGB(m_bg_color,m_transparency));
//--- Criação dos eixos e do fundo do gráfico
   m_canvas.FillRectangle(m_gap-1,m_gap-1,XSize()-m_gap+1,YSize()-m_gap+1,ColorToARGB(m_border_color,m_transparency));
   m_canvas.FillRectangle(m_gap,m_gap,XSize()-m_gap,YSize()-m_gap,ColorToARGB(m_bg_graph_color,m_transparency));
//--- Se, na matriz, o valor máximo de dados excede o limite superior do eixo Y, então dimensionamos o eixo.
   if(data[ArrayMaximum(data)]>m_y_max)
      m_y_max=data[ArrayMaximum(data)];
//--- Criação de divisões de eixo e seus valores
   VerticalScale(m_y_min,m_y_max,m_num_grid);
   HorizontalScale(ArraySize(data));
//--- Construção do gráfico de acordo com a matriz de dados
   for(int i=0;i<ArraySize(data)-1;i++)
     {
      y0=int((YSize()-2*m_gap)*(1-data[i]/m_y_max));
      y0=int((YSize()-m_gap)-(YSize()-2*m_gap)/m_y_max*data[i]);
      y0=(y0<m_gap)?m_gap:y0;
      y1=int((YSize()-m_gap)-(YSize()-2*m_gap)/m_y_max*data[i+1]);
      y1=(y1<m_gap)?m_gap:y1;
      m_canvas.LineAA(m_x[i+1],y0,m_x[i+2],y1,ColorToARGB(m_graph_color,m_transparency),STYLE_SOLID);
      m_canvas.FillCircle(m_x[i+1],y0,2,ColorToARGB(m_graph_color,m_transparency));
      m_canvas.FillCircle(m_x[i+2],y1,2,ColorToARGB(m_graph_color,m_transparency));
     }
   m_canvas.Update();
  }

Como um exemplo de utilização desta classe, decidiu-se construir um gráfico de valores para o oscilador selecionado e compará-lo com a exibição original. Para este fim foi usado um RSI normal. Na lista abaixo, pode ser vista a realização de construção de seus valores - a partir do buffer - usando a classe ClineGraph.

//+------------------------------------------------------------------+
//|                                                            4.mq5 |
//|                                Copyright 2017, Alexander Fedosov |
//|                           https://www.mql5.com/pt/users/alex2356 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2017, Alexander Fedosov"
#property link      "https://www.mql5.com/pt/users/alex2356"
#property version   "1.00"
#property indicator_plots 0
#property indicator_chart_window

#include <CustomGUI\CustomGUI.mqh>
//---
CLineGraph ind;
int InpInd_Handle;
double rsi[];
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//---- obtenção do identificador do indicador
   InpInd_Handle=iRSI(Symbol(),PERIOD_CURRENT,10,PRICE_CLOSE);
   if(InpInd_Handle==INVALID_HANDLE)
     {
      Print("Failed to get indicator handle");
      return(INIT_FAILED);
     }
//---
   ind.NumGrid(10);
   ind.YMax(100);
   ind.Create("rsi",350,250);

//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| 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[])
  {
//---
   if(CopyBuffer(InpInd_Handle,0,0,10,rsi)<=0)
      return(0);
   ind.SetArrayValue(rsi);
//--- return value of prev_calculated for next call
   return(rates_total);
  }
//+------------------------------------------------------------------+
//| Custom indicator deinitialization function                       |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   ind.Delete();
   ChartRedraw();
  }
//+------------------------------------------------------------------+

Agora definiremos o indicador original com os mesmos parâmetros e compararemos os resultados na Figura 7:

Figura 7. Comparação de construção com ajuda da classe CLineGraph e o RSI original.

Note-se que a matriz, em que os dados são registrados pelo método CopyBuffer, deve ter a aparência original. Ou seja, não é necessário mudar a indexação, como ns TimeSeries.


Conclusão

No artigo, são discutidos exemplos para criar passo a passo indicadores personalizados de forma livre usando a biblioteca de gráficos personalizados CSanvas. São descritos exemplos de diferente complexidade, desde um indicador circular simples composto de apenas quatro elementos até uma indicação circular secionada. Além disso, consideramos a possibilidade de realização dos tipos conhecidos de indicadores de uma nova maneira. Assim possibilidades não se limitam estritamente par o conjunto de propriedades especificado. Esses exemplos de implementação foram estruturados sob a forma de classes, ou seja, elas são inerentemente adição ou expansão da classe já acabada CCanvas. 

No final do artigo é anexado um arquivo com todos os ficheiros listados, classificados em pastas. Portanto, para uma correta operação é suficiente colocar a pasta MQL5 na raiz do terminal. 

Programas utilizados no artigo:

#
 Nome
Tipo
Descrição
1
CanvasBase.mqh Biblioteca  Classe base de gráfico personalizado
2
CustomGUI.mqh Biblioteca  Arquivo da lista de inclusão de todas as classes de biblioteca 
3 CircleArc.mqh Biblioteca  Contém a classe de indicador circular com indicação de arco CCircleArc
4 CircleSection.mqh Biblioteca  Contém a classe de indicador circular com indicação secional de arco CCircleSection
5 CircleSimple.mqh Biblioteca  Contém a classe de indicador circular simples CCircleSimple
LineGraph.mqh Biblioteca  Contém a classe de gráfico linear CLineGraph
7 1.mq5 Indicador  Exemplo de implementação de classe CCirleSimple
8 2.mq5 Indicador  Exemplo de implementação de classe CCirleArc
9 3.mq5 Indicador  Exemplo de implementação de classe CCirleSection
 10 4.mq5 Indicador  Exemplo de implementação de classe CLineGraph


Traduzido do russo por MetaQuotes Software Corp.
Artigo original: https://www.mql5.com/ru/articles/3236

Arquivos anexados |
MQL5.zip (234.75 KB)
Sistema de negociação DiNapoli Sistema de negociação DiNapoli

No artigo, é examinado o sistema de negociação com níveis de Fibonacci desenvolvido e descrito por Joe DiNapoli. Além disso, são explicados os conceitos básicos e a essência do sistema, e é fornecido um exemplo de um indicador simples.

Criação de documentos com base em códigos-fonte MQL5 Criação de documentos com base em códigos-fonte MQL5

No artigo é estudada a criação de documentação para um código em linguagem MQL5, partindo da automação de tags (marcação). Além disso, quanto ao programa Doxygen, é descrito seu funcionamento, adequada configuração e obtenção de resultados em vários formatos (HTML, HtmlHelp e PDF).

Quanto dura a tendência? Quanto dura a tendência?

No artigo, são selecionadas várias maneiras de identificar a tendência, a fim de definir sua duração em relação ao estado de correção do mercado. Na teoria, acredita-se numa correlação tendência-fase de correção de 30% para 70%. Nós temos que verificar isso.

Interfaces gráficas X: Seleção de texto na caixa de texto multilinha (build 13) Interfaces gráficas X: Seleção de texto na caixa de texto multilinha (build 13)

Este artigo implementará a possibilidade de selecionar o texto usando várias combinações de teclas e excluir o texto selecionado, da mesma maneira que é feito em outros editores de texto. Além disso, vamos continuar com a otimização do código e prepararemos as classes para avançar para o processo final do segundo estágio da evolução da biblioteca, onde todos os controles serão renderizados como imagens separadas (telas).