Desarrollo de un kit de herramientas para el análisis de la acción del precio (Parte 19): ZigZag Analyzer
Introducción
Las líneas de tendencia constituyen la columna vertebral del análisis técnico y del análisis de la evolución de los precios. Constituyen la base de los patrones de precios en los que se basan los traders de divisas, criptomonedas, materias primas, acciones y derivados. Cuando se trazan correctamente, las líneas de tendencia revelan con eficacia las tendencias del mercado y ayudan a los traders a detectar posibles oportunidades.
Tanto si eres un trader intradía como un trader a corto plazo, las líneas de tendencia desempeñan un papel fundamental en muchos sistemas y estrategias de trading. En nuestra serie sobre el desarrollo del kit de herramientas de análisis de la acción del precio, presentamos una herramienta llamada «ZigZag Analyzer». Esta herramienta se centra en trazar líneas de tendencia utilizando el indicador ZigZag para identificar los puntos de oscilación (swings) que constituyen la base para la construcción de dichas líneas. MQL5 es un lenguaje potente para automatizar sistemas de negociación, y nos permite crear herramientas avanzadas como el analizador ZigZag (ZigZag Analyzer), que se adaptan a las condiciones del mercado y facilitan la toma de decisiones en tiempo real.
Comenzaremos analizando las líneas de tendencia; a continuación, examinaremos el indicador Zigzag, describiremos el algoritmo del sistema, presentaremos el código MQL5, revisaremos los resultados y concluiremos con nuestras conclusiones finales. Veamos el índice a continuación:
Líneas de tendencia
Una línea de tendencia es una línea inclinada que conecta puntos de precio significativos, normalmente mínimos crecientes o máximos decrecientes, y se proyecta hacia el futuro como un nivel de soporte o resistencia. Indica la probable dirección y el impulso de los movimientos de precios. Para trazar una línea de tendencia, comience por identificar la dirección general del mercado. Una tendencia alcista se caracteriza por una secuencia de máximos y mínimos cada vez más altos, mientras que una tendencia bajista se caracteriza por máximos y mínimos cada vez más bajos. A modo de ejemplo, consideremos un gráfico que muestre una tendencia a la baja, donde conectaríamos los máximos de oscilación descendentes para establecer la línea de tendencia. Durante la consolidación de precios, la formación de múltiples líneas de tendencia puede revelar patrones que refuerzan las señales de negociación. A continuación se muestra una representación gráfica.

Figura 1. Líneas de tendencia
El gráfico ilustra dos conceptos clave: soporte y resistencia. Un nivel de soporte es una zona en la que el movimiento bajista del precio suele detenerse porque los compradores se activan lo suficiente como para absorber la presión vendedora. Cuando se trazan líneas de tendencia, el soporte suele representarse mediante líneas ascendentes que conectan los mínimos más importantes. Por el contrario, un nivel de resistencia es una zona en la que el movimiento alcista del precio suele detenerse debido a una mayor presión vendedora. En las líneas de tendencia, la resistencia se representa mediante líneas descendentes que unen los máximos significativos. Los traders suelen trazar líneas de tendencia en distintos horizontes temporales para determinar si un mercado se mueve al alza o a la baja en el corto, medio o largo plazo.Las líneas de tendencia son una herramienta muy útil en el análisis técnico, ya que ayudan a los participantes en el mercado a evaluar la tendencia general del precio de un activo. Al unir los máximos o mínimos más destacados de un periodo de tiempo determinado, estas líneas muestran visualmente si el mercado está al alza, a la baja o se mantiene lateral. Esta información resulta especialmente útil para los traders y los inversores a corto plazo que se basan en las tendencias de los precios para tomar sus decisiones.
Indicador ZigZag
Aunque el origen exacto del indicador ZigZag no está documentado de forma concluyente, algunas fuentes atribuyen su descubrimiento a Bill Wolfe, un trader del S&P 500 conocido por haber desarrollado las «Ondas de Wolfe», un método algo similar a las ondas de Elliott, pero con técnicas gráficas distintas. Las Ondas de Wolfe consisten en cinco ondas que ilustran cómo la oferta y la demanda se mueven hacia un precio de equilibrio, véase. El indicador ZigZag destaca los cambios significativos de precio en los precios que superan un umbral determinado, expresado normalmente en forma de porcentaje. Cuando el precio supera este umbral, el indicador traza un nuevo punto y dibuja una línea recta desde el punto anterior, lo que permite filtrar eficazmente las fluctuaciones menores. De este modo, resulta más fácil detectar las tendencias subyacentes en distintos periodos de tiempo.
Los traders pueden ajustar el umbral porcentual (por ejemplo, al 4 % o al 5 %) para captar más o menos fluctuaciones de precios, dependiendo de la volatilidad del activo o de su estilo personal de negociación. Esta flexibilidad permite afinar la forma en que el indicador ZigZag identifica posibles puntos de oscilación (swings). En los análisis basados en ondas, como la teoría de las ondas de Elliot, el diagrama en zigzag puede ayudar a clarificar la estructura de cada onda. En definitiva, a menudo es necesario experimentar con diferentes configuraciones para encontrar el equilibrio óptimo entre filtrar el ruido y detectar movimientos de precios significativos.

Figura 2. Indicador ZigZag
La figura 2 anterior muestra un gráfico con el indicador ZigZag. Demuestra cómo se pueden construir líneas de tendencia a partir de sus oscilaciones. El indicador ZigZag simplifica el análisis de tendencias al filtrar las fluctuaciones menores de precios y resaltar los puntos de oscilación (swings) clave. Marca máximos y mínimos significativos, que sirven como puntos de referencia para trazar líneas de tendencia. En una tendencia alcista, la unión de estos mínimos de oscilación crea una línea de soporte, mientras que en una tendencia bajista, la unión de los máximos de oscilación forma una línea de resistencia. Este método pone de relieve la dirección general del mercado al eliminar el ruido. Ajustar la sensibilidad del indicador puede mejorar aún más la precisión de estas líneas de tendencia para obtener mejores perspectivas de trading.
Algoritmo del sistema
1. Declaraciones globales y parámetros de entrada
En esta sección inicial, configuramos las entradas clave y las variables globales que le permiten personalizar el analizador sin necesidad de profundizar en el código. Definimos parámetros como el intervalo de tiempo del gráfico y propiedades específicas del indicador ZigZag, tales como la profundidad, la desviación y el paso atrás (back step). Estos ajustes determinan cómo el analizador selecciona los puntos de oscilación (swings). También declaramos matrices para almacenar los datos de ZigZag y para registrar la hora y el precio de los puntos de pivote clave.
Valoramos esta configuración por su simplicidad y diseño modular. Por ejemplo, al cambiar el parámetro del marco temporal, puede alternar fácilmente entre tendencias a corto y largo plazo. Las propiedades de ZigZag te permiten ajustar la sensibilidad del indicador, para que puedas adaptar el análisis a diferentes condiciones de mercado o activos. Utilizamos variables globales y matrices como centro neurálgico de todos los datos dinámicos. Este enfoque mejora el rendimiento y facilita el mantenimiento del código. Cuando llegan nuevos datos, se almacenan de forma eficiente y quedan listos para su análisis, lo que simplifica considerablemente la personalización y la depuración.
En general, al centralizar estos datos y declaraciones fundamentales, creamos una herramienta flexible y clara que te permite adaptar el analizador a tus necesidades, al tiempo que se mantiene la solidez y fiabilidad de los procesos subyacentes.
// Input parameters input ENUM_TIMEFRAMES InpTimeFrame = PERIOD_CURRENT; // Timeframe to analyze input int ZZ_Depth = 12; // ZigZag depth input int ZZ_Deviation = 5; // ZigZag deviation input int ZZ_Backstep = 3; // ZigZag backstep input int LookBackBars = 200; // Bars to search for pivots input int ExtendFutureBars = 100; // Bars to extend trendlines into the future // Global indicator handle for ZigZag int zzHandle; // Arrays for ZigZag data and pivot storage double zzBuffer[]; datetime pivotTimes[]; double pivotPrices[]; bool pivotIsPeak[]; // Variable to detect new bars datetime lastBarTime = 0;
- InpTimeFrame: El marco temporal del análisis.
- ZZ_Depth, ZZ_Deviation, ZZ_Backstep: Parámetros que influyen en la sensibilidad y el comportamiento del indicador ZigZag.
Las matrices como zzBuffer almacenan los resultados del indicador ZigZag. Matrices adicionales (pivotTimes, pivotPrices, pivotIsPeak) están reservados para almacenar detalles sobre cada punto de pivote detectado. Una variable (lastBarTime) ayuda a determinar si ha comenzado una nueva barra, lo que garantiza que el análisis solo se actualice cuando sea necesario.
2. Función de inicialización (OnInit)
En la función de inicialización, ejecutamos el código una vez cuando se carga el indicador. Nuestro objetivo principal aquí es crear y configurar el indicador ZigZag utilizando los datos introducidos por el usuario. Llamamos a la función iCustom para intentar instanciar el indicador ZigZag y guardar su handle. Si el indicador no se inicializa y devuelve un handle no válido, registramos un error y detenemos el procesamiento posterior.
Utilizamos esta función para asegurarnos de que todo esté correctamente configurado antes de realizar cualquier análisis. Actúa como un filtro, impidiendo que se ejecute el resto del código si hay algún problema con la configuración del indicador. Esta comprobación temprana ahorra tiempo y evita errores innecesarios más adelante en la ejecución. Al centralizar la inicialización aquí, facilitamos la gestión y la depuración del sistema en caso de que algo falle. Esta función también garantiza que todas las configuraciones definidas por el usuario se apliquen desde el principio, sentando así una base sólida para el análisis posterior.
int OnInit() { zzHandle = iCustom(_Symbol, InpTimeFrame, "ZigZag", ZZ_Depth, ZZ_Deviation, ZZ_Backstep); if(zzHandle == INVALID_HANDLE) { Print("Error creating ZigZag handle"); return(INIT_FAILED); } return(INIT_SUCCEEDED); }
- Creación de indicadores: Utiliza iCustom para cargar el indicador ZigZag con parámetros específicos.
- Gestión de errores: Comprueba si el handle devuelto es válido. Registra un mensaje de error y se detiene si falla la inicialización.
3. Función de desinicialización (OnDeinit)
Cuando se elimina el indicador o se cierra la plataforma, es imprescindible retirar cualquier objeto y liberar los recursos asignados. La función OnDeinit se encarga de ello eliminando cualquier objeto gráfico (como líneas de tendencia o líneas horizontales) que se haya dibujado en el gráfico y liberando el handle del indicador ZigZag. De este modo se garantiza que no queden elementos no deseados en el gráfico y que los recursos del sistema se gestionen adecuadamente.
Limpieza de recursos: Elimina todos los objetos de gráficos creados para evitar el desorden.Liberación del handle: Libera el handle del indicador ZigZag mediante IndicatorRelease.
void OnDeinit(const int reason) { ObjectDelete(0, "Downtrend_HighLine"); ObjectDelete(0, "Uptrend_LowLine"); ObjectDelete(0, "Major_Resistance"); ObjectDelete(0, "Major_Support"); ObjectDelete(0, "Minor_Resistance"); ObjectDelete(0, "Minor_Support"); IndicatorRelease(zzHandle); }
4. Función de ejecución principal (OnTick)
Este es el núcleo del algoritmo que se ejecuta con cada nuevo tick. La función primero comprueba si se ha formado una nueva barra comparando la marca de tiempo de la barra actual con la última hora almacenada. Si no se detecta ninguna barra nueva, la función finaliza prematuramente para ahorrar recursos de procesamiento. Una vez confirmada una nueva barra, elimina los objetos antiguos del gráfico, recupera los datos más recientes de ZigZag mediante CopyBuffer y, a continuación, llama a dos funciones auxiliares: una para dibujar líneas de tendencia y otra para dibujar niveles de soporte y resistencia.
void OnTick() { datetime currentBarTime = iTime(_Symbol, InpTimeFrame, 0); if(currentBarTime == lastBarTime) return; lastBarTime = currentBarTime; // Remove previous objects ObjectDelete(0, "Downtrend_HighLine"); ObjectDelete(0, "Uptrend_LowLine"); ObjectDelete(0, "Major_Resistance"); ObjectDelete(0, "Major_Support"); ObjectDelete(0, "Minor_Resistance"); ObjectDelete(0, "Minor_Support"); if(CopyBuffer(zzHandle, 0, 0, LookBackBars, zzBuffer) <= 0) { Print("Failed to copy ZigZag data"); return; } ArraySetAsSeries(zzBuffer, true); DrawZigZagTrendlines(); DrawSupportResistance(); }
- Nueva comprobación de barra: Utiliza la función iTime para detectar un cambio en la barra.
- Actualización del búfer: Actualiza zzBuffer con los datos más recientes de ZigZag.
- Actualizaciones del gráfico: Borra los objetos gráficos anteriores para preparar el espacio para los nuevos. Llama a funciones para trazar líneas de tendencia y líneas de soporte y resistencia.
5. Dibujar líneas de tendencia basadas en ZigZag (DrawZigZagTrendlines)
Esta función se encarga de identificar los puntos de oscilación (swings) significativos a partir de los datos de ZigZag y de utilizarlos para calcular las líneas de tendencia. Recorre el búfer para recopilar los máximos y mínimos en función de si el valor actual de ZigZag coincide con el máximo o el mínimo de la barra. Una vez recopilados los puntos de oscilación, el código utiliza la regresión lineal para determinar la línea de tendencia de cada conjunto de puntos. El cálculo de la regresión utiliza las siguientes fórmulas:
1. Pendiente (m)

Figura 3. Fórmula de regresión
2. Intercepto (b)

Figura 4. Intercepto
- (t) representa los valores de tiempo (o la variable independiente).
- (p) representa los valores de precio (o la variable dependiente).
- (N) es el número de puntos de datos utilizados en la regresión.
Extracción del punto de oscilación: Recorre zzBuffer para recopilar hasta 10 valores máximos y mínimos.
Exclusión de la última oscilación: Descarta la oscilación más reciente para reducir el ruido.
Análisis de regresión: Calcula la pendiente (m) y la ordenada en el origen (b) utilizando las fórmulas anteriores.
void DrawZigZagTrendlines() { double highPrices[10], lowPrices[10]; datetime highTimes[10], lowTimes[10]; int highCount = 0, lowCount = 0; // Extract swing points from the ZigZag buffer for(int i = 0; i < LookBackBars - 1; i++) { if(zzBuffer[i] != 0) { if(iHigh(_Symbol, InpTimeFrame, i) == zzBuffer[i] && highCount < 10) { highPrices[highCount] = zzBuffer[i]; highTimes[highCount] = iTime(_Symbol, InpTimeFrame, i); highCount++; } else if(iLow(_Symbol, InpTimeFrame, i) == zzBuffer[i] && lowCount < 10) { lowPrices[lowCount] = zzBuffer[i]; lowTimes[lowCount] = iTime(_Symbol, InpTimeFrame, i); lowCount++; } } } // Exclude the most recent swing if possible int usedHighCount = (highCount >= 4) ? highCount - 1 : highCount; int usedLowCount = (lowCount >= 4) ? lowCount - 1 : lowCount; double mHigh = 0, bHigh = 0, mLow = 0, bLow = 0; bool validHigh = false, validLow = false; // Regression for highs if(usedHighCount >= 3) { double sumT = 0, sumP = 0, sumTP = 0, sumT2 = 0; for(int i = 0; i < usedHighCount; i++) { double t = (double)highTimes[i]; double p = highPrices[i]; sumT += t; sumP += p; sumTP += t * p; sumT2 += t * t; } int N = usedHighCount; double denominator = N * sumT2 - sumT * sumT; if(denominator != 0) { mHigh = (N * sumTP - sumT * sumP) / denominator; bHigh = (sumP - mHigh * sumT) / N; } else bHigh = sumP / N; validHigh = true; } // Regression for lows if(usedLowCount >= 3) { double sumT = 0, sumP = 0, sumTP = 0, sumT2 = 0; for(int i = 0; i < usedLowCount; i++) { double t = (double)lowTimes[i]; double p = lowPrices[i]; sumT += t; sumP += p; sumTP += t * p; sumT2 += t * t; } int N = usedLowCount; double denominator = N * sumT2 - sumT * sumT; if(denominator != 0) { mLow = (N * sumTP - sumT * sumP) / denominator; bLow = (sumP - mLow * sumT) / N; } else bLow = sumP / N; validLow = true; } // Define time limits for trendlines datetime pastTime = iTime(_Symbol, InpTimeFrame, LookBackBars - 1); datetime futureTime = lastBarTime + ExtendFutureBars * PeriodSeconds(); // Draw trendlines if both regressions are valid if(validHigh && validLow) { // When slopes have the same sign, use average slope for parallel lines if(mHigh * mLow > 0) { double mParallel = (mHigh + mLow) / 2.0; double bHighParallel = highPrices[0] - mParallel * (double)highTimes[0]; double bLowParallel = lowPrices[0] - mParallel * (double)lowTimes[0]; datetime highStartTime = pastTime; double highStartPrice = mParallel * (double)highStartTime + bHighParallel; double highEndPrice = mParallel * (double)futureTime + bHighParallel; if(!ObjectCreate(0, "Downtrend_HighLine", OBJ_TREND, 0, highStartTime, highStartPrice, futureTime, highEndPrice)) Print("Failed to create High Trendline"); else { ObjectSetInteger(0, "Downtrend_HighLine", OBJPROP_COLOR, clrRed); ObjectSetInteger(0, "Downtrend_HighLine", OBJPROP_RAY_LEFT, true); ObjectSetInteger(0, "Downtrend_HighLine", OBJPROP_RAY_RIGHT, true); } datetime lowStartTime = pastTime; double lowStartPrice = mParallel * (double)lowStartTime + bLowParallel; double lowEndPrice = mParallel * (double)futureTime + bLowParallel; if(!ObjectCreate(0, "Uptrend_LowLine", OBJ_TREND, 0, lowStartTime, lowStartPrice, futureTime, lowEndPrice)) Print("Failed to create Low Trendline"); else { ObjectSetInteger(0, "Uptrend_LowLine", OBJPROP_COLOR, clrGreen); ObjectSetInteger(0, "Uptrend_LowLine", OBJPROP_RAY_LEFT, true); ObjectSetInteger(0, "Uptrend_LowLine", OBJPROP_RAY_RIGHT, true); } } else // Draw separate trendlines if slopes differ { datetime highStartTime = pastTime; double highStartPrice = mHigh * (double)highStartTime + bHigh; double highEndPrice = mHigh * (double)futureTime + bHigh; if(!ObjectCreate(0, "Downtrend_HighLine", OBJ_TREND, 0, highStartTime, highStartPrice, futureTime, highEndPrice)) Print("Failed to create High Trendline"); else { ObjectSetInteger(0, "Downtrend_HighLine", OBJPROP_COLOR, clrRed); ObjectSetInteger(0, "Downtrend_HighLine", OBJPROP_RAY_LEFT, true); ObjectSetInteger(0, "Downtrend_HighLine", OBJPROP_RAY_RIGHT, true); } datetime lowStartTime = pastTime; double lowStartPrice = mLow * (double)lowStartTime + bLow; double lowEndPrice = mLow * (double)futureTime + bLow; if(!ObjectCreate(0, "Uptrend_LowLine", OBJ_TREND, 0, lowStartTime, lowStartPrice, futureTime, lowEndPrice)) Print("Failed to create Low Trendline"); else { ObjectSetInteger(0, "Uptrend_LowLine", OBJPROP_COLOR, clrGreen); ObjectSetInteger(0, "Uptrend_LowLine", OBJPROP_RAY_LEFT, true); ObjectSetInteger(0, "Uptrend_LowLine", OBJPROP_RAY_RIGHT, true); } } } else { // Draw only available regression if only one set of points is valid if(validHigh) { datetime highStartTime = pastTime; double highStartPrice = mHigh * (double)highStartTime + bHigh; double highEndPrice = mHigh * (double)futureTime + bHigh; if(!ObjectCreate(0, "Downtrend_HighLine", OBJ_TREND, 0, highStartTime, highStartPrice, futureTime, highEndPrice)) Print("Failed to create High Trendline"); else { ObjectSetInteger(0, "Downtrend_HighLine", OBJPROP_COLOR, clrRed); ObjectSetInteger(0, "Downtrend_HighLine", OBJPROP_RAY_LEFT, true); ObjectSetInteger(0, "Downtrend_HighLine", OBJPROP_RAY_RIGHT, true); } } if(validLow) { datetime lowStartTime = pastTime; double lowStartPrice = mLow * (double)lowStartTime + bLow; double lowEndPrice = mLow * (double)futureTime + bLow; if(!ObjectCreate(0, "Uptrend_LowLine", OBJ_TREND, 0, lowStartTime, lowStartPrice, futureTime, lowEndPrice)) Print("Failed to create Low Trendline"); else { ObjectSetInteger(0, "Uptrend_LowLine", OBJPROP_COLOR, clrGreen); ObjectSetInteger(0, "Uptrend_LowLine", OBJPROP_RAY_LEFT, true); ObjectSetInteger(0, "Uptrend_LowLine", OBJPROP_RAY_RIGHT, true); } } } }
Dibujo de la línea de tendencia (si ambas tendencias son válidas)
- Pendientes del mismo signo: Utiliza una pendiente media para las líneas paralelas.
- Signos diferentes: Dibujar líneas de tendencia individuales. Utiliza ObjectCreate y ObjectSetInteger para colocar y aplicar estilos a las líneas.
6. Dibujar niveles de soporte y resistencia (DrawSupportResistance)
La parte del código dedicada a los niveles de soporte y resistencia está diseñada para identificar dinámicamente los niveles de precios horizontales clave basándose en los puntos de oscilación confirmados que proporciona el indicador ZigZag. Más allá de limitarse a designar el máximo confirmado más alto como la resistencia principal y el mínimo confirmado más bajo como el soporte principal, el algoritmo va más allá y filtra los máximos y mínimos significativos para determinar tanto los niveles principales como los secundarios. Este proceso es crucial, ya que el soporte y la resistencia son conceptos fundamentales en el análisis técnico; el soporte representa un nivel en el que el precio tiende a dejar de bajar debido al aumento de la demanda, mientras que la resistencia indica un nivel en el que el precio suele tener dificultades para subir debido a la presión de la oferta.
En esta implementación, el código examina todos los puntos de oscilación confirmados, clasificándolos y comparándolos meticulosamente para aislar los valores más extremos. Esto garantiza que los niveles trazados en el gráfico no sean meras fluctuaciones pasajeras, sino que representen zonas significativas que, históricamente, han influido en los cambios de tendencia de los precios. Al identificar los valores segundo más alto y segundo más bajo como resistencia y soporte secundarios, el algoritmo ofrece información adicional. Estos niveles secundarios pueden servir como señales tempranas u objetivos secundarios, ofreciendo a los traders una visión más detallada de las posibles reacciones de los precios antes de alcanzar los niveles principales, que son más críticos.
Este enfoque resulta increíblemente útil en un mercado en constante evolución. A medida que se reciben nuevos datos de precios, el indicador ZigZag detecta nuevos puntos de oscilación y actualiza automáticamente los niveles de soporte y resistencia para reflejar la estructura más reciente del mercado. Este ajuste en tiempo real es fundamental para los traders, ya que les ayuda a mantenerse al día con los cambios en los niveles de precios que podrían influir en sus decisiones de entrada o salida. Estos niveles se representan gráficamente en el gráfico mediante líneas horizontales de distintos colores, lo que permite identificarlos fácilmente de un solo vistazo. Esta mayor claridad permite a los traders identificar rápidamente los puntos clave en los que el precio podría reaccionar, lo que les ayuda a colocar órdenes de stop-loss o a fijar objetivos de ganancias con mayor confianza. En definitiva, esta sección del código no solo calcula los niveles de soporte y resistencia, sino que transforma los datos brutos del mercado en información clara y útil, lo que la convierte en una herramienta esencial para cualquier trader que utilice instrumentos técnicos.
void DrawSupportResistance() { double confirmedHighs[10], confirmedLows[10]; int confHighCount = 0, confLowCount = 0; for(int i = 0; i < LookBackBars - 1; i++) { if(zzBuffer[i] != 0) { if(iHigh(_Symbol, InpTimeFrame, i) == zzBuffer[i] && confHighCount < 10) { confirmedHighs[confHighCount] = zzBuffer[i]; confHighCount++; } else if(iLow(_Symbol, InpTimeFrame, i) == zzBuffer[i] && confLowCount < 10) { confirmedLows[confLowCount] = zzBuffer[i]; confLowCount++; } } } int usedHighCount = (confHighCount >= 4) ? confHighCount - 1 : confHighCount; int usedLowCount = (confLowCount >= 4) ? confLowCount - 1 : confLowCount; double majorResistance = -1e9, majorSupport = 1e9; double minorResistance = -1e9, minorSupport = 1e9; double tempHigh = -1e9, tempLow = -1e9; for(int i = 0; i < usedHighCount; i++) { if(confirmedHighs[i] > majorResistance) { tempHigh = majorResistance; majorResistance = confirmedHighs[i]; } else if(confirmedHighs[i] > tempHigh) { tempHigh = confirmedHighs[i]; } } if(tempHigh > -1e9) minorResistance = tempHigh; for(int i = 0; i < usedLowCount; i++) { if(confirmedLows[i] < majorSupport) { tempLow = majorSupport; majorSupport = confirmedLows[i]; } else if(confirmedLows[i] < tempLow) { tempLow = confirmedLows[i]; } } if(tempLow < 1e9) minorSupport = tempLow; if(usedHighCount > 0) { if(!ObjectCreate(0, "Major_Resistance", OBJ_HLINE, 0, 0, majorResistance)) Print("Failed to create Major Resistance"); else ObjectSetInteger(0, "Major_Resistance", OBJPROP_COLOR, clrMagenta); if(minorResistance > -1e9 && minorResistance < majorResistance) { if(!ObjectCreate(0, "Minor_Resistance", OBJ_HLINE, 0, 0, minorResistance)) Print("Failed to create Minor Resistance"); else ObjectSetInteger(0, "Minor_Resistance", OBJPROP_COLOR, clrFuchsia); } } if(usedLowCount > 0) { if(!ObjectCreate(0, "Major_Support", OBJ_HLINE, 0, 0, majorSupport)) Print("Failed to create Major Support"); else ObjectSetInteger(0, "Major_Support", OBJPROP_COLOR, clrAqua); if(minorSupport < 1e9 && minorSupport > majorSupport) { if(!ObjectCreate(0, "Minor_Support", OBJ_HLINE, 0, 0, minorSupport)) Print("Failed to create Minor Support"); else ObjectSetInteger(0, "Minor_Support", OBJPROP_COLOR, clrBlue); } } }
La recopilación de datos recorre el búfer para extraer los máximos y mínimos confirmados. La determinación de los niveles se basa en comparaciones para aislar los valores extremos. Los niveles de resistencia y soporte principales se establecen como los valores más extremos, mientras que los siguientes valores más extremos se convierten en niveles secundarios. La representación gráfica crea líneas horizontales (OBJ_HLINE) para cada nivel identificado. Se han asignado colores a cada línea para facilitar su distinción.
Código MQL5
//+------------------------------------------------------------------+ //| ZigZag Analyzer.mq5| //| Copyright 2025, MetaQuotes Ltd.| //| https://www.mql5.com/en/users/lynnchris| //+------------------------------------------------------------------+ #property copyright "Copyright 2025, MetaQuotes Ltd." #property link "https://www.mql5.com/en/users/lynnchris" #property version "1.0" #property strict // Input parameters input ENUM_TIMEFRAMES InpTimeFrame = PERIOD_CURRENT; // Timeframe to analyze input int ZZ_Depth = 12; // ZigZag depth input int ZZ_Deviation = 5; // ZigZag deviation input int ZZ_Backstep = 3; // ZigZag backstep input int LookBackBars = 200; // Bars to search for pivots input int ExtendFutureBars = 100; // Bars to extend trendlines into the future // Global indicator handle for ZigZag int zzHandle; // Arrays for ZigZag data and pivot storage double zzBuffer[]; datetime pivotTimes[]; double pivotPrices[]; bool pivotIsPeak[]; // Variable to detect new bars datetime lastBarTime = 0; //+------------------------------------------------------------------+ //| Initialization function | //+------------------------------------------------------------------+ int OnInit() { zzHandle = iCustom(_Symbol, InpTimeFrame, "ZigZag", ZZ_Depth, ZZ_Deviation, ZZ_Backstep); if(zzHandle == INVALID_HANDLE) { Print("Error creating ZigZag handle"); return(INIT_FAILED); } return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { ObjectDelete(0, "Downtrend_HighLine"); ObjectDelete(0, "Uptrend_LowLine"); ObjectDelete(0, "Major_Resistance"); ObjectDelete(0, "Major_Support"); ObjectDelete(0, "Minor_Resistance"); ObjectDelete(0, "Minor_Support"); IndicatorRelease(zzHandle); } //+------------------------------------------------------------------+ //| Tick function | //+------------------------------------------------------------------+ void OnTick() { datetime currentBarTime = iTime(_Symbol, InpTimeFrame, 0); if(currentBarTime == lastBarTime) return; lastBarTime = currentBarTime; // Remove previous objects ObjectDelete(0, "Downtrend_HighLine"); ObjectDelete(0, "Uptrend_LowLine"); ObjectDelete(0, "Major_Resistance"); ObjectDelete(0, "Major_Support"); ObjectDelete(0, "Minor_Resistance"); ObjectDelete(0, "Minor_Support"); if(CopyBuffer(zzHandle, 0, 0, LookBackBars, zzBuffer) <= 0) { Print("Failed to copy ZigZag data"); return; } ArraySetAsSeries(zzBuffer, true); DrawZigZagTrendlines(); DrawSupportResistance(); } //+------------------------------------------------------------------+ //| Draw ZigZag-based Trendlines | //+------------------------------------------------------------------+ void DrawZigZagTrendlines() { double highPrices[10], lowPrices[10]; datetime highTimes[10], lowTimes[10]; int highCount = 0, lowCount = 0; // Extract swing points from the ZigZag buffer for(int i = 0; i < LookBackBars - 1; i++) { if(zzBuffer[i] != 0) { if(iHigh(_Symbol, InpTimeFrame, i) == zzBuffer[i] && highCount < 10) { highPrices[highCount] = zzBuffer[i]; highTimes[highCount] = iTime(_Symbol, InpTimeFrame, i); highCount++; } else if(iLow(_Symbol, InpTimeFrame, i) == zzBuffer[i] && lowCount < 10) { lowPrices[lowCount] = zzBuffer[i]; lowTimes[lowCount] = iTime(_Symbol, InpTimeFrame, i); lowCount++; } } } // Exclude the most recent swing if possible int usedHighCount = (highCount >= 4) ? highCount - 1 : highCount; int usedLowCount = (lowCount >= 4) ? lowCount - 1 : lowCount; double mHigh = 0, bHigh = 0, mLow = 0, bLow = 0; bool validHigh = false, validLow = false; // Regression for highs if(usedHighCount >= 3) { double sumT = 0, sumP = 0, sumTP = 0, sumT2 = 0; for(int i = 0; i < usedHighCount; i++) { double t = (double)highTimes[i]; double p = highPrices[i]; sumT += t; sumP += p; sumTP += t * p; sumT2 += t * t; } int N = usedHighCount; double denominator = N * sumT2 - sumT * sumT; if(denominator != 0) { mHigh = (N * sumTP - sumT * sumP) / denominator; bHigh = (sumP - mHigh * sumT) / N; } else bHigh = sumP / N; validHigh = true; } // Regression for lows if(usedLowCount >= 3) { double sumT = 0, sumP = 0, sumTP = 0, sumT2 = 0; for(int i = 0; i < usedLowCount; i++) { double t = (double)lowTimes[i]; double p = lowPrices[i]; sumT += t; sumP += p; sumTP += t * p; sumT2 += t * t; } int N = usedLowCount; double denominator = N * sumT2 - sumT * sumT; if(denominator != 0) { mLow = (N * sumTP - sumT * sumP) / denominator; bLow = (sumP - mLow * sumT) / N; } else bLow = sumP / N; validLow = true; } // Define time limits for trendlines datetime pastTime = iTime(_Symbol, InpTimeFrame, LookBackBars - 1); datetime futureTime = lastBarTime + ExtendFutureBars * PeriodSeconds(); // Draw trendlines if both regressions are valid if(validHigh && validLow) { // When slopes have the same sign, use average slope if(mHigh * mLow > 0) { double mParallel = (mHigh + mLow) / 2.0; double bHighParallel = highPrices[0] - mParallel * (double)highTimes[0]; double bLowParallel = lowPrices[0] - mParallel * (double)lowTimes[0]; datetime highStartTime = pastTime; double highStartPrice = mParallel * (double)highStartTime + bHighParallel; double highEndPrice = mParallel * (double)futureTime + bHighParallel; if(!ObjectCreate(0, "Downtrend_HighLine", OBJ_TREND, 0, highStartTime, highStartPrice, futureTime, highEndPrice)) Print("Failed to create High Trendline"); else { ObjectSetInteger(0, "Downtrend_HighLine", OBJPROP_COLOR, clrRed); ObjectSetInteger(0, "Downtrend_HighLine", OBJPROP_RAY_LEFT, true); ObjectSetInteger(0, "Downtrend_HighLine", OBJPROP_RAY_RIGHT, true); } datetime lowStartTime = pastTime; double lowStartPrice = mParallel * (double)lowStartTime + bLowParallel; double lowEndPrice = mParallel * (double)futureTime + bLowParallel; if(!ObjectCreate(0, "Uptrend_LowLine", OBJ_TREND, 0, lowStartTime, lowStartPrice, futureTime, lowEndPrice)) Print("Failed to create Low Trendline"); else { ObjectSetInteger(0, "Uptrend_LowLine", OBJPROP_COLOR, clrGreen); ObjectSetInteger(0, "Uptrend_LowLine", OBJPROP_RAY_LEFT, true); ObjectSetInteger(0, "Uptrend_LowLine", OBJPROP_RAY_RIGHT, true); } } else { datetime highStartTime = pastTime; double highStartPrice = mHigh * (double)highStartTime + bHigh; double highEndPrice = mHigh * (double)futureTime + bHigh; if(!ObjectCreate(0, "Downtrend_HighLine", OBJ_TREND, 0, highStartTime, highStartPrice, futureTime, highEndPrice)) Print("Failed to create High Trendline"); else { ObjectSetInteger(0, "Downtrend_HighLine", OBJPROP_COLOR, clrRed); ObjectSetInteger(0, "Downtrend_HighLine", OBJPROP_RAY_LEFT, true); ObjectSetInteger(0, "Downtrend_HighLine", OBJPROP_RAY_RIGHT, true); } datetime lowStartTime = pastTime; double lowStartPrice = mLow * (double)lowStartTime + bLow; double lowEndPrice = mLow * (double)futureTime + bLow; if(!ObjectCreate(0, "Uptrend_LowLine", OBJ_TREND, 0, lowStartTime, lowStartPrice, futureTime, lowEndPrice)) Print("Failed to create Low Trendline"); else { ObjectSetInteger(0, "Uptrend_LowLine", OBJPROP_COLOR, clrGreen); ObjectSetInteger(0, "Uptrend_LowLine", OBJPROP_RAY_LEFT, true); ObjectSetInteger(0, "Uptrend_LowLine", OBJPROP_RAY_RIGHT, true); } } } else { if(validHigh) { datetime highStartTime = pastTime; double highStartPrice = mHigh * (double)highStartTime + bHigh; double highEndPrice = mHigh * (double)futureTime + bHigh; if(!ObjectCreate(0, "Downtrend_HighLine", OBJ_TREND, 0, highStartTime, highStartPrice, futureTime, highEndPrice)) Print("Failed to create High Trendline"); else { ObjectSetInteger(0, "Downtrend_HighLine", OBJPROP_COLOR, clrRed); ObjectSetInteger(0, "Downtrend_HighLine", OBJPROP_RAY_LEFT, true); ObjectSetInteger(0, "Downtrend_HighLine", OBJPROP_RAY_RIGHT, true); } } if(validLow) { datetime lowStartTime = pastTime; double lowStartPrice = mLow * (double)lowStartTime + bLow; double lowEndPrice = mLow * (double)futureTime + bLow; if(!ObjectCreate(0, "Uptrend_LowLine", OBJ_TREND, 0, lowStartTime, lowStartPrice, futureTime, lowEndPrice)) Print("Failed to create Low Trendline"); else { ObjectSetInteger(0, "Uptrend_LowLine", OBJPROP_COLOR, clrGreen); ObjectSetInteger(0, "Uptrend_LowLine", OBJPROP_RAY_LEFT, true); ObjectSetInteger(0, "Uptrend_LowLine", OBJPROP_RAY_RIGHT, true); } } } } //+------------------------------------------------------------------+ //| Draw Support and Resistance Levels | //+------------------------------------------------------------------+ void DrawSupportResistance() { double confirmedHighs[10], confirmedLows[10]; int confHighCount = 0, confLowCount = 0; for(int i = 0; i < LookBackBars - 1; i++) { if(zzBuffer[i] != 0) { if(iHigh(_Symbol, InpTimeFrame, i) == zzBuffer[i] && confHighCount < 10) { confirmedHighs[confHighCount] = zzBuffer[i]; confHighCount++; } else if(iLow(_Symbol, InpTimeFrame, i) == zzBuffer[i] && confLowCount < 10) { confirmedLows[confLowCount] = zzBuffer[i]; confLowCount++; } } } int usedHighCount = (confHighCount >= 4) ? confHighCount - 1 : confHighCount; int usedLowCount = (confLowCount >= 4) ? confLowCount - 1 : confLowCount; double majorResistance = -1e9, majorSupport = 1e9; double minorResistance = -1e9, minorSupport = 1e9; double tempHigh = -1e9, tempLow = -1e9; for(int i = 0; i < usedHighCount; i++) { if(confirmedHighs[i] > majorResistance) { tempHigh = majorResistance; majorResistance = confirmedHighs[i]; } else if(confirmedHighs[i] > tempHigh) { tempHigh = confirmedHighs[i]; } } if(tempHigh > -1e9) minorResistance = tempHigh; for(int i = 0; i < usedLowCount; i++) { if(confirmedLows[i] < majorSupport) { tempLow = majorSupport; majorSupport = confirmedLows[i]; } else if(confirmedLows[i] < tempLow) { tempLow = confirmedLows[i]; } } if(tempLow < 1e9) minorSupport = tempLow; if(usedHighCount > 0) { if(!ObjectCreate(0, "Major_Resistance", OBJ_HLINE, 0, 0, majorResistance)) Print("Failed to create Major Resistance"); else ObjectSetInteger(0, "Major_Resistance", OBJPROP_COLOR, clrMagenta); if(minorResistance > -1e9 && minorResistance < majorResistance) { if(!ObjectCreate(0, "Minor_Resistance", OBJ_HLINE, 0, 0, minorResistance)) Print("Failed to create Minor Resistance"); else ObjectSetInteger(0, "Minor_Resistance", OBJPROP_COLOR, clrFuchsia); } } if(usedLowCount > 0) { if(!ObjectCreate(0, "Major_Support", OBJ_HLINE, 0, 0, majorSupport)) Print("Failed to create Major Support"); else ObjectSetInteger(0, "Major_Support", OBJPROP_COLOR, clrAqua); if(minorSupport < 1e9 && minorSupport > majorSupport) { if(!ObjectCreate(0, "Minor_Support", OBJ_HLINE, 0, 0, minorSupport)) Print("Failed to create Minor Support"); else ObjectSetInteger(0, "Minor_Support", OBJPROP_COLOR, clrBlue); } } } //+------------------------------------------------------------------+ //+------------------------------------------------------------------+
Resultados
Antes de analizar los resultados, añadamos primero el indicador ZigZag al gráfico. Para comprender mejor el proceso, consulte el diagrama GIF que aparece a continuación. Esta guía visual te ayudará a entender cada uno de los pasos a seguir. Siguiendo el diagrama, podrá ver cómo se integra y configura el indicador. Una vez que eso quede claro, podremos pasar al análisis de los resultados.

Figura 5. Inicialización del indicador ZigZag
Esta serie de diagramas ilustra los resultados, y cada diagrama va acompañado de explicaciones detalladas. En primer lugar, el diagrama que aparece a continuación muestra el resultado obtenido tras ejecutar la herramienta ZigZag Analyzer en el Step Index. Como se puede observar, las líneas de tendencia se dibujan con precisión cuando se aplica el EA al gráfico. Tanto la línea inferior como la línea superior presentan una pendiente descendente, lo que indica una tendencia a la baja. Además, los diagramas muestran claramente los niveles de soporte y resistencia, tanto principales como secundarios.

Figura 6. Resultado en el Step Index
Esta captura de pantalla ofrece información adicional sobre el análisis. En la imagen se pueden observar puntos de oscilación (swings) clave, como máximos y mínimos cada vez más bajos, que se utilizan para trazar líneas de tendencia. Los niveles de soporte y resistencia principales y secundarios están claramente marcados. Cabe destacar que las intersecciones entre las líneas de tendencia y los niveles clave ponen de relieve puntos de reversión importantes. El máximo decreciente más alto se encuentra con una resistencia importante y el mínimo decreciente más bajo se encuentra con un soporte importante, donde se produce un cambio de tendencia.

Figura 7. Step Index
Este gráfico USDCHF muestra líneas de tendencia dibujadas. Están presentes en el mercado, pero no son excesivamente dominantes. El mercado respeta los niveles de soporte y resistencia, lo que valida la eficacia del sistema. He resaltado los puntos donde las líneas de tendencia tocan las fluctuaciones del mercado.

Figura 8. USDCHF
Por último, podemos observar su rendimiento en el índice Volatility 25. Se trazan líneas de tendencia que conectan claramente los puntos de oscilación (swings). Las líneas resaltan con precisión el movimiento del mercado. Esta claridad refuerza la fiabilidad del sistema en diversas condiciones de mercado. Los resultados respaldan la eficacia de nuestro enfoque.

Figura 9. Índice Volatility 25
Conclusión
La herramienta ZigZag Analyzer es una forma eficaz de automatizar el análisis de la evolución de los precios mediante MQL5. Nuestras pruebas indican que las líneas de tendencia se trazan de forma eficaz en mercados con tendencia definida. Creo que este enfoque puede conducir al desarrollo de herramientas adicionales que detecten patrones como formaciones de banderas y triángulos. Al ejecutarse, el analizador ZigZag sirve como punto de partida para que los principiantes en el trading de divisas observen las tendencias e identifiquen posibles zonas de soporte o resistencia. También resulta valioso para los traders experimentados, ya que las líneas de tendencia son fundamentales para el análisis de la acción del precio. Esta herramienta es excelente para el aprendizaje y se puede personalizar para adaptarla a diferentes estrategias de negociación, incorporando además otros métodos de confirmación.
| Fecha | Nombre de la herramienta | Descripción | Versión | Actualizaciones | Notas |
|---|---|---|---|---|---|
| 01/10/24 | Chart Projector | Script para superponer un efecto fantasma a la evolución del precio del día anterior. | 1.0 | Versión inicial | Herramienta número 1 |
| 18/11/24 | Analytical Comment | Proporciona la información del día anterior en formato tabular, además de anticipar la dirección futura del mercado. | 1.0 | Versión inicial | Herramienta número 2 |
| 27/11/24 | Analytics Master | Actualización periódica de las métricas del mercado cada dos horas. | 1.01 | Segundo lanzamiento | Herramienta número 3 |
| 02/12/24 | Analytics Forecaster | Actualización periódica de las métricas del mercado cada dos horas con integración en Telegram. | 1.1 | Tercera edición | Herramienta número 4 |
| 09/12/24 | Volatility Navigator | El EA analiza las condiciones del mercado utilizando los indicadores Bandas de Bollinger, RSI y ATR. | 1.0 | Versión inicial | Herramienta número 5 |
| 19/12/24 | Mean Reversion Signal Reaper | Analiza el mercado utilizando una estrategia de reversión a la media y proporciona una señal. | 1.0 | Versión inicial | Herramienta número 6 |
| 09/01/25 | Signal Pulse | Analizador de múltiples marcos temporales. | 1.0 | Versión inicial | Herramienta número 7 |
| 17/01/25 | Metrics Board | Panel con botón para análisis. | 1.0 | Versión inicial | Herramienta número 8 |
| 21/01/25 | External Flow | Análisis a través de bibliotecas externas. | 1.0 | Versión inicial | Herramienta número 9 |
| 27/01/25 | VWAP | Volume Weighted Average Price | 1.3 | Versión inicial | Herramienta número 10 |
| 02/02/25 | Heikin Ashi | Suavizado de tendencias e identificación de señales de reversión. | 1.0 | Versión inicial | Herramienta número 11 |
| 04/02/25 | FibVWAP | Generación de señales mediante análisis con Python. | 1.0 | Versión inicial | Herramienta número 12 |
| 14/02/25 | RSI DIVERGENCE | Divergencias entre la acción del precio y el RSI. | 1.0 | Versión inicial | Herramienta número 13 |
| 17/02/25 | Parabolic Stop and Reverse (PSAR) | Automatización de la estrategia PSAR. | 1.0 | Versión inicial | Herramienta número 14 |
| 20/02/25 | Quarters Drawer Script | Dibujar niveles de cuartos en el gráfico. | 1.0 | Versión inicial | Herramienta número 15 |
| 27/02/25 | Intrusion Detector | Detecta y alerta cuando el precio alcanza niveles de cuartos. | 1.0 | Versión inicial | Herramienta número 16 |
| 27/02/25 | TrendLoom Tool | Panel de análisis de múltiples marcos temporales. | 1.0 | Versión inicial | Herramienta número 17 |
| 11/03/25 | Quarters Board | Panel con botones para activar o desactivar los niveles de cuartos. | 1.0 | Versión inicial | Herramienta número 18 |
| 26/03/25 | ZigZag Analyzer | Dibujar líneas de tendencia con el indicador ZigZag. | 1.0 | Versión inicial | Herramienta número 19 |
Traducción del inglés realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/en/articles/17625
Advertencia: todos los derechos de estos materiales pertenecen a MetaQuotes Ltd. Queda totalmente prohibido el copiado total o parcial.
Este artículo ha sido escrito por un usuario del sitio web y refleja su punto de vista personal. MetaQuotes Ltd. no se responsabiliza de la exactitud de la información ofrecida, ni de las posibles consecuencias del uso de las soluciones, estrategias o recomendaciones descritas.
Búsqueda oscilatoria determinista (DOS) — Deterministic Oscillatory Search (DOS)
Redes neuronales en el trading: Generalización de series temporales sin vinculación a datos (Final)
Particularidades del trabajo con números del tipo double en MQL4
Dominando los registros (Parte 6): Guardando los registros en la base de datos
- Aplicaciones de trading gratuitas
- 8 000+ señales para copiar
- Noticias económicas para analizar los mercados financieros
Usted acepta la política del sitio web y las condiciones de uso
Me parece muy interesante este indicador. Me ayudaría mucho en mi análisis técnico, ya que es al que más tiempo le dedico para encontrar la tendencia correcta, el soporte y la resistencia. He descargado el código, y compila correctamente, pero al añadirlo al gráfico, no se refleja ninguna información. ¿Estoy haciendo algo mal? Vuelvo a adjuntar un video. Saludos.
>Hola Cristina, saludos.
Me parece muy interesante este indicador. Me ayudaría mucho en mi análisis técnico, ya que es al que más tiempo le dedico para encontrar la tendencia correcta, el soporte y la resistencia. He descargado el código, y compila correctamente, pero al añadirlo al gráfico, no se refleja ninguna información. ¿Estoy haciendo algo mal? Vuelvo a adjuntar un video. Saludos.
Hie diego herrera
>Hola Cristina, saludos.
Me parece muy interesante este indicador. Me ayudaría mucho en mi análisis técnico, ya que es al que más tiempo le dedico para encontrar la tendencia correcta, el soporte y la resistencia. He descargado el código, y compila correctamente, pero al añadirlo al gráfico, no se refleja ninguna información. ¿Estoy haciendo algo mal? Vuelvo a adjuntar un video. Saludos.
>Hola Cristina, saludos.
Me parece muy interesante este indicador. Me ayudaría mucho en mi análisis técnico, ya que es al que más tiempo le dedico para encontrar la tendencia correcta, el soporte y la resistencia. He descargado el código, y compila correctamente, pero al añadirlo al gráfico, no se refleja ninguna información. ¿Estoy haciendo algo mal? Vuelvo a adjuntar un video. Saludos.
prueba esto