English Русский 中文 Español Deutsch 日本語
Diagramas horizontais nos gráficos do MetaTrader 5

Diagramas horizontais nos gráficos do MetaTrader 5

MetaTrader 5Exemplos | 19 fevereiro 2019, 08:21
3 393 2
Andrei Novichkov
Andrei Novichkov

Introdução

Embora a tarefa de plotar diagramas horizontais no gráfico do terminal não seja frequente, é o desenvolvedor que deve lidar com ela. Essa tarefa envolve indicadores de distribuição de volumes para um período específico. Também implica distribuição de preços, diversos livros de ofertas, etc. Adicionalmente, há outras mais exóticas, como a distribuição de leituras de indicadores personalizados (ou padrão). Mas, em qualquer caso, essas tarefas têm em comum a criação, a colocação no gráfico, o dimensionamento, o deslocamento e a exclusão de diagramas. Observemos algumas coisas:

  1. Pode haver vários diagramas.
  2. A grande maioria dos diagramas que nos interessam são colunares.
  3. As colunas dos diagramas são organizadas horizontalmente.

Vejamos um exemplo bastante comum de como esses gráficos podem parecer:



Este é outro exemplo. Os mesmos diagramas, mas desenhados por outras primitivas gráficas:


Neste caso, é um indicador de distribuição de volumes de ticks por dia. Este exemplo mostra claramente quais tarefas o desenvolvedor precisou resolver:

  • Criar um conjunto de objetos gráficos, atribuindo nomes exclusivos e colocando-os no gráfico.
  • Dimensionar e mover objetos conforme necessário.
  • Removê-los do gráfico no final do trabalho do indicador.

E mais uma vez, notamos que existem vários diagramas, o que ainda nos dá a oportunidade de falar sobre um array de diagramas.

Outro exemplo final:

Neste caso, vemos uma distribuição mais complicada de todos os mesmos volumes de ticks. Apesar de o diagrama parecer uma unidade, ele consiste em três diagramas diferentes localizados num só lugar, isto é:

  • "Volumes de ticks para venda"
  • "Volumes de ticks para compra"
  • "Volumes de ticks totais"

Alguém poderia perguntar: "será que existem maneiras mais fáceis de exibir os dados mostrados? É possível fazer isso sem controlar tantas primitivas gráficas? Sim, essas maneiras podem ser encontradas e discutidas quanto a eficácia. No entanto, é mais fácil de resolver todo o conjunto de tarefas mencionadas - no início do artigo - usando os diagramas horizontais mostrados nos exemplos.

Formulação do problema

Delineemos duas partes da implementação do plano:

  1. Como todas as primitivas gráficas têm coordenadas de âncora na forma de tempo e preços, é óbvio que de alguma forma devem ser obtidos arrays de âncora que permitam posicionar diagramas no gráfico.
  2. É necessário exibir diagramas e gerenciá-los, usando os arrays obtidos na primeira etapa.

No exemplo do nosso caso, formulamos as principais formas de ancorar objetos gráficos. As primeiras capturas de tela mostram, talvez, a disposição mais comum dos diagramas horizontais, isto é, a âncora ao início de um determinado período, neste caso, ao início do dia.

Isso, obviamente, não esgota a lista de opções de âncora para a coordenada de tempo. Outra opção é ancorar ao lado esquerdo ou direito da janela do terminal. Essa opção pode ser usada, por exemplo, caso o gráfico cubra um período muito grande e seu início esteja fora da janela. Nesse caso, pode-se usar a âncora à borda esquerda da janela.

Ou outra opção, quando o gráfico está ancorado ao período atual, mas o desenvolvedor não quer construir na área de trabalho do gráfico a mais. Quando isso acontece, pode-se usar o lado direito da janela do terminal. Em qualquer caso, uma das coordenadas temporais das primitivas gráficas que compõem o diagrama será a mesma. A outra coordenada será calculada e determinará o “comprimento da coluna horizontal” do diagrama.

Em relação à âncora de preço de primitivas gráficas, tudo é muito mais simples. Uma certa diferença de preço é dividida em intervalos com um incremento constante. Por exemplo, pode-se pegar o intervalo de preço original como cem por cento e o incremento como dez por cento. Assim, serão obtidos diagramas com um número constante de "barras horizontais", o que levará a um estrito arredondamento dos resultados em alguns casos. Portanto, neste artigo, vamos aplicar um esquema mais eficiente, que será considerado um pouco mais tarde.
Do acima exposto, podemos concluir que, neste caso, pode não ser necessário um array de âncora segundo o preço. Para calcular a i-ésima âncora de preço, pode-se usar esta fórmula simples: 

i-ésima âncora de preço = início do intervalo de preços do diagrama + i * incremento de divisão do intervalo.

Lembramos que para os exemplos já dados com volumes de ticks, a diferença de preço na qual os diagramas podem ser construídos é a diferença entre o Low e o High do período em análise.

Ainda deve ser detalhada a questão da exibição dos diagramas. Assim, já obtivemos arrays com âncoras para diagramas de acordo com o princípio um diagrama, um conjunto de arrays para âncora. Outra observação óbvia é que muitas vezes no gráfico existem diagramas construídos a partir das mesmas primitivas gráficas, da mesma cor, feitos no mesmo “estilo”. Isso se refere, por exemplo, aos diagramas nas primeiras e segundas capturas de tela. Mas, na terceira tela, todos os gráficos são diferentes. Eles diferem em cor e em "direção". Uma coluna é "direcionada" da direita para a esquerda e as outras duas da esquerda para a direita. Seria lógico combinar os diagramas do "mesmo tipo" - como nas duas primeiras capturas de tela - num array, e fornecer seu controle a algum gerenciador. Para padronizar a tarefa deve ser usado o princípio:

  • Criar, para cada conjunto de diagramas do "mesmo tipo", seu próprio gerenciador, mesmo se o conjunto consistir num diagrama. Assim, para os gráficos nas primeiras duas capturas de tela, será criado um array de três gráficos (pelo menos) sob o controle de um gerenciador e, para os gráficos da terceira captura de tela, precisar-se-ão de três arrays baseados num diagrama para cada um deles e três gerenciadors, um para cada um dos arrays.

Desse modo, temos um diagrama de projeção. No entanto, há certa questão importante. Deve ser lembrado que os gráficos horizontais desenham uma certa distribuição no gráfico, que tem a ver com volumes de ticks, com o preço, etc. Portanto, os métodos e princípios para obter arrays de origem para ancorar primitivas gráficas podem ser muito diferentes e não se deve tentar criar um arquivo de biblioteca para essa parte da tarefa.

Em particular, ao criar um indicador educacional da distribuição dos volumes de ticks por dia, será aplicada uma abordagem diferente e mais eficiente. Deste artigo se pode aprender mais uma maneira. Em outras palavras, a primeira parte da tarefa é resolvida todas as vezes de maneiras diferentes. Pelo contrário, a segunda parte da tarefa, ou seja, criar um array de diagramas e gerenciar esse array com a ajuda de um gerenciador, ou vários agrupamentos "gerenciador - array de diagramas", é quase a mesma em todos os casos. Isso faz com que se possa criar um arquivo de biblioteca, para ser incluído em todos os projetos onde é necessário criar gráficos horizontais.


Só mais uma pergunta, isto é, o tempo todo se fala de primitivas gráficas, é hora de decidir exatamente em que consistem os diagramas. Os diagramas consistem em segmentos de linhas horizontais ou de retângulos. Estas são as duas opções mais orgânicas que são usadas.


Neste ponto, a formulação do problema pode ser considerada concluída e, assim, seguir diretamente para o código:

Constantes e parâmetros de entrada

É bastante óbvio que muitos dos parâmetros mencionados nos gráficos futuros são armazenados usando os tipos enumerados.

Localização do diagrama:

enum HD_POSITION
{
        HD_LEFT  = -1,
        HD_RIGHT =  1,
        HD_CNDLE =  2 
};

Existem três opções para colocar diagramas — âncora à borda esquerda do terminal (HD_LEFT), à direita (HD_RIGHT) e ao candle (HD_CNDLE), ou aos candles. Em todas as três capturas de tela no início do artigo, os gráficos são organizados usando HD_CNDLE. Nos dois primeiros com âncora aos candles no início de certos períodos (no início do dia), no terceiro - a um candle, localizado no início do dia atual.

Aparência do diagrama (aparência das primitivas gráficas):

enum HD_STYLE 
{
        HD_LINE      = OBJ_HLINE,        
        HD_RECTANGLE = OBJ_RECTANGLE,    
};

Há duas aparências, isto é, trechos de linhas horizontais (HD_LINE) e retângulos (HD_RECTANGLE). Na primeira e terceira capturas de tela no início do artigo, os diagramas consistem em primitivas HD_LINE e na segunda em HD_RECTANGLE.

Direção dos diagramas de "colunas horizontais":

enum HD_DIRECT 
{
   HD_LEFTRIGHT = -1,
   HD_RIGHTLEFT =  1 
};

Na terceira captura de tela no início do artigo, o diagrama que consiste em segmentos de vermelho é desenhado como HD_RIGHTLEFT, enquanto os outros dois são plotados como HD_LEFTRIGHT.

O último tipo enumerado está relacionado ao número de níveis do gráfico. Foi mencionado anteriormente que, para calcular o número de níveis, seria usado um método melhor do que simplesmente dividir o intervalo de preços num determinado número de níveis. É muito importante observar que esse tipo enumerado se refere à primeira parte Formulação do problema, portanto, o arquivo de biblioteca final não é incluído.

Descrevemos o método usado. Ele é muito simples e se resume a arredondar o nível de preços para os dez mais próximos, ou para cem. Assim, o tamanho do incremento dos níveis de preço também é igual a dez ou cem. O número de níveis de preços com essa abordagem é obtido como variável. Para aqueles que desejam obter cálculos com precisão máxima (junto com o aumento do consumo de recursos), o método HD_MIN é deixado sem arredondamento:

enum HD_ZOOM {
   HD_MIN    = 0,  //1
   HD_MIDDLE = 1,  //10
   HD_BIG    = 2   //100
}; 

Por padrão, é usado a maneira HD_MIDDLE.

Examinemos um exemplo. Como objeto de desenvolvimento, adotamos um indicador de estudo que desenha a distribuição dos volumes de ticks no gráfico. Um indicador semelhante é exemplificado nas duas primeiras imagens no início do artigo.

Continuamos com o bloco de parâmetros de entrada:

input HD_STYLE        hdStyle      = HD_LINE;
input int             hdHorSize    = 20;
input color           hdColor      = clrDeepSkyBlue;
input int             hdWidth      = 2;
input ENUM_TIMEFRAMES TargetPeriod = PERIOD_D1;
input ENUM_TIMEFRAMES SourcePeriod = PERIOD_M1;
input HD_ZOOM         hdStep       = HD_MIDDLE;   
input int             MaxHDcount   = 5;
input int             iTimer       = 1;   

Imediatamente surge a pergunta: por que não há parâmetro responsável pela posição? A resposta é óbvia. Para o indicador necessário, somente é aplicável um método de localização, nomeadamente HD_CNDLE. Portanto, não pode ser especificado.

O destino do parâmetro HD_STYLE é bastante óbvio e não requer esclarecimentos adicionais.

  • O parâmetro hdHorSize desempenha um papel muito grande. Ele determina o tamanho máximo da “coluna horizontal” do diagrama nos candles. Neste caso, a maior "coluna horizontal" não pode exceder vinte candles. É claro que quanto maior esse parâmetro, mais preciso é o diagrama. A limitação é que, com um tamanho grande desse parâmetro, os diagramas começam a se sobrepor uns aos outros.
  • Os parâmetros hdColor e hdWidth se referem à aparência dos diagramas, isto é, à cor e à espessura das linhas, respectivamente.
  • O parâmetro TargetPeriod contém o período em estudo. Neste caso, o indicador mostra a distribuição de volumes de ticks por dia.
  • O parâmetro SourcePeriod contém o período a partir do qual o indicador usa os dados de origem para criar a distribuição. Nesse caso, é usado o período de minuto. Este parâmetro deve ser usado com cautela. Se o período em estudo for um timeframe mensal, os cálculos podem demorar.
  • O parâmetro hdStep contém um parâmetro para arredondar os níveis de preço. Já falamos sobre esse parâmetro e o que afeta sua escolha.
  • O parâmetro MaxHDcount contém o número máximo de diagramas num gráfico. Deve ser lembrado que cada diagrama consiste em muitas primitivas gráficas e muitos diagramas podem retardar a operação do terminal.
  • O parâmetro iTimer contém a frequência de ativação do temporizador. Quando ativado, é verificada a criação de novos candles e são executadas as ações necessárias. Poderia se colocar aqui o resultado da chamada PeriodSeconds(SourcePeriod). No entanto, o padrão é um segundo, o que é feito para determinar com mais precisão o tempo do surgimento de novos candles.

Inicialização

Nesta etapa, é preciso criar um objeto para gerenciar diagramas. Como todos os diagramas são do mesmo tipo, o gerenciador precisa de apenas um. Como a classe do gerenciador em si ainda não está escrita, lembremos que ela é criada aqui no manipulador OnInit(). Aqui são criados dois diagramas:

  1. O diagrama responsável pela distribuição dos volumes de ticks para o período atual. Este diagrama será redesenhado periodicamente.
  2. O diagrama que desenha a distribuição dos volumes de ticks no histórico. Essas distribuições não são redesenhadas, portanto, após a plotagem, o diagrama “as esquece”, dando controle ao terminal.

Esse esquema é a abordagem mais eficaz, mencionada no início do artigo, assim, o indicador não tenta gerenciar as primitivas gráficas dos gráficos cuja aparência não muda.

Em seguida, as variáveis são inicializadas para cálculo subsequente dos níveis de preços em incrementos. Estas variáveis são duas, são derivadas de Digit() e Point():

   hdDigit = Digits() - (int)hdStep; 
   switch (hdStep)
    {
      case HD_MIN:
         hdPoint =       Point();
         break;
      case HD_MIDDLE:
         hdPoint = 10 *  Point();
         break;     
      case HD_BIG:
         hdPoint = 100 * Point();
         break;      
      default:
         return (INIT_FAILED);
    }

No mesmo manipulador, são executadas algumas ações menores e o início do temporizador.


Cálculos básicos

A próxima tarefa é dividida em duas etapas:

  1. Cálculo de dados necessários e plotagem do número de diagramas necessário, exceto o atual. Esses diagramas não são mais alterados e basta exibi-los uma vez.
  2. Cálculo dos dados necessários e desenho do gráfico para o período, incluindo o presente. Os cálculos neste item têm que ser repetidos periodicamente. Isso pode ser feito no manipulador OnTimer().

Vamos fazer um pouco do trabalho no manipulador OnCalculate() enquanto estiver sob a forma de pseudo-código:

int OnCalculate(...)
  {
   if (prev_calculated == 0 || rates_total > prev_calculated + 1) {
   }else {
      if (!bCreateHis) 
       {
         int br = 1;
         while (br < MaxHDcount) {
           {
            if(Calculate for bar "br") 
                 {
                  sdata.bRemovePrev = false;
                  Print("Send data to the new Diagramm");
                 }

           }
         ChartRedraw();
         bCreateHis = true;
      }
   }  
   return(rates_total);
  }  

Aqui são executados os cálculos necessários e a plotagem do número de diagramas necessário, exceto o atual. Para isso, um ciclo executa cálculos para cada barra do tempo gráfico TargetPeriod, começando com o primeiro e terminando com MaxHDcount. Se os cálculos forem bem-sucedidos, no mesmo ciclo, um comando é dado ao gerenciador para plotar o diagrama, passando novos dados para o gerenciador. No final de todo o ciclo, é redesenhado o gráfico e é definido um sinalizador para indicar que essa parte do trabalho não é mais necessária. Os gráficos em si são agora controlados pelo terminal.

A criação e a plotagem do diagrama que inclui o período atual são executadas no manipulador OnTimer(). O pseudocódigo deste manipulador não é mostrado devido à sua óbvia simplicidade:

  1. Aguardamos o surgimento de um novo candle no período gráfico SourcePeriod.
  2. Executamos os cálculos necessários.
  3. Enviamos os dados para o gráfico do período atual a fim de criar novas primitivas e plotar.
Retornamos um pouco mais tarde para a função em que os cálculos principais são realizados para uma barra específica do intervalo de tempo TargetPeriod.

Outros manipuladores e funções de indicadores não são tão interessantes e estão disponíveis no código anexo. Agora é hora de começar a descrever as classes responsáveis por criar, por desenhar e por gerenciar diagramas:

Classe de gerenciamento de diagramas

Vamos começar com o gerenciador de diagramas horizontal. Esta é uma classe que não contém primitivas gráficas, mas controla um array de outras classes que essas primitivas contêm. Como todos os diagramas num gerenciador são do mesmo tipo (como mencionado acima), muitas propriedades desses diagramas são as mesmas. Portanto, não faz sentido manter o mesmo conjunto de propriedades em cada diagrama, mas vale a pena colocar um único conjunto de propriedades no gerenciador. Vamos chamar a classe de gerenciador "CHDiags" e começar a escrever o código:

  1.   Campos privados da classe CHDiags contêm, entre outras coisas, um conjunto de propriedades que são as mesmas para todos os diagramas controlados por este gerenciador:
private:    
                HD_POSITION m_position;  
                HD_STYLE    m_style;     
                HD_DIRECT   m_dir;       
                int         m_iHorSize;     
                color       m_cColor;    
                int         m_iWidth;     
                int         m_id;
                int         m_imCount;       
                long        m_chart;    
                datetime    m_dtVis; 
   static const string      m_BaseName;  
                CHDiagDraw* m_pHdArray[];

Descrevemos este conjunto:

  • m_position,  m_style,  m_dir — estes três parâmetros descrevem a âncora e a aparência dos diagramas, já foi falado sobre eles.
  • m_iHorSize — este é o maior tamanho horizontal possível de gráfico. Também foi falado sobre esse parâmetro.
  • m_cColor и  m_iWidth — cor do diagrama e espessura da linha.
Os campos descritos acima são propriedades uniformes para todos os diagramas controlados pelo gerenciador.
  • m_id — identificador exclusivo do gerenciador. Dado que o gerenciador pode não ser um, ele deve ter um identificador único. Ele é necessário para gerar nomes exclusivos de objetos.
  • m_chart — ID do gráfico no qual os diagramas são exibidos. Por padrão, o valor desse campo é zero (gráfico atual).
  • m_imCount — número máximo de diagramas em um gráfico. Finalmente, o número de diagramas no gráfico é determinado por este e pelos seguintes campos.
  • m_dtVis — não se devem criar diagramas à esquerda dessa marca de tempo.
  • m_BaseName — parâmetro extremamente importante que define o nome "base". Todos os elementos dos diagramas, assim como os próprios diagramas, devem ter nomes exclusivos para criação bem-sucedida. Esses nomes serão dados com base no tal nome "base".
Todos os campos acima estão disponíveis usando as funções do tipo GetXXXX()
  • m_pHdArray[] — Matriz com ponteiros para objetos contendo diagramas individuais. Este campo não é uma propriedade e não tem função GetXXXX().

Não são fornecidas funções do tipo SetXXXX() para propriedades. Todos eles (exceto m_BaseName) são definidos no construtor da classe. Outra exceção é o campo m_dtVis. Ele é definido no construtor por um parâmetro do tipo bool, que tem o seguinte intuito:

  • Exibir gráficos apenas em candles visíveis na janela do terminal. Isso é feito para não carregar o terminal mostrando diagramas que estão à esquerda da borda esquerda do terminal. Por padrão, é definido true.

Após criar o gerenciador, podem-se criar objetos — diagramas . Isso faz o método da classe CHDiags:

int CHDiags::AddHDiag(datetime dtCreatedIn)

O método retorna o índice do objeto criado da classe CHDiagDraw no array m_pHdArray do gerenciador ou -1 em caso de erro. Como parâmetro, a âncora de tempo do início dos diagramas é passada para o método dtCreatedIn. Por exemplo, para o indicador analisado, aqui é passado o tempo de abertura do candle diário. Se a âncora de tempo não for usada (os candles são ancorados às bordas da janela do terminal), aqui é preciso passar TimeCurrent(). Nesse caso, se o gráfico estiver localizado à esquerda da marca de tempo do campo m_dtVis, o objeto não será criado. No código a seguir fica claro como o método funciona:

int CHDiags::AddHDiag(datetime dtCreatedIn) {
   if(dtCreatedIn < m_dtVis ) return (-1);
   int iSize = ArraySize(m_pHdArray);
   if (iSize >= m_imCount) return (-1);
   if (ArrayResize(m_pHdArray,iSize+1) == -1) return (-1);
   m_pHdArray[iSize] = new CHDiagDraw(GetPointer(this) );
   if (m_pHdArray[iSize] == NULL) {
      return (-1);
   }
   return (iSize);
}//AddHDiag()

Como se pode ver, o método executa algumas verificações, aumenta o tamanho do array para armazenar os diagramas, cria o objeto desejado, passando um ponteiro para o próprio gerenciador, a fim de acessar posteriormente as propriedades.

O gerenciador possui outros métodos que, embora não diretamente, permitem interagir com o diagrama, mas exclusivamente através do gerenciador de diagramas:

   bool        RemoveDiag(const string& dname);
   void        RemoveContext(int index, bool bRemovePrev);
   int         SetData(const HDDATA& hddata, int index); 
  1. O primeiro método remove o diagrama do gerenciador do gráfico, usando o nome do diagrama como um parâmetro. Essa é atualmente uma opção reservada.
  2. O segundo exclui apenas as primitivas gráficas nas quais consiste o diagrama. O diagrama é removido do gráfico, mas está presente no gerenciador, estando “vazio”. O valor do sinalizador bRemovePrev é explicado mais adiante.
  3. O terceiro método transfere para o diagrama a estrutura com os dados iniciais a fim de criar primitivas gráficas e de desenhar o diagrama. Neste e nos métodos anteriores, o índice do diagrama no array do gerenciador m_pHdArray é usado como um parâmetro.

O último método que merece uma menção rápida é o método da classe CHDiags:

void        Align();

O método é chamado se os diagramas forem criados jundo da boda esquerda ou junto da borda direita da janela do terminal. Em seguida, esse método é chamado no manipulador OnChartEvent do evento CHARTEVENT_CHART_CHANGE, retornando diagramas para locais antigos.

Outros métodos da classe do gerenciador de diagramas CHDiags são secundários e estão disponíveis no arquivo anexado.

Classe de plotagem e controle de primitivas gráficas

Vamos chamar essa classe de "CHDiagDraw" e herdá-la a partir do CObject. No construtor de classe, obtemos um ponteiro para o gerenciador de controle (salvamo-lo no campo m_pProp). Neste ponto, também é definido o nome exclusivo do diagrama.

Em seguida, precisamos implementar o método Type():

int CHDiagDraw::Type() const
  {
   switch (m_pProp.GetHDStyle() ) {
      case HD_RECTANGLE:
         return (OBJ_RECTANGLE);
      case HD_LINE:
         return (OBJ_TREND);
   }
   return (0);
  }

O tipo de diagrama é exibido para os mesmos tipos de primitivas gráficas usadas, o que é bastante lógico.

Os principais cálculos na classe CHDiagDraw são executados pelo método chamado pelo método SetData do gerenciador:

int       SetData(const HDDATA& hddata);

A tarefa do método é determinar o tamanho do diagrama e criar o número necessário de primitivas num determinado local no gráfico. Para isso, uma referência à instância de estrutura é passada para o método no ponto de chamada:

struct HDDATA
 {
   double   pcur[];
   double   prmax; 
   double   prmin;   
   int      prsize;
   double   vcur[];
   datetime dtLastTime;
   bool     bRemovePrev;
 };

Descrevemos os campos dessa estrutura em mais detalhes:

  • pcur[] — Matriz dos níveis de preço do diagrama. Para as primitivas gráficas criadas, essa é um array de âncoras de preços.
  • prmax — Máximo valor horizontal que o gráfico pode ter. Nesse caso, esse é o valor máximo do volume de ticks negociado num determinado nível.
  • prmin — Parâmetro reservado.
  • prsize — Número de níveis do diagrama. Em outras palavras, esse é o número de primitivas em que consiste o diagrama.
  • vcur[] — Matriz de valores que define o "tamanho horizontal das barras" no diagrama. Nesse caso, o array contém volumes de ticks negociados nos níveis respectivos do array pcur[]. O tamanho dos arrays pcur e vcur deve corresponder e ser igual a prsize.
  • dtLastTime — Localização do gráfico. Para primitivas gráficas, isso é a âncora de tempo. Esse campo tem uma prioridade mais alta do que o argumento do método AddHDiag do gerenciador.
  • bRemovePrev — Se este sinalizador estiver definido como true, cada vez que os dados forem atualizados, o gráfico será completamente redesenhado, removendo as antigas primitivas gráficas. Se o sinalizador for definido como false, o diagrama será “recusado” para gerenciar as antigas primitivas gráficas e desenhará o novo diagrama sem excluir as antigas primitivas gráficas, como se “esquecesse” delas.

Em vista de sua importância, aqui está o código do método SetData:

int CHDiagDraw::SetData(const HDDATA &hddata) 
  {
   RemoveContext(hddata.bRemovePrev);
   if(hddata.prmax == 0.0 || hddata.prsize == 0) return (0);
   double dZoom=NormalizeDouble(hddata.prmax/m_pProp.GetHDHorSize(),Digits());
   if(dZoom==0.0) dZoom=1;
   ArrayResize(m_hItem,hddata.prsize);
   m_hItemCount=hddata.prsize;
   int iTo,t;
   datetime dtTo;

   string n;
   double dl=hddata.pcur[0],dh=0;
   

   GetBorders(hddata);
   for(int i=0; i<hddata.prsize; i++) 
     {
      if (hddata.vcur[i] == 0) continue;
      t=(int)MathCeil(hddata.vcur[i]/dZoom);
      switch(m_pProp.GetHDPosition()) 
        {
         case HD_LEFT:
         case HD_RIGHT:
            iTo=m_iFrom+m_pProp.GetHDPosition()*t;
            dtTo=m_pProp.GetBarTime(iTo);
            break;
         case HD_CNDLE:
            iTo   = m_iFrom + m_pProp.GetHDDirect() * t;
            dtTo  = m_pProp.GetBarTime(iTo);
            break;
         default:
            return (-1);
        }//switch (m_pProp.m_position)
      n=CHDiags::GetUnicObjNameByPart(m_pProp.GetChartID(),m_hname,m_iNameBase);
      m_iNameBase++;
      bool b=false;
      switch(m_pProp.GetHDStyle()) 
        {
         case HD_LINE:
            b=CHDiags::ObjectCreateRay(m_pProp.GetChartID(),n,dtTo,hddata.pcur[i],m_dtFrom,hddata.pcur[i]);
            break;
         case HD_RECTANGLE:
            if(dl!=hddata.pcur[i]) dl=dh;
            dh=(i == hddata.prsize-1) ? hddata.pcur[i] :(hddata.pcur[i]+hddata.pcur[i+1])/2;
            b = ObjectCreate(m_pProp.GetChartID(),n,OBJ_RECTANGLE,0,dtTo,dl,m_dtFrom,dh);
            break;
        }//switch(m_pProp.m_style)
      if(!b) 
        {
         Print("ERROR while creating graphic item: ",n);
         return (-1);
           } else {
         m_hItem[i]=n;
         ObjectSetInteger(m_pProp.GetChartID(), n, OBJPROP_COLOR, m_pProp.GetHDColor() );
         ObjectSetInteger(m_pProp.GetChartID(), n, OBJPROP_WIDTH, m_pProp.GeHDWidth() );
         ObjectSetInteger(m_pProp.GetChartID(), n, OBJPROP_SELECTABLE, false);
         ObjectSetInteger(m_pProp.GetChartID(), n, OBJPROP_BACK, true);
        }//if (!ObjectCreateRay(n, dtTo, hddata.pcur[i], m_dtFrom, hddata.pcur[i]) )    
     }// for (int i = 0; i < l; i++)      
   return (hddata.prsize);
  }//int CHDiagDraw::SetData(const HDDATA& hddata)

A primeira coisa que o método faz é limpar e dimensionar. Há um comprimento máximo para as "colunas horizontais" do diagrama e há um tamanho horizontal máximo para os diagrama nos candles. Pode-se obter o fator de proporcionalidade. É fácil notar que, como resultado, o arredondamento é forçado, o tamanho do diagrama de candles é um número inteiro.

Em seguida, é preparado um array para armazenar os nomes das primitivas. São calculados parâmetros adicionais de âncora do diagrama, isto é, o número do candle e a âncora de tempo no método GetBorders, após o qual no ciclo é determinada a segunda âncora de tempo do diagrama. Assim, jã estão todas as âncoras para criar uma primitiva gráfica. Obtemos nomes exclusivos de primitivas e os criamos sequencialmente usando os parâmetros recebidos. Armazenamos os nomes das primitivas num array. Ajustamso as propriedades das primitivas. Aqui termina o trabalho, uma vez que o diagrama fica criado. O método retorna o número de níveis do diagrama.

Talvez o método pareça muito grande e seja possível mover o código para criar e plotar primitivas para um método separado e protegido. No entanto, ambas as partes do método parecem organicamente compatíveis, a segunda parte é uma continuação clara da primeira. Essa consideração, além da falta de vontade para criar várias chamadas adicionais para um novo método com um grande número de argumentos, levou ao desenvolvimento do método SetData em sua forma atual.

Outras funções da classe CHDiagDraw são secundárias e são usadas para integrar com o gerenciador.

Deve-se notar que o usuário não chama nenhum método da classe CHDiagDraw diretamente, mas age exclusivamente através do gerenciador de diagramas horizontais.

Todo o código acima para a classe do gerenciador de diagramas horizontais, a classe para desenhar diagramas, estruturas e enumerações está disponível no arquivo HDiagsE.mqh anexado.

E agora pode-se retornar ao código do EA e olhar com mais detalhes seu conteúdo, sem notas e usando pseudo-código:

Voltando ao indicador

Declaramos dois objetos e variáveis num contexto global:

CHDiags    *pHd;
int         iCurr, iCurr0;
HDDATA      sdata;

É um ponteiro para um gerenciador de diagramas, índices de diagramas e uma estrutura para transferir dados do diagrama.

Ciamos o gerenciador e os dois diagramas imediatamente em OnInit(), usando os parâmetros de entrada do indicador:

   pHd         = new CHDiags(HD_CNDLE, hdStyle, HD_LEFTRIGHT, hdHorSize, hdColor, hdWidth, 0, MaxHDcount);
   if(pHd      == NULL) return (INIT_FAILED);
   iCurr       = pHd.AddHDiag(TimeCurrent() );
   if(iCurr  == -1) return (INIT_FAILED); 
   iCurr0       = pHd.AddHDiag(TimeCurrent() );
   if(iCurr0  == -1) return (INIT_FAILED);    

Esta é a aparência da parte do manipulador OnCalculate para a qual teve que ser usado anteriormente o pseudo-código:

        {
         int br=1;
         while(br<MaxHDcount) 
           {
            if(PrepareForBar(br++,sdata)) 
              {
               sdata.bRemovePrev = false;
               if(iCurr!=-1) 
                 {
                  Print(br-1," diag level: ",pHd.SetData(sdata,iCurr));
                 }
              }
           }
         ChartRedraw();
         bCreateHis=true;
        }

Resta considerar a função que, para a barra selecionada a partir do período para o qual a distribuição é construída (TargetPeriod), preenche a estrutura HDDATA com dados:

bool PrepareForBar(int bar, HDDATA& hdta) {

   hdta.prmax  = hdta.prmin  = hdta.prsize  = 0;
   int iSCount;
   datetime dtStart, dtEnd;
   dtEnd = (bar == 0)? TimeCurrent() : iTime(Symbol(), TargetPeriod, bar - 1);
   hdta.dtLastTime = dtStart = iTime(Symbol(), TargetPeriod, bar);
   
   hdta.prmax = iHigh(Symbol(), TargetPeriod, bar);
   if(hdta.prmax == 0) return (false);
   hdta.prmax      = (int)MathCeil(NormalizeDouble(hdta.prmax, hdDigit) / hdPoint );
   
   hdta.prmin = iLow(Symbol(), TargetPeriod, bar);
   if(hdta.prmin == 0) return (false);
   hdta.prmin      = (int)MathCeil(NormalizeDouble(hdta.prmin, hdDigit) / hdPoint );

   iSCount = CopyRates(Symbol(), SourcePeriod, dtStart, dtEnd, source);
   if (iSCount < 1) return (false);
   
   hdta.prsize = (int)hdta.prmax - (int)hdta.prmin + 10;
   
   ArrayResize(hdta.pcur,  hdta.prsize);
   ArrayResize(hdta.vcur,  hdta.prsize);
   ArrayInitialize(hdta.pcur, 0);
   ArrayInitialize(hdta.vcur, 0);
   
   double avTick;
   int i, delta;
   hdta.prmax = 0;
   
   for (i = 0; i < hdta.prsize; i++) hdta.pcur[i] = (hdta.prmin + i) * hdPoint;
   int rs = 0;
   for (i = 1; i < iSCount; i++) {
      if (source[i].tick_volume == 0.0) continue;
      if (!MqlRatesRound(source[i], (int)hdta.prmin) ) continue;
      delta = (int)(source[i].high - source[i].low);
      if (delta == 0) delta = 1;
      avTick = (double)(source[i].tick_volume / delta);
      int j;
      for (j = (int)source[i].low; j <= (int)(source[i].low) + delta; j++) {
         if (j >= hdta.prsize) {
            Print("Internal ERROR. Wait for next source period or switch timeframe");
            return false;
         }
         hdta.vcur[j] += avTick;
         if (hdta.vcur[j] > hdta.prmax) hdta.prmax = (int)hdta.vcur[j];
      }//for (int j = (int)source[i].low; j <= (int)(source[i].low) + delta; j++)   
      if (j > rs) rs = j; //real size
   }//for (int i = 1; i < iSCount; i++)
   hdta.prsize = rs + 1;
   return (true);
}  

No início, o método deixa claro para qual período precisa fazer cálculos. Ele também determina os limites da faixa de preço para cálculos, levando em conta a divisão posterior em níveis para a plotagem.

Em seguida, são lidos MqlRates com o período gráfico que é tomado como a fonte de dados para o período que acabou de ser definido. Para cada estrutura de MqlRates obtida, considera-se que o volume de ticks tick_volume é distribuído uniformemente na faixa de low a high da estrutura. Conhecendo os limites já calculados da faixa de preço para todo o diagrama futuro, essa distribuição de volume de ticks é posicionada no diagrama. Assim, é formada o array de distribuição de volumes de ticks para o período desejado.


Os cálculos terminam com a determinação do tamanho real do array de distribuição de volumes de ticks e, consequentemente, o número de primitivas gráficas no futuro diagrama.

Isso conclui o cálculo, os campos da estrutura de dados para o diagrama são preenchidos e estão prontos para transferência pelo método SetData(...).

O esquema de trabalho geral com as classes descritas é o seguinte:

  1. Inserimos HDiagsE.mqh.
  2. É criado um objeto, isto é, um gerenciador para cada grupo de diagramas de “mesmo tipo”.
  3. Com a chamada do método do gerenciador AddHDiag são criados os diagramas. O método retorna seu índice num array conhecido pelo gerenciador.
  4. Ao chamar o método do gerenciador RemoveContext, o diagrama é limpo de dados irrelevantes. Novos dados são transferidos para o diagrama chamando o método do gerenciador SetData e transmitindo-lhe uma estrutura HDDATA com dados. O receptor da chamada assume a responsabilidade de preencher corretamente os campos dessa estrutura.
  5. Se necessário, os diagramas podem ser pressionados para o lado esquerdo ou direito da janela do terminal, chamando o método do gerenciador Align.
  6. Todos os diagrama são destruídos no destruidor da classe do gerenciador CHDiags.

O código completo do indicador pode ser encontrado no arquivo VolChart.mq5 anexado e no arquivo de biblioteca em HDiagsE.mqh.

Sobre os nomes

Praticamente todos os objetos relacionados a diagramas horizontais possuem nomes. Eles são formados hierarquicamente da seguinte forma:

  1. O gerenciador possui um campo privado m_BaseName que define o "nome base". Com este nome começa todo o resto.
  2. Ao criar um objeto gerenciador, ele recebe um identificador exclusivo. O código de chamada é responsável pela exclusividade desse parâmetro. O campo m_BaseName e esse identificador formam o nome do gerenciador.
  3. Ao criar um objeto de gráfico, ele também obtém um nome exclusivo com base no nome do gerenciador.
  4. Por fim, as primitivas gráficas criadas no objeto de diagrama recebem seus nomes exclusivos com base no nome do objeto de diagrama do proprietário das primitivas.

O esquema usado permite filtrar facilmente objetos necessários e gerenciá-los.

Mais um indicador

Vamos refazer o indicador já desenvolvido num indicador que plota a distribuição dos volumes de ticks na forma de um diagrama fixo no lado direito da janela do terminal:


Para fazer isso, executamos alterações mínimas no código:

  • É necessário alterar parte do código de inicialização.
       pHd         = new CHDiags(HD_RIGHT, hdStyle, HD_RIGHTLEFT, hdHorSize, hdColor, hdWidth, 0, MaxHDcount);
       if(pHd      == NULL) return (INIT_FAILED);
       iCurr       = pHd.AddHDiag(TimeCurrent() );
       if(iCurr    == -1) return (INIT_FAILED); 
    
  • Remove todo o código do manipulador OnCalculate.
  • Adiciona o manipulador OnChartEvent
    void OnChartEvent(const int id,
                      const long &lparam,
                      const double &dparam,
                      const string &sparam)
      {
          switch (id) {
             case CHARTEVENT_CHART_CHANGE:
                pHd.Align();
                break;
            default:
                break;    
          }//switch (id)
      }  
    
Isso é tudo, o novo indicador é criado e fica funcionando. Seu código completo está no arquivo anexado VolChart1.mq5.

Fim do artigo

Bem, tivemos a oportunidade de criar gráficos horizontais incluindo apenas um arquivo. Agora a tarefa do desenvolvedor é preparar os dados para montagem, tópico esse que neste artigo desempenhou um papel secundário. O código do arquivo de biblioteca sugere um possível desenvolvimento, tendo em vista a disponibilidade de métodos reservados. Também é possível adicionar outras primitivas gráficas para plotagem.

Não esqueça que os indicadores em anexo são destinados a demonstração e não podem ser aplicados em negociações reais. Em particular, deve-se prestar atenção ao fato de que não devem haver artefatos no período gráfico que é usado como fonte de dados.

Programas e arquivos usados no artigo:

 # Nome
Tipo
 Descrição
1 VolChart.mq5 Indicador
Indicador da distribuição dos volumes de tick.
2
HDiagsE.mqh Arquivo de biblioteca
Arquivo de biblioteca com gerenciador de gráficos horizontais.
3
VolChart1.mq5
Indicador
Indicador da distribuição dos volumes de tick, ancorado ao lado direito da janela do terminal.


Traduzido do russo pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/ru/articles/4907

Arquivos anexados |
HDiagsE.mqh (36.66 KB)
VolChart.mq5 (12.65 KB)
VolChart1.mq5 (12.21 KB)
Últimos Comentários | Ir para discussão (2)
Enoir Heringer Da Silveira
Enoir Heringer Da Silveira | 22 fev 2019 em 18:21
gostei muito, mais não consegui fazer funcionar esse 
  • "Volumes de ticks para venda"
  • "Volumes de ticks para compra"
  • "Volumes de ticks totais"
se puder me ajudar
Andrei Novichkov
Andrei Novichkov | 22 fev 2019 em 20:49
Enoir Heringer Da Silveira:
Мне действительно понравилось это, но я не мог заставить это работать.
  • "Объемы продаж клещей"
  • «Объемы тиков для покупки»
  • «Всего тиковых объемов»
если вы можете мне помочь
В статье описан подход, сам принцип. Индикаторы не предназначены для конкретной работы. Они изготовлены только для демонстрации этого принципа. В Маркете есть Демо индикатор на этом принципе. Он распространяется бесплатно. Название можно увидеть в моем профиле.
Uso Prático das Redes Neurais de Kohonen na Negociação Algorítmica. Parte I. Ferramentas Uso Prático das Redes Neurais de Kohonen na Negociação Algorítmica. Parte I. Ferramentas
O presente artigo desenvolve a ideia de usar os Mapas de Kohonen na MetaTrader 5, abordado em algumas publicações anteriores. As classes avançadas e aprimoradas fornecem ferramentas para solucionar as tarefas da aplicação.
Utilitário de seleção e navegação em MQL5 e MQL4: incremetando abas de "lembretes" e salvando objetos gráficos Utilitário de seleção e navegação em MQL5 e MQL4: incremetando abas de "lembretes" e salvando objetos gráficos
Neste artigo, vamos expandir os recursos criados em publicação anterior, acrescentando abas para selecionar os símbolos que precisamos. Também aprenderemos como salvar objetos gráficos que criamos na plataforma, referente a símbolos específicos e assim, se necessitarmos, não tenhamos que criá-los novamente. E ainda, vamos descobrir como trabalhar apenas com símbolos que foram selecionados preliminarmente usando um site específico.
Analisando resultados de negociação usando relatórios HTML Analisando resultados de negociação usando relatórios HTML
A plataforma MetaTrader 5 apresenta funcionalidade para salvar relatórios de negociação, bem como relatórios de testes e otimização de Expert Advisor. Os relatórios de negociações e testes podem ser salvos em dois formatos: XLSX e HTML, enquanto o relatório de otimização pode ser salvo em XML. Neste artigo, analisamos o relatório de teste HTML, o relatório de otimização XML e o relatório de histórico de negociação HTML.
Otimização separada de uma estratégia em condições de tendência e lateralizada Otimização separada de uma estratégia em condições de tendência e lateralizada
O artigo considera a aplicação do método de otimização separada durante várias condições de mercado. A otimização separada significa definir os parâmetros ideais do sistema de negociação, otimizando para uma tendência de alta e tendência de baixa separadamente. Para reduzir o efeito de sinais falsos e melhorar a lucratividade, os sistemas são flexíveis, o que significa que eles têm um conjunto específico de configurações ou dados de entrada, o que se justifica porque o comportamento do mercado está em constante alteração.