Leer cualquier indicador desde un Asesor Experto: flechas, líneas e índices de color en CopyBuffer

13 noviembre 2026, 08:29
Bohdan Klok
0
4

Introducción

Tarde o temprano, todo trader que usa un indicador pide "lo mismo, pero que opere solo". La respuesta del programador suena sencilla: crear el indicador con iCustom() o IndicatorCreate(), leer sus valores con CopyBuffer() y abrir una posición cuando aparezca la señal. En la práctica, la primera pregunta no tiene nada que ver con el trading. Es esta: ¿qué búfer contiene la señal?

Un indicador de flechas puede tener un búfer para las flechas de compra y otro para las de venta, o un único búfer para ambas con un índice de color al lado. Un indicador de "tendencia" puede dibujar una sola línea que cambia de color, y el color se guarda en un búfer aparte que nunca se dibuja. Un indicador compilado comprado en el Market llega sin código fuente, así que no hay forma de leer sus llamadas a SetIndexBuffer(). La Data Window («Ventana de datos») ayuda un poco, pero solo muestra los valores dibujados de una barra cada vez y oculta por completo los búferes de color y de cálculo.

Esta publicación muestra cómo responder a la pregunta desde fuera, para cualquier indicador: cómo se almacenan los búferes, cómo leerlos todos desde otro programa, cómo distinguir un búfer de flechas de una línea y de un índice de color solo por sus valores, cómo comprobar si las señales se redibujan y cómo llevar la respuesta de vuelta al gráfico para que el usuario vea qué número corresponde a qué flecha. El resultado son tres programas pequeños con código fuente: un script que inspecciona todos los indicadores de un gráfico (gratuito en Code Base), un indicador de demostración que tiene todos los tipos de búfer y un Asesor Experto que opera con cualquier indicador según cuatro reglas genéricas (ambos se incluyen completos al final de la publicación).

1. Qué es realmente un búfer de indicador

Un indicador guarda su salida en arrays vinculados con SetIndexBuffer(). Existen tres tipos:

  • INDICATOR_DATA: valores que se dibujan (líneas, histogramas, flechas) y se muestran en la Data Window;
  • INDICATOR_COLOR_INDEX: para los estilos DRAW_COLOR_*, el número del color usado en cada barra;
  • INDICATOR_CALCULATIONS: arrays intermedios que ni se dibujan ni se muestran.

Los tres se numeran en una sola secuencia, en el orden en que el indicador los declaró, y los tres se pueden leer con CopyBuffer(). Es la primera sorpresa para muchos programadores: un búfer que nunca se ve en el gráfico sigue estando disponible, y en los indicadores de color suele ser el más útil.

Las construcciones gráficas (plots) y los búferes no son lo mismo. Una construcción DRAW_COLOR_LINE usa dos búferes (el valor y el índice de color), DRAW_COLOR_CANDLES usa cinco (cuatro precios y el índice de color), DRAW_COLOR_ARROW usa dos (el precio de la flecha y su color). El indicador Heiken Ashi que viene de serie con el terminal es un buen ejemplo: los búferes 0 a 3 son los precios de las velas y el búfer 4 es el índice de color, 0 para una vela alcista y 1 para una bajista.

El segundo punto es cómo se guarda la "nada". Un búfer de línea suele tener un valor en cada barra. Un búfer de flechas solo tiene un precio en las barras con flecha y un valor "vacío" en el resto. Qué valor significa vacío lo decide el autor: EMPTY_VALUE (DBL_MAX) es la opción habitual, fijada con PlotIndexSetDouble(PLOT_EMPTY_VALUE), pero muchos indicadores usan 0.0. Un programa que trate 0.0 como precio de flecha válido verá una flecha en cada barra.

Para tener algo concreto que examinar, el indicador DemoBuffers.mq5 (código fuente al final) declara todos los tipos de búfer: una media móvil simple como línea, flechas donde el cierre cruza la media hacia arriba, una media exponencial dibujada como línea de color y la pendiente de esa media como búfer 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);

La Data Window muestra tres valores para él: la media, la flecha y la media exponencial. Los búferes 3 y 4 no aparecen ahí y, sin embargo, el color de la línea - la señal de "tendencia" más natural de este indicador - está en el búfer 3. La figura 1 muestra lo que ve un programa cuando lee los cinco.

Figura 1. DemoBuffers en EURUSD H1: dos líneas, un búfer de flechas, un índice de color y un búfer de cálculo, tal como los lee el script Buffer Inspector Kb.

2. Leer todos los búferes de un indicador

Para un indicador que ya está en un gráfico, el manejador (handle) se obtiene sin conocer sus parámetros de entrada:

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
     }

Esto importa más de lo que parece: el usuario ya ha elegido los parámetros de entrada del indicador en el gráfico, y el programa lee exactamente esa instancia. Para un indicador que no está en el gráfico, IndicatorCreate() con IND_CUSTOM recibe el nombre relativo a MQL5\Indicators y un array de MqlParam. Cuando los parámetros vienen de un campo de texto, por ejemplo "14,0,1.5,true", hay que dar a cada elemento el tipo correcto:

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));
  }

Tres reglas ahorran mucho tiempo con esta función. Los valores deben seguir el orden de la ventana de parámetros de entrada del indicador, y no se puede saltar ninguno: para fijar el tercer parámetro hay que indicar también los dos primeros. Una enumeración se pasa como su número, así que MODE_EMA es 1 y PRICE_CLOSE es 1. Y los parámetros que no se indican conservan sus valores por defecto, de modo que una cadena vacía crea el indicador tal como viene.

¿Cuántos búferes hay? El manejador no lo dice, pero CopyBuffer() sí: devuelve -1 para un número de búfer que no existe. Leer los búferes 0, 1, 2 y así sucesivamente hasta el primer fallo da el recuento, con los búferes de datos, de color y de cálculo juntos.

Dos detalles prácticos:

  • después de crear el manejador, el indicador necesita tiempo para calcular; BarsCalculated(handle) devuelve -1 hasta que termina, así que un script lo espera en un bucle corto con Sleep(), y un Asesor Experto simplemente lo vuelve a intentar en el siguiente tick;
  • empiece a copiar desde la barra 1, la última barra cerrada. En muchos indicadores de flechas la barra en formación cambia hasta que se cierra, y un análisis basado en ella muestra flechas que luego desaparecen.

3. Distinguir flechas, líneas e índices de color

Con N barras cerradas de un búfer en un array, bastan tres estadísticas sencillas:

  • válidos: valores que no son ni EMPTY_VALUE ni NaN;
  • flechas: valores válidos y distintos de 0.0;
  • enteros pequeños: valores que son números enteros con un valor absoluto pequeño.

y de ahí sale la clasificación:

Qué contiene el búfer Cómo se ve en 500 barras
nada ningún valor válido, o solo ceros
flechas algunos valores, pero en el 30% de las barras o menos
índice de color solo números enteros pequeños en un rango estrecho (por ejemplo, 0 y 1)
línea todo lo demás: un valor en (casi) todas las 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 qué el 30%? Los indicadores de flechas marcan puntos de giro, que por definición son poco frecuentes: Fractals marca un pequeño porcentaje de las barras, y los indicadores de "señales" típicos, menos del 10%. Una línea que queda vacía en una parte de las barras (una media móvil al principio del historial) sigue teniendo valores en la mayoría. El umbral es deliberadamente generoso; los casos cercanos a él se comunican al usuario, no se adivinan en silencio.

¿Por qué "enteros pequeños en un rango estrecho" para los colores? Un índice de color toma los valores 0, 1, 2 y así sucesivamente, uno por cada color de la construcción gráfica. Una línea también puede ser entera (un oscilador redondeado por su autor, una función escalonada), pero entonces se mueve en un rango mucho más amplio que el puñado de colores que puede tener una construcción gráfica.

La figura 2 muestra el resultado para Heiken Ashi: cuatro líneas de precio y, en el búfer 4, el índice de color con los valores 0 y 1 - exactamente lo que necesita un Asesor Experto para reaccionar a un cambio de color de la vela.

Figura 2. Heiken Ashi: cuatro líneas de precio y el índice de color en el búfer 4, que la Data Window no muestra.

Algunos casos merecen un comentario, porque es en ellos donde un vistazo rápido da la respuesta equivocada:

  • Flechas guardadas con 0.0 en lugar de EMPTY_VALUE. La clasificación anterior ya trata 0.0 como "sin flecha", pero un programa que solo compare con EMPTY_VALUE se disparará en cada barra. Compruebe siempre ambos valores.
  • Puntos en cada barra, como en Parabolic SAR. En el gráfico parecen flechas, pero el búfer tiene un valor en cada barra, así que es una línea, y la señal es el momento en que los puntos saltan al otro lado del precio, no la presencia de un punto.
  • Flechas de color (DRAW_COLOR_ARROW). El búfer de flechas da la barra y el precio, y el índice de color que lo acompaña indica si es una flecha de compra o de venta. Para una sola señal hacen falta los dos búferes.
  • Búferes de cálculo. Normalmente se clasifican como líneas, a menudo con valores lejos del precio. Vale la pena mirarlos: algunos indicadores guardan ahí la señal "en bruto", antes de suavizarla o filtrarla.

4. Mostrar al usuario qué flecha es cuál

Una tabla con "búfer 1: flechas, 37, la última el 2026.10.07 08:00" es correcta, pero el trader todavía tiene que comparar fechas con el gráfico para saber si el búfer 1 es la flecha hacia arriba o la flecha hacia abajo. Es mucho más rápido escribir el número de búfer junto a las propias flechas:

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++;
  }

La etiqueta se coloca en el valor guardado en el búfer, que en los indicadores de flechas es el precio donde se dibuja la flecha, así que "B0" y "B1" aparecen justo sobre las flechas. Con un color por búfer, dos búferes de flechas se distinguen con facilidad.

Figura 3. Examples\Fractals: B0 aparece encima de los fractales superiores, B1 debajo de los inferiores.

5. ¿Se redibuja el indicador?

Un búfer con flechas es solo la mitad de la respuesta. La otra mitad es si una flecha, una vez que ha aparecido en una barra cerrada, se queda ahí. Algunos indicadores mueven o borran sus flechas cuando llegan barras nuevas: Fractals lo hace por diseño en las dos últimas barras, y los indicadores del tipo ZigZag durante mucho más tiempo. Un Asesor Experto que opera con una flecha que luego desaparece opera con una señal que, vista a posteriori, nunca existió, y sus resultados en el Probador de Estrategias no coincidirán con el gráfico.

La comprobación es fácil de automatizar: recordar los valores de las últimas barras cerradas por su hora de apertura, esperar unas cuantas barras nuevas, volver a leer las mismas barras y 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);
  }

Aquí importa leer por tiempo y no por índice: cuando llegan barras nuevas, la misma barra pasa a tener un índice mayor. El resultado indica hasta cuántas barras atrás el indicador sigue cambiando de opinión, y ese número es la barra de señal que hay que usar. En Fractals la respuesta es siempre dos barras, y por eso un fractal se lee en la barra 3 y no en la barra 1. Un indicador que sigue cambiando sus flechas diez barras atrás no se puede operar en la barra en la que la flecha aparece por primera vez, por bien que se vea en el Probador de Estrategias.

6. Cuando no hay ningún búfer que leer

Algunos indicadores dibujan sus flechas con ObjectCreate() en lugar de con búferes. Las flechas están en el gráfico, la Data Window está vacía y CopyBuffer() devuelve solo líneas o nada. Ningún programa puede leer esas señales a través del manejador del indicador. Conviene decírselo claramente al usuario, porque de lo contrario la búsqueda del "número de búfer correcto" no termina nunca. Un script puede detectar la situación de forma indirecta: un indicador que no tiene ningún búfer de flechas aunque el usuario vea flechas casi con seguridad está dibujando objetos. Buffer Inspector Kb lo indica en su informe.

7. De los búferes a las reglas de trading

Una vez conocido el búfer, cuatro reglas cubren casi cualquier indicador que traiga un trader:

Regla Indicadores típicos Condición en la barra de señal
aparece una flecha Fractals, indicadores de flechas el valor no está vacío y no es 0
un búfer cruza un nivel RSI 30/70, CCI +-100, osciladores en torno a 0 anterior < nivel <= actual
un búfer cruza otro MACD principal/señal, Stochastic %K/%D, líneas de Alligator a < b en la anterior y a >= b en la actual
un índice de color toma un valor Heiken Ashi, medias de color, barras de "tendencia" anterior != k y actual == k

BufferSignalEA.mq5 (código fuente al final) implementa exactamente estas reglas, con el nombre del indicador, sus parámetros, la regla y los números de búfer como parámetros de entrada, de modo que un solo Asesor Experto puede apuntarse a cualquier indicador. Su núcleo es una función que lee dos valores, el de la barra de señal y el de la 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);
  }

Un índice de color se compara con una tolerancia de 0,5 porque se guarda como double. La decisión se toma una vez por cada barra nueva, antes que cualquier otra cosa en 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

Una regla evaluada en cada tick de la barra en formación se dispararía varias veces y con señales que luego desaparecen; una sola decisión por barra, tomada sobre una barra cerrada, mantiene de acuerdo el Probador de Estrategias y el gráfico en vivo.

Para comprobar que cada regla se dispara de verdad, el Asesor Experto se ejecutó en el Probador de Estrategias con cinco indicadores, con la misma salida sencilla para todos (0,1 lotes, stop loss de 300 puntos, take profit de 600 puntos, una posición a la vez). Las cifras siguientes son una prueba funcional de las reglas, no un resultado de trading: un indicador operado sin ningún filtro ni gestión de la posición no es una estrategia.

Indicador y regla Búferes Operaciones Rentables
Fractals, aparece una flecha (barra de señal 3) Compra 1, venta 0 184 53
MACD, la línea principal cruza la de señal 0 y 1 160 46
RSI(14), cruza 30 hacia arriba / 70 hacia abajo 0 113 36
Heiken Ashi, el color pasa a 0 / 1 4 170 53
DemoBuffers, flecha para la compra, color 1 para la venta 1 y 3 152 39

EURUSD H1, 2025.10.01 - 2026.10.01, modelo "1 minute OHLC". Las cinco pruebas se ejecutan sin una sola solicitud fallida; las diferencias en el número de operaciones se deben a la frecuencia con la que cada indicador da una señal mientras no hay ninguna posición abierta.

8. Leer un indicador en otro marco temporal

La misma lectura funciona con un indicador en un marco temporal superior, que es como se construyen la mayoría de los filtros ("comprar solo cuando el precio esté por encima de la EMA 200 en H4"). El manejador se crea con ese marco temporal, y la barra 1 de ese manejador es la última barra cerrada de ese marco temporal:

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;

Dos observaciones. Primera: puede que los datos de otro marco temporal no estén listos cuando arranca el programa; BarsCalculated() devuelve -1 y CopyBuffer() falla hasta que el terminal ha construido el historial, así que la comprobación debe repetirse en lugar de interpretarse como "no hay señal". Segunda: nunca lea la barra 0 del marco temporal superior en una decisión que se toma en el inferior. En un gráfico H1, la barra H4 en formación cambia cada hora, así que un filtro que la lea parecerá perfecto en el probador, que ya sabe cómo termina la barra H4, y se comportará de otra manera en un gráfico en vivo.

9. Los programas

  • Buffer Inspector Kb (script, MQL5\Scripts, gratuito en Code Base): arrástrelo a un gráfico que ya tenga el indicador. Inspecciona todos los indicadores de todas las ventanas, imprime la tabla en el diario de la pestaña Expertos, la muestra en el gráfico con Comment() y etiqueta las últimas flechas de cada búfer de flechas. Si se indica el nombre de un indicador en sus parámetros de entrada, crea el indicador por sí mismo, lo que resulta útil con los indicadores compilados del Market. Al ejecutarlo con "Only remove the labels" se limpia el gráfico.
  • DemoBuffers.mq5 (indicador, MQL5\Indicators): un solo indicador con una línea, flechas, una línea de color con su índice de color y un búfer de cálculo, para experimentar. Código fuente más abajo.
  • BufferSignalEA.mq5 (Asesor Experto, MQL5\Experts): opera con cualquier indicador según las cuatro reglas anteriores; el indicador, sus parámetros, las reglas y los números de búfer son parámetros de entrada. Código fuente más abajo.

Conclusión

Los búferes de un indicador son su interfaz pública, incluso cuando su código fuente no está disponible. Leerlos todos, clasificarlos por sus valores, etiquetar las flechas en el gráfico y comprobar hasta cuántas barras atrás cambian los valores convierte "¿qué número de búfer debo usar?" de una pregunta para el autor del indicador en una comprobación de diez segundos. La misma clasificación es la base de cuatro reglas de trading genéricas - flecha, nivel, cruce de líneas y cambio de color - que permiten a un solo Asesor Experto trabajar con indicadores que nunca ha visto, sobre barras cerradas, de modo que lo que muestra el Probador de Estrategias es lo que hará un gráfico en vivo.

Código fuente

Guarde cada listado con su nombre en la carpeta indicada (Archivo → Abrir carpeta de datos en el terminal) y compílelo en MetaEditor con 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);
     }
  }
//+------------------------------------------------------------------+