Construcción de Indicadores Personalizados en MQL5

0
204
anatomia indicador mql5

Si has operado de forma algorítmica en MetaTrader 5, probablemente ya llegaste a ese punto en el que los indicadores integrados simplemente no son suficientes. Quizás quieres ajustar un cálculo, combinar múltiples señales en una sola visualización, o implementar algún oscuro artículo académico con el que te topaste. Sea cual sea el motivo, aprender a programar indicadores personalizados en MQL5 es una habilidad que vale la pena.

Esta publicación te guía a través de la construcción de un indicador de Media Móvil Simple desde cero. Sí, MT5 ya trae uno integrado. Ese es precisamente el punto. Al recrear algo familiar, podemos concentrarnos completamente en los mecanismos del desarrollo de indicadores sin perdernos en matemáticas complejas.

¿Qué broker recomendamos para usar la plataforma MT5? Consulta la siguiente sección: Lista de br0kers de MT5

La anatomía de un indicador en MQL5

Antes de escribir cualquier código, entendamos qué estamos construyendo. Un indicador en MQL5 es fundamentalmente distinto de un Expert Advisor. Los EAs ejecutan operaciones. Los indicadores calculan y muestran valores en los gráficos. No operan, informan.

Todo indicador en MQL5 tiene una estructura predecible:

#property indicator_chart_window
#property indicator_buffers 1
#property indicator_plots 1

double Buffer[];

int OnInit()
{
   // El código de configuración se ejecuta una vez al cargar el indicador
   return(INIT_SUCCEEDED);
}

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[])
{
   // La lógica de cálculo se ejecuta en cada tick
   return(rates_total);
}

anatomia indicador mql5

Esa es la estructura base. Dos funciones: OnInit() para la configuración, OnCalculate() para el trabajo real. Todo lo demás es configuración y lógica.

Entendiendo las propiedades del indicador

Las directivas #property al inicio no son solo decoración. Le indican a MetaTrader cómo manejar tu indicador.

#property indicator_chart_window

Esto coloca tu indicador directamente en el gráfico de precios. Si estuvieras construyendo un oscilador como el RSI, usarías indicator_separate_window en su lugar para crear un panel separado debajo del gráfico.

#property indicator_buffers 1
#property indicator_plots 1

Estos dos están relacionados pero son distintos. Los buffers son arreglos que almacenan valores calculados. Los plots son representaciones visuales que se dibujan en el gráfico. Puedes tener más buffers que plots si algunos buffers se utilizan para cálculos intermedios que no deseas mostrar.

Construcción de una Media Móvil Simple en MT5

Construyamos un indicador SMA completo y funcional. Primero te mostraré el código completo y luego desglosaremos las partes importantes.

#property copyright "Tu nombre"
#property version   "1.00"
#property indicator_chart_window
#property indicator_buffers 1
#property indicator_plots   1

#property indicator_label1  "SMA"
#property indicator_type1   DRAW_LINE
#property indicator_color1  clrDodgerBlue
#property indicator_style1  STYLE_SOLID
#property indicator_width1  2
input int InpPeriod = 14; // Periodo de SMA
double SmaBuffer[];
int OnInit()
{
   SetIndexBuffer(0, SmaBuffer, INDICATOR_DATA);
   PlotIndexSetInteger(0, PLOT_DRAW_BEGIN, InpPeriod - 1);
   PlotIndexSetString(0, PLOT_LABEL, "SMA(" + IntegerToString(InpPeriod) + ")");
   
   IndicatorSetInteger(INDICATOR_DIGITS, _Digits);
   IndicatorSetString(INDICATOR_SHORTNAME, "Custom SMA(" + IntegerToString(InpPeriod) + ")");
   
   return(INIT_SUCCEEDED);
}
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 < InpPeriod)
      return(0);
   
   int start;
   
   if(prev_calculated == 0)
      start = InpPeriod - 1;
   else
      start = prev_calculated - 1;
   
   for(int i = start; i < rates_total; i++)
   {
      double sum = 0.0;
      for(int j = 0; j < InpPeriod; j++)
      {
         sum += close[i - j];
      }
      SmaBuffer[i] = sum / InpPeriod;
   }
   
   return(rates_total);
}

Conceptos críticos

Parámetros de entrada

input int InpPeriod = 14; // Periodo de SMA

La palabra clave input crea un parámetro que los usuarios pueden modificar en el cuadro de configuración del indicador. El comentario después de la declaración se convierte en la etiqueta que ven los usuarios. Proporciona siempre valores predeterminados razonables y valídalos en tu código.

Registro del buffer

SetIndexBuffer(0, SmaBuffer, INDICATOR_DATA);

Esta línea conecta tu arreglo con el sistema de indicadores de MetaTrader. El primer argumento es el índice del buffer (base cero), el segundo es tu arreglo, y el tercero especifica el tipo de buffer.

INDICATOR_DATA significa que este buffer contiene valores que serán dibujados. También puedes usar INDICATOR_CALCULATIONS para valores intermedios que no deseas mostrar, o INDICATOR_COLOR_INDEX para indicadores multicolor.

Configuración del plot del indicador

PlotIndexSetInteger(0, PLOT_DRAW_BEGIN, InpPeriod - 1);

Esto le indica a MT5 que comience a dibujar desde la barra InpPeriod – 1. Para una SMA de 14 períodos, las primeras 13 barras no tienen suficientes datos para un cálculo válido, por lo que las omitimos. Sin esto, verías valores engañosos o ceros al inicio de tu gráfico.

Los parámetros de OnCalculate

La firma de esta función parece intimidante, pero cada parámetro tiene un propósito:

  • rates_total: Número total de barras disponibles
  • prev_calculated: Lo que tu indicador devolvió la última vez que se ejecutó
  • time[], open[], high[], low[], close[]: Arreglos de datos de precios
  • tick_volume[], volume[]: Datos de volumen
  • spread[]: Valores del spread

El par más importante es rates_total y prev_calculated. Estos permiten un cálculo eficiente.

La lógica de optimización

Aquí es donde la mayoría de los principiantes cometen errores:

int start;
if(prev_calculated == 0)
   start = InpPeriod - 1;
else
   start = prev_calculated - 1;

Cuando el indicador se carga por primera vez, prev_calculated es cero. Necesitas calcular todo desde el principio. Pero en los ticks siguientes, prev_calculated te indica cuántas barras ya has procesado. Solo necesitas actualizar a partir de ese punto.

¿Por qué prev_calculated – 1 en lugar de simplemente prev_calculated? Porque la última barra (la barra actual, incompleta) cambia con cada tick. Necesitas recalcularla cada vez. Las barras históricas completadas no cambian, por lo que calcularlas una sola vez es suficiente.

Esta optimización importa. Sin ella, una SMA de 14 períodos en un gráfico con 10,000 barras realizaría 140,000 sumas en cada tick. Con ella, solo realizas 14 sumas por tick después de la carga inicial.

Indexación de arrays en MQL5

Aquí hay algo que confunde a los programadores que vienen de otros lenguajes. Los arreglos de indicadores en MQL5 se indexan con el cero representando la barra más antigua de forma predeterminada. Esto es lo opuesto a como podrías pensarlo.

Indice:  0    1    2    3    ...  rates_total-1
Barras:  Old  ...  ...  ...  ...  Actual

Entonces close[rates_total – 1] es el precio de cierre de la barra actual, y close[0] es la barra más antigua del gráfico.

En nuestro bucle:

for(int i = start; i < rates_total; i++)
{
   double sum = 0.0;
   for(int j = 0; j < InpPeriod; j++)
   {
      sum += close[i - j];
   }
   SmaBuffer[i] = sum / InpPeriod;
}

Iteramos hacia adelante en el tiempo (i aumenta). Para cada barra, miramos j barras hacia atrás para calcular el promedio. Este es el enfoque estándar para la mayoría de los indicadores.

Valor de retorno (return)

return(rates_total);

Lo que retorna el indicador se convierte en prev_calculated en la siguiente llamada. El retorno de rates_total le indica a MT5 que se han procesado todas las barras disponibles. Si algo sale mal y no se puede calcular, devuelve 0 para forzar un recálculo completo la próxima vez.

Errores comunes

Olvidar los casos límite

Verifica siempre si tienes suficientes datos:

if(rates_total < InpPeriod)
   return(0);

Sin esto, tu indicador falla o produce resultados incorrectos cuando no hay suficientes barras para calcular.

Ignorar la validación de entradas

¿Qué ocurre si alguien establece el período en cero o en un valor negativo? Agrega validación:

int OnInit()
{
   if(InpPeriod < 1)
   {
      Print("Error: Period must be at least 1");
      return(INIT_PARAMETERS_INCORRECT);
   }
   // ... resto de init
}

Cálculos costosos dentro de bucles

Nuestra implementación de la SMA recalcula la suma para cada barra. Para una demostración simple, está bien. Para código en producción con períodos grandes, considera un enfoque de suma acumulativa:

// Más eficiente para períodos grandes
double sum = 0.0;

// Suma inicial para la primera barra válida
for(int j = 0; j < InpPeriod; j++)
   sum += close[start - j];
SmaBuffer[start] = sum / InpPeriod;

// Barras siguientes: agregar el nuevo valor, restar el valor antiguo
for(int i = start + 1; i < rates_total; i++)
{
   sum = sum + close[i] - close[i - InpPeriod];
   SmaBuffer[i] = sum / InpPeriod;
}

Esto reduce la complejidad de O(n × período) a O(n).

Construcción de tu propio indicador

Una vez que comprendes la estructura, las modificaciones se vuelven sencillas. ¿Quieres una EMA en su lugar? Cambia el cálculo:

double multiplier = 2.0 / (InpPeriod + 1);

// El primer valor es una SMA
double sum = 0.0;
for(int j = 0; j < InpPeriod; j++)
   sum += close[start - j];
SmaBuffer[start] = sum / InpPeriod;

// Los valores siguientes usan la fórmula de la EMA
for(int i = start + 1; i < rates_total; i++)
{
   SmaBuffer[i] = (close[i] - SmaBuffer[i-1]) * multiplier + SmaBuffer[i-1];
}

¿Quieres basarlo en el punto medio entre el máximo y el mínimo en lugar del cierre? Reemplaza close[i] con (high[i] + low[i]) / 2.0.

¿Quieres múltiples MAs en un solo indicador? Agrega más buffers, más plots y más parámetros de entrada.

Consejos para depuración

La función Print() de MQL5 envía la salida a la pestaña Experts del terminal:

Print("rates_total: ", rates_total, " prev_calculated: ", prev_calculated);

Úsala generosamente durante el desarrollo. Solo recuerda eliminar o comentar las instrucciones de depuración antes de desplegar, ya que ralentizan el rendimiento.

Para depuración visual, puedes establecer temporalmente los valores del buffer en números específicos para verificar si se están calculando:

SmaBuffer[i] = 1.0; // Debería dibujar una línea plana en 1.0

Si ves la línea, tu buffer está conectado correctamente. Si no, revisa tu llamada a SetIndexBuffer().

Reflexiones finales

Los indicadores personalizados en MQL5 siguen un patrón. Una vez que internalizas la estructura — propiedades, buffers, inicialización y el bucle de cálculo — puedes implementar casi cualquier cosa. Las matemáticas pueden volverse más complejas para indicadores avanzados, pero el marco de trabajo sigue siendo el mismo.

Empieza simple. Haz que un indicador básico funcione. Luego itera. Agrega funcionalidades, optimiza el rendimiento, maneja los casos límite. Así es como se construyen los indicadores de calidad para producción.

La SMA que construimos es trivial matemáticamente, pero la infraestructura que la rodea — gestión de buffers, actualizaciones eficientes, inicialización correcta — aplica a indicadores de cualquier complejidad. Domina estos fundamentos y estarás programando señales personalizadas, osciladores y detectores de patrones en poco tiempo.

¿Qué broker recomendamos para operar en los mercados financieros mediante Metatrader 5? EXNess es una opción recomendada. Más información en: Análisis del broker EXNess

 

Aviso legal:

Este artículo es únicamente con fines educativos y no constituye asesoramiento financiero. Operar en los mercados financieros implica un riesgo significativo y puede resultar en la pérdida de tu capital. El rendimiento pasado no es indicativo de resultados futuros. Eres el único responsable de tus decisiones de trading. Realiza siempre tu propia investigación y consulta con un asesor financiero calificado antes de tomar cualquier decisión de inversión.


 

Raul Canessa

Leave a reply