Indicadores baseados na classe CCanvas: Preenchendo canais com transparência
Introdução
Neste artigo, exploraremos os métodos de criação de indicadores personalizados que são desenhados usando a classe CCanvas da Biblioteca Padrão. Daremos especial atenção aos indicadores que preenchem a área entre duas linhas com uma cor sólida. Primeiro, examinaremos as razões pelas quais o uso da classe CCanvas pode ser a melhor opção em comparação com outras alternativas para indicadores desse tipo. Em seguida, analisaremos algumas propriedades do gráfico necessárias para o cálculo de coordenadas e o processo principal envolvido no trabalho com CCanvas.
O objetivo final é criar um indicador que aplique transparência. Todo o trabalho será realizado levando em consideração apenas a janela principal do gráfico. Assim que alcançarmos nosso objetivo, poderemos começar a trabalhar com indicadores em subjanelas.
Os tópicos do artigo estão listados abaixo:
- Motivos para usar a classe CCanvas
- Propriedades da janela do gráfico
- Compreendendo as propriedades da janela do gráfico
- Indicador de visualização das propriedades do gráfico
- Transformação de coordenadas
- DRAW_FILLING com transparência
- Expansão do método para trabalhar em indicadores de subjanela
Motivos para usar a classe CCanvas
Alguém poderia perguntar por que usar CCanvas, já que existe o DRAW_FILLING para indicadores personalizados. Existem pelo menos duas razões:
- As cores do indicador se mesclam com as cores de outros indicadores, velas e objetos do gráfico.
- DRAW_FILLING não permite transparência.
Propriedades da janela do gráfico
Antes de criar um gráfico personalizado, é necessário pensar em suas propriedades. Você pode encontrar todas as propriedades na documentação. Para obter os valores das propriedades, é necessário usar as funções correspondentes ChartGetInteger e ChartGetDouble. Também há a função ChartGetString, mas não a utilizaremos aqui.
As propriedades que pretendemos usar são listadas abaixo, juntamente com uma breve descrição. Se precisarmos de algo mais, mencionarei separadamente.
- CHART_WIDTH_IN_PIXELS — largura da janela do gráfico, sem a escala de preços
- CHART_HEIGTH _IN_PIXELS — altura da subjanela, sem a escala de datas
- CHART_PRICE_MIN — preço correspondente ao topo da subjanela
- CHART_PRICE_MAX — preço correspondente à parte inferior da subjanela
- CHART_SCALE — distância entre as barras. Após alguns testes, descobri que se trata de uma portência de dois pow(2, CHART_SCALE)
- CHART_FISRT_VISIBLE_BAR — primeira barra visível no gráfico, da esquerda para a direita
- CHART_VISIBLE_BARS — número de barras visíveis no gráfico
Compreendendo as propriedades da janela do gráfico
Essas propriedades são claramente visíveis na imagem a seguir.
Usamos as propriedades CHART_WIDTH_IN_PIXELS e CHART_HEIGHT_IN_PIXELS para determinar o tamanho do canvas necessário. Ao alterar o tamanho da janela e as propriedades, é necessário ajustar o tamanho do canvas.
Para um melhor entendimento, criaremos um indicador simples que exibe as propriedades e como elas mudam com base na alteração do preço e na interação com o usuário. Também começaremos a usar o canvas para aprender o processo de desenho.
Indicador de visualização das propriedades do gráfico
Nesta etapa, supõe-se que você já saiba como criar um indicador personalizado. Se não souber, sugiro que primeiro leia os artigos "MQL5: Crie o seu próprio indicador" e "Explorando as possibilidades de criar gráficos de velas multicoloridas". Agora, vamos começar.
Criei meu indicador no caminho especificado abaixo. Por motivos organizacionais, sugiro que você faça o mesmo.
Quando a estrutura do indicador estiver pronta, precisamos adicionar a biblioteca CCanvas ao arquivo. Podemos fazer isso usando a diretiva #include.
Em seguida, criamos uma instância da classe CCanvas. Tudo isso deve ser colocado após as diretivas #property do indicador.
#property copyright "Copyright 2023, Samuel Manoel De Souza" #property link "https://www.mql5.com/en/users/samuelmnl" #property version "1.00" #property indicator_chart_window #include <Canvas/Canvas.mqh> CCanvas Canvas;
A primeira coisa que precisamos fazer ao trabalhar com CCanvas é criar um OBJ_BITMAP_LABEL e adicionar um recurso a ele. Isso deve ser feito se você quiser adicioná-lo ao gráfico, geralmente no bloco de inicialização do indicador, usando o método CreateBitampLabel(...). Por fim, precisamos remover o OBJ_BITMAP_LABEL e o recurso anexado a ele. Isso deve ser feito se você quiser removê-lo do gráfico, geralmente no bloco de desinicialização, usando o método Destroy(void). Enquanto isso, realizamos o desenho principal, que consiste em apagar desenhos (limpar ou definir os valores padrão dos pixels do recurso), criar desenhos e atualizar o recurso. O ciclo de vida completo do canvas é mostrado no diagrama abaixo.
Para simplificar, armazenaremos Erase, Draw e Update em uma única função chamada Redraw. Ao colocar tudo isso no código, obtemos a seguinte estrutura.
//+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- indicator buffers mapping Canvas.CreateBitmapLabel(0, 0, "Canvas", 0, 0, 200, 150, COLOR_FORMAT_ARGB_NORMALIZE); //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Custom indicator deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { Canvas.Destroy(); } //+------------------------------------------------------------------+ //| Custom indicator redraw function | //+------------------------------------------------------------------+ void Redraw(void) { uint default_color = ColorToARGB(clrBlack); uint text_color = ColorToARGB(clrWhite); //--- canvas erase Canvas.Erase(default_color); //--- add first draw //--- add second draw //--- add ... draw //--- add last draw //--- canvas update Canvas.Update(); }
Para exibir as propriedades, escrevemos seus valores usando o método TextOut. Os valores dessas propriedades serão armazenados como uma string na variável struct.
struct StrProperty { string name; string value; };A estrutura pode ser a seguinte. Em seguida, podemos exibi-los brevemente em um laço. Como ainda não temos um array, passaremos um array como parâmetro para a função Redraw. A função Redraw fica assim:
void Redraw(StrProperty &array[]) { uint default_color = ColorToARGB(clrBlack); uint text_color = ColorToARGB(clrWhite); //--- canvas erase Canvas.Erase(default_color); //--- add first draw int total = ArraySize(array); for(int i=0;i<total;i++) { int padding = 2; int left = padding, right = Canvas.Width() - padding, y = i * 20 + padding; Canvas.TextOut(left, y, array[i].name, text_color, TA_LEFT); Canvas.TextOut(right, y, array[i].value, text_color, TA_RIGHT); } //--- canvas update Canvas.Update(); }Por fim, podemos obter os valores das propriedades e exibi-los. Se o seu código não tiver um manipulador de função OnChartEvent, você precisará adicioná-lo. Lá, verificaremos o identificador do evento CHARTEVENT_CHART_CHANGE. Quando o evento ocorrer, declararemos algumas variáveis que receberão os valores das propriedades e as passaremos para o array de estruturas, e então chamaremos a função Redraw. Podemos compilar o indicador, adicioná-lo ao gráfico e manipular o gráfico para ver as atualizações no canvas.
//+------------------------------------------------------------------+ //| Custom indicator chart event handler function | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long& lparam, const double& dparam, const string& sparam) { if(id != CHARTEVENT_CHART_CHANGE) return; int chart_width = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS); int chart_height = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS); int chart_scale = (int)ChartGetInteger(0, CHART_SCALE); int chart_first_vis_bar = (int)ChartGetInteger(0, CHART_FIRST_VISIBLE_BAR); int chart_vis_bars = (int)ChartGetInteger(0, CHART_VISIBLE_BARS); double chart_prcmin = ChartGetDouble(0, CHART_PRICE_MIN); double chart_prcmax = ChartGetDouble(0, CHART_PRICE_MAX); //--- StrProperty array[] { {"Width", (string)chart_width}, {"Height", (string)chart_height}, {"Scale", (string)chart_scale}, {"First Vis. Bar", (string)chart_first_vis_bar}, {"Visible Bars", (string)chart_vis_bars}, {"Price Min", (string)chart_prcmin}, {"Price Max", (string)chart_prcmax}, }; Redraw(array); }
Transformação de coordenadas
Nesta etapa, precisamos de algumas funções básicas para transformar data e hora ou índice de barra em coordenadas x em pixels, preço em coordenadas y em pixels, coordenadas x em índice de barra e coordenadas y em preço (algumas delas não serão usadas agora, mas podemos incluí-las todas de uma vez). Por isso, moveremos as variáveis de propriedades do gráfico para a área global, enquanto na função OnChartEvent apenas atualizaremos os valores e chamaremos a função Redraw quando necessário. A solução ideal seria encapsular as variáveis e funções de transformação em uma classe ou estrutura, mas não vamos complicar por enquanto. Sugiro que você comece a aprender OOP lendo o artigo "Fundamentos da programação orientada a objetos" e o tópico correspondente na documentação (Programação Orientada a Objetos). Vamos usar isso na próxima oportunidade.
As funções estão principalmente relacionadas a proporções.
//+------------------------------------------------------------------+ //| Converts the chart scale property to bar width/spacing | //+------------------------------------------------------------------+ int BarWidth(int scale) {return (int)pow(2, scale);} //+------------------------------------------------------------------+ //| Converts the bar index(as series) to x in pixels | //+------------------------------------------------------------------+ int ShiftToX(int shift) {return (chart_first_vis_bar - shift) * BarWidth(chart_scale) - 1;} //+------------------------------------------------------------------+ //| Converts the price to y in pixels | //+------------------------------------------------------------------+ int PriceToY(double price) { // avoid zero divider if(chart_prcmax - chart_prcmin == 0.) return 0.; return (int)round(chart_height * (chart_prcmax - price) / (chart_prcmax - chart_prcmin) - 1); } //+------------------------------------------------------------------+ //| Converts x in pixels to bar index(as series) | //+------------------------------------------------------------------+ int XToShift(int x) { // avoid zero divider if(BarWidth(chart_scale) == 0) return 0; return chart_first_vis_bar - (x + BarWidth(chart_scale) / 2) / BarWidth(chart_scale); } //+------------------------------------------------------------------+ //| Converts y in pixels to price | //+------------------------------------------------------------------+ double YToPrice(int y) { // avoid zero divider if(chart_height == 0) return 0; return chart_prcmax - y * (chart_prcmax - chart_prcmin) / chart_height; }
DRAW_FILLING com transparência
Agora temos tudo o que precisamos para implementar nosso DRAW_FILLING usando CCanvas.
Não vamos perder tempo criando um novo indicador. Em vez disso, vamos pegar um exemplo usado na plataforma MetaTrader 5 e adicionar o preenchimento entre duas linhas. Vou usar o indicador Envelopes em \\MQL5\\Indicators\\Examples\\, localizado na pasta de dados do terminal. Vou copiar o Envelopes.mq5 para o mesmo diretório onde criei o indicador ChartPropertiesViewer. Você pode escolher qualquer indicador, mas sugiro que use o mesmo indicador seguindo as etapas descritas neste artigo.
A primeira coisa que precisamos fazer é copiar tudo o que fizemos no indicador ChartPropertiesViewer para o Envelopes.
Conforme mencionado anteriormente, vamos preencher o canal entre duas linhas. Para isso, criaremos uma função que receberá arrays correspondentes aos valores das linhas. No indicador Envelopes, os arrays são definidos pelas variáveis ExtUpBuffer e ExtMABuffer.
double ExtUpBuffer[]; double ExtDownBuffer[];
Junto com os arrays, passaremos algumas variáveis adicionais que nos permitirão usar duas cores, definir um nível de transparência e deslocar o indicador para a esquerda ou direita do gráfico.
Parâmetros | Descrição da variável |
---|---|
serie1 | Array de valores correspondentes à primeira linha |
serie2 | Array de valores correspondentes à segunda linha |
clr1 | Cor quando serie1 >= serie2 |
clr2 | Cor quando serie1 < serie2 |
alpha | Valor de transparência do canal |
plot_shift | Deslocar o indicador para a direita ou para a esquerda do gráfico |
A função, usando as variáveis existentes e os parâmetros mencionados, será assim:
//+------------------------------------------------------------------+ //| Fill the area between two lines | //+------------------------------------------------------------------+ void DrawFilling(double &serie1[], double &serie2[], color clr1, color clr2, uchar alpha = 255, int plot_shift = 0) { int start = chart_first_vis_bar; int total = chart_vis_bars + plot_shift; uint argb1 = ColorToARGB(clr1, alpha); uint argb2 = ColorToARGB(clr2, alpha); int limit = fmin(ArraySize(serie1), ArraySize(serie2)); int px, py1, py2; for(int i = 0; i < total; i++) { int bar_position = start - i; int bar_shift = start - i + plot_shift; int bar_index = limit - 1 - bar_shift; if(serie1[bar_index] == EMPTY_VALUE || serie1[bar_index] == EMPTY_VALUE || bar_shift >= limit) continue; int x = ShiftToX(bar_position); int y1 = PriceToY(serie1[bar_index]); int y2 = PriceToY(serie2[bar_index]); uint argb = serie1[bar_index] < serie2[bar_index] ? argb2 : argb1; if(i > 0 && serie1[bar_index - 1] != EMPTY_VALUE && serie2[bar_index - 1] != EMPTY_VALUE) { if(py1 != py2) Canvas.FillTriangle(px, py1, px, py2, x, y1, argb); if(y1 != y2) Canvas.FillTriangle(px, py2, x, y1, x, y2, argb); } px = x; py1 = y1; py2 = y2; } }
Até este ponto, usamos um canvas de tamanho fixo. No entanto, os indicadores exigem um canvas que preencha toda a área do gráfico. Além disso, sempre que o tamanho da janela do gráfico é alterado, seja por aumento, redução, alongamento em qualquer direção ou adição de indicadores em subjanelas, precisamos garantir que o canvas continue preenchendo toda a área do gráfico. Para isso, vamos alterar o tamanho do canvas, fazendo uma pequena modificação em nossa função OnChartEvent.
void OnChartEvent(const int id, const long& lparam, const double& dparam, const string& sparam) { if(id != CHARTEVENT_CHART_CHANGE) return; chart_width = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS); chart_height = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS); chart_scale = (int)ChartGetInteger(0, CHART_SCALE); chart_first_vis_bar = (int)ChartGetInteger(0, CHART_FIRST_VISIBLE_BAR); chart_vis_bars = (int)ChartGetInteger(0, CHART_VISIBLE_BARS); chart_prcmin = ChartGetDouble(0, CHART_PRICE_MIN, 0); chart_prcmax = ChartGetDouble(0, CHART_PRICE_MAX, 0); if(chart_width != Canvas.Width() || chart_height != Canvas.Height()) Canvas.Resize(chart_width, chart_height);
Agora faremos algumas pequenas atualizações para fazer a função funcionar.
- Vamos atualizar nossa função Redraw, removendo os parâmetros adicionados no indicador anterior e adicionando a função DrawFilling.
- Adicionaremos nossa função Redraw em OnCalculation para atualizar o desenho quando os valores do indicador forem alterados.
- Alteraremos o nome do objeto passado como parâmetro ao chamar CreateBitmapLabel.
//+------------------------------------------------------------------+ //| Custom indicator redraw function | //+------------------------------------------------------------------+ void Redraw(void) { uint default_color = 0; color clrup = (color)PlotIndexGetInteger(0, PLOT_LINE_COLOR, 0); color clrdn = (color)PlotIndexGetInteger(1, PLOT_LINE_COLOR, 0); //--- canvas erase Canvas.Erase(default_color); //--- add first draw DrawFilling(ExtUpBuffer, ExtDownBuffer,clrup, clrdn, 128, InpMAShift); //--- canvas update Canvas.Update(); }
//--- the main loop of calculations for(int i=start; i<rates_total && !IsStopped(); i++) { ExtUpBuffer[i]=(1+InpDeviation/100.0)*ExtMABuffer[i]; ExtDownBuffer[i]=(1-InpDeviation/100.0)*ExtMABuffer[i]; } Redraw(); //--- OnCalculate done. Return new prev_calculated. return(rates_total);
Canvas.CreateBitmapLabel(0, 0, short_name, 0, 0, 200, 150, COLOR_FORMAT_ARGB_NORMALIZE);
Agora podemos ver como o gráfico fica com dois envelopes de diferentes períodos e um objeto retangular.
Como você pode ver, o problema com os indicadores foi resolvido, mas o problema com os objetos do gráfico ainda permanece, mas isso é assunto para outro capítulo.
Expansão do método para trabalhar em indicadores de subjanela
Vejamos a figura abaixo. Aqui vemos um indicador de subjanela usando a função DRAW_FILLING. Esta imagem foi retirada da documentação do MQL. Faremos a mesma coisa, mas com transparência usando CCanvas e, o mais importante, evitaremos problemas com áreas de sobreposição.
As alterações necessárias são as seguintes:
- Criar uma etiqueta bitmap na mesma subjanela em que o indicador está localizado.
- Alterar o tamanho do canvas com base no tamanho da subjanela em vez da janela principal do gráfico.
Para criar uma etiqueta bitmap na mesma subjanela e obter o tamanho da subjanela, precisamos descobrir em qual subjanela o indicador está localizado. Pode-se pensar que é apenas a última subjanela do gráfico, mas a plataforma permite que você coloque dois ou mais indicadores na mesma subjanela, e não necessariamente na última. Também precisamos de uma função que retorne o número da subjanela em que o indicador está localizado. Vamos dar uma olhada na seguinte função:
//+------------------------------------------------------------------+ //| return the number of the subwindow where the indicator is located| //+------------------------------------------------------------------+ int ChartIndicatorFind(string shortname) { int subwin = ChartGetInteger(0, CHART_WINDOWS_TOTAL); while(subwin > 0) { subwin--; int total = ChartIndicatorsTotal(0, subwin); for(int i = 0; i < total; i++) { string name = ChartIndicatorName(0, subwin, i); if(name == shortname) return subwin; } } return -1; }
No último indicador, usamos o exemplo do indicador Envelopes. Agora, usaremos o código da documentação (DRAW_FILLING) como fonte para o nosso exemplo. Podemos criar um novo indicador no mesmo diretório onde criamos os dois indicadores anteriormente. Vamos chamá-lo de SubwindowIndicator. Em seguida, copiamos o código da documentação.
O indicador é construído usando a função DRAW_FILLING. Como usaremos CCanvas para preencher o canal, podemos substituir o tipo de desenho por linhas. Abaixo estão as alterações nas propriedades do indicador.
#property indicator_plots 2 //--- plot Intersection #property indicator_label1 "Fast" #property indicator_type1 DRAW_LINE #property indicator_color1 clrRed #property indicator_width1 1 #property indicator_label2 "Slow" #property indicator_type2 DRAW_LINE #property indicator_color2 clrBlue #property indicator_width2 1
Alterações na função OnInit.
//--- indicator buffers mapping SetIndexBuffer(0,IntersectionBuffer1,INDICATOR_DATA); SetIndexBuffer(1,IntersectionBuffer2,INDICATOR_DATA); //--- PlotIndexSetInteger(0,PLOT_SHIFT,InpMAShift); PlotIndexSetInteger(1,PLOT_SHIFT,InpMAShift);
Também não precisamos do indicador para alterar a aparência da linha. Podemos comentar essa linha na função OnCalculate.
//--- If a sufficient number of ticks has been accumulated if(ticks>=N) { //--- Change the line properties //ChangeLineAppearance(); //--- Reset the counter of ticks to zero ticks=0; }
Agora podemos adicionar as variáveis de propriedades do gráfico e as funções criadas neste artigo. Neste indicador, os arrays que precisamos passar como parâmetros para a função DrawFilling têm nomes diferentes. Precisamos ajustá-los na função Redraw.
double IntersectionBuffer1[]; double IntersectionBuffer2[];
A função Redraw será assim:
//+------------------------------------------------------------------+ //| Custom indicator redraw function | //+------------------------------------------------------------------+ void Redraw(void) { uint default_color = 0; color clrup = (color)PlotIndexGetInteger(0, PLOT_LINE_COLOR, 0); color clrdn = (color)PlotIndexGetInteger(1, PLOT_LINE_COLOR, 0); //--- canvas erase Canvas.Erase(default_color); //--- add first draw DrawFilling(IntersectionBuffer1, IntersectionBuffer2, clrup, clrdn, 128, InpMAShift); //--- canvas update Canvas.Update(); }
Após compilar o código, obtemos o resultado esperado.
Considerações finais
Neste artigo, exploramos o uso do CCanvas, algumas propriedades do gráfico e como obter seus valores, além de como usá-los para realizar algumas transformações básicas de coordenadas que são úteis e aplicáveis para diversos propósitos. Em seguida, desenvolvemos um indicador com transparência e expandimos o método para trabalhar com indicadores de subjanela.
Os arquivos dos indicadores desenvolvidos no artigo estão anexados abaixo.
Traduzido do Inglês pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/en/articles/12357
- Aplicativos de negociação gratuitos
- 8 000+ sinais para cópia
- Notícias econômicas para análise dos mercados financeiros
Você concorda com a política do site e com os termos de uso