Diminuindo o Consumo de Memória pelos Indicadores Auxiliares

ds2 | 6 março, 2014

1. O problema

Provavelmente, você já usou ou criou Expert Advisors ou indicadores que usam outros indicadores auxiliares para o seu funcionamento.

Por exemplo, o indicador famoso MACD usa duas cópias do indicador EMA (média móvel exponencial) calculando a diferença entre seus valores:


Tal indicador composto é equivalente a vários simples, de fato. Por exemplo, o MACD mencionado anteriormente consome a memória e o tempo de processamento três vezes mais do que o simples EMA, uma vez que tem que alocar memória para buffers do indicador principal e buffers de todos os seus indicadores auxiliares.

Além do MACD, existem indicadores mais complexos que utilizam mais que dois indicadores auxiliares.

Além disso, as despesas de memória aumentam consideravelmente se:

A combinação dessas condições pode levar a uma simples falta de memória em um computador (eu conheço casos reais, quando um terminal cliente exige gigabytes de memória devido ao uso de tais indicadores). Aqui está um exemplo de falta de memória no terminal do cliente MetaTrader 5:


Em tal situação, o terminal não é capaz de colocar o indicador em um gráfico ou calculá-lo corretamente (se o código do indicador não manipula o erro de alocação da memória), ou pode até mesmo ser desligado.

Felizmente, o terminal pode compensar a falta de memória usando mais memória virtual, ou seja, armazenar uma parte da informação no disco rígido. Todos os programas irão funcionar, mas muito lentamente...


2. O indicador Composto de Teste

Para continuar a nossa investigação no âmbito deste artigo, vamos criar um indicador composto, o qual é mais complexo que o MACD.

Que seja um indicador que acompanha o início das tendências. Ele vai resumir sinais de 5 períodos de tempo, por exemplo: H4, H1, M15, M5, M1. Ele permitirá a fixação da ressonância das grandes e pequenas tendências que surgem, o que deve aumentar a confiabilidade das previsões. Como a fonte de sinal em cada período de tempo, vamos utilizar os indicadores Ichimoku e Price_Channel incluídos no MetaTrader 5 entregue:



No total, o indicador vai usar 10 indicadores auxiliares: 5 períodos de tempo com dois indicadores em cada um. Vamos chamar nosso Trender indicador.

Aqui está o código fonte completo (que também é ligado ao artigo):

#property indicator_separate_window
#property indicator_buffers 1
#property indicator_plots   1
#property indicator_minimum -1
#property indicator_maximum  1

#property indicator_type1   DRAW_HISTOGRAM
#property indicator_color1  DarkTurquoise

// The only buffer of the indicator
double ExtBuffer[];

// Timeframes of auxiliary indicators
ENUM_TIMEFRAMES TF[5] = {PERIOD_H4, PERIOD_H1, PERIOD_M15, PERIOD_M5, PERIOD_M1};

// Handles of auxiliary indicators for all timeframes
int h_Ichimoku[5], h_Channel[5];

//+------------------------------------------------------------------+
void OnInit()
  {
   SetIndexBuffer(0, ExtBuffer);
   ArraySetAsSeries(ExtBuffer, true);
   
   // Create auxiliary indicators
   for (int itf=0; itf<5; itf++)
     {
      h_Ichimoku[itf] = iCustom(Symbol(), TF[itf], 
                                "TestSlaveIndicators\\Ichimoku",
                                9, 26, 52
                               );
      h_Channel [itf] = iCustom(Symbol(), TF[itf],
                                "TestSlaveIndicators\\Price_Channel",
                                22
                               );
     }
  }
//+------------------------------------------------------------------+
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[])
  {
   ArraySetAsSeries(time, true);
  
   int limit = prev_calculated ? rates_total - prev_calculated : rates_total -1;

   for (int bar = limit; bar >= 0; bar--)
     {
      // Time of the current bar
      datetime Time  = time [bar];
      
      //--- Gather signals from all timeframes
      double Signal = 0; // total signal
      double bufPrice[1], bufTenkan[1], bufKijun [1], bufMid[1], bufSignal[1];
      for (int itf=0; itf<5; itf++)
        {
         //=== Bar price
         CopyClose(Symbol(), TF[itf], Time, 1, bufPrice);
         double Price = bufPrice[0];

         //=== The Ichimoku indicator         
         CopyBuffer(h_Ichimoku[itf], 0, Time, 1, bufTenkan);
         double Tenkan = bufTenkan[0];
         CopyBuffer(h_Ichimoku[itf], 1, Time, 1, bufKijun );    
         double Kijun  = bufKijun [0];
           
         if (Tenkan > Kijun) Signal++;
         if (Tenkan < Kijun) Signal--;
          
         //=== The channel indicator
         CopyBuffer(h_Channel [itf], 2, Time, 1, bufMid);
         double Mid = bufMid[0];

         if (Price > Mid) Signal++;
         if (Price < Mid) Signal--;
        }
        
      ExtBuffer[bar] = Signal/10;
     }

   return(rates_total);
  }
//+------------------------------------------------------------------+

Você deve usar esse indicador em um gráfico com o menor período de tempo entre os que recolhem os sinais, ele é a única maneira de ver todas as pequenas tendências. No nosso caso, é o período de tempo M1. é assim que o indicador se parece:


E agora vamos passar para a parte mais importante: vamos calcular a quantidade de memória consumida por esse indicador.

Dê uma olhada no código fonte do indicador Ichimoku (o código completo está em anexo):

#property indicator_buffers 5

e o indicador de Price_Channel (o código completo está em anexo):

#property indicator_buffers 3

Nestas linhas, você pode ver que oito buffers são criados. Multiplique-o por cinco períodos de tempo. E adicione um buffer próprio indicador Trender. Temos 41 buffers no total! Tais valores impressionantes estão por trás desses indicadores aparentemente simples (em um gráfico).

Nas propriedades padrão do terminal do cliente, um buffer contém cerca de 100.000 valores do tipo duplo que consome 8 bytes cada. Assim, 41 buffers consomem cerca de 31 Mb de memória. E estes são apenas os próprios valores, eu não sei qual serviço de informação é armazenado nos buffers além deles.

Você pode dizer: "31 Mb não é muito". Mas quando um negociante usa um monte de pares de moedas, tal volume se torna um problema. Além dos indicadores, o gráfico deles consomem uma grande quantidade de memória. Ao contrário de indicadores, cada barra tem vários valores de uma só vez: OHLC, tempo e volume. Como podemos enquadrá-la em um computador?


3. Os Caminhos para a Resolução do Problema

Claro, você pode instalar mais memória em seu computador. Mas, se esta forma não é adequada para você, devido a técnica financeira ou quaisquer outras razões, ou se você já esgotou a quantidade de memória que pode ser instalada e não é o suficiente, você deve examinar os indicadores de consumo e diminuir seu apetite.

Para fazê-lo... lembre-se da sua geometria escolar. Suponha que todos os buffers dos nossos indicadores compostos são um retângulo sólido:


A área deste retângulo é a memória consumida. é possível diminuir a área através da diminuição da largura ou altura.

Neste caso, a largura é o número de barras que os indicadores são desenhados. A altura é o número de buffers do indicador.


4. Diminuindo o Número de Barras

4.1. A Solução Simples

Você não precisa nem ser um programador para ajustar as configurações do MetaTrader:



Diminuindo o valor das "barras máximas no gráfico", você diminui o tamanho dos buffers indicadores nessas janelas. é simples, eficaz e acessível a todos (se um comerciante não precisa de um histórico de preços profundo quando faz a negociação).

4.2. Há Alguma Outra Solução?

Programadores MQL5 sabem que buffers indicadores são declarados em indicadores como matrizes dinâmicas sem tamanho predefinido. Por exemplo, cinco buffers de Ichimoku:

double    ExtTenkanBuffer[];
double    ExtKijunBuffer[];
double    ExtSpanABuffer[];
double    ExtSpanBBuffer[];
double    ExtChinkouBuffer[];

O tamanho das matrizes não é especificado porque ele é definido para todo o histórico disponibilizado pelo próprio terminal do cliente MetaTrader 5.

O mesmo se encontra na função OnCalculate:

int OnCalculate (const int rates_total,      // size of the array price[]
               const int prev_calculated,  // number of bars processed at the previous call
               const int begin,            // the start of reliable data
               const double& price[]       // array for the calculation
   );

Nesta função, o buffer de preço é passado ao indicador. A memória para ele já está alocada pelo terminal, e um programador não pode afetar seu tamanho.

Além disso, MQL5 permite utilizar um buffer de um indicador como um buffer de preço para um outro (desenho"um indicador baseado em outro indicador"). Mas mesmo aqui, um programador não pode definir qualquer limitação no tamanho, eles só passam o cabo do indicador.

Assim, não há nenhuma maneira em MQL5 de limitar o tamanho dos buffers indicadores.


5. Diminuindo o Número de Buffers

Um programador tem uma enorme variedade de opções aqui. Eu encontrei várias formas teóricas simples de diminuir o número de buffers de um indicador composto. No entanto, todos eles implicam na diminuição do número de buffers dos indicadores auxiliares, uma vez que no principal indicador todos os buffers são necessários.

Vamos olhar detalhadamente sobre esses meios e verificar se eles realmente funcionam e quais as vantagens e desvantagens que eles têm.

5,1. O Método "Necessário"

Se um indicador auxiliar contém vários buffers, pode parecer que nem todos eles são necessários para o indicador principal. Assim, podemos desativar os indicadores não utilizados para liberar a memória que eles consomem. Para fazê-lo, precisamos fazer algumas mudanças no código fonte desse indicador auxiliar.

Vamos fazê-lo com um de nossos indicadores auxiliares - Price_Channel. Ele contém três buffers, e o Trender lê apenas um deles, assim temos coisas desnecessárias para remover.

Todo o código do Price_Channel (indicador inicial) e indicadores Price_Channel-Need (completamente refeito) estão ligados ao artigo. Além disso, vou descrever apenas as alterações que foram feitas para ele.

Primeiro de tudo, diminua o contador de buffers de 3 para 1:

//#property indicator_buffers 3
  #property indicator_buffers 1
//#property indicator_plots   2
  #property indicator_plots   1

Em seguida, remova duas matrizes de buffer desnecessários:

//--- indicator buffers
//double    ExtHighBuffer[];
//double    ExtLowBuffer[];
 double    ExtMiddBuffer[];

Agora, se tentarmos compilar este indicador, o compilador irá mostrar toda as linhas onde as matrizes são chamadas:


Este método permite localizar rapidamente as coisas que precisam ser alteradas. é muito conveniente quando o código indicador é enorme.

No nosso caso, há 4 linhas de "identificadores não declarados" no total. Vamos corrigi-los.

Como poderíamos esperar, dois deles estão localizados em OnInit. Mas junto com eles, tivemos que remover a linha com o ExtMiddBuffer necessário. Em vez disso, nós adicionamos um semelhante, mas com outro índice de buffer. Como o indicador não tem mais um buffer com índice 2, somente o 0 é possível:

//   SetIndexBuffer(0,ExtHighBuffer,INDICATOR_DATA);
//   SetIndexBuffer(1,ExtLowBuffer,INDICATOR_DATA);
//   SetIndexBuffer(2,ExtMiddBuffer,INDICATOR_DATA);
     SetIndexBuffer(0,ExtMiddBuffer,INDICATOR_DATA);

Se você planeja usar o indicador de "corte" em um modo visual, então considere que as configurações de aparência devem ser alteradas juntamente com o índice do buffer. No nosso caso:

//#property indicator_type1   DRAW_FILLING
  #property indicator_type1   DRAW_LINE

Se você não precisa de visualização, você pode pular alterar aparência - ela não leva a erros.

Vamos continuar trabalhando com a lista de "identificador não declarado". Duas últimas alterações (o que é expectável também) estão em OnCalculate, onde as matrizes do buffer indicador estão cheias. Uma vez que o requerido ExtMiddBuffer chama o ExtHighBuffer e o ExtLowBuffer removidos, as variáveis ​intermédias são substituídas ao invés deles:

   //--- the main loop of calculations
   for(i=limit;i<rates_total;i++)
     {
//      ExtHighBuffer[i]=Highest(High,InpChannelPeriod,i);
        double      high=Highest(High,InpChannelPeriod,i);
//      ExtLowBuffer[i]=Lowest(Low,InpChannelPeriod,i);
        double      low=Lowest(Low,InpChannelPeriod,i);
//      ExtMiddBuffer[i]=(ExtHighBuffer[i]+ExtLowBuffer[i])/2.0;;
        ExtMiddBuffer[i]=(   high         +   low         )/2.0;;
     }

Como você pode ver, não há nada difícil em toda esta "operação de cirurgia". As coisas necessárias foram localizados rapidamente, vários "cortes" e dois buffers foram excluídos. Dentro de todo o Trender indicador composto, a economia total é de 10 buffers (2*5 períodos de tempo).

Você pode abrir o Price_Channel e o Price_Channel-Need um sobre o outro e ver os buffers desaparecidos:


Para usar o Price_Channel-Need no indicador Trender, precisamos corrigir o nome do indicador auxiliar de "Price_Channel" para "Price_Channel-Need" no código do Trender. Além disso, precisamos mudar o índice do buffer exigido de 2 para 0. O Trender necessário já pronto está anexado ao artigo.


5,2. O Método "Agregado"

Se um indicador principal lê dados de mais de um buffer de um indicador auxiliar e, em seguida, efetua uma ação de agregação com ele (por exemplo, adição ou comparação), então não é necessário executar esta ação no indicador principal. Nós podemos fazê-lo em um indicador auxiliar e, em seguida, passar o resultado para o principal. Assim, não é necessário ter vários buffers, todos eles são substituídos por um só.

No nosso caso, este método é aplicável a Ichimoku. Conforme, o Trender usa dois buffers a partir dele (0 - Tenkan e 1 - Kijun):

         CopyBuffer(h_Ichimoku[itf], 0, Time, 1, bufTenkan);
         double Tenkan = bufTenkan[0];
         CopyBuffer(h_Ichimoku[itf], 1, Time, 1, bufKijun );    
         double Kijun  = bufKijun [0];
           
         if (Tenkan > Kijun) Signal++;
         if (Tenkan < Kijun) Signal--;

Se agregarmos o buffer 0 e 1 do Ichimoku a um buffer de sinal, em seguida, o fragmento do Trender mostrado acima irá ser substituído com o seguinte:

         CopyBuffer(h_Ichimoku[itf], 0, Time, 1, bufSignal);
         
         Signal += bufSignal[0];

A Agregação completa do Trender está anexada ao artigo.

Agora, vamos olhar para as principais alterações que devem ser feitas ao Ichimoku.

Além disso, este indicador contém buffers não utilizados. Assim, além do método de "agregação", podemos aplicar o método de "necessidade". Assim, somente um buffer de 5 está à esquerda em Ichimoku - aquele que agrega os buffers necessários:

//#property indicator_buffers 5
  #property indicator_buffers 1
//#property indicator_plots   4
  #property indicator_plots   1

Vamos dar um novo nome ao único buffer:

//--- indicator buffers
//double    ExtTenkanBuffer[];
//double    ExtKijunBuffer[];
//double    ExtSpanABuffer[];
//double    ExtSpanBBuffer[];
//double    ExtChinkouBuffer[];
  double    ExtSignalBuffer[];

O novo nome tem um significado prático - permite apagar todos os nomes dos buffers usados anteriormente a partir do indicador. Ele permitirá (usando compilação conforme descrito no método "Necessidade") encontrar rapidamente todas as linhas que devem ser alteradas.

Se você for visualizar o indicador em um gráfico, então não se esqueça de alterar as configurações de aparência. Além disso, você considere que, no nosso caso, o buffer de agregação tem uma gama diferente de valores comparado a dois buffers consumidos por ele. Agora ele não mostra um derivativo preço, mas qual dos dois buffers é maior. é mais conveniente apresentar estes resultados em uma janela separada embaixo de um gráfico:

//#property indicator_chart_window
  #property indicator_separate_window

Então, faça as seguintes alterações no OnInit:

//--- indicator buffers mapping
//   SetIndexBuffer(0,ExtTenkanBuffer,INDICATOR_DATA);
//   SetIndexBuffer(1,ExtKijunBuffer,INDICATOR_DATA);
//   SetIndexBuffer(2,ExtSpanABuffer,INDICATOR_DATA);
//   SetIndexBuffer(3,ExtSpanBBuffer,INDICATOR_DATA);
//   SetIndexBuffer(4,ExtChinkouBuffer,INDICATOR_DATA);
     SetIndexBuffer(0,ExtSignalBuffer,INDICATOR_DATA);

E a parte mais interessante está em OnCalculate. Nota: três buffers desnecessários são simplesmente eliminados (conforme usamos o método de "Necessidade"), e os ExtTenkanBuffer e ExtKijunBuffer necessários são substituídos por variáveis ​temporárias Tenkan e Kijun. Estas variáveis​ são utilizadas no final do ciclo, para o cálculo da agregação do buffer do ExtSignalBuffer:

   for(int i=limit;i<rates_total;i++)
     {
//     ExtChinkouBuffer[i]=Close[i];
      //--- tenkan sen
      double high=Highest(High,InpTenkan,i);
      double low=Lowest(Low,InpTenkan,i);
//     ExtTenkanBuffer[i]=(high+low)/2.0;
       double  Tenkan    =(high+low)/2.0;
      //--- kijun sen
      high=Highest(High,InpKijun,i);
      low=Lowest(Low,InpKijun,i);
//     ExtKijunBuffer[i]=(high+low)/2.0;
       double  Kijun    =(high+low)/2.0;
      //--- senkou span a
//     ExtSpanABuffer[i]=(ExtTenkanBuffer[i]+ExtKijunBuffer[i])/2.0;
      //--- senkou span b
      high=Highest(High,InpSenkou,i);
      low=Lowest(Low,InpSenkou,i);
//     ExtSpanBBuffer[i]=(high+low)/2.0;

       //--- SIGNAL
       double Signal = 0;
       if (Tenkan > Kijun) Signal++;
       if (Tenkan < Kijun) Signal--;
       ExtSignalBuffer[i] = Signal;
     }

Menos 4 buffers no total! E, se tivéssemos aplicado apenas o método de "Necessidade" de Ichimoku, teríamos apenas três buffers a menos.

Dentro de todo Trender a economia é de 20 buffers no total (4 * 5 períodos de tempo).

O código completo de Agregação Ichimoku está anexado ao artigo. Para comparar esse indicador ao original, abra os dois em um único gráfico. Como você se lembra, o indicador de modificação agora é exibido abaixo do gráfico em uma janela separada:


5,3. O Método "Incluso"

A maneira mais radical para diminuir o número de buffers é livrar-se de todos os indicadores auxiliares. Se fizermos isso, apenas um buffer será deixado em nosso indicador - o que pertence ao indicador principal. Não pode haver menos buffers.

O mesmo resultado pode ser alcançado movendo o código de indicadores auxiliares para o indicador principal. às vezes, pode parecer uma coisa que leva tempo, mas o efeito final vale a pena. O mais difícil é adaptar o código movido dos indicadores. Ele não tem a intenção de trabalhar no código de outro indicador.

Aqui estão os principais problemas que surgem durante esta ação:

Vamos demonstrar os métodos que permitem resolver estes problemas de forma eficaz.

Cada indicador auxiliar deve ser escrito como uma classe. Então, todas as variáveis ​e funções dos indicadores terão sempre (dentro de suas classes) nomes exclusivos e não estarão em conflito com os demais indicadores.

Se muitos indicadores são movidos, você pode pensar na padronização dessas classes para evitar confusão quando usá-los. Para este efeito, crie uma classe indicadora de base e herde as classes de todos os indicadores auxiliares a partir dele.

Eu escrevi a seguinte classe:

class CIndicator
  {
protected:
   string symbol;             // currency pair
   ENUM_TIMEFRAMES timeframe;  // timeframe

   double Open[], High[], Low[], Close[]; // simulation of price buffers
   int BufLen; // necessary depth of filling of price buffers

public:
   //--- Analogs of standard functions of indicators
   void Create(string sym, ENUM_TIMEFRAMES tf) {symbol = sym; timeframe = tf;};
   void Init();
   void Calculate(datetime start_time); // start_time - address of bar that should be calculated
  };

Agora vamos criar uma classe para o indicador Ichimoku na base do mesmo. Primeiro de tudo, na forma de propriedades, escreva os parâmetros de entrada com os nomes originais. Não mude nada no código do indicador depois:

class CIchimoku: public CIndicator
  {
private:
   // Simulation of input parameters of the indicator
   int InpTenkan;
   int InpKijun;
   int InpSenkou;

Mantenha os nomes de todos os buffers. Sim, isso é o que você ouviu - nós declaramos todos os cinco buffers desse indicador. Mas eles são falsos. Eles consistem de uma única barra cada:

public:   
   // Simulation of indicator buffers
   double ExtTenkanBuffer [1];
   double ExtKijunBuffer  [1];
   double ExtSpanABuffer  [1];
   double ExtSpanBBuffer  [1];
   double ExtChinkouBuffer[1];   

Por que fazemos isso? Para diminuir a quantidade de novas mudanças no código. Você verá. Redefinir o método herdado CIchimoku.Calculate pelo preenchimento com o código da função OnCalculate retirado do Ichimoku.

Preste atenção que o ciclo pelas barras históricas foi removido ao mover esta função. Agora, apenas uma barra com um tempo especificado é calculada lá. E o código principal de cálculos permanece o mesmo. é por isso que mantemos tão cuidadosamente todos os nomes dos buffers e os parâmetros do indicador.

Você também deve prestar atenção que os buffers dos preços são preenchidos com valores no próprio início do método Calculate. Existem tantos valores quanto necessário para o cálculo de uma barra.

   void Calculate(datetime start_time)
     {
      CopyHigh (symbol,timeframe,start_time,BufLen,High);
      CopyLow  (symbol,timeframe,start_time,BufLen,Low );
      CopyClose(symbol,timeframe,start_time,1     ,Close);

//    int limit;
      //---
//    if(prev_calculated==0) limit=0;
//    else                   limit=prev_calculated-1;
      //---
//    for(int i=limit;i<rates_total;i++)
      int i=0;
        {
         ExtChinkouBuffer[i]=Close[i];
         //--- tenkan sen
         double high=Highest(High,InpTenkan,i);
         double low=Lowest(Low,InpTenkan,i);
         ExtTenkanBuffer[i]=(high+low)/2.0;
         //--- kijun sen
         high=Highest(High,InpKijun,i);
         low=Lowest(Low,InpKijun,i);
         ExtKijunBuffer[i]=(high+low)/2.0;
         //--- senkou span a
         ExtSpanABuffer[i]=(ExtTenkanBuffer[i]+ExtKijunBuffer[i])/2.0;
         //--- senkou span b
         high=Highest(High,InpSenkou,i);
         low=Lowest(Low,InpSenkou,i);
         ExtSpanBBuffer[i]=(high+low)/2.0;
        }
      //--- done
//    return(rates_total);     
     };

Claro que poderíamos pular mantendo o código original. Mas neste caso, teríamos que reescrever uma grande parte dele, o que exige a compreensão da lógica de seu funcionamento. No nosso caso, o indicador é simples e seria fácil de entender. Mas e se o indicador for complexo? Mostrei-lhe o método que pode ajudar neste caso.

Agora vamos preencher o método CIchimoku.Init, tudo é simples aqui:

   void Init(int Tenkan = 9, int Kijun = 26, int Senkou = 52)
     {
      InpTenkan = Tenkan; InpKijun = Kijun; InpSenkou = Senkou;
      BufLen = MathMax(MathMax(InpTenkan, InpKijun), InpSenkou);
     };

O Ichimoku contém duas outras funções que devem ser tomadas à classe CIchimoku: Superior e inferior. Eles procuram por valores máximos e mínimos em uma parte especificada de buffers de preço.

Nossos buffers de preços não são reais, eles têm um tamanho muito pequeno (você já viu seu preenchimento no método Calculate acima). é por isso que temos que mudar um pouco a lógica de funcionamento das funções superiores e inferiores.

Nesta situação, eu também segui o princípio de fazer alterações mínimas. Todas as modificações consistem em adicionar uma linha que altera a indexação de barras no buffer do global (quando a duração do buffer é todo o histórico disponível) para o local (como agora os buffers de preços contêm apenas valores que são necessários para o cálculo de um indicador de barra):

   double Highest(const double&array[],int range,int fromIndex)
     {
       fromIndex=MathMax(ArraySize(array)-1, 0);
      double res=0;
   //---
      res=array[fromIndex];
      for(int i=fromIndex;i>fromIndex-range && i>=0;i--)
        {
         if(res<array[i]) res=array[i];
        }
   //---
      return(res);
     }

O método inferior é modificado da mesma maneira.

Modificações semelhantes são feitas ao indicador Price_Channel, mas ele será representado pela classe chamada CChannel. O código completo de ambas as classes está no arquivo Trender-Include anexado ao artigo.

Eu descrevi o aspecto principal de movimento do código. Penso que esses métodos são suficientes para a maioria dos indicadores.

Indicadores com configurações fora do padrão podem causar dificuldades adicionais. Por exemplo, o Price_Channel contém desinteressantes:

   PlotIndexSetInteger(0,PLOT_SHIFT,1);
   PlotIndexSetInteger(1,PLOT_SHIFT,1);

Eles querem dizer que o gráfico do indicador é deslocado em uma barra. No nosso caso, ele leva a uma situação onde as funções CopyBuffer e CopyHigh utilizam duas barras diferentes, apesar de que as mesmas coordenadas de barras (tempo) são fixadas em seus parâmetros.

Este problema é resolvido em Trender-Include ("uns" são adicionados nas partes necessárias da classe CChannel em distinção da classe CIchimoku onde o problema não existe). Então, se você precisa de um tal indicador "astuto", você sabe onde procurar ele.

Bem, estamos fazendo com o movimento, e ambos os indicadores estão agora escritos como duas classes dentro do indicador Trender-Include. Ele é renunciado a mudar a maneira de chamar aqueles indicadores. Em Trender tivemos as matrizes de cabos, e em Trender-Include eles são substituídos com os matrizes de objetos:

// Handles of auxiliary indicator for all timeframes
//int h_Ichimoku[5], h_Channel[5];
// Instances of embedded auxiliary indicators
CIchimoku o_Ichimoku[5]; CChannel o_Channel[5];

A criação de indicadores auxiliares no OnInit agora parece da seguinte forma:

   for (int itf=0; itf<5; itf++)
     {
      o_Ichimoku[itf].Create(Symbol(), TF[itf]);
      o_Ichimoku[itf].Init(9, 26, 52);
      o_Channel [itf].Create(Symbol(), TF[itf]);
      o_Channel [itf].Init(22);
     }

E o CopyBuffer em OnCalculate é substituído com chamada direta para as propriedades dos objetos:

         //=== The Ichimoku indicator
         o_Ichimoku[itf].Calculate(Time);

         //CopyBuffer(h_Ichimoku[itf], 0, Time, 1, bufTenkan);
         //double Tenkan = bufTenkan[0];
         double Tenkan = o_Ichimoku[itf].ExtTenkanBuffer[0];

         //CopyBuffer(h_Ichimoku[itf], 1, Time, 1, bufKijun );    
         //double Kijun  = bufKijun [0];
         double Kijun  = o_Ichimoku[itf].ExtKijunBuffer [0];
           
         if (Tenkan > Kijun) Signal++;
         if (Tenkan < Kijun) Signal--;
          
         //=== The Channel indicator
         o_Channel[itf].Calculate(Time);

         //CopyBuffer(h_Channel [itf], 2, Time, 1, bufMid);
         //double Mid = bufMid[0];
         double Mid = o_Channel[itf].ExtMiddBuffer[0];

         if (Price > Mid) Signal++;
         if (Price < Mid) Signal--;

Menos 40 buffers. Vale a pena o esforço.

Após cada modificação do Trender de acordo com a "Necessidade" e métodos "Agregados" descritos anteriormente, eu testei os indicadores obtidos no modo visual.

Vamos realizar esse teste agora mesmo: abra o indicador inicial (Trender) e refaça um (Trender-Include) em um gráfico. Podemos dizer que tudo foi feito corretamente, uma vez que as linhas de ambos os indicadores coincidem precisamente com o outro:


5,4. Podemos Fazer Um por Um?

Já consideramos três maneiras de diminuir o número de buffers de indicadores auxiliares. Mas se tentarmos mudar a abordagem radicalmente - se tentarmos diminuir o número de buffers que são simultaneamente guardados na memória em vez de diminuir o seu número total? Em outras palavras, vamos carregar os indicadores para a memória, um a um, em vez de colocar todos os indicadores de uma vez. Precisamos organizar um "desvio": criar um indicador auxiliar, ler seus dados, excluí-lo, criar um próximo, etc., até que passemos por cima de todos os períodos de tempo. O indicador Ichimoku tem o maior número de buffers - 5. Assim, teoricamente, um máximo de 5 buffers podem ser mantidos simultaneamente na memória (mais um buffer do indicador principal), e a economia total é de 35 tampões!

Isso é possível? Em MQL5 existe uma função especial para eliminar indicadores - IndicatorRelease.

No entanto, não é tão fácil como parece. O MetaTrader 5 se preocupa com a alta velocidade de operação dos programas MQL5, é por isso que todos os chamados períodos de tempo são mantidos em cache - no caso de outro EA, indicador ou script precisarem deles. E somente se eles não forem chamados por um longo período de tempo, eles são descarregados para a memória livre. Esse intervalo pode ser de até 30 minutos de duração.

Assim, uma criação constante e a deleção de indicadores não permitirá alcançar uma grande economia de memória instantaneamente. No entanto, pode desacelerar significativamente o computador, porque o indicador é calculado para todo o histórico de preços cada vez que ele é criado. Pense em quão razoável é realizar tal operação em cada barra do indicador principal...

No entanto, a ideia do "desvio indicador" foi muito interessante para um "estalo". Se você pensar em qualquer outra ideia original de otimização da memória do indicador, escreva seus comentários ao artigo. Talvez eles vão ser postos em uso teórico ou prático em um dos próximos artigos sobre este assunto.


6. Medindo o Consumo Real da Memória

Bem, nos capítulos anteriores, nós implementamos três métodos de trabalho de diminuição do número de buffers dos indicadores auxiliares. Agora, vamos analisar como ele diminui o consumo real de memória.

Nós mediremos o tamanho de memória consumida pelo terminal usando o "Gerenciador de Tarefas" no Windows MS. Na aba "Processos", você pode ver o tamanho da RAM e da memória virtual consumida pelo terminal do cliente. Por exemplo:


As medidas são feitas de acordo com o seguinte algoritmo que permite ver o consumo mínimo de memória pelo terminal (que será próximo ao consumo de memória pelos indicadores):

  1. Baixe um histórico de preços fundamente do servidor MetaQuotes-Demo (é suficiente para executar um teste em um símbolo para o download automático de seu histórico);
  2. Defina o terminal para uma próxima medida (gráficos e indicadores necessários abertos) e o reinicie para limpar a memória de informações desnecessárias;
  3. Espere até que o terminal reiniciado conclua o cálculo de todos os indicadores. Você vai vê-lo pela carga zero do processador;
  4. Minimize o terminal na barra de tarefas (clicando no botão padrão "Minimizar" no canto superior direito do terminal). Ele irá liberar a memória que não é usada para cálculos no momento (na imagem acima você pode ver o exemplo do consumo de memória no estado ainda minimizado - você pode ver que muito menos RAM do que a memória virtual é consumida);
  5. No "Gerenciador de Tarefas" soma-se ao "Mem Usage" (RAM) e às colunas de "tamanho da VM" (memória virtual). Esta é a maneira como são chamados no Windows XP, os nomes podem ser ligeiramente diferentes em outra versão do OS.

Parâmetros de medidas:

O que se espera do resultado de medidas? Existem duas notas:

  1. Mesmo que o indicador Trender use 41 buffers, isso não significa que ele consome 41*100000 barras. A razão é que os buffers estão distribuídos em cinco períodos de tempo, e o maior deles contém menos barras do que o menor período de tempo. Por exemplo, o histórico de M1 do EURUSD contém cerca de 4 milhões de barras, e o histórico H1 consiste de apenas 70000 barras (4000000/60). é por isso que você não deve esperar a mesma redução de consumo de memória depois de diminuir o número de buffers em Trender;
  2. A memória é consumida não só pelo indicador em si, mas pelas séries de preços utilizados pelo indicador. Trender usa cinco períodos de tempo. Assim, se diminuirmos o número de buffers por várias vezes, o consumo total de memória não diminuirá tanto. Porque todas aquelas cinco séries de preço na memória serão usadas.

Ao medir o consumo, você pode enfrentar alguns outros fatores que afetam o consumo de memória. Essa é a razão pela qual nós conduzimos estas medidas práticas - para ver a economia real como resultado da otimização do indicador.

Abaixo, você pode encontrar a tabela com o resultado de todas as medidas. Primeiro de tudo, eu medi o tamanho da memória consumida pelo terminal vazio. Subtraindo esse valor a partir da próxima medida, podemos calcular o tamanho da memória consumida por um gráfico. Subtraindo a memória consumida pelo terminal e um gráfico das seguintes medidas, obtém-se o tamanho da memória consumida por cada indicador.

Consumidor de Memória
Buffers dos Indicadores
Períodos de Tempo
Consumo da Memória
O terminal do cliente
0
0
38 Mb para o terminal
Gráfico
0
1
12 Mb para um gráfico vazio
O indicador Trender
41
5
46 Mb para um indicador
O indicador de Necessidade Trender
31
5
42 Mb para um indicador
O indicador de Agregação Trender 21
5
37 Mb para um indicador
O indicador de Inclusão Trender 1
5
38 Mb para um indicador


A conclusão feita na base dos resultados das medidas:

A origem deste efeito é descrito anteriormente neste capítulo. Talvez se o indicador tivesse utilizado menos períodos de tempo, o efeito da diminuição do número de buffers seria mais significante.

Então, por que é o método de inclusão não é tão eficaz como o de agregação? Para determinar a razão, precisamos lembrar as principais diferenças do código desses indicadores. Em agregação, a série de preço necessário para cálculos são passados pelo terminal como matrizes de entrada em OnCalculate. Em inclusão, todos os dados (para todos os períodos de tempo) são solicitados ativamente para cada barra usando CopyHigh, CopyLow e CopyClose. Provavelmente, isso é o que leva ao consumo adicional de memória, causada pelas peculiaridades de ocultação de períodos de tempo dos preços quando essas funções são usadas.


Conclusão

Assim, este artigo fala sobre três métodos de trabalho de diminuição do consumo de memória em indicadores auxiliares e um método de poupar memória pelo ajuste do terminal do cliente.

Um método que deve ser aplicado depende da aceitabilidade e adequação de sua situação. O número de buffers e megabytes poupados depende dos indicadores com os quais você trabalha: em alguns deles você vai ser capaz de "cortar" um monte, e em outros, você não será capaz de fazer qualquer coisa.

Poupando a memória permitirá aumentar o número de pares monetários usados simultaneamente no terminal. Ele aumenta a confiabilidade de seu portfólio comercial. Esse cuidado simples sobre os recursos técnicos do seu computador pode se transformar em recursos monetários em seu depósito.


Anexos

Os indicadores descritos no artigo estão anexados. Para que tudo funcione, os salve na pasta "MQL5\Indicators\TestSlaveIndicators", porque todas as versões do indicador Trender (exceto Trender-Include) procurarão por seus indicadores nela.