Aplicar um indicador a outro

4 dezembro 2013, 12:39
MetaQuotes Software Corp.
1
1 991

Introdução

Vamos considerar uma tarefa de melhoria de um indicador, a qual é aplicada a valores de um outro indicador. Neste artigo continuaremos a trabalhar com True Strength Index (TSI), o qual foi criado e considerado no artigo anterior "MQL5: Criar seu próprio indicador".

Indicador personalizado baseado em valores de outros indicadores

Ao escrever um indicador que usa a forma curta da solicitação de função OnCalculate(), você pode deixar passar o fato de que um indicador pode ser calculado, não apenas pelos dados de preço, mas também pelos dados de algum outro indicador (não importando se for incorporado ou um personalizado).

Vamos fazer um experimento simples: anexe o indicador de TSI incorporado com configurações padrão a uma tabela, e arraste o indicador personalizado True_Strength_Index_ver2.mq5 à janela do indicador de TSI. Na aba Parâmetros da janela que apareceu, especifique que o indicador deve ser aplicado aos Dados do indicador anterior (RSI(14)).

O resultado será bastante diferente do que nós esperávamos. A linha adicional do indicador de TSI não apareceu na janela do indicador de RSI, e na janela de dados, você verá que estes valores também não são claros.

Apesar do fato de que os valores do RSI serem definidos durante quase todo o histórico, os valores do TSI (aplicados aos dados do RSI) estão ou completamente ausentes (no início) ou sempre iguais a -100:

Tal comportamento é causado pelo fato de que o valor do parâmetro begin não é utilizado em nenhum lugar em OnCalculate() de nosso True_Strength_Index_ver2.mq5. O parâmetro begin especifica o número de valores vazios no parâmetro de entrada price[]. Estes valores vazios não podem ser usados no cálculo dos valores do indicador. Vamos lembrar a definição da primeira forma da solicitação dae função OnCalculate().
int OnCalculate (const int rates_total,      // price[] array length
                 const int prev_calculated,  // número de barras calculadas depois da última chamada
                 const int begin,            // início do índice de dados significativos
                 const double& price[]       // array para cálculos
   );

Ao aplicar o indicador aos dados de preço, especificando uma das constante de preço, o parâmetro begin é igual a 0, pois há um tipo de preço especificado para cada barra. Portanto a matriz de entrada price[] sempre possui dados corretos começando de seu primeiro elemento price[0]. Mas se especificarmos dados de um outro indicador como uma fonte para cálculos, isso já não é mais garantido.

O parâmetro begin da função OnCalculate()

Vamos verificar os valores contidos na matriz price[] quando o cálculo é realizado utilizando dados de um outro indicador. Para assim fazê-lo, na função OnCalculate() nós adicionaremos alguns códigos, que produzirão os valores que queremos verificar. Agora o início da função OnCalculate() parece:

//+------------------------------------------------------------------+
//| Função de iteração do indicador personalizado                    |
//+------------------------------------------------------------------+
int OnCalculate (const int rates_total,    // tamanho do array price[];
                 const int prev_calculated,// número de barras disponíveis depois da chamada anterior;
                 const int begin,          // início do índice de dados significativos no array price[] 
                 const double &price[])    // array para cálculos;
  {
//--- marcador para uma única saída de dados do price[]
   static bool printed=false;
//--- se begin não for zero, então existem valores que não levaremos em conta

   if(begin>0 && !printed)
     {
      //--- vamos enviá-las
      Print("Os dados para o cálculo começam em ",begin,
            "   tamanho do array price[] =",rates_total);

      //--- Vamos mostrar os valores que não devem se levar em consideração para o cálculo
      for(int i=0;i<=begin;i++)
        {
         Print("i =",i,"  valor =",price[i]);
        }
      //--- vamos usar o marcador para confirmar que usamos o valors
      printed=true;
     }

Mais uma vez, vamos arrastar e soltar a versão modificada de nosso indicador à janela do RSI(14) e especificar dados de indicadores anteriores para cálculo. Agora nós veremos os valores que não estão organizados e não devem ser levados em consideração para cálculos, onde os valores do indicador do RSI(14) são utilizados.


Valores vazios nos buffers indicadores e DBL_MAX

Os primeiros 14 elementos da matriz price[] com índices de 0 a 13 inclusivos, têm o mesmo valor igual a 1.797693134862316e+308. Você encontrará este número muito frequentemente, pois ele é um valor numérico da constante EMPTY_VALUE incorporada, que é utilizada para apontar valores vazios em um buffer indicador.

Preencher valores vazios com zeros não é uma solução universal, pois este valor pode ser o resultado do cálculo por alguns outros indicadores. Por esta razão, todos os indicadores incorporados do terminal do cliente retornam este número para valores vazios. O valor 1.797693134862316e+308 foi escolhido pois é o valor máximo possível de tipo double, e pela conveniência em que é apresentado como a constante DBL_MAX no MLQ5.

Para verificar se um certo número de tipo duplo está vazio ou não, você pode compará-lo com as constantes EMPTY_VALUE ou DBL_MAX . Ambas as variantes são iguais, mas é melhor usar a constante EMPTY_VALUE sem seu código para deixá-lo claro.

//+------------------------------------------------------------------+
//| retorna verdadeira para valores "vazios"                      |
//+------------------------------------------------------------------+
bool isEmptyValue(double value_to_check)
  {
//--- se o valor for igual DBL_MAX, ele possui valor vazio
   if(value_to_check==EMPTY_VALUE) return(true);
//--- ele não é igual a DBL_MAX
   return(false);
  }

O DBL_MAX é um número muito grande e o indicador RSI inerentemente não consegue reter tais valores! E somente o décimo quinto elemento da matriz (com índice 14) tem um valor razoável igual a 50. Portanto, mesmo se nós não saibamos nada sobre o indicador como uma fonte de dados a ser calculada, usando o parâmetro begin nós podemos organizar os dados processando corretamente em tais casos. Para sermos mais precisos, nós devemos evitar usar estes valores vazios em nossos cálculos.

Relacionamento entre o parâmetro begin e a propriedade PLOT_DRAW_BEGIN

Deve ser notado que há um relacionamento próximo entre o parâmetro begin, que é transferido à função OnCalculate() e a propriedade PLOT_DRAW_BEGIN, que define o número de barras iniciais sem esboço. Se investigarmos o código-fonte do RSI do pacote padrão do MetaTrader5, nós veremos o seguinte código na função OnInit():
//--- a primeira barra das quais serão usadas para desenhar o indicador
   PlotIndexSetInteger(0,PLOT_DRAW_BEGIN,ExtPeriodRSI);

Isto significa que a plotagem gráfica com índice 0 inicia somente da barra que possui índice igual ao ExtPeriodRSI (é uma variável de entrada, que especifica o período do indicador do RSI), e não há plotagem de barras precoces.

Na linguagem MQL5, o cálculo de algum indicador A baseado nos dados do indicador B é sempre realizado em valores de buffer zero do indicador B. Os valores zero de buffer do indicador B são transferidos como um parâmetro de entrada price[] à função OnCalculate() do indicador A. Inerentemente, o buffer zero é designado à plotagem gráfica zero com a função SetIndexBuffer(). Portanto:

Regra de transferência da propriedade PLOT_DRAW_BEGIN ao parâmetro begin: para cálculos do indicador personalizado A baseado nos dados de outro indicador (base) B, o valor do parâmetro de entrada begin na função OnCalculate() é sempre igual ao valor da propriedade PLOT_DRAW_BEGIN da plotagem gráfica zero B do indicador.

Logo, se nós criamos um indicador RSI (indicador B) com período 14 e depois criamos nosso indicador personalizado do índice de força verdadeira (indicador A) baseado em seus dados:

  • O indicador do RSI (14) é plotado, começando da décima-quarta barra por causa da matriz de entrada PlotIndexSetInteger(0,PLOT_DRAW_BEGIN,14);
  • price[] na função OnCalculate() e contém valores do buffer zero do indicador do IFR;
  • O valor do parâmetro de entrada begin na função OnCalculate() do indicador do TSI é obtido da propriedade PLOT_DRAW_BEGIN da plotagem gráfica zero do indicador do RSI.

Lembre-se que o indicador do TSI não é esboçado desde o início da tabela, pois o valor do indicador não é determinado para algumas das primeiras barras. O primeiro índice de barra, que será plotado como linha no indicador do TSI, é igual a r+s-1, onde:

  • r - período da primeira suavização exponencial das ordens MTMBuffer[] e AbsMTMBuffer[] nas ordens EMA_MTMBuffer[] e EMA_AbsMTMBuffer[] correspondentes;
  • s - período da suavização subsequente das ordens EMA_MTMBuffer[] e EMA_AbsMTMBuffer[].

Para barras com índices menores que r+s-1, não há valores para plotar o indicador do TSI. Portanto, para ordens finais MA2_MTMBuffer[] e EMA2_AbsMTMBuffer[] usadas para calcular o indicador do TSI, os dados têm compensação adicional e começam desde o índice r+s-1. Você pode achar mais informações no artigo "MQL5: Criar seu próprio indicador".

Há uma afirmação na função OnInit() para desativar o esboço das primeiras barras r+s-1:

//--- primeira barra à desenhar
   PlotIndexSetInteger(0,PLOT_DRAW_BEGIN,r+s-1);

Como o início dos dados de entrada foi desviado às barras begin, nós devemos levar isso em consideração e aumentar a posição de início do esboço de dados pelas barras begin na função OnCalculate():

   if(prev_calculated==0)
     { 
      //--- vamos aumentar a posição inicial dos dados com a barra begin,
      //--- porque nós usamos os dados de outro indicador para o cálculo
      if(begin>0)PlotIndexSetInteger(0,PLOT_DRAW_BEGIN,begin+r+s-1);
     }
Agora nós estamos levando em consideração o parâmetro begin para calcular os valores do indicador do TSI. Além disso, o parâmetro begin será transferido corretamente se algum outro indicador usar os valores do TSI para cálculo: beginother_indicator=beginour_indicator+r+s-1. Portanto, nós podemos formular a regra de imposição de um indicador sobre valores de outro indicador:

Regras de imposição de indicadores: se algum indicador personalizado A é esboçado iniciando desde a posição Na (os primeiros valores de Na não são plotados) e é baseado em dados de outro indicador B, que é esboçado desde a posição Nb, o indicador resultante A{B} será esboçado começando da posição Nab=Na+Nb onde A{B} significa que o indicador A é calculado em valores de buffer zero do indicador B.

Portanto, TSI (25,13) {RSI (14)} significa que o indicador TSI (25,13) é feito de valores do indicador RSI (14). Como um resultado da imposição, agora o início dos dados é (25+13-1)+14=51. Em outras palavras, o esboço do indicador começará da 52ª barra (a indexação das barras inicia em 0).

Adicionar valores begin para usar em cálculos de indicador

Agora nós sabemos exatamente que valores significativos da matriz price[] sempre iniciam a posição do formulário, especificados pelo parâmetro begin. Vamos modificar nosso código, passo a passo. Primeiro vem o código que calcula os valores de matriz MTMBuffer[] e AbsMTMBuffer[]. Sem o preenchimento da matriz do parâmetro begin iniciado com o índice 1.

//--- calcula os valores de mtm e |mtm|
   int start;
   if(prev_calculated==0) start=1;  // start filling MTMBuffer[] and AbsMTMBuffer[] arrays from 1st index 
   else start=prev_calculated-1;    // define o início igual ao último índice nas matrizes
   for(int i=start;i<rates_total;i++)
     {
      MTMBuffer[i]=price[i]-price[i-1];
      AbsMTMBuffer[i]=fabs(MTMBuffer[i]);
     }

Agora nós começaremos da posição (begin+1), e o código modificado parece o seguinte (mudanças de código são destacadas com negrito):

//--- calcula os valores de mtm e |mtm|
   int start;
   if(prev_calculated==0) start=begin+1;  // start filling MTMBuffer[] and AbsMTMBuffer[] arrays from begin+1 index 
   else start=prev_calculated-1;           // define o início igual ao último índice nas matrizes
   for(int i=start;i<rates_total;i++)
     {
      MTMBuffer[i]=price[i]-price[i-1];
      AbsMTMBuffer[i]=fabs(MTMBuffer[i]);
     }

Como valores de price[0] a price[begin-1] não podem ser usados para cálculos, nós começos desde price[begin]. Os primeiros valores calculados para ordens MTMBuffer[] e AbsMTMBuffer[] parecerão o seguinte:

      MTMBuffer[begin+1]=price[begin+1]-price[begin];
      AbsMTMBuffer[begin+1]=fabs(MTMBuffer[begin+1]);

Por esta razão, a variável start no ciclo for agora possui valor inicial de start=begin+1, ao invés de 1.

Responsabilizar begin para ordens dependentes

Agora vem a suavização exponencial das ordens MTMBuffer[] e AbsMTMBuffer[]. A regra é também simples: se a posição inicial da matriz de base aumentou pelas barras begin, então a posição inicial para todas as ordens dependentes devem ser aumentadas pelas barras begin também.

//--- calcula a primeira média móvel
   ExponentialMAOnBuffer(rates_total,prev_calculated,
                         1,               // índice do elemento inicial do array 
                         r,               // período da média exponencial
                         MTMBuffer,       // buffer da média
                         EMA_MTMBuffer);  // buffer alvo
   ExponentialMAOnBuffer(rates_total,prev_calculated,
                         1,r,AbsMTMBuffer,EMA_AbsMTMBuffer);

Aqui, MTMBuffer [] e AbsMTMBuffer [] são ordens de base, e os valores calculados nestas ordens agora começam desde o índice que é maior, pelo begin. Portanto, nós apenas adicionaremos esta compensação à função ExponentialMAOnBuffer().

Agora, este bloco parece assim:

//--- calcula a primeira média móvel
   ExponentialMAOnBuffer(rates_total,prev_calculated,
                         begin+1,        // índice do elemento inicial do array 
                         r,               // período da média exponencial
                         MTMBuffer,       // buffer para calcular a média
                         EMA_MTMBuffer);  // buffer alvo
   ExponentialMAOnBuffer(rates_total,prev_calculated,
                         begin+1,r,AbsMTMBuffer,EMA_AbsMTMBuffer);

Como você pode ver, a modificação inteira foi para acomodar o aumento da posição de início dos dados, definida pelo parâmetro begin. Nada complicado. Do mesmo modo, nós mudaremos o segundo bloco de suavização.

Antes:

//--- cálculo da segunda média móvel
   ExponentialMAOnBuffer(rates_total,prev_calculated,
                         r,s,EMA_MTMBuffer,EMA2_MTMBuffer);
   ExponentialMAOnBuffer(rates_total,prev_calculated,
                         r,s,EMA_AbsMTMBuffer,EMA2_AbsMTMBuffer);

Depois:

//--- cálculo da segunda média móvel
   ExponentialMAOnBuffer(rates_total,prev_calculated,
                         begin+r,s,EMA_MTMBuffer,EMA2_MTMBuffer);
   ExponentialMAOnBuffer(rates_total,prev_calculated,
                         begin+r,s,EMA_AbsMTMBuffer,EMA2_AbsMTMBuffer);

Do mesmo modo, nós mudaremos o último bloco de cálculo.

Antes:

//--- cálculo dos valores do nosso indicador
   if(prev_calculated==0) start=r+s-1; // define o início do índice nas matrizes
   else start=prev_calculated-1;       // define o início igual ao último índice nas matrizes
   for(int i=start;i<rates_total;i++)
     {
      TSIBuffer[i]=100*EMA2_MTMBuffer[i]/EMA2_AbsMTMBuffer[i];
     }

Depois:

//--- cálculo dos valores do nosso indicador
   if(prev_calculated==0) start=begin+r+s-1; // define o início do índice nas matrizes
   else start=prev_calculated-1;              // define o início igual ao último índice nas matrizes
   for(int i=start;i<rates_total;i++)
     {
      TSIBuffer[i]=100*EMA2_MTMBuffer[i]/EMA2_AbsMTMBuffer[i];
     }

Os ajustes do indicador foram finalizados e, agora, ele ignorará os primeiros valores begin vazios da matriz de entrada price[] em OnCalculate() e levará em consideração a compensação causada por esta omissão. Mas nós temos que lembrar que alguns outros indicadores podem usar valores do TSI para cálculos. Por esta razão, nós definiremos os valores vazios de nosso indicador para EMPTY_VALUE.

A inicialização dos buffers indicadores é necessária?

No MQL5, as ordens não são inicializadas por padrão com alguns valores definidos. O mesmo é aplicado às ordens, que são especificadas pela função SetIndexBuffer() para buffers de indicador. Se uma matriz é um buffer indicador, seu tamanho dependerá do valor do parâmetro rates_total na função OnCalculate();

Você pode ser tentado a inicializar todos os buffers indicadores com valores EMPTY_VALUE utilizando a função ArrayInitialize() por exemplo, imediatamente no início de OnCalculate():

//--- se este é o primeiro cálculo de OnCalculate() 
   if(prev_calculated==0)
     {
      ArrayInitialize(TSIBuffer,EMPTY_VALUE);
     }

Mas não é recomendado por causa das seguintes razões: enquanto o terminal do cliente está funcionando, novas quotas são recebidas para o símbolo, cujos dados são usados para calcular o indicador. Após algum tempo, o número de barras aumentará, então o terminal do cliente reservará memória adicional para os buffers indicadores.

Mas os valores dos novos elementos ("anexados") de matriz podem possuir qualquer valor, desde que durante a realocação de memória para qualquer matriz, a inicialização não seja realizada. A inicialização primária pode dar a você uma falsa segurança, que todos os elementos de matriz que não foram definidos explicitamente, serão preenchidos com valores que foram especificados durante a inicialização. É claro que isso não é verdade, e você não deve nunca pensar que o valor numérico da variável ou algum elemento de matriz será inicializado com valor necessário para nós.

Você deve definir o valor para cada elemento do buffer indicador. Se os valores de algumas barras não são definidos pelo algoritmo do indicador, você deve defini-los explicitamente sem valor vazio. Por exemplo, se algum valor do buffer indicador é calculado pelo operação de divisão, em alguns casos o divisor pode ser zero.

Nós sabemos, a divisão por zero é um erro crítico de tempo de execução no MQL5 e ela leva a uma terminação imediata de um programa-mql5. Ao invés de evitar divisão por zero, lidando com este caso especial em código, é necessário definir o valor para este elemento de buffer. Talvez, seja melhor usar um valor que tenhamos designado como vazio para este estilo de esboço.

Por exemplo, para algum estilo de esboço, nós definimos zero como valor vazio usando a função PlotIndexSetDouble():

   PlotIndexSetDouble(plotting_style_index,PLOT_EMPTY_VALUE,0);   

Então para todos os valores vazios do buffer indicador neste esboço é necessário definir valor zero explicitamente:

   if(divider==0)
      IndicatorBuffer[i]=0;
   else
      IndicatorBuffer[i]=... 

Além disso, se DRAW_BEGIN foi especificado para algum esboço, todos os elementos do buffer indicador com índices de 0 a DRAW_BEGIN serão preenchidos com zeros automaticamente.

Conclusão

Então, vamos fazer um breve sumário. Há algumas condições necessárias para o indicador ser corretamente calculado, baseado nos dados de um outro indicador (e para torná-lo adequado para uso em outros programas-mql5):

  1. Outros valores em indicadores incorporados são preenchidos com os valores da constante EMPTY_VALUE que é exatamente igual ao valor máximo para tipo duplo (DBL_MAX);
  2. Para detalhes sobre o índice inicial de valores significativos de um indicador, você deve analisar o parâmetro de entrada begin da forma curta da OnCalculate();
  3. A fim de proibir o esboço dos primeiros valores N para o estilo de esboço especificado, defina o parâmetro DRAW_BEGIN usando o seguinte código:
    PlotIndexSetInteger(plotting_style_index,PLOT_DRAW_BEGIN,N);
  4. Se DRAW_BEGIN é especificado para algum esboço, todos os elementos de buffer indicador com índices de 0 a DRAW_BEGIN serão preenchidos automaticamente com valores vazios (o padrão é EMPTY_VALUE);
  5. Na função OnCalculate(), adicione uma compensação adicional pelas barras begin para uso correto de dados de outros indicadores em seu próprio indicador:
    //--- se este é o primeiro cálculo 
       if(prev_calculated==0)
         { 
          //--- aumente a posição inicial dos dados com a barra begin, 
          //--- porque outro indicador  usa      
          if(begin>0)PlotIndexSetInteger(plotting_style_index,PLOT_DRAW_BEGIN,begin+N);
         }
    
  6. Você pode especificar o seu próprio valor vazio que difere a função EMPTY_VALUE em OnInit() usando o seguinte código:
    PlotIndexSetDouble(plotting_style_index,PLOT_EMPTY_VALUE,your_empty_value);
  7. Não confie em uma inicialização única dos buffers indicador usando o seguinte código:
    ArrayInitialize(buffer_number,value);
        
    Você deve definir todos os valores do buffer indicador para OnCalculate() da função, explicitamente e de forma consistente, incluindo os valores vazios.

É claro que, no futuro, quando você tiver alguma experiência em escrever indicadores, você encontrará alguns casos que estão além do escopo deste artigo, mas eu espero que quando isso acontecer, seu conhecimento de MQL5 permita que você resolva-os.

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

Arquivos anexados |
Últimos Comentários | Ir para discussão (1)
Alexander Knyazev
Alexander Knyazev | 23 dez 2013 em 12:20
Attached source code files and source code insets in HTML code are now completely translated into Portuguese for your convenience.
MQL5: Crie o seu próprio indicador MQL5: Crie o seu próprio indicador

O que é um indicador? É um conjunto de valores calculados que deseja-se que sejam exibidos em uma tela de forma conveniente. Os conjuntos de valores são representados em programas como arrays. Deste modo, a criação de um indicador significa escrever um algorítimo que manuseia algumas séries (séries de preço) e registra os resultados do manuseamento para outras séries (valores de indicador). Descrevendo a criação do Force Index, o autor mostra como escrever indicadores no MQL5.

Caminhe em novos trilhos: Personalize indicadores no MQL5 Caminhe em novos trilhos: Personalize indicadores no MQL5

Vou agora listar todas as possibilidades novas e recursos do novo terminal e linguagem. Elas são várias, e algumas novidades valem a discussão em um artigo separado. Além disso, não há códigos aqui escritos com programação orientada ao objeto, é um tópico muito importante para ser simplesmente mencionado em um contexto como vantagens adicionais para os desenvolvedores. Neste artigo vamos considerar os indicadores, sua estrutura, desenho, tipos e seus detalhes de programação em comparação com o MQL4. Espero que este artigo seja útil tanto para desenvolvedores iniciantes quanto para experientes, talvez alguns deles encontrem algo novo.

Limitações e verificações em Expert Advisors Limitações e verificações em Expert Advisors

É permitido negociar este símbolo na segunda? Há dinheiro suficiente para abrir posição? Qual o tamanho da perda se o Stop Loss acionar? Como limitar o número de ordens pendentes? A operação de negócio foi executada na barra atual ou na anterior? Se um robô de negócio não puder realizar este tipo de verificações, então qualquer estratégia de negócio pode se tornar uma de perda. Este artigo mostra os exemplos de verificações que são úteis em qualquer Expert Advisor.

A ordem de destruição e criação do objeto em MQL5 A ordem de destruição e criação do objeto em MQL5

Cada objeto, quer seja um objeto personalizado, um array dinâmico ou um array de objetos, é criado e excluído no programa MQL5 desta forma em particular. Geralmente, alguns objetos são parte de outros objetos, e a ordem de exclusão do objeto na desinicialização se torna especialmente importante. Este artigo fornece alguns exemplos que cobrem os mecanismos de trabalho com objetos.