Lendo qualquer indicador a partir de um Expert Advisor: setas, linhas e índices de cor no CopyBuffer

13 novembro 2026, 08:29
Bohdan Klok
0
2

Introdução

Mais cedo ou mais tarde, todo trader que usa um indicador pede "a mesma coisa, mas operando sozinho". A resposta do programador parece simples: criar o indicador com iCustom() ou IndicatorCreate(), ler os valores dele com CopyBuffer() e abrir uma posição quando o sinal aparecer. Na prática, a primeira pergunta nem é sobre negociação. É esta: qual buffer guarda o sinal?

Um indicador de setas pode ter um buffer para as setas de compra e outro para as de venda, ou um único buffer para as duas com um índice de cor ao lado. Um indicador de "tendência" pode desenhar uma única linha que muda de cor, e a cor fica guardada em um buffer separado que nunca é desenhado. Um indicador compilado comprado no Market vem sem código-fonte, então não há como ler as chamadas SetIndexBuffer() dele. A Data Window (Janela de Dados) ajuda um pouco, mas mostra apenas os valores desenhados, de uma barra por vez, e esconde por completo os buffers de cor e de cálculo.

Este post mostra como responder a essa pergunta de fora, para qualquer indicador: como os buffers são armazenados, como ler todos eles a partir de outro programa, como distinguir um buffer de setas de uma linha e de um índice de cor apenas pelos valores, como verificar se os sinais repintam e como devolver a resposta ao gráfico, para que o usuário veja qual número corresponde a qual seta. O resultado são três pequenos programas com código-fonte: um script que inspeciona todos os indicadores de um gráfico (gratuito no Code Base), um indicador de demonstração que tem todos os tipos de buffer e um Expert Advisor que opera qualquer indicador segundo quatro regras genéricas (estes dois publicados na íntegra no final do post).

1. O que é, de fato, um buffer de indicador

Um indicador guarda a sua saída em arrays vinculados com SetIndexBuffer(). Existem três tipos:

  • INDICATOR_DATA: valores que são desenhados (linhas, histogramas, setas) e exibidos na Data Window;
  • INDICATOR_COLOR_INDEX: nos estilos DRAW_COLOR_*, o número da cor usada em cada barra;
  • INDICATOR_CALCULATIONS: arrays intermediários que não são desenhados nem exibidos.

Os três tipos são numerados em uma única sequência, na ordem em que o indicador os declarou, e todos podem ser lidos com CopyBuffer(). Esta é a primeira surpresa para muitos programadores: um buffer que você nunca vê no gráfico continua disponível, e nos indicadores de cor muitas vezes é o mais útil.

Plotagens e buffers não são a mesma coisa. Uma plotagem DRAW_COLOR_LINE usa dois buffers (valor e índice de cor), DRAW_COLOR_CANDLES usa cinco (quatro preços e o índice de cor), DRAW_COLOR_ARROW usa dois (o preço da seta e a cor dela). O indicador Heiken Ashi que vem com o terminal é um bom exemplo: os buffers 0 a 3 são os preços do candle e o buffer 4 é o índice de cor, 0 para um candle de alta e 1 para um de baixa.

O segundo ponto é como o "nada" é armazenado. Um buffer de linha normalmente tem um valor em cada barra. Um buffer de setas tem um preço apenas nas barras com seta e um valor "vazio" nas demais. Qual valor significa vazio fica a critério do autor: EMPTY_VALUE (DBL_MAX) é a escolha habitual, definida com PlotIndexSetDouble(PLOT_EMPTY_VALUE), mas muitos indicadores usam 0.0. Um código de leitura que trate 0.0 como preço de seta válido verá uma seta em todas as barras.

Para ter algo concreto para observar, o indicador DemoBuffers.mq5 (código-fonte no final) declara todos os tipos de buffer: uma média móvel simples como linha, setas onde o fechamento cruza a média para cima, uma média exponencial desenhada como linha colorida e a inclinação dessa média como buffer de cálculo.

SetIndexBuffer(0,ExtSma,INDICATOR_DATA);             // line
SetIndexBuffer(1,ExtCross,INDICATOR_DATA);           // arrows, EMPTY_VALUE where there is none
SetIndexBuffer(2,ExtEma,INDICATOR_DATA);             // colour line: the value
SetIndexBuffer(3,ExtEmaColor,INDICATOR_COLOR_INDEX); // colour line: 0 rising, 1 falling
SetIndexBuffer(4,ExtSlope,INDICATOR_CALCULATIONS);   // not drawn, not in the Data Window
PlotIndexSetInteger(1,PLOT_ARROW,233);
PlotIndexSetDouble(1,PLOT_EMPTY_VALUE,EMPTY_VALUE);

A Data Window mostra três valores dele: a média, a seta e a média exponencial. Os buffers 3 e 4 não aparecem ali, e mesmo assim a cor da linha - o sinal de "tendência" mais natural deste indicador - está no buffer 3. A Figura 1 mostra o que um programa vê quando lê os cinco.

Figura 1. DemoBuffers no EURUSD H1: duas linhas, um buffer de setas, um índice de cor e um buffer de cálculo, conforme lidos pelo script Buffer Inspector Kb.

2. Lendo todos os buffers de um indicador

Para um indicador já anexado a um gráfico, o handle está disponível sem que se conheçam os parâmetros de entrada dele:

for(int w=0; w<(int)ChartGetInteger(0,CHART_WINDOWS_TOTAL); w++)
   for(int i=0; i<ChartIndicatorsTotal(0,w); i++)
     {
      string name=ChartIndicatorName(0,w,i);
      int    h=ChartIndicatorGet(0,w,name);   // the same instance the user sees
      ...
      IndicatorRelease(h);                    // releases our handle, not the indicator
     }

Isso importa mais do que parece: o usuário já escolheu os parâmetros de entrada do indicador no gráfico, e o programa lê exatamente essa instância. Para um indicador que não está no gráfico, IndicatorCreate() com IND_CUSTOM recebe o nome relativo a MQL5\Indicators e um array de MqlParam. Quando os parâmetros vêm de um campo de texto, por exemplo "14,0,1.5,true", cada item precisa receber o tipo certo:

int CreateByName(const string name,const string params)
  {
   MqlParam p[];
   ArrayResize(p,1);
   p[0].type=TYPE_STRING;               // the first element is always the name
   p[0].string_value=name;
   string parts[];
   const int n=(StringLen(params)>0) ? StringSplit(params,',',parts) : 0;
   ArrayResize(p,n+1);
   for(int i=0; i<n; i++)
     {
      string s=parts[i];
      StringTrimLeft(s);
      StringTrimRight(s);
      if(s=="true" || s=="false")
        { p[i+1].type=TYPE_BOOL;   p[i+1].integer_value=(s=="true"); }
      else if(StringFind(s,".")>=0 && MathIsValidNumber(StringToDouble(s)))
        { p[i+1].type=TYPE_DOUBLE; p[i+1].double_value=StringToDouble(s); }
      else if(s=="0" || StringToInteger(s)!=0)
        { p[i+1].type=TYPE_INT;    p[i+1].integer_value=StringToInteger(s); }
      else
        { p[i+1].type=TYPE_STRING; p[i+1].string_value=s; }
     }
   return(IndicatorCreate(_Symbol,_Period,IND_CUSTOM,ArraySize(p),p));
  }

Três regras poupam muito tempo com esta função. Os valores precisam seguir a ordem da janela de parâmetros de entrada do indicador, e não é possível pular um parâmetro: para definir o terceiro, é preciso informar também os dois primeiros. Uma enumeração é passada pelo seu número, então MODE_EMA é 1 e PRICE_CLOSE é 1. E os parâmetros não informados mantêm os valores padrão, então uma string vazia cria o indicador exatamente como ele vem.

Quantos buffers existem? O handle não diz, mas CopyBuffer() diz: retorna -1 para um número de buffer que não existe. Ler os buffers 0, 1, 2 e assim por diante até a primeira falha dá a contagem, com os buffers de dados, de cor e de cálculo juntos.

Dois detalhes práticos:

  • depois que o handle é criado, o indicador precisa de tempo para calcular; BarsCalculated(handle) retorna -1 até que isso aconteça, então um script espera por ele em um laço curto com Sleep(), e um Expert Advisor simplesmente tenta de novo no próximo tick;
  • comece a copiar a partir da barra 1, a última barra fechada. Em muitos indicadores de setas, a barra em formação muda até fechar, e uma análise baseada nela mostra setas que depois desaparecem.

3. Distinguindo setas, linhas e índices de cor

Com N barras fechadas de um buffer em um array, três estatísticas simples bastam:

  • válidos: valores que não são EMPTY_VALUE nem NaN;
  • setas: valores válidos e diferentes de 0.0;
  • inteiros pequenos: valores que são números inteiros com valor absoluto pequeno.

e a classificação decorre delas:

O que o buffer contém Como ele aparece em 500 barras
nada nenhum valor válido, ou apenas zeros
setas alguns valores, mas em 30% das barras ou menos
índice de cor apenas números inteiros pequenos em uma faixa estreita (por exemplo, 0 e 1)
linha todo o resto: um valor em (quase) todas as barras
int valid=0, arrows=0, ints=0;
double lo=DBL_MAX, hi=-DBL_MAX;
for(int i=0; i<n; i++)
  {
   if(v[i]==EMPTY_VALUE || !MathIsValidNumber(v[i])) continue;
   valid++; lo=MathMin(lo,v[i]); hi=MathMax(hi,v[i]);
   if(v[i]!=0.0) arrows++;
   if(MathAbs(v[i]-MathRound(v[i]))<1e-9 && MathAbs(v[i])<64) ints++;
  }
if(valid==0 || (arrows==0 && hi==0.0 && lo==0.0))  kind="empty";
else if(arrows>0 && arrows<=n*0.3)                  kind="arrows";
else if(ints==valid && hi-lo<=16)                    kind="colour index";
else                                                 kind="line";

Por que 30%? Indicadores de setas marcam pontos de virada, que por natureza são raros: o Fractals marca uma pequena porcentagem das barras, e os indicadores de "sinal" típicos, menos de 10%. Uma linha que fica vazia em parte das barras (uma média móvel no início do histórico) ainda tem valores na maioria delas. O limiar é deliberadamente folgado; os casos próximos dele são informados ao usuário, e não adivinhados em silêncio.

Por que "inteiros pequenos em uma faixa estreita" para as cores? Um índice de cor assume os valores 0, 1, 2 e assim por diante, um para cada cor da plotagem. Uma linha também pode ser inteira (um oscilador arredondado pelo autor, uma função em degraus), mas nesse caso ela percorre uma faixa muito mais ampla do que o punhado de cores que uma plotagem pode ter.

A Figura 2 mostra o resultado para o Heiken Ashi: quatro linhas de preço e, no buffer 4, o índice de cor com os valores 0 e 1 - exatamente o que um Expert Advisor precisa para reagir a uma mudança na cor do candle.

Figura 2. Heiken Ashi: quatro linhas de preço e o índice de cor no buffer 4, que a Data Window não mostra.

Alguns casos merecem um comentário, porque é neles que uma olhada rápida dá a resposta errada:

  • Setas armazenadas com 0.0 em vez de EMPTY_VALUE. A classificação acima já trata 0.0 como "sem seta", mas um programa que compara apenas com EMPTY_VALUE vai disparar em todas as barras. Teste sempre os dois.
  • Pontos em todas as barras, como no Parabolic SAR. No gráfico eles parecem setas, mas o buffer tem valor em todas as barras, então é uma linha, e o sinal é o momento em que os pontos saltam para o outro lado do preço, não a presença de um ponto.
  • Setas coloridas (DRAW_COLOR_ARROW). O buffer de setas dá a barra e o preço, e o índice de cor ao lado diz se é uma seta de compra ou de venda. Os dois buffers são necessários para um único sinal.
  • Buffers de cálculo. Em geral são classificados como linhas, muitas vezes com valores longe do preço. Vale a pena olhá-los: alguns indicadores guardam ali o sinal "bruto", antes da suavização ou da filtragem.

4. Mostrando ao usuário qual seta é qual

Uma tabela com "buffer 1: setas, 37, a última em 2026.10.07 08:00" está correta, mas o trader ainda precisa comparar as datas com o gráfico para saber se o buffer 1 é a seta para cima ou a seta para baixo. É muito mais rápido escrever o número do buffer ao lado das próprias setas:

for(int i=n-1, done=0; i>=0 && done<20; i--)
  {
   if(v[i]==EMPTY_VALUE || v[i]==0.0) continue;
   int      shift=n-i;                          // v[] runs from shift n (oldest) to shift 1
   datetime t=iTime(_Symbol,_Period,shift);
   string   obj=StringFormat("BufInspector_%d_%d",buffer,shift);
   ObjectCreate(0,obj,OBJ_TEXT,0,t,v[i]);       // at the arrow's own price
   ObjectSetString(0,obj,OBJPROP_TEXT,"B"+(string)buffer);
   done++;
  }

O rótulo é colocado no valor armazenado no buffer, que nos indicadores de setas é o preço em que a seta é desenhada, então "B0" e "B1" aparecem bem em cima das setas. Uma cor por buffer facilita distinguir dois buffers de setas.

Figura 3. Examples\Fractals: B0 aparece acima dos fractais superiores, B1 abaixo dos inferiores.

5. O indicador repinta?

Um buffer que contém setas é só metade da resposta. A outra metade é saber se uma seta, depois de aparecer em uma barra fechada, permanece ali. Alguns indicadores movem ou apagam as suas setas quando chegam novas barras - o Fractals faz isso por projeto nas duas últimas barras, e os indicadores do tipo ZigZag por muito mais tempo. Um Expert Advisor que opera uma seta que depois desaparece está operando um sinal que, em retrospecto, nunca existiu, e os seus resultados no Testador de Estratégia não vão corresponder ao gráfico.

A verificação é simples de automatizar: memorizar os valores das últimas barras fechadas pelo horário de abertura, esperar algumas barras novas, ler as mesmas barras de novo e comparar.

datetime ExtTimes[];   // open times of the remembered bars
double   ExtValues[];  // buffer values on those bars
//--- remember the last 100 closed bars of buffer b
int Remember(const int handle,const int b)
  {
   if(CopyTime(_Symbol,_Period,1,100,ExtTimes)!=100) return(0);
   return(CopyBuffer(handle,b,1,100,ExtValues));
  }
//--- later: how many of the remembered bars changed their value?
int Changed(const int handle,const int b)
  {
   int changed=0;
   for(int i=0; i<ArraySize(ExtTimes); i++)
     {
      double v[];
      if(CopyBuffer(handle,b,ExtTimes[i],1,v)==1 && v[0]!=ExtValues[i])
         changed++;
     }
   return(changed);
  }

Ler pelo horário, e não pelo índice, é essencial aqui: depois que chegam novas barras, a mesma barra tem um índice maior. O resultado diz até quantas barras para trás o indicador ainda muda de ideia, e é esse número que define a barra de sinal a usar. No Fractals a resposta é sempre duas barras, e é por isso que um fractal é lido na barra 3, e não na barra 1. Um indicador que continua mudando as suas setas dez barras para trás não pode ser operado na barra em que a seta aparece pela primeira vez, por melhor que pareça a sua imagem no Testador de Estratégia.

6. Quando não há buffer para ler

Alguns indicadores desenham as suas setas com ObjectCreate() em vez de buffers. As setas estão no gráfico, a Data Window está vazia e CopyBuffer() retorna apenas linhas ou nada. Nenhum programa consegue ler esses sinais por meio do handle do indicador. Vale a pena dizer isso claramente ao usuário, porque senão a busca pelo "número de buffer certo" nunca termina. Um script pode detectar a situação de forma indireta: um indicador que não tem buffer de setas, embora o usuário veja setas, quase certamente está desenhando objetos. O Buffer Inspector Kb diz isso no seu relatório.

7. Dos buffers às regras de negociação

Uma vez conhecido o buffer, quatro regras cobrem quase todos os indicadores que um trader traz:

Regra Indicadores típicos Condição na barra de sinal
surge uma seta Fractals, indicadores de setas o valor não é vazio nem 0
um buffer cruza um nível RSI 30/70, CCI +-100, osciladores em torno de 0 anterior < nível <= atual
um buffer cruza outro MACD principal/sinal, Stochastic %K/%D, linhas do Alligator anterior a < b e atual a >= b
um índice de cor assume um valor Heiken Ashi, médias coloridas, barras de "tendência" anterior != k e atual == k

O BufferSignalEA.mq5 (código-fonte no final) implementa exatamente essas regras, com o nome do indicador, os seus parâmetros de entrada, a regra e os números dos buffers como parâmetros de entrada, de modo que um único Expert Advisor pode ser apontado para qualquer indicador. O coração dele é uma função que lê dois valores, o da barra de sinal e o da barra anterior:

bool RuleFires(const ENUM_SIGNAL_RULE rule,const int buffer,const double level,const int buffer2)
  {
   if(rule==SIGNAL_OFF)
      return(false);
   double a[],b[];
   //--- a[0] = the bar before the signal bar, a[1] = the signal bar
   if(CopyBuffer(ExtHandle,buffer,InpSignalBar,2,a)!=2)
      return(false);
   switch(rule)
     {
      case SIGNAL_ARROW:
         return(!IsEmpty(a[1]) && a[1]!=0.0);
      case SIGNAL_CROSS_UP:
         return(!IsEmpty(a[0]) && !IsEmpty(a[1]) && a[0]<level && a[1]>=level);
      case SIGNAL_CROSS_DOWN:
         return(!IsEmpty(a[0]) && !IsEmpty(a[1]) && a[0]>level && a[1]<=level);
      case SIGNAL_BECOMES:
         return(!IsEmpty(a[1]) && MathAbs(a[1]-level)<0.5 && (IsEmpty(a[0]) || MathAbs(a[0]-level)>=0.5));
      case SIGNAL_LINE_UP:
      case SIGNAL_LINE_DOWN:
        {
         if(CopyBuffer(ExtHandle,buffer2,InpSignalBar,2,b)!=2)
            return(false);
         if(IsEmpty(a[0]) || IsEmpty(a[1]) || IsEmpty(b[0]) || IsEmpty(b[1]))
            return(false);
         if(rule==SIGNAL_LINE_UP)
            return(a[0]<b[0] && a[1]>=b[1]);
         return(a[0]>b[0] && a[1]<=b[1]);
        }
     }
   return(false);
  }

Um índice de cor é comparado com uma tolerância de 0,5 porque é armazenado como double. A decisão é tomada uma vez a cada nova barra, antes de qualquer outra coisa em OnTick():

const datetime bar=iTime(_Symbol,_Period,0);
if(bar==ExtLastBar)
   return;
if(BarsCalculated(ExtHandle)<InpSignalBar+2)
   return;                                     // the indicator is not ready yet: try on the next tick
ExtLastBar=bar;
const bool buy =RuleFires(InpBuyRule, InpBuyBuffer, InpBuyLevel, InpBuyBuffer2);
const bool sell=RuleFires(InpSellRule,InpSellBuffer,InpSellLevel,InpSellBuffer2);
if(buy==sell || HasPosition())
   return;                                     // no signal, both at once, or already in the market

Uma regra avaliada a cada tick da barra em formação dispararia várias vezes e com sinais que depois desaparecem; uma decisão por barra, tomada sobre uma barra fechada, mantém o Testador de Estratégia e o gráfico ao vivo em concordância.

Para verificar que cada regra realmente dispara, o Expert Advisor foi executado no Testador de Estratégia com cinco indicadores, com a mesma saída simples para todos (0,1 lote, stop loss de 300 pontos, take profit de 600 pontos, uma posição por vez). Os números abaixo são um teste funcional das regras, não um resultado de negociação: um indicador operado sem nenhum filtro nem gestão de posição não é uma estratégia.

Indicador e regra Buffers Operações Lucrativas
Fractals, surge uma seta (barra de sinal 3) compra 1, venda 0 184 53
MACD, a linha principal cruza a de sinal 0 e 1 160 46
RSI(14), cruza 30 para cima / 70 para baixo 0 113 36
Heiken Ashi, a cor passa a 0 / 1 4 170 53
DemoBuffers, seta para compra, cor 1 para venda 1 e 3 152 39

EURUSD H1, 2025.10.01 - 2026.10.01, modelo "OHLC em 1 minuto". Os cinco testes rodam sem uma única solicitação com falha; as diferentes quantidades de operações vêm da frequência com que cada indicador dá sinal enquanto não há posição aberta.

8. Lendo um indicador em outro timeframe

A mesma leitura funciona para um indicador em um timeframe maior, e é assim que se constrói a maioria dos filtros ("comprar apenas quando o preço estiver acima da EMA 200 no H4"). O handle é criado com o timeframe, e a barra 1 desse handle é a última barra fechada desse timeframe:

int h4=iMA(_Symbol,PERIOD_H4,200,0,MODE_EMA,PRICE_CLOSE);
...
double ma[];
if(CopyBuffer(h4,0,1,1,ma)==1 && iClose(_Symbol,PERIOD_H4,1)>ma[0])
   allowBuy=true;

Duas observações. Primeiro, os dados de outro timeframe podem não estar prontos quando o programa inicia: BarsCalculated() retorna -1 e CopyBuffer() falha até que o terminal tenha montado o histórico, então a verificação precisa ser repetida, e não interpretada como "sem sinal". Segundo, nunca leia a barra 0 do timeframe maior em uma decisão tomada no menor. Em um gráfico H1, a barra H4 em formação muda a cada hora, então um filtro que a lê vai parecer perfeito no testador, que já sabe como a barra H4 termina, e vai se comportar de outra forma em um gráfico ao vivo.

9. Os programas

  • Buffer Inspector Kb (script, MQL5\Scripts, gratuito no Code Base): coloque-o em um gráfico que já tenha o indicador. Ele inspeciona todos os indicadores em todas as janelas, imprime a tabela no diário Experts, mostra-a no gráfico com Comment() e rotula as últimas setas de cada buffer de setas. Com o nome de um indicador nos parâmetros de entrada, ele mesmo cria o indicador, o que é útil para indicadores compilados do Market. Executá-lo com "Only remove the labels" limpa o gráfico.
  • DemoBuffers.mq5 (indicador, MQL5\Indicators): um único indicador com uma linha, setas, uma linha colorida com o seu índice de cor e um buffer de cálculo, para experimentos. Código-fonte abaixo.
  • BufferSignalEA.mq5 (Expert Advisor, MQL5\Experts): opera qualquer indicador segundo as quatro regras acima; o indicador, os seus parâmetros de entrada, as regras e os números dos buffers são parâmetros de entrada. Código-fonte abaixo.

Conclusão

Os buffers de um indicador são a sua interface pública, mesmo quando o código-fonte não está disponível. Ler todos eles, classificá-los pelos valores, rotular as setas no gráfico e verificar até quantas barras para trás os valores mudam transforma "qual número de buffer devo usar?" de uma pergunta para o autor do indicador em uma verificação de dez segundos. A mesma classificação é a base de quatro regras de negociação genéricas - seta, nível, cruzamento de linhas e mudança de cor - que permitem a um único Expert Advisor trabalhar com indicadores que ele nunca viu, em barras fechadas, de modo que o que o Testador de Estratégia mostra é o que um gráfico ao vivo vai fazer.

Código-fonte

Salve cada listagem com o seu nome na pasta indicada (Arquivo → Abrir Pasta de Dados no terminal) e compile-a no MetaEditor com F7.

DemoBuffers.mq5 (MQL5\Indicators)

//+------------------------------------------------------------------+
//|                                                  DemoBuffers.mq5 |
//|                                                      Bohdan Klok |
//|                             https://www.mql5.com/en/users/kbposht |
//+------------------------------------------------------------------+
#property copyright   "Bohdan Klok"
#property link        "https://www.mql5.com/en/users/kbposht"
#property version     "1.00"
#property description "Example: one indicator with every kind of buffer - a line, arrows,"
#property description "a colour line with its colour index, and a calculation buffer that is not drawn."
#property indicator_chart_window
#property indicator_buffers 5
#property indicator_plots   3
//--- plot 0: a plain line (buffer 0)
#property indicator_label1  "SMA"
#property indicator_type1   DRAW_LINE
#property indicator_color1  clrSilver
#property indicator_width1  1
//--- plot 1: arrows (buffer 1), drawn only where the close crosses the SMA upward
#property indicator_label2  "Cross up"
#property indicator_type2   DRAW_ARROW
#property indicator_color2  clrDodgerBlue
#property indicator_width2  2
//--- plot 2: a colour line (buffer 2 = value, buffer 3 = colour index)
#property indicator_label3  "Trend EMA"
#property indicator_type3   DRAW_COLOR_LINE
#property indicator_color3  clrSeaGreen,clrOrangeRed
#property indicator_width3  2
//--- input parameters
input int InpSmaPeriod=20;   // SMA period
input int InpEmaPeriod=50;   // EMA period
//--- indicator buffers
double ExtSma[];             // 0: line
double ExtCross[];           // 1: arrows, EMPTY_VALUE where there is none
double ExtEma[];             // 2: colour line value
double ExtEmaColor[];        // 3: colour index, 0 rising, 1 falling
double ExtSlope[];           // 4: calculations, EMA change over one bar
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
   SetIndexBuffer(0,ExtSma,INDICATOR_DATA);
   SetIndexBuffer(1,ExtCross,INDICATOR_DATA);
   SetIndexBuffer(2,ExtEma,INDICATOR_DATA);
   SetIndexBuffer(3,ExtEmaColor,INDICATOR_COLOR_INDEX);
   SetIndexBuffer(4,ExtSlope,INDICATOR_CALCULATIONS);
   PlotIndexSetInteger(1,PLOT_ARROW,233);
   PlotIndexSetDouble(1,PLOT_EMPTY_VALUE,EMPTY_VALUE);
   PlotIndexSetInteger(0,PLOT_DRAW_BEGIN,InpSmaPeriod);
   PlotIndexSetInteger(2,PLOT_DRAW_BEGIN,InpEmaPeriod);
   IndicatorSetString(INDICATOR_SHORTNAME,StringFormat("DemoBuffers(%d,%d)",InpSmaPeriod,InpEmaPeriod));
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| 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[])
  {
   if(rates_total<InpEmaPeriod+2)
      return(0);
   const double k=2.0/(InpEmaPeriod+1.0);
   int start=(prev_calculated>1) ? prev_calculated-1 : 1;
   if(prev_calculated==0)
     {
      ExtEma[0]=close[0];
      ExtSlope[0]=0.0;
      ExtEmaColor[0]=0.0;
     }
   for(int i=start; i<rates_total; i++)
     {
      //--- SMA, empty until there are enough bars
      if(i>=InpSmaPeriod-1)
        {
         double sum=0.0;
         for(int j=0; j<InpSmaPeriod; j++)
            sum+=close[i-j];
         ExtSma[i]=sum/InpSmaPeriod;
        }
      else
         ExtSma[i]=EMPTY_VALUE;
      //--- EMA, its slope and the colour of the slope
      ExtEma[i]=ExtEma[i-1]+k*(close[i]-ExtEma[i-1]);
      ExtSlope[i]=ExtEma[i]-ExtEma[i-1];
      ExtEmaColor[i]=(ExtSlope[i]>=0.0) ? 0.0 : 1.0;
      //--- an arrow under the bar where the close crosses the SMA upward
      ExtCross[i]=EMPTY_VALUE;
      if(i>=InpSmaPeriod && ExtSma[i-1]!=EMPTY_VALUE && close[i-1]<ExtSma[i-1] && close[i]>=ExtSma[i])
         ExtCross[i]=low[i];
     }
   return(rates_total);
  }
//+------------------------------------------------------------------+

BufferSignalEA.mq5 (MQL5\Experts)

//+------------------------------------------------------------------+
//|                                               BufferSignalEA.mq5 |
//|                                                      Bohdan Klok |
//|                             https://www.mql5.com/en/users/kbposht |
//+------------------------------------------------------------------+
#property copyright   "Bohdan Klok"
#property link        "https://www.mql5.com/en/users/kbposht"
#property version     "1.00"
#property description "Example: trades the signals of any indicator by four generic rules on its"
#property description "buffers - an arrow appears, a level is crossed, one buffer crosses another, a colour"
#property description "index becomes a value. Signals are read on the closed bar, once per bar."
#include <Trade\Trade.mqh>
//--- the four generic rules
enum ENUM_SIGNAL_RULE
  {
   SIGNAL_OFF=0,          // Disabled
   SIGNAL_ARROW=1,        // Arrow appears in the buffer
   SIGNAL_CROSS_UP=2,     // Buffer crosses the level upward
   SIGNAL_CROSS_DOWN=3,   // Buffer crosses the level downward
   SIGNAL_LINE_UP=4,      // Buffer crosses the second buffer upward
   SIGNAL_LINE_DOWN=5,    // Buffer crosses the second buffer downward
   SIGNAL_BECOMES=6       // Buffer becomes equal to the level (colour index)
  };
//--- input parameters
input string           InpIndicator="Examples\\Fractals"; // Indicator name inside MQL5\Indicators
input string           InpParams="";                      // Its inputs, comma separated (empty = defaults)
input int              InpSignalBar=3;                    // Signal bar (1 = last closed bar)
input ENUM_SIGNAL_RULE InpBuyRule=SIGNAL_ARROW;           // Buy rule
input int              InpBuyBuffer=1;                    // Buy: buffer
input double           InpBuyLevel=0.0;                   // Buy: level
input int              InpBuyBuffer2=0;                   // Buy: second buffer
input ENUM_SIGNAL_RULE InpSellRule=SIGNAL_ARROW;          // Sell rule
input int              InpSellBuffer=0;                   // Sell: buffer
input double           InpSellLevel=0.0;                  // Sell: level
input int              InpSellBuffer2=1;                  // Sell: second buffer
input double           InpLots=0.1;                       // Lot
input int              InpStopPoints=300;                 // Stop loss, points
input int              InpTakePoints=600;                 // Take profit, points
input ulong            InpMagic=20261010;                 // Magic number
//--- global variables
CTrade   ExtTrade;
int      ExtHandle=INVALID_HANDLE;
datetime ExtLastBar=0;
//+------------------------------------------------------------------+
//| Indicator inputs from "14,0,1.5,true,text"                       |
//+------------------------------------------------------------------+
int CreateByName(const string name,const string params)
  {
   MqlParam p[];
   ArrayResize(p,1);
   p[0].type=TYPE_STRING;
   p[0].string_value=name;
   string parts[];
   const int n=(StringLen(params)>0) ? StringSplit(params,',',parts) : 0;
   ArrayResize(p,n+1);
   for(int i=0; i<n; i++)
     {
      string s=parts[i];
      StringTrimLeft(s);
      StringTrimRight(s);
      if(s=="true" || s=="false")
        {
         p[i+1].type=TYPE_BOOL;
         p[i+1].integer_value=(s=="true");
        }
      else
         if(StringFind(s,".")>=0 && MathIsValidNumber(StringToDouble(s)))
           {
            p[i+1].type=TYPE_DOUBLE;
            p[i+1].double_value=StringToDouble(s);
           }
         else
            if(s=="0" || StringToInteger(s)!=0)
              {
               p[i+1].type=TYPE_INT;
               p[i+1].integer_value=StringToInteger(s);
              }
            else
              {
               p[i+1].type=TYPE_STRING;
               p[i+1].string_value=s;
              }
     }
   return(IndicatorCreate(_Symbol,_Period,IND_CUSTOM,ArraySize(p),p));
  }
//+------------------------------------------------------------------+
//| A value is empty if it is EMPTY_VALUE or not a number            |
//+------------------------------------------------------------------+
bool IsEmpty(const double v)
  {
   return(v==EMPTY_VALUE || !MathIsValidNumber(v));
  }
//+------------------------------------------------------------------+
//| Does the rule fire on the signal bar?                            |
//+------------------------------------------------------------------+
bool RuleFires(const ENUM_SIGNAL_RULE rule,const int buffer,const double level,const int buffer2)
  {
   if(rule==SIGNAL_OFF)
      return(false);
   double a[],b[];
   //--- two values: a[0] = the bar before the signal bar, a[1] = the signal bar
   if(CopyBuffer(ExtHandle,buffer,InpSignalBar,2,a)!=2)
      return(false);
   switch(rule)
     {
      case SIGNAL_ARROW:
         return(!IsEmpty(a[1]) && a[1]!=0.0);
      case SIGNAL_CROSS_UP:
         return(!IsEmpty(a[0]) && !IsEmpty(a[1]) && a[0]<level && a[1]>=level);
      case SIGNAL_CROSS_DOWN:
         return(!IsEmpty(a[0]) && !IsEmpty(a[1]) && a[0]>level && a[1]<=level);
      case SIGNAL_BECOMES:
         return(!IsEmpty(a[1]) && MathAbs(a[1]-level)<0.5 && (IsEmpty(a[0]) || MathAbs(a[0]-level)>=0.5));
      case SIGNAL_LINE_UP:
      case SIGNAL_LINE_DOWN:
        {
         if(CopyBuffer(ExtHandle,buffer2,InpSignalBar,2,b)!=2)
            return(false);
         if(IsEmpty(a[0]) || IsEmpty(a[1]) || IsEmpty(b[0]) || IsEmpty(b[1]))
            return(false);
         if(rule==SIGNAL_LINE_UP)
            return(a[0]<b[0] && a[1]>=b[1]);
         return(a[0]>b[0] && a[1]<=b[1]);
        }
     }
   return(false);
  }
//+------------------------------------------------------------------+
//| Is there an open position of this EA?                            |
//+------------------------------------------------------------------+
bool HasPosition()
  {
   for(int i=PositionsTotal()-1; i>=0; i--)
      if(PositionGetSymbol(i)==_Symbol && PositionGetInteger(POSITION_MAGIC)==(long)InpMagic)
         return(true);
   return(false);
  }
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
   if(InpSignalBar<1)
     {
      Print("Signal bar must be 1 or more: the forming bar can still change");
      return(INIT_PARAMETERS_INCORRECT);
     }
   ExtHandle=CreateByName(InpIndicator,InpParams);
   if(ExtHandle==INVALID_HANDLE)
     {
      PrintFormat("Cannot load %s (error %d)",InpIndicator,GetLastError());
      return(INIT_FAILED);
     }
   ExtTrade.SetExpertMagicNumber(InpMagic);
   ExtLastBar=0;
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   if(ExtHandle!=INVALID_HANDLE)
      IndicatorRelease(ExtHandle);
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
   //--- one decision per bar
   const datetime bar=iTime(_Symbol,_Period,0);
   if(bar==ExtLastBar)
      return;
   if(BarsCalculated(ExtHandle)<InpSignalBar+2)
      return;
   ExtLastBar=bar;
   const bool buy=RuleFires(InpBuyRule,InpBuyBuffer,InpBuyLevel,InpBuyBuffer2);
   const bool sell=RuleFires(InpSellRule,InpSellBuffer,InpSellLevel,InpSellBuffer2);
   if(buy==sell || HasPosition())
      return;
   const double point=SymbolInfoDouble(_Symbol,SYMBOL_POINT);
   if(buy)
     {
      const double ask=SymbolInfoDouble(_Symbol,SYMBOL_ASK);
      ExtTrade.Buy(InpLots,_Symbol,ask,ask-InpStopPoints*point,ask+InpTakePoints*point);
     }
   else
     {
      const double bid=SymbolInfoDouble(_Symbol,SYMBOL_BID);
      ExtTrade.Sell(InpLots,_Symbol,bid,bid+InpStopPoints*point,bid-InpTakePoints*point);
     }
  }
//+------------------------------------------------------------------+