English Русский 中文 Español Deutsch 日本語
preview
Tipo de desenho DRAW_ARROW em indicadores multissímbolos e multiperíodos

Tipo de desenho DRAW_ARROW em indicadores multissímbolos e multiperíodos

MetaTrader 5Exemplos | 3 julho 2024, 07:08
187 0
Artyom Trishkin
Artyom Trishkin

Conteúdo


Introdução

Continuamos o tema dos indicadores multissímbolos e multiperíodos. O último artigo desta série foi sobre buffers coloridos em indicadores multissímbolos e multiperíodos. Hoje, vamos aprimorar a classe de multi-indicadores para que ela possa trabalhar com indicadores de seta.

Os indicadores de seta envolvem a ausência constante de dados no seu buffer de desenho. Onde a seta é exibida existe um valor no buffer, enquanto nos outros momentos, o buffer contém um valor vazio estabelecido para ele. Normalmente, isso é EMPTY_VALUE, mas para o buffer é possível definir qualquer valor como "vazio", que não será exibido no gráfico. Isso pode ser feito com a função:

PlotIndexSetDouble(buffer_index,PLOT_EMPTY_VALUE,empty_value);

onde buffer_index é o índice do buffer ao qual se define o valor vazio, e empty_value é o valor "vazio" que será estabelecido para esse buffer.

Nos indicadores multiperíodos, onde o buffer é preenchido com dados de forma intermitente, é necessário considerar a presença de valores vazios onde não há setas, e não inserir esses valores vazios na barra do gráfico onde já há um valor não vazio. Caso contrário, a seta anteriormente colocada será apagada por um novo valor vazio. Isso é verdadeiro se os dados de um indicador calculado em um timeframe menor forem copiados para um timeframe maior.

Vou explicar com um exemplo. Na imagem do gráfico M5 as barras do gráfico M15 são marcadas:


Aqui, vemos fractais do gráfico M5 que precisam ser estabelecidos nas barras do gráfico M15.

  • Para a primeira barra M15 (I), é necessário estabelecer o fractal superior localizado na barra 3 — esse fractal será visível em M15, pois é o último dos três.
  • Para a segunda barra M15 (II), é necessário estabelecer o fractal localizado na barra 2 — esse fractal será visível em M15 somente se não copiar o valor vazio da barra 3, pois ele substituirá o valor do fractal inferior. A barra no M15 terá dois fractais — superior e inferior, pois no M5 há um fractal inferior na barra 2 e um fractal superior na barra 3.
  • Para a terceira barra M15 (III), é necessário estabelecer o fractal superior da barra 1 — esse fractal será visível em M15 apenas se não copiar os valores vazios das barras 2 e 3, pois eles substituirão o valor do fractal.

Assim, é necessário rastrear o valor na barra do gráfico atual e, se for vazio, pode-se copiar qualquer valor do buffer do timeframe menor. Mas se o valor já não for vazio, deve-se copiar apenas o valor também não vazio. Neste caso, na barra do gráfico do timeframe maior será estabelecido o valor do último fractal das barras do timeframe menor, que fazem parte da barra do período maior do gráfico. Por que usar o último valor da barra, e não o primeiro? Considero que este valor é mais relevante, pois está mais próximo do tempo atual.


Ao copiar dados do indicador calculado no timeframe maior para o gráfico do timeframe menor, é necessário considerar que a seta pode ser colocada não na barra zero ou primeira, mas, por exemplo, na segunda, terceira, ou em uma barra ainda mais antiga. Se copiar os dados apenas das barras zero e primeira, como está atualmente na classe de multi-indicadores, nenhuma seta será colocada no gráfico, até porque elas simplesmente estão em barras com índices mais altos e, consequentemente, o algoritmo de cálculo do indicador não pode vê-las:


Aqui vemos que o fractal foi formado na barra com índice 2. Se copiar apenas as barras zero e primeira, esse fractal não será visível para o programa.

Dessa forma, como não podemos saber exatamente em qual barra o indicador coloca suas setas, para a classe de multi-indicadores é necessário introduzir uma variável que indique explicitamente a barra a partir da qual começar a busca e a cópia dos valores. Atualmente, a classe copia apenas as barras zero e primeira no tick atual, portanto, os fractais não podem ser vistos, porque para eles, os valores começam na barra com índice 2.

Se usarmos um indicador personalizado como base, ele pode colocar setas em qualquer outra barra, isto é, não apenas na segunda, como o indicador Fractals. Por exemplo, se for um indicador de fractais personalizados, onde são procurados fractais de outra dimensão, como 3-X-3, a seta pode ser colocada na barra com índice 3 (se o fractal for redesenhado) ou 4 (se o fractal não for redesenhado).

Para indicadores padrão, vamos indicar explicitamente o índice da barra, pois ele é conhecido. Para indicadores personalizados, faremos com que seja possível especificar a barra de início da busca nos parâmetros do construtor da classe.


Aprimoramento das classes de multi-indicadores

Ao copiar dados nos métodos da classe, usamos arrays temporários locais. Vamos torná-los globais para não alocar memória novamente para esses arrays toda vez.

Na seção privada da classe base de indicadores multissímbolos e multiperíodos CIndMSTF declaramos novos arrays, e na seção protegida, uma variável para armazenar o número de barras para calcular o indicador no tick atual:

//+------------------------------------------------------------------+
//| Base class of the multi-symbol multi-period indicator            |
//+------------------------------------------------------------------+
class CIndMSTF : public CObject
  {
private:
   ENUM_PROGRAM_TYPE m_program;           // Program type
   ENUM_INDICATOR    m_type;              // Indicator type
   ENUM_TIMEFRAMES   m_timeframe;         // Chart timeframe
   string            m_symbol;            // Chart symbol
   int               m_handle;            // Indicator handle
   int               m_id;                // Identifier
   bool              m_success;           // Successful calculation flag
   ENUM_ERR_TYPE     m_type_err;          // Calculation error type
   string            m_description;       // Custom description of the indicator
   string            m_name;              // Indicator name
   string            m_parameters;        // Description of indicator parameters
   double            m_array_data[];      // Temporary array for copying data
   datetime          m_array_time[];      // Temporary array for copying time
   
protected:
   ENUM_IND_CATEGORY m_category;          // Indicator category
   MqlParam          m_param[];           // Array of indicator parameters
   string            m_title;             // Title (indicator name + description of parameters)
   SBuffer           m_buffers[];         // Indicator buffers
   int               m_digits;            // Digits in indicator values
   int               m_limit;             // Number of bars required to calculate the indicator on the current tick
   int               m_rates_total;       // Number of available bars for indicator calculation
   int               m_prev_calculated;   // Number of calculated bars on the previous indicator call
   uint              m_bars_to_calculate; // Number of bars required to calculate the indicator on the current tick


Na seção pública da classe, escreveremos um método que define o número de barras necessárias para calcular o indicador no tick atual:

//--- Returns amount of calculated data
   int               Calculated(void)                          const { return ::BarsCalculated(this.m_handle); }
//--- Set the number of bars required to calculate the indicator on the current tick
   void              SetBarsToCalculate(const int bars)
                       {
                        this.m_bars_to_calculate=bars;
                        if(this.m_array_data.Size()!=this.m_bars_to_calculate)
                          {
                           ::ArrayResize(this.m_array_data,this.m_bars_to_calculate);
                           ::ArrayResize(this.m_array_time,this.m_bars_to_calculate);
                          }
                       }
   
//--- Virtual method returning the type of object (indicator)

O método recebe o número necessário de barras, este valor é armazenado na nova variável e os tamanhos dos arrays são definidos como iguais ao valor dessa variável.

Assim, ao alterar com sucesso os tamanhos dos arrays, não será necessário redefini-los novamente em cada método de cópia de dados. Se não for possível alterar os tamanhos dos arrays aqui, as funções de cópia de dados dentro dos métodos de cópia tentarão alterar o tamanho do array para o número necessário. Se isso também não for possível, os métodos emitirão uma mensagem de erro de cópia.

Dessa forma, definimos uma vez o tamanho necessário dos arrays quer seja no construtor da classe, ou (em caso de falha) nos métodos de cópia, e depois usamos os arrays com o tamanho definido, o que nos livra da necessidade de realocar memória constantemente para os arrays, caso fossem locais.

No construtor da classe chamamos esse método com um valor padrão para a maioria dos indicadores, igual a duas barras, para copiar os dados (primeira e zero):

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CIndMSTF::CIndMSTF(const ENUM_INDICATOR type,const uint buffers,const string symbol,const ENUM_TIMEFRAMES timeframe)
  {
//--- Start the timer
   ::ResetLastError();
   if(!::EventSetTimer(1))
      ::PrintFormat("%s: EventSetTimer failed. Error %lu",__FUNCTION__,::GetLastError());
//--- Set properties to the values passed to the constructor or to default values
   this.m_program=(ENUM_PROGRAM_TYPE)::MQLInfoInteger(MQL_PROGRAM_TYPE);
   this.m_type=type;
   this.m_symbol=(symbol==NULL || symbol=="" ? ::Symbol() : symbol);
   this.m_timeframe=(timeframe==PERIOD_CURRENT ? ::Period() : timeframe);
   this.m_handle=INVALID_HANDLE;
   this.m_digits=::Digits();
   this.m_success=true;
   this.m_type_err=ERR_TYPE_NO_ERROR;
//--- Set the size of the buffer structure array to the number of indicator buffers
   ::ResetLastError();
   if(::ArrayResize(this.m_buffers,buffers)!=buffers)
      ::PrintFormat("%s: Buffers ArrayResize failed. Error  %lu"__FUNCTION__,::GetLastError());
//--- Set the "empty" value for each buffer by default (can be changed it later)
   for(int i=0;i<(int)this.m_buffers.Size();i++)
      this.SetBufferInitValue(i,EMPTY_VALUE);
//--- Set initial values of variables involved in resource-efficient calculation of the indicator
   this.m_prev_calculated=0;
   this.m_limit=0;
   this.m_rates_total=::Bars(this.m_symbol,this.m_timeframe);
   this.SetBarsToCalculate(2);
//--- If the indicator is calculated on non-current chart, request data from the required chart
//--- (the first access to data starts data pumping)
   datetime array[];
   if(symbol!=::Symbol() || timeframe!=::Period())
      ::CopyTime(this.m_symbol,this.m_timeframe,0,this.m_rates_total,array);
  }


No destrutor da classe liberamos a memória alocada para os arrays temporários:

//+------------------------------------------------------------------+
//| Destructor                                                       |
//+------------------------------------------------------------------+
CIndMSTF::~CIndMSTF()
  {
//--- Delete timer
   ::EventKillTimer();
//--- Release handle of the indicator
   ::ResetLastError();
   if(this.m_handle!=INVALID_HANDLE && !::IndicatorRelease(this.m_handle))
      ::PrintFormat("%s: %s, handle %ld IndicatorRelease failed. Error %ld",__FUNCTION__,this.Title(),m_handle,::GetLastError());
//--- Free up the memory of buffer arrays
   for(int i=0;i<(int)this.BuffersTotal();i++)
     {
      ::ArrayFree(this.m_buffers[i].array0);
      ::ArrayFree(this.m_buffers[i].array1);
      ::ArrayFree(this.m_buffers[i].array2);
      ::ArrayFree(this.m_buffers[i].array3);
      ::ArrayFree(this.m_buffers[i].color_indexes);
     }
//--- Free up the memory of temporary arrays
   ::ArrayFree(this.m_array_data);
   ::ArrayFree(this.m_array_time);
  }


No método de cópia de dados do array especificado do buffer especificado agora verificamos a quantidade de dados copiados pelo valor definido na nova variável. Anteriormente, o valor fixo era de duas barras:

//+------------------------------------------------------------------+
//| Copy data of the specified array of the specified buffer         |
//+------------------------------------------------------------------+
bool CIndMSTF::CopyArray(const uint buff_num,const uint array_num,const int to_copy,double &array[])
  {
   ::ResetLastError();
//--- Copy either the last two bars to 'array' or all available historical data from indicator's calculation part array to buffer array of indicator object
   int copied=0;
   if(to_copy==this.m_bars_to_calculate)
     {
      switch(array_num)
        {
         case 0   :  case 1 : case 2 :
         case 3   :  copied=::CopyBuffer(this.m_handle,this.m_buffers[buff_num].BufferFrom(),  -this.m_buffers[buff_num].Shift(),to_copy,array);   break;
         case 4   :  copied=::CopyBuffer(this.m_handle,this.m_buffers[buff_num].BufferFrom()+1,-this.m_buffers[buff_num].Shift(),to_copy,array);   break;
         default  :  break;
        }
     }
   else
     {
      switch(array_num)
        {
         case 0   :  copied=::CopyBuffer(this.m_handle,this.m_buffers[buff_num].BufferFrom(),  -this.m_buffers[buff_num].Shift(),to_copy,this.m_buffers[buff_num].array0);          break;
         case 1   :  copied=::CopyBuffer(this.m_handle,this.m_buffers[buff_num].BufferFrom(),  -this.m_buffers[buff_num].Shift(),to_copy,this.m_buffers[buff_num].array1);          break;
         case 2   :  copied=::CopyBuffer(this.m_handle,this.m_buffers[buff_num].BufferFrom(),  -this.m_buffers[buff_num].Shift(),to_copy,this.m_buffers[buff_num].array2);          break;
         case 3   :  copied=::CopyBuffer(this.m_handle,this.m_buffers[buff_num].BufferFrom(),  -this.m_buffers[buff_num].Shift(),to_copy,this.m_buffers[buff_num].array3);          break;
         case 4   :  copied=::CopyBuffer(this.m_handle,this.m_buffers[buff_num].BufferFrom()+1,-this.m_buffers[buff_num].Shift(),to_copy,this.m_buffers[buff_num].color_indexes);   break;
         default  :  break;
        }
     }
//--- If copied successfully
   if(copied>0)
      return true;
//--- If not all data is copied
//--- If CopyBuffer returned -1, this means the start of historical data downloading
//--- print a message about this to the log
   if(copied==WRONG_VALUE)
      ::PrintFormat("%s::%s: Start downloading data by %s/%s. Waiting for the next tick...",__FUNCTION__,this.Title(),this.m_symbol,this.TimeframeDescription());
//--- In any other case, not all data has been copied yet
//--- print a message about this to the log
   else
      ::PrintFormat("%s::%s: Not all data was copied. Data available: %lu, total copied: %ld",__FUNCTION__,this.Title(),this.m_rates_total,copied);
   return false;
  }


No método de cópia de dados de todos os arrays do buffer especificado anteriormente era fixado em duas barras, e era especificado diretamente no código de qual índice do array temporário para qual índice do array-buffer copiar os dados:

//+------------------------------------------------------------------+
//| Copy data of all arrays of the specified buffer                  |
//+------------------------------------------------------------------+
bool CIndMSTF::CopyArrays(const uint buff_num,const int to_copy)
  {
   bool res=true;
   double array[2];
   if(to_copy==2)
     {

      switch(this.BufferDrawType(buff_num))
        {
         //--- One buffer
         case DRAW_LINE             :
         case DRAW_HISTOGRAM        :
         case DRAW_ARROW            :
         case DRAW_SECTION          :
            res=this.CopyArray(buff_num,0,to_copy,array);
            if(res)
              {
               this.m_buffers[buff_num].array0[this.DataTotal(buff_num,0)-1]=array[1];
               this.m_buffers[buff_num].array0[this.DataTotal(buff_num,0)-2]=array[0];
              }
            return res;
         //--- Two buffers
         case DRAW_HISTOGRAM2       :
         case DRAW_ZIGZAG           :
         case DRAW_FILLING          :

Agora, o número de dados copiados é desconhecido previamente, pois ele está definido na nova variável. Portanto, vamos verificar o valor dessa variável, e copiar os dados em um loop de acordo com a quantidade de dados copiados.
Os índices dos arrays de origem e de destino serão calculados a partir do índice do loop. Agora temos um array temporário global declarado, então o array local, como era anteriormente, já não é necessário:

//+------------------------------------------------------------------+
//| Copy data of all arrays of the specified buffer                  |
//+------------------------------------------------------------------+
bool CIndMSTF::CopyArrays(const uint buff_num,const int to_copy)
  {
   bool res=true;
   if(to_copy==this.m_bars_to_calculate)

     {
      int total=(int)this.m_array_data.Size();
      switch(this.BufferDrawType(buff_num))
        {
         //--- One buffer
         case DRAW_LINE             :
         case DRAW_HISTOGRAM        :
         case DRAW_ARROW            :
         case DRAW_SECTION          :
            res=this.CopyArray(buff_num,0,to_copy,this.m_array_data);
            if(res)
              {
               for(int i=0;i<total;i++)
                  this.m_buffers[buff_num].array0[this.DataTotal(buff_num,0)-(total-i)]=this.m_array_data[i];
              }
            return res;
         //--- Two buffers
         case DRAW_HISTOGRAM2       :
         case DRAW_ZIGZAG           :
         case DRAW_FILLING          :
            res=this.CopyArray(buff_num,0,to_copy,this.m_array_data);
            if(res)
              {
               for(int i=0;i<total;i++)
                  this.m_buffers[buff_num].array0[this.DataTotal(buff_num,0)-(total-i)]=this.m_array_data[i];
              }
            res &=this.CopyArray(buff_num,1,to_copy,this.m_array_data);
            if(res)
              {
               for(int i=0;i<total;i++)
                  this.m_buffers[buff_num].array1[this.DataTotal(buff_num,1)-(total-i)]=this.m_array_data[i];
              }
            return res;
         //--- Four buffers
         case DRAW_BARS             :
         case DRAW_CANDLES          :
            res=this.CopyArray(buff_num,0,to_copy,this.m_array_data);
            if(res)
              {
               for(int i=0;i<total;i++)
                  this.m_buffers[buff_num].array0[this.DataTotal(buff_num,0)-(total-i)]=this.m_array_data[i];
              }
            res &=this.CopyArray(buff_num,1,to_copy,this.m_array_data);
            if(res)
              {
               for(int i=0;i<total;i++)
                  this.m_buffers[buff_num].array1[this.DataTotal(buff_num,1)-(total-i)]=this.m_array_data[i];
              }
            res &=this.CopyArray(buff_num,2,to_copy,this.m_array_data);
            if(res)
              {
               for(int i=0;i<total;i++)
                  this.m_buffers[buff_num].array2[this.DataTotal(buff_num,2)-(total-i)]=this.m_array_data[i];
              }
            res &=this.CopyArray(buff_num,3,to_copy,this.m_array_data);
            if(res)
              {
               for(int i=0;i<total;i++)
                  this.m_buffers[buff_num].array3[this.DataTotal(buff_num,3)-(total-i)]=this.m_array_data[i];
              }
            return res;
         //--- One buffer + color buffer
         case DRAW_COLOR_LINE       :
         case DRAW_COLOR_HISTOGRAM  :
         case DRAW_COLOR_ARROW      :
         case DRAW_COLOR_SECTION    :
            res=this.CopyArray(buff_num,0,to_copy,this.m_array_data);
            if(res)
              {
               for(int i=0;i<total;i++)
                  this.m_buffers[buff_num].array0[this.DataTotal(buff_num,0)-(total-i)]=this.m_array_data[i];
              }
            res &=this.CopyArray(buff_num,4,to_copy,this.m_array_data);
            if(res)
              {
               for(int i=0;i<total;i++)
                  this.m_buffers[buff_num].color_indexes[this.DataTotal(buff_num,4)-(total-i)]=this.m_array_data[i];
              }
            return res;
         //--- Two buffers + color buffer
         case DRAW_COLOR_HISTOGRAM2 :
         case DRAW_COLOR_ZIGZAG     :
            res=this.CopyArray(buff_num,0,to_copy,this.m_array_data);
            if(res)
              {
               for(int i=0;i<total;i++)
                  this.m_buffers[buff_num].array0[this.DataTotal(buff_num,0)-(total-i)]=this.m_array_data[i];
              }
            res &=this.CopyArray(buff_num,1,to_copy,this.m_array_data);
            if(res)
              {
               for(int i=0;i<total;i++)
                  this.m_buffers[buff_num].array1[this.DataTotal(buff_num,1)-(total-i)]=this.m_array_data[i];
              }
            res &=this.CopyArray(buff_num,4,to_copy,this.m_array_data);
            if(res)
              {
               for(int i=0;i<total;i++)
                  this.m_buffers[buff_num].color_indexes[this.DataTotal(buff_num,4)-(total-i)]=this.m_array_data[i];
              }
            return res;
         //--- Four buffers + color buffer
         case DRAW_COLOR_BARS       :
         case DRAW_COLOR_CANDLES    :
            res=this.CopyArray(buff_num,0,to_copy,this.m_array_data);
            if(res)
              {
               for(int i=0;i<total;i++)
                  this.m_buffers[buff_num].array0[this.DataTotal(buff_num,0)-(total-i)]=this.m_array_data[i];
              }
            res &=this.CopyArray(buff_num,1,to_copy,this.m_array_data);
            if(res)
              {
               for(int i=0;i<total;i++)
                  this.m_buffers[buff_num].array1[this.DataTotal(buff_num,1)-(total-i)]=this.m_array_data[i];
              }
            res &=this.CopyArray(buff_num,2,to_copy,this.m_array_data);
            if(res)
              {
               for(int i=0;i<total;i++)
                  this.m_buffers[buff_num].array2[this.DataTotal(buff_num,2)-(total-i)]=this.m_array_data[i];
              }
            res &=this.CopyArray(buff_num,3,to_copy,this.m_array_data);
            if(res)
              {
               for(int i=0;i<total;i++)
                  this.m_buffers[buff_num].array3[this.DataTotal(buff_num,3)-(total-i)]=this.m_array_data[i];
              }
            res &=this.CopyArray(buff_num,4,to_copy,this.m_array_data);
            if(res)
              {
               for(int i=0;i<total;i++)
                  this.m_buffers[buff_num].color_indexes[this.DataTotal(buff_num,4)-(total-i)]=this.m_array_data[i];
              }
            return res;
         //---DRAW_NONE
         default:
           break;
        }
     }
   else
     {
      switch(this.BufferDrawType(buff_num))
        {
         //--- One buffer
         case DRAW_LINE             :
         case DRAW_HISTOGRAM        :
         case DRAW_ARROW            :
         case DRAW_SECTION          :
            return this.CopyArray(buff_num,0,to_copy,this.m_array_data);
         //--- Two buffers
         case DRAW_HISTOGRAM2       :
         case DRAW_ZIGZAG           :
         case DRAW_FILLING          :
            res  =this.CopyArray(buff_num,0,to_copy,this.m_array_data);
            res &=this.CopyArray(buff_num,1,to_copy,this.m_array_data);
            return res;
         //--- Four buffers
         case DRAW_BARS             :
         case DRAW_CANDLES          :
            res  =this.CopyArray(buff_num,0,to_copy,this.m_array_data);
            res &=this.CopyArray(buff_num,1,to_copy,this.m_array_data);
            res &=this.CopyArray(buff_num,2,to_copy,this.m_array_data);
            res &=this.CopyArray(buff_num,3,to_copy,this.m_array_data);
            return res;
         //--- One buffer + color buffer
         case DRAW_COLOR_LINE       :
         case DRAW_COLOR_HISTOGRAM  :
         case DRAW_COLOR_ARROW      :
         case DRAW_COLOR_SECTION    :
            res  =this.CopyArray(buff_num,0,to_copy,this.m_array_data);
            res &=this.CopyArray(buff_num,4,to_copy,this.m_array_data);
            return res;
         //--- Two buffers + color buffer
         case DRAW_COLOR_HISTOGRAM2 :
         case DRAW_COLOR_ZIGZAG     :
            res  =this.CopyArray(buff_num,0,to_copy,this.m_array_data);
            res &=this.CopyArray(buff_num,1,to_copy,this.m_array_data);
            res &=this.CopyArray(buff_num,4,to_copy,this.m_array_data);
            return res;
         //--- Four buffers + color buffer
         case DRAW_COLOR_BARS       :
         case DRAW_COLOR_CANDLES    :
            res  =this.CopyArray(buff_num,0,to_copy,this.m_array_data);
            res &=this.CopyArray(buff_num,1,to_copy,this.m_array_data);
            res &=this.CopyArray(buff_num,2,to_copy,this.m_array_data);
            res &=this.CopyArray(buff_num,3,to_copy,this.m_array_data);
            res &=this.CopyArray(buff_num,4,to_copy,this.m_array_data);
            return res;
         //---DRAW_NONE
         default:
           break;
        }
     }
   return false;
  }


No método de preenchimento dos buffers do objeto com dados do buffer de cálculo na seção de código responsável pelo trabalho no tick atual, agora copiamos não duas barras, mas a quantidade de barras definida na variável m_bars_to_calculate:

//--- If calculated m_limit is less than or equal to 1, this means either opening of a new bar (m_limit==1) or current tick (m_limit==0)
//--- In this case, it is necessary to calculate the number of bars specified in the m_bars_to_calculate variable - the current one and the rest
   if(this.m_limit<=1)
     {
      //--- In a loop over the number of indicator buffers
      for(int i=0;i<total;i++)
        {
         //--- If this is the opening of a new bar and resizing the indicator buffer failed,
         if(this.m_limit==1 && !this.BufferResize(i,this.m_rates_total))
           {
            //--- add 'false' to the m_success variable and return 'false'
            //--- Here, an error message will be printed to log from the BufferResize method
            this.m_success=false;
            return false;
           }
         //--- If failed to copy bars in the amount of m_bars_to_calculate from the indicator's calculation part buffer,
         ::ResetLastError();
         if(!this.CopyArrays(i,this.m_bars_to_calculate))
           {
            //--- report this via the log, add 'false' to the m_success variable and return 'false'
            ::PrintFormat("%s::%s: CopyBuffer(%lu) failed. Error %lu",__FUNCTION__,this.Title(),i,::GetLastError());
            this.m_success &=false;
           }
        }
      //--- If the loop is followed by errors, return 'false'
      if(!this.m_success)
        {
         this.m_type_err=ERR_TYPE_NO_DATA;
         return false;
        }
      
      //--- Success
      this.m_type_err=ERR_TYPE_NO_ERROR;
      this.m_success=true;
      return true;
     }
//--- Undefined 'limit' option - return 'false'
   return false;
  }


O método que preenche o array desenhado com dados do buffer da classe foi completamente reescrito. Nele, foram considerados as nuances de copiar dados do timeframe menor para o maior, do maior para o menor e a quantidade de dados copiados no tick atual, conforme discutido no início deste artigo. A lógica deste método foi completamente reescrita e comentada:

//+------------------------------------------------------------------+
//| Fills the passed plot array with data from the class buffer      |
//+------------------------------------------------------------------+
bool CIndMSTF::DataToBuffer(const string symbol_to,const ENUM_TIMEFRAMES timeframe_to,const uint buffer_num,const uint array_num,const int limit,double &buffer[])
  {
//--- Set the success flag
   this.m_success=true;
//--- Get the indexing direction of the buffer array passed to the method and,
//--- if non-timeseries indexing, set timeseries indexing
   bool as_series=::ArrayGetAsSeries(buffer);
   if(!as_series)
      ::ArraySetAsSeries(buffer,true);
//--- Set the symbol name and timeframe value passed to the method
   string symbol=(symbol_to=="" || symbol_to==NULL ? ::Symbol() : symbol_to);
   ENUM_TIMEFRAMES timeframe=(timeframe_to==PERIOD_CURRENT ? ::Period() : timeframe_to);

//--- If this is the first launch or history changes, initialize the buffer array passed to the method
   if(limit>1 && this.m_limit>1)
     {
      ::PrintFormat("%s::%s First start, or historical data has been changed. Initialize Buffer(%lu)",__FUNCTION__,this.Title(),buffer_num);
      ::ArrayInitialize(buffer,this.BufferInitValue(buffer_num));
     }
      
//--- Set the value of the loop counter (no more than the maximum number of bars in the terminal on the chart)
   int count=(limit<=1 ? (int)this.m_bars_to_calculate : ::fmin(::TerminalInfoInteger(TERMINAL_MAXBARS),limit));
//--- In a loop from the zero bar to the value of the loop counter
   for(int i=0;i<count;i++)
     {
      //--- If the chart timeframe matches the class object timeframe, fill the buffer directly from the class object array
      if(timeframe==::Period() && this.m_timeframe==::Period())
         buffer[i]=this.GetData(buffer_num,array_num,i);
      //--- Otherwise, if the chart timeframe is not equal to the timeframe of the class object
      else
        {
         //--- If the chart timeframe is lower than the timeframe of the class object,
         if(timeframe<this.m_timeframe)
           {
            //--- If this is historical data (a bar with an index greater than m_bars_to_calculate-1)
            if(i>(int)this.m_bars_to_calculate-1)
              {
               //--- Find out which time of this class the bar of the current chart timeframe, corresponding to the loop index, belongs to
               ::ResetLastError();
               if(::CopyTime(symbol,timeframe,i,1,this.m_array_time)!=1)
                 {
                  //--- If there is no data in the terminal, move on
                  if(::GetLastError()==4401)
                     continue;
                  //--- Error in obtaining existing data - return false
                  this.m_success &=false;
                  return false;
                 }
               //--- Using time of bar of current chart timeframe, find the corresponding bar index of the chart period in the class object buffer array
               int bar=::iBarShift(this.m_symbol,this.m_timeframe,this.m_array_time[0]);
               if(bar==WRONG_VALUE)
                 {
                  this.m_success &=false;
                  continue;
                 }
               //--- in the indicator buffer at the loop index, write the value obtained from the calculation part buffer
               buffer[i]=this.GetData(buffer_num,array_num,bar);
              }
            //--- If this is the current (zero) bar or a bar with an index less than or equal to m_bars_to_calculate-1
            else
              {
               //--- Get the time of bars by symbol/timeframe of the class object in the amount of m_bars_to_calculate
               if(::CopyTime(this.m_symbol,this.m_timeframe,0,this.m_bars_to_calculate,this.m_array_time)!=this.m_bars_to_calculate)
                 {
                  this.m_success=false;
                  return false;
                 }
               
               //--- Get the number of bars of the current chart period contained in the chart period of the class object
               int num_bars=::PeriodSeconds(this.m_timeframe)/::PeriodSeconds(timeframe);
               if(num_bars<1)
                  num_bars=1;
               
               //--- In a loop through the array of received bar times
               for(int j=0;j<(int)this.m_array_time.Size();j++)
                 {
                  //--- Get the corresponding bar on the chart by the time of the bar in the buffer array of the class object
                  int barN=::iBarShift(symbol,timeframe,this.m_array_time[j]);
                  if(barN==WRONG_VALUE)
                    {
                     this.m_success &=false;
                     return false;
                    }
                  //--- Calculate the final bar on the chart to copy data from the buffer of the calculation part of the indicator 
                  int end=barN-num_bars;
                  if(end<0)
                     end=-1;
                  //--- In a loop from barN to end, copy data from the buffer array of the calculation part to the indicator buffer being drawn on the chart 
                  for(int n=barN;n>end;n--)
                     buffer[n]=this.GetData(buffer_num,array_num,this.m_bars_to_calculate-1-j);
                 }
              }
           }
         
         //--- If the chart timeframe is higher than the timeframe of the class object,
         else if(timeframe>this.m_timeframe)
           {
            //--- Get the number of bars of the class object chart period contained in the bar of the current chart period
            int num_bars=::PeriodSeconds(timeframe)/::PeriodSeconds(this.m_timeframe);
            //--- Find out which time of this class the bar of the current chart timeframe, corresponding to the loop index, belongs to
            ::ResetLastError();
            if(::CopyTime(symbol,timeframe,i,1,this.m_array_time)!=1)
              {
               //--- If there is no data in the terminal, move on
               if(::GetLastError()==4401)
                  continue;
               //--- Error in obtaining existing data - return false
               this.m_success &=false;
               return false;
              }
            //--- Using time of bar of current chart timeframe, find the corresponding bar index of the chart period in the class object buffer array
            //--- This bar will be the first of several bars of the class object in the amount of num_bars included in the chart bar
            int bar=::iBarShift(this.m_symbol,this.m_timeframe,this.m_array_time[0]);
            if(bar==WRONG_VALUE)
              {
               this.m_success &=false;
               continue;
              }
            //--- Calculate the index of the last bar to copy in the loop
            int end=bar-num_bars;
            if(end<0)
               end=-1;
            //--- In a loop from the first to the last bar included in the bar of the older chart period 
            for(int j=bar;j>end;j--)
              {
               //--- Find out which time of this class the bar of the current chart timeframe, corresponding to the loop index, belongs to
               ::ResetLastError();
               if(::CopyTime(this.m_symbol,this.m_timeframe,j,1,this.m_array_time)!=1)
                 {
                  //--- If there is no data in the terminal, move on
                  if(::GetLastError()==4401)
                     continue;
                  //--- Error in obtaining existing data - return false
                  this.m_success &=false;
                  return false;
                 }
               //--- Get the bar number on the chart corresponding to the time of the buffer array of the calculation part by j loop index
               int barN=::iBarShift(symbol,timeframe,this.m_array_time[0]);
               //--- Get the data of the j bar in the buffer array of the calculation part and the data of barN contained in the chart indicator buffer
               double value_data=this.GetData(buffer_num,array_num,j);
               double value_buff=buffer[barN];
               //--- If the bar on the chart has an empty value, then write data from the buffer array of the calculation part into it. Or
               //--- if there is already data on the chart, then we write new data to this bar only if the data contains a non-empty value.
               if(value_buff==this.BufferInitValue(buffer_num) || (value_buff!=this.BufferInitValue(buffer_num) && value_data!=this.BufferInitValue(buffer_num)))
                  buffer[barN]=value_data;
              }
           }
        }
     }
//--- Set initial indexing of the buffer array passed to the method
   ::ArraySetAsSeries(buffer,as_series);
//--- Successful
   return true;
  }

A classe base do indicador multissímbolo multiperíodo está pronta.

Agora, vamos aprimorar as classes derivadas.

De todos os indicadores padrão, apenas o indicador de fractais de Bill Williams desenha setas com interrupções (Parabolic SAR também desenha setas, mas sem interrupções no gráfico).

E o desenho do indicador Fractals começa na barra com índice 2, ou seja, para calcular o indicador é necessário atualizar em cada tick três barras: 2, 1 e 0.

No construtor da classe desse indicador definimos a quantidade de barras de cálculo igual a três:

//+------------------------------------------------------------------+
//| Fractals indicator class                                         |
//+------------------------------------------------------------------+
class CIndFractals : public CIndMSTF
  {
public:
//--- Constructor
   CIndFractals(const string symbol,const ENUM_TIMEFRAMES timeframe) : CIndMSTF(IND_FRACTALS,2,symbol,timeframe)
     {
      // Buffer indexes: 0 - UPPER_LINE, 1 - LOWER_LINE
      //--- Create description of parameters
      //--- If non-current chart symbol or period, their descriptions are added to parameters
      bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period());
      string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription()));
      string param=(current ? "" : StringFormat("(%s)",symbol_period));
      //--- Write description of parameters, indicator name, its description, title and category
      this.SetParameters(param);
      this.SetName("Fractals");
      this.SetDescription("Fractals");
      this.m_title=this.Name()+this.Parameters();
      this.m_category=IND_CATEGORY_WILLIAMS;
      //--- Description of UPPER_LINE and LOWER_LINE line buffers
      this.SetBufferDescription(UPPER_LINE,this.m_title+" Up");
      this.SetBufferDescription(LOWER_LINE,this.m_title+" Down");
      
      //--- Set drawing style for buffers 0 and 1 and the numbers of calculation part data buffers
      this.SetBufferDrawType(0,DRAW_ARROW,UPPER_LINE);
      this.SetBufferDrawType(1,DRAW_ARROW,LOWER_LINE);
      
      //--- Set default colors for buffers 0 and 1
      this.SetBufferColorToIndex(UPPER_LINE,0,clrGray);
      this.SetBufferColorToIndex(LOWER_LINE,0,clrGray);
      
      //--- Set the number of bars required to calculate the indicator on the current tick
      this.SetBarsToCalculate(3);
     }
  };

Na classe base, essa quantidade é definida por padrão igual a duas barras. Aqui, redefinimos a quantidade para três barras e, consequentemente, aumentamos também as dimensões dos arrays temporários para três.


Na classe de indicador personalizado, não se sabe antecipadamente quantas barras são necessárias para seu cálculo no tick atual. Portanto, não podemos especificar antecipadamente o número necessário de barras, como feito no construtor da classe do indicador de fractais. Aqui, será necessário especificar esse número nos parâmetros do construtor da classe, passando e definindo através de uma variável de entrada:

//+------------------------------------------------------------------+
//| Custom indicator class                                           |
//+------------------------------------------------------------------+
class CIndCustom : public CIndMSTF
  {
public:
//--- Constructor
   CIndCustom(const string symbol,const ENUM_TIMEFRAMES timeframe,
              const string path,                      // path to the indicator (for example, "Examples\\MACD.ex5")
              const string name,                      // name of the custom indicator
              const uint   buffers,                   // number of indicator buffers
              const uint   bars_to_calculate,         // number of bars required to calculate the indicator on the current tick
              const MqlParam &param[]                 // array of custom indicator parameters
             ) : CIndMSTF(IND_CUSTOM,buffers,symbol,timeframe)
     {
      //--- If an empty array of parameters is passed, print this to log
      int total=(int)param.Size();
      if(total==0)
         ::PrintFormat("%s Error. Passed an empty array",__FUNCTION__);
      //--- If the array is not empty and its size is increased by 1 (the first string parameter must contain the indicator name)
      ResetLastError();
      if(total>0 && ::ArrayResize(this.m_param,total+1)==total+1)
        {
         //--- Reset data in the array and enter name (path to file and name of .ex5 file)
         ::ZeroMemory(this.m_param);
         //--- name of the custom indicator
         this.m_param[0].type=TYPE_STRING;
         this.m_param[0].string_value=path;
         //--- fill the array of indicator parameters
         for(int i=0;i<total;i++)
           {
            this.m_param[i+1].type=param[i].type;
            this.m_param[i+1].double_value=param[i].double_value;
            this.m_param[i+1].integer_value=param[i].integer_value;
            this.m_param[i+1].string_value=param[i].string_value;
           }
         //--- Create description of parameters
         //--- If non-current chart symbol or period, their descriptions are added to parameters
         bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period());
         string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription()));
         string param=(current ? "" : StringFormat("(%s)",symbol_period));
         //--- Write description of parameters, indicator name, its description, title and category
         this.SetParameters(param);
         this.SetName(name);
         this.SetDescription(name);
         this.m_title=this.Name()+this.Parameters();
         this.m_category=IND_CATEGORY_CUSTOM;
         //--- Write a description of the first line buffer
         this.SetBufferDescription(0,this.m_title);
         //--- Set the number of bars required to calculate the indicator on the current tick
         this.SetBarsToCalculate(bars_to_calculate);
        }
     }
  };


Na classe-coleção de indicadores, no método de criação de um novo objeto de indicador personalizado, também é necessário especificar o número de barras para o cálculo do indicador:

   int               AddNewVolumes(const string symbol,const ENUM_TIMEFRAMES timeframe,const ENUM_APPLIED_VOLUME applied_volume=VOLUME_TICK);
   int               AddNewCustom(const string symbol,const ENUM_TIMEFRAMES timeframe,const string path,             // path to the indicator (for example, "Examples\\MACD.ex5")
                                                                                      const string name,             // name of custom indicator (for example, "Custom MACD")
                                                                                      const uint   buffers,          // number of buffers
                                                                                      const uint   bars_to_calculate,// number of bars required to calculate the indicator on the current tick
                                                                                      const MqlParam &param[]);      // Array of parameters
//--- Timer

e no código de implementação desse método, passar o valor dessa variável para o construtor da classe do indicador personalizado:

//+------------------------------------------------------------------+
//| Add a custom indicator to the collection                         |
//+------------------------------------------------------------------+
int CMSTFIndicators::AddNewCustom(const string symbol,const ENUM_TIMEFRAMES timeframe,const string path,const string name,const uint buffers,const uint bars_to_calculate,const MqlParam &param[])
  {
//--- Create a new indicator object. If there is an error, add a journal message and return INVALID_HANDLE
   CIndCustom *ind_obj=new CIndCustom(symbol,timeframe,path,name,buffers,bars_to_calculate,param);
   if(ind_obj==NULL)
     {
      ::PrintFormat("%s: Error. Failed to create %s custom indicator object",__FUNCTION__,name);
      return INVALID_HANDLE;
     }
//--- Return the result of adding the created indicator object to the collection list
   return this.AddNewIndicator(ind_obj,__FUNCTION__);
  }

Agora, as classes de indicadores multissímbolos e multiperíodos devem funcionar com indicadores de seta e com aqueles indicadores cujo buffer contém dados com interrupções — indicadores com estilos de desenho DRAW_ARROW, DRAW_COLOR_ARROW, DRAW_SECTION, DRAW_ZIGZAG, DRAW_COLOR_SECTION, e DRAW_COLOR_ZIGZAG.


Teste

Para testar, usaremos qualquer indicador criado anteriormente, como Parabolic SAR, já que ele também desenha setas. Ele tem um buffer de desenho, enquanto o indicador de fractais possui dois. Um buffer é usado para desenhar os fractais superiores, e o outro — para desenhar os fractais inferiores. Vamos aprimorar o código do indicador para que ele desenhe setas em dois buffers e salvá-lo com o nome TestMSTFFractals.mq5.

O indicador exibirá no gráfico os dados de dois indicadores de fractais ao mesmo tempo, já que um desenhará fractais do período atual do gráfico, e o outro plotará fractais do indicador Fractals, calculado no símbolo e período do gráfico especificados nas configurações. Além disso, para exibir os dados dos indicadores de fractais, será utilizada um painel informativo, que é usado em todos os indicadores dos artigos desta série:

//+------------------------------------------------------------------+
//|                                             TestMSTFFractals.mq5 |
//|                                  Copyright 2023, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2023, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"
#property indicator_chart_window
#property indicator_buffers 4
#property indicator_plots   4 
//--- enums

//--- plot FractalsUp1
#property indicator_label1  "FractalsUp1"
#property indicator_type1   DRAW_ARROW
#property indicator_color1  clrGray
#property indicator_width1  1

//--- plot FractalsDown1
#property indicator_label2  "FractalsDown1"
#property indicator_type2   DRAW_ARROW
#property indicator_color2  clrGray
#property indicator_width2  1

//--- plot FractalsUp2
#property indicator_label3  "FractalsUp2"
#property indicator_type3   DRAW_ARROW
#property indicator_color3  clrDodgerBlue
#property indicator_width3  1

//--- plot FractalsDown2
#property indicator_label4  "FractalsDown2"
#property indicator_type4   DRAW_ARROW
#property indicator_color4  clrDodgerBlue
#property indicator_width4  1

//--- includes
#include <IndMSTF\IndMSTF.mqh>
#include <Dashboard\Dashboard.mqh>
//--- input parameters
input string               InpSymbol      =  NULL;             /* Symbol                     */ // Moving average symbol
input ENUM_TIMEFRAMES      InpTimeframe   =  PERIOD_CURRENT;   /* Timeframe                  */ // Moving average timeframe
input uchar                InpArrowShift1 =  10;               /* Senior period Arrow VShift */ // Shift the arrows vertically for the higher period
input uchar                InpArrowShift2 =  10;               /* Junior period Arrow VShift */ // Shift the arrows vertically for the lower period
input bool                 InpAsSeries    =  true;             /* As Series flag             */ // Timeseries flag of indicator buffer arrays

//--- indicator buffers
double         BufferFractalsUp1[];
double         BufferFractalsDn1[];
double         BufferFractalsUp2[];
double         BufferFractalsDn2[];
//--- global variables
int handle_fractals1;
int handle_fractals2;
CMSTFIndicators indicators;      // An instance of the indicator collection object
//--- variables for the panel
CDashboard *panel=NULL;          // Pointer to the panel object
int         mouse_bar_index;     // Index of the bar the data is taken from
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Set a timer with an interval of 1 second
   EventSetTimer(1);
//--- Assign the BufferFractalsUp1 and BufferFractalsDn1 arrays to the plot buffers 0 and 1, respectively
   SetIndexBuffer(0,BufferFractalsUp1,INDICATOR_DATA);
   SetIndexBuffer(1,BufferFractalsDn1,INDICATOR_DATA);
//--- Assign the BufferFractalsUp2 and BufferFractalsDn2 arrays to the plot buffers 2 and 3, respectively
   SetIndexBuffer(2,BufferFractalsUp2,INDICATOR_DATA);
   SetIndexBuffer(3,BufferFractalsDn2,INDICATOR_DATA);
//--- Define the symbol code from the Wingdings font to draw in PLOT_ARROW
   PlotIndexSetInteger(0,PLOT_ARROW,217);
   PlotIndexSetInteger(1,PLOT_ARROW,218);
   PlotIndexSetInteger(2,PLOT_ARROW,217);
   PlotIndexSetInteger(3,PLOT_ARROW,218);
//--- Set the vertical shift of the arrows 
   PlotIndexSetInteger(0,PLOT_ARROW_SHIFT,-InpArrowShift1);
   PlotIndexSetInteger(1,PLOT_ARROW_SHIFT, InpArrowShift1);
   PlotIndexSetInteger(2,PLOT_ARROW_SHIFT,-InpArrowShift2);
   PlotIndexSetInteger(3,PLOT_ARROW_SHIFT, InpArrowShift2);
//--- Set the timeseries flags for the indicator buffer arrays (for testing, to see that there is no difference)
   ArraySetAsSeries(BufferFractalsUp1,InpAsSeries);
   ArraySetAsSeries(BufferFractalsDn1,InpAsSeries);
   ArraySetAsSeries(BufferFractalsUp2,InpAsSeries);
   ArraySetAsSeries(BufferFractalsDn2,InpAsSeries);
   
//--- Create two indicators of the same type
//--- The first one is calculated on the current chart symbol/period, the second - on those specified in the settings
   handle_fractals1=indicators.AddNewFractals(NULL,PERIOD_CURRENT);
   handle_fractals2=indicators.AddNewFractals(InpSymbol,InpTimeframe);

//--- If failed to create indicator handles, return initialization error
   if(handle_fractals1==INVALID_HANDLE || handle_fractals2==INVALID_HANDLE)
      return INIT_FAILED;
//--- Set descriptions for indicator lines from buffer descriptions of calculation part of created indicators
   indicators.SetPlotLabelFromBuffer(0,handle_fractals1,0);
   indicators.SetPlotLabelFromBuffer(1,handle_fractals1,1);
   indicators.SetPlotLabelFromBuffer(2,handle_fractals2,0);
   indicators.SetPlotLabelFromBuffer(3,handle_fractals2,1);
      
//--- Dashboard
//--- Create the panel
   int width=311;
   panel=new CDashboard(1,20,20,width,264);
   if(panel==NULL)
     {
      Print("Error. Failed to create panel object");
      return INIT_FAILED;
     }
//--- Set font parameters
   panel.SetFontParams("Calibri",9);
//--- Display the panel with the "Symbol, Timeframe description" header text
   panel.View(Symbol()+", "+StringSubstr(EnumToString(Period()),7));

//--- Create a table with ID 0 to display bar data in it
   panel.CreateNewTable(0);
//--- Draw a table with ID 0 on the panel background
   panel.DrawGrid(0,2,20,6,2,18,width/2-2);

//--- Create a table with ID 1 to display the data of indicator 1
   panel.CreateNewTable(1);
//--- Get the Y2 table coordinate with ID 0 and
//--- set the Y1 coordinate for the table with ID 1
   int y1=panel.TableY2(0)+22;
//--- Draw a table with ID 1 on the panel background
   panel.DrawGrid(1,2,y1,2,2,18,width/2-2);

//--- Create a table with ID 2 to display the data of indicator 2
   panel.CreateNewTable(2);
//--- Get the Y2 coordinate of the table with ID 1 and
//--- set the Y1 coordinate for the table with ID 2
   int y2=panel.TableY2(1)+3;
//--- Draw a table with ID 2 on the background of the dashboard
   panel.DrawGrid(2,2,y2,3,2,18,width/2-2);
   
//--- Initialize the variable with the index of the mouse cursor bar
   mouse_bar_index=0;
//--- Display the data of the current bar on the panel
   DrawData(mouse_bar_index,TimeCurrent());

//--- Successful initialization
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Custom indicator deinitialization function                       |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- Delete the timer
   EventKillTimer();
//--- If the panel object exists, delete it
   if(panel!=NULL)
      delete panel;
//--- Delete all comments
   Comment("");
  }
//+------------------------------------------------------------------+
//| 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[])
  {
//--- Number of bars for calculation
   int limit=rates_total-prev_calculated;
//--- If limit > 1, then this is the first calculation or change in the history
   if(limit>1)
     {
      //--- specify all the available history for calculation
      limit=rates_total-1;
      /*
      // If the indicator has any buffers that display other calculations (not multi-indicators),
      // initialize them here with the "empty" value set for these buffers
      */
     }
//--- Calculate all created multi-symbol multi-period indicators
   if(!indicators.Calculate())
      return 0;

//--- Display the bar data under cursor (or current bar if cursor is outside the chart) on the dashboard
   DrawData(mouse_bar_index,time[mouse_bar_index]);

//--- From buffers of calculated indicators, output data to indicator buffers
   if(!indicators.DataToBuffer(NULL,PERIOD_CURRENT,handle_fractals1,0,0,limit,BufferFractalsUp1))
      return 0;
   if(!indicators.DataToBuffer(NULL,PERIOD_CURRENT,handle_fractals1,1,0,limit,BufferFractalsDn1))
      return 0;
   if(!indicators.DataToBuffer(NULL,PERIOD_CURRENT,handle_fractals2,0,0,limit,BufferFractalsUp2))
      return 0;
   if(!indicators.DataToBuffer(NULL,PERIOD_CURRENT,handle_fractals2,1,0,limit,BufferFractalsDn2))
      return 0;

//--- return value of prev_calculated for the next call
   return(rates_total);
  }
//+------------------------------------------------------------------+
//| Timer function                                                   | 
//+------------------------------------------------------------------+
void OnTimer()
  {
//--- Call the indicator collection timer
   indicators.OnTimer();
  }
//+------------------------------------------------------------------+
//| ChartEvent function                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
//--- Handling the panel
//--- Call the panel event handler
   panel.OnChartEvent(id,lparam,dparam,sparam);

//--- If the cursor moves or a click is made on the chart
   if(id==CHARTEVENT_MOUSE_MOVE || id==CHARTEVENT_CLICK)
     {
      //--- Declare the variables to record time and price coordinates in them
      datetime time=0;
      double price=0;
      int wnd=0;
      //--- If the cursor coordinates are converted to date and time
      if(ChartXYToTimePrice(ChartID(),(int)lparam,(int)dparam,wnd,time,price))
        {
         //--- write the bar index where the cursor is located to a global variable
         mouse_bar_index=iBarShift(Symbol(),PERIOD_CURRENT,time);
         //--- Display the bar data under the cursor on the panel 
         DrawData(mouse_bar_index,time);
        }
     }

//--- If we received a custom event, display the appropriate message in the journal
   if(id>CHARTEVENT_CUSTOM)
     {
      //--- Here we can implement handling a click on the close button on the panel
      PrintFormat("%s: Event id=%ld, object id (lparam): %lu, event message (sparam): %s",__FUNCTION__,id,lparam,sparam);
     }
  }
//+------------------------------------------------------------------+
//| Display data from the specified timeseries index to the panel    |
//+------------------------------------------------------------------+
void DrawData(const int index,const datetime time)
  {
//--- Declare the variables to receive data in them
   MqlRates rates[1];

//--- Exit if unable to get the bar data by the specified index
   if(CopyRates(Symbol(),PERIOD_CURRENT,index,1,rates)!=1)
      return;

//--- Set font parameters for bar and indicator data headers
   int  size=0;
   uint flags=0;
   uint angle=0;
   string name=panel.FontParams(size,flags,angle);
   panel.SetFontParams(name,9,FW_BOLD);
   panel.DrawText("Bar data ["+(string)index+"]",3,panel.TableY1(0)-16,clrMaroon,panel.Width()-6);
   panel.DrawText("Indicators data ["+(string)index+"]",3,panel.TableY1(1)-16,clrGreen,panel.Width()-6);
//--- Set font parameters for bar and indicator data
   panel.SetFontParams(name,9);

//--- Display the data of the specified bar in table 0 on the panel
   panel.DrawText("Date",  panel.CellX(0,0,0)+2, panel.CellY(0,0,0)+2); panel.DrawText(TimeToString(  rates[0].time,TIME_DATE),     panel.CellX(0,0,1)+2, panel.CellY(0,0,1)+2,clrNONE,90);
   panel.DrawText("Time",  panel.CellX(0,1,0)+2, panel.CellY(0,1,0)+2); panel.DrawText(TimeToString(  rates[0].time,TIME_MINUTES),  panel.CellX(0,1,1)+2, panel.CellY(0,1,1)+2,clrNONE,90);
   panel.DrawText("Open",  panel.CellX(0,2,0)+2, panel.CellY(0,2,0)+2); panel.DrawText(DoubleToString(rates[0].open,Digits()),      panel.CellX(0,2,1)+2, panel.CellY(0,2,1)+2,clrNONE,90);
   panel.DrawText("High",  panel.CellX(0,3,0)+2, panel.CellY(0,3,0)+2); panel.DrawText(DoubleToString(rates[0].high,Digits()),      panel.CellX(0,3,1)+2, panel.CellY(0,3,1)+2,clrNONE,90);
   panel.DrawText("Low",   panel.CellX(0,4,0)+2, panel.CellY(0,4,0)+2); panel.DrawText(DoubleToString(rates[0].low,Digits()),       panel.CellX(0,4,1)+2, panel.CellY(0,4,1)+2,clrNONE,90);
   panel.DrawText("Close", panel.CellX(0,5,0)+2, panel.CellY(0,5,0)+2); panel.DrawText(DoubleToString(rates[0].close,Digits()),     panel.CellX(0,5,1)+2, panel.CellY(0,5,1)+2,clrNONE,90);

//--- Output the data of buffer 0 of indicator 1 from the specified bar into table 1
   panel.DrawText(indicators.Title(handle_fractals1)+" Up", panel.CellX(1,0,0)+2, panel.CellY(1,0,0)+2);
   double value10=indicators.GetData(handle_fractals1,0,0,index);
   string value_str10=(value10!=EMPTY_VALUE ? DoubleToString(value10,indicators.Digits(handle_fractals1)) : " ");
   panel.DrawText(value_str10,panel.CellX(1,0,1)+2,panel.CellY(1,0,1)+2,clrNONE,110);
   
//--- Output the data of buffer 1 of indicator 1 from the specified bar into table 1
   panel.DrawText(indicators.Title(handle_fractals1)+" Down", panel.CellX(1,1,0)+2, panel.CellY(1,1,0)+2);
   double value11=indicators.GetData(handle_fractals1,1,0,index);
   string value_str11=(value11!=EMPTY_VALUE ? DoubleToString(value11,indicators.Digits(handle_fractals1)) : " ");
   panel.DrawText(value_str11,panel.CellX(1,1,1)+2,panel.CellY(1,1,1)+2,clrNONE,110);
   
//--- Output the data of buffer 0 of indicator 2 from the specified bar into table 2
   panel.DrawText(indicators.Title(handle_fractals2)+" Up", panel.CellX(2,0,0)+2, panel.CellY(2,0,0)+2);
   double value20=indicators.GetDataTo(Symbol(),PERIOD_CURRENT,handle_fractals2,0,0,index);
   string value_str20=(value20!=EMPTY_VALUE ? DoubleToString(value20,indicators.Digits(handle_fractals2)) : " ");
   panel.DrawText(value_str20,panel.CellX(2,0,1)+2,panel.CellY(2,0,1)+2,clrNONE,110);
   
//--- Output the data of buffer 1 of indicator 2 from the specified bar into table 2
   panel.DrawText(indicators.Title(handle_fractals2)+" Down", panel.CellX(2,1,0)+2, panel.CellY(2,1,0)+2);
   double value21=indicators.GetDataTo(Symbol(),PERIOD_CURRENT,handle_fractals2,1,0,index);
   string value_str21=(value21!=EMPTY_VALUE ? DoubleToString(value21,indicators.Digits(handle_fractals2)) : " ");
   panel.DrawText(value_str21,panel.CellX(2,1,1)+2,panel.CellY(2,1,1)+2,clrNONE,110);
   
//--- Redraw the chart to immediately display all changes on the panel
   ChartRedraw(ChartID());
  }
//+------------------------------------------------------------------+


Vamos compilar o indicador e executá-lo no gráfico M1, depois mudar o período do gráfico para M5 e M15:


Vemos como os buffers do indicador no gráfico M1 são preenchidos com dados do indicador calculado em M5.

Ao mesmo tempo, ao mudar o gráfico para M15, vemos como todos os fractais do indicador calculado em M5 são desenhados nas barras de M15. Vemos que em cada barra do gráfico M15, onde há fractais, eles não deveriam estar lá, segundo o gráfico. Mas não é o caso, visto que em cada barra do gráfico M15 há três barras do período M5, e lá esses fractais existem, e é isso que é exibido nas barras do gráfico M15, pois o indicador foi criado para ser um indicador multissímbolo e multiperíodo.


Conclusão

Hoje criamos e testamos o funcionamento das classes de indicadores multissímbolos e multiperíodos com o estilo de desenho DRAW_ARROW. Nos próximos artigos, tentaremos entender os outros estilos de desenho que envolvem interrupções de dados no gráfico e completaremos os demais indicadores padrão ainda não abordados, para, desse modo, fechar o tema dos indicadores multissímbolos e multiperíodos.

Todos os arquivos utilizados no artigo, como classes de multi-indicadores, classe do painel e arquivo do indicador estão anexados ao artigo para teste e uso próprio.



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

Arquivos anexados |
IndMSTF.mqh (645.11 KB)
Dashboard.mqh (217.99 KB)
Introdução ao MQL5 (Parte 3): Estudando os elementos básicos do MQL5 Introdução ao MQL5 (Parte 3): Estudando os elementos básicos do MQL5
Neste artigo, continuamos a estudar os fundamentos da programação em MQL5. Vamos abordar arrays, funções personalizadas, pré-processadores e manipulação de eventos. Para maior clareza, cada passo de todas as explicações será acompanhado por código. Esta série de artigos estabelece a base para o estudo do MQL5, com ênfase na explicação de cada linha de código.
Redes neurais de maneira fácil (Parte 73): AutoBots para previsão de movimentos de preço Redes neurais de maneira fácil (Parte 73): AutoBots para previsão de movimentos de preço
Continuamos a análise dos algoritmos de aprendizado de modelos de previsão de trajetórias. E neste artigo, proponho que você conheça o método chamado “AutoBots”.
Redes neurais de maneira fácil (Parte 74): previsão adaptativa de trajetórias Redes neurais de maneira fácil (Parte 74): previsão adaptativa de trajetórias
Proponho a você conhecer um método bastante eficaz de previsão de trajetórias multiagentes, que é capaz de se adaptar a diferentes condições ambientais.
Algoritmos de otimização populacionais: algoritmo genético binário (Binary Genetic Algorithm, BGA). Parte I Algoritmos de otimização populacionais: algoritmo genético binário (Binary Genetic Algorithm, BGA). Parte I
Neste artigo, vamos realizar um estudo sobre vários métodos aplicados em algoritmos genéticos binários e outros algoritmos populacionais. Vamos examinar os componentes principais do algoritmo, como seleção, crossover e mutação, bem como seu impacto no processo de otimização. Além disso, vamos explorar as formas de representação de informações e seu impacto nos resultados de otimização.