Desarrollo de un kit de herramientas para el análisis de la acción del precio (Parte 27): Herramienta de barrido de liquidez con filtro de media móvil
Contenido
Introducción
Los grandes operadores institucionales no influyen en los mercados por casualidad. Su estrategia a menudo consiste en empujar los precios más allá de los niveles de soporte o resistencia bien conocidos, activando intencionadamente órdenes más pequeñas, como órdenes stop-loss y órdenes pendientes de entrada. Este breve movimiento, denominado barrido de liquidez, les permite adquirir o deshacer grandes posiciones a precios más favorables sin provocar de inmediato un movimiento contrario en el mercado.
Para muchos traders minoristas, estos movimientos pueden parecer trampas engañosas. El precio puede caer por debajo de un mínimo conocido, activando las órdenes de stop-loss, para luego recuperarse rápidamente. Por el contrario, podría superar la resistencia, activando los stop-loss, y luego revertir la tendencia bruscamente. Una vez que los principales actores han absorbido esta afluencia de liquidez, el mercado suele retomar su tendencia anterior con renovada fuerza. Reconocer con antelación estos barridos de liquidez puede ayudar a los operadores a evitar que sus posiciones se cierren prematuramente y, en cambio, a posicionarse para seguir el flujo institucional, en lugar de reaccionar a sus barridas temporales.
En este artículo, desarrollaremos un Asesor Experto MQL5 diseñado para identificar barridos de liquidez a medida que se producen. El EA comienza analizando las velas que rompen por debajo o por encima de los máximos y mínimos oscilantes previos y luego vuelven a cerrar dentro del rango, indicadores de una posible absorción de liquidez. Incorpora filtros opcionales, como cambios de color de las velas o confirmaciones de medias móviles, para garantizar que las señales se ajusten a su sesgo de mercado. Cuando se detecta un patrón válido, el Asesor Experto lo marca visualmente en el gráfico con flechas o etiquetas y genera una alerta.
Al finalizar el tutorial, dispondrá de un Asesor Experto completo, claro y sencillo que resaltará de inmediato los barridos de liquidez. Aprenderá cómo el Asesor Experto (EA) detecta estos patrones de mercado específicos, cómo utiliza filtros para reducir las señales falsas y cómo le ayuda a mantenerte alineado con las acciones de los operadores institucionales. Con esta herramienta a tu disposición, podrás anticiparte a los movimientos institucionales antes de que barran su stop-loss, lo que te proporcionará una ventaja estratégica en cualquier entorno de negociación.
Comprender la estrategia
Un barrido de liquidez puede formarse de dos maneras: como un barrido alcista (una ruptura falsa por debajo del soporte) o como un barrido bajista (una ruptura falsa por encima de la resistencia). La lógica de detección principal del EA se encuentra en la función DetectLiquiditySweep. A continuación, veremos paso a paso cómo distingue ambos casos, seguido del fragmento exacto de código MQL5 que realiza estas comprobaciones booleanas.
En cuanto se cierra una nueva vela, el EA ejecuta
DetectLiquiditySweep(1);
Pasar shift=1 significa:
- El índice 1 (shift) hace referencia a la barra que acaba de cerrarse (la vela «actual» que queremos analizar).
- El índice 2 (shift + 1) hace referencia a la barra inmediatamente anterior (la «vela anterior»).
- Dentro de DetectLiquiditySweep, estas líneas recogen los valores de apertura, máximo, mínimo y cierre de ambas barras:
double o = iOpen(Symbol(), Period(), shift); double c = iClose(Symbol(), Period(), shift); double h = iHigh(Symbol(), Period(), shift); double l = iLow(Symbol(), Period(), shift); double o1 = iOpen(Symbol(), Period(), shift + 1); double c1 = iClose(Symbol(), Period(), shift + 1); double h1 = iHigh(Symbol(), Period(), shift + 1); double l1 = iLow(Symbol(), Period(), shift + 1);
- (o, h, l, c) son la apertura, el máximo, el mínimo y el cierre de la nueva barra.
- (o1, h1, l1, c1) son los mismos valores que para la barra inmediatamente anterior.
El EA permite dos definiciones ligeramente diferentes, controladas por la entrada SignalStrict:
1. LessStrict (predeterminado)
Barrido alcista:
- La nueva barra debe cerrar más alto de lo que abrió: c > o.
- Su mínimo debe caer por debajo del mínimo de la barra anterior: l < l1.
- Su cierre también debe estar por encima de la apertura de la barra anterior: c > o1. (Esto impide que un doji puro o una pequeña vela falsa califiquen).
- La barra anterior no era un doji: c1 != o1.
- La nueva barra debe cerrar más baja de lo que abrió: c < o.
- Su máximo debe superar el máximo de la barra anterior: h > h1.
- Su cierre también debe estar por debajo de la apertura de la barra anterior: c < o1.
- La barra anterior no era un doji: c1 != o1.
2. Strict
Barrido alcista:
Es exactamente igual que LessStrict, salvo que en el paso 2 se endurece el requisito, pasando de «por debajo del mínimo anterior» a exigir además que el cierre sea superior al máximo anterior (c > h1). En otras palabras, la nueva barra debe caer por debajo del mínimo anterior y, además, recuperar el movimiento hasta cerrar por encima del máximo anterior.
Barrido bajista:
Del mismo modo, la nueva barra debe cerrar por debajo del mínimo anterior (c < l1) tras superar brevemente el máximo anterior (h > h1), en lugar de limitarse a superar dicho máximo.
Estas comprobaciones booleanas se encuentran en el bloque de código siguiente
//--- Liquidity sweep logic (LessStrict vs Strict) bool bullSweep, bearSweep; if (SignalStrict == LessStrict) { bullSweep = (c > o && // 1) Bullish candle l < l1 && // 2) Low dipped below previous low c > o1 && // 3) Close above previous open c1 != o1); // 4) Previous bar was not a doji bearSweep = (c < o && // 1) Bearish candle h > h1 && // 2) High spiked above previous high c < o1 && // 3) Close below previous open c1 != o1); // 4) Previous bar was not a doji } else // Strict { bullSweep = (c > o && // 1) Bullish candle l < l1 && // 2) Low dipped below previous low c > h1 && // 3) Close above previous high (stricter) c1 != o1); // 4) Previous bar was not a doji bearSweep = (c < o && // 1) Bearish candle h > h1 && // 2) High spiked above previous high c < l1 && // 3) Close below previous low (stricter) c1 != o1); // 4) Previous bar was not a doji }
- En el modo LessStrict, el paso 3 solo requiere que c > o1 (en caso de tendencia alcista) o que c < o1 (en caso de tendencia bajista).
- En modo Strict, el paso 3 cambia a c > h1 (alza) o c < l1 (baja).
Si el usuario ha establecido ColorChangeOnly = true, el EA requiere que la nueva barra sea de un color opuesto al de la barra anterior. En concreto:
bool bullCC = (c > o && c1 < o1); // New bar bullish, old bar bearish bool bearCC = (c < o && c1 > o1); // New bar bearish, old bar bullish if (ColorChangeOnly) { bullSweep &= bullCC; // Only a bullish sweep if also a bull‐after‐bear color flip bearSweep &= bearCC; // Only a bearish sweep if also a bear‐after‐bull color flip }
Si ColorChangeOnly es falso, estas dos líneas no tienen ningún efecto y tanto bullSweep como bearSweep se mantienen tal y como se determinó en las comprobaciones de precio anteriores.
Una vez que bullSweep o bearSweep resultan verdaderos (tras las comprobaciones de precio y color opcionales), el EA puede aplicar un filtro adicional basado en una media móvil. Esto se controla mediante UseMAFilter y PriceAboveMA. En resumen:
- Calcular un único valor de media móvil en la posición shift (la barra que acaba de cerrarse), ya sea mediante el método integrado iMA() (para SMA, EMA, LWMA o RMA) o mediante una función personalizada (CalcVWMA o CalcHMA).
bool cond = PriceAboveMA ? (c > maValue) : (c < maValue);
- Si PriceAboveMA == true, el EA solo mantiene bullSweep si c > maValue y establece de forma forzada bearSweep = false.
- Si PriceAboveMA == false, el EA solo mantiene bearSweep si c < maValue y establece de forma forzada bullSweep = false.
if (UseMAFilter) { double maValue = 0.0; if (MAType == VWMA) maValue = CalcVWMA(shift); else if (MAType == HMA) maValue = CalcHMA(shift); else { double buf[]; if (CopyBuffer(MAHandle, 0, shift, 1, buf) != 1) return; // not enough MA data yet maValue = buf[0]; } // Keep only bullish sweeps if price > MA, or only bearish if price < MA bool cond = PriceAboveMA ? (c > maValue) : (c < maValue); bullSweep &= cond; bearSweep &= !cond; }
En resumen
Barrido alcista:
- La vela cierra por encima del precio de apertura, baja por debajo del mínimo anterior y cierra por encima del precio de apertura anterior (LessStrict) o del máximo anterior (Strict).
- Opcionalmente, debe tratarse de un cambio de color de bajista a alcista.
- Opcionalmente, el cierre debe situarse por encima de la media móvil.
- Si se superan todas las pruebas, el EA coloca una flecha verde hacia arriba (OBJ_ARROW_UP) unos pocos puntos por debajo del mínimo de esa vela.

Figura 1: Barrido alcista.
Barrido bajista:
- La vela cierra por debajo de su precio de apertura, alcanza un máximo superior al máximo anterior y cierra por debajo del precio de apertura anterior (LessStrict) o del mínimo anterior (Strict).
- Opcionalmente, debe tratarse de un cambio de color bajista tras uno alcista.
- Opcionalmente, el cierre debe situarse por debajo de la media móvil.
- Si se superan todas las pruebas, el EA coloca una flecha roja hacia abajo (OBJ_ARROW_DOWN) unos pocos puntos por encima del máximo de esa vela.

Figura 2: Barrido bajista.
Desglose del código
El EA «Liquidity Sweep with MA filter» está diseñado para identificar y marcar visualmente los movimientos de ruptura falsa —a menudo denominados «liquidity sweeps»— en cualquier gráfico de MetaTrader 5. En esencia, el EA busca barras que bajen por debajo (o suban por encima) del mínimo/máximo de swing de la barra anterior y que, a continuación, cierren de una forma que sugiera la presencia de órdenes atrapadas. Al combinar esta lógica de detección con un filtro de media móvil opcional, los operadores pueden afinar las señales para adaptarlas al contexto general de la tendencia. En los siguientes párrafos, analizaremos cada uno de los componentes principales del EA, explicando tanto la función de cada sección como su importancia, de forma clara y paso a paso.
Para empezar, el archivo EA declara sus metadatos —título, derechos de autor, enlace al autor y versión— seguidos inmediatamente de #property strict. Estas líneas no son meramente decorativas: tienen dos funciones. En primer lugar, identifican el script para cualquiera que esté explorando varios EA, dejando claro quién es el autor y dónde se pueden encontrar más obras suyas. En segundo lugar, al especificar #property strict, el compilador aplica una comprobación más estricta de la sintaxis y los tipos, detectando errores comunes (como variables no declaradas o discrepancias de tipos) antes incluso de que se ejecute el código. Esta práctica de utilizar metadatos claros y una compilación rigurosa refleja un nivel de profesionalidad que garantiza tanto la facilidad de mantenimiento como la fiabilidad.
//+------------------------------------------------------------------+ //| Liquidity Sweep with MA filter| //| 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
A continuación, el EA incluye la biblioteca Trade.mqh. Aunque esta versión concreta no realiza operaciones reales de forma automática, incluir <Trade\Trade.mqh> es una medida con visión de futuro: pone a disposición la potente clase CTrade para cualquier mejora futura que permita abrir, modificar o cerrar posiciones. En el contexto de un tutorial, esto también indica a los lectores que este EA podría convertirse fácilmente en un ejecutor de estrategias totalmente automatizado, simplemente llamando a trade.Buy(...) o trade.Sell(...) dentro de la lógica de detección. Al poner de manifiesto esta inclusión desde el principio, el autor prepara al lector para que piense tanto en el análisis como en la ejecución.
#include <Trade\Trade.mqh> Tras la inclusión de la biblioteca, el EA define un conjunto de parámetros de entrada, que aparecen en el cuadro de diálogo «Entradas» de MetaTrader cuando el EA se aplica a un gráfico. Estos datos se clasifican en tres grupos lógicos. El primer grupo, «Configuración de la media móvil», permite al usuario activar o desactivar un filtro de media móvil (UseMAFilter), elegir si se muestra dicha media móvil en el gráfico (ShowMA), seleccionar el período de la media móvil (MALength) y elegir entre seis tipos de media móvil (SMA, EMA, LWMA, VWMA, RMA o HMA). Un valor booleano PriceAboveMA determina si el EA debe exigir que el precio se sitúe por encima (para barridos alcistas) o por debajo (para barridos bajistas) de la media móvil.
En la práctica, este filtro garantiza que los operadores solo tomen señales de barrido de liquidez en la dirección de la tendencia predominante, lo que reduce los falsos positivos cuando el mercado les es desfavorable.
//--- Inputs: Moving Average Filter input bool UseMAFilter = false; // Enable Moving Average Filter input bool ShowMA = false; // Show MA on chart input int MALength = 20; // MA period (must be >=1) enum MA_Type {SMA=0, EMA, LWMA, VWMA, RMA, HMA}; input MA_Type MAType = SMA; // Moving Average type input bool PriceAboveMA = true; // Filter: price above MA?
El segundo grupo de parámetros controla el grado de rigidez con el que el EA define un barrido. Una enumeración SignalStrict ofrece dos modos: «LessStrict» solo exige que el mínimo de la nueva barra (para señales alcistas) esté por debajo del mínimo anterior (y viceversa para las bajistas), mientras que «Strict» exige además que se supere el máximo (o el mínimo) de la barra anterior. Al mostrar este indicador, los lectores aprenden a ajustar el nivel de sensibilidad: algunos prefieren una interpretación más flexible que detecte más señales, mientras que otros optan por un enfoque más conservador que espere a que se produzca una ruptura clara.
Un parámetro complementario booleano, ColorChangeOnly, endurece aún más las condiciones al exigir que el color de la nueva barra (alcista o bajista) sea opuesto al de la barra anterior. En muchas filosofías de trading, un cambio de color indica un cambio en el impulso, por lo que esta opción resulta atractiva para aquellos que solo quieren señalar los cambios de tendencia que coinciden con un cambio visible en la vela.
//--- Inputs: Sweep Definition Strictness enum Strictness {LessStrict=0, Strict}; input Strictness SignalStrict = LessStrict; // Signal strictness input bool ColorChangeOnly = false; // Only on color-change candles
El tercer grupo se refiere a los elementos visuales que aparecen en el gráfico. La enumeración LabelType permite no incluir ninguna etiqueta, una etiqueta corta de dos letras («BS» para «bull sweep» y «BRS» para «bear sweep») o una etiqueta de texto completo («Bull Sweep» o «Bear Sweep»). Al mismo tiempo, PlotArrow permite a los operadores dibujar una flecha en lugar de (o además de) texto. Para evitar el desorden y garantizar que las etiquetas no se superpongan a las velas, el campo de entrada ArrowOffsetPoints desplaza cada flecha u objeto de texto un número determinado de puntos por encima (en caso de tendencia bajista) o por debajo (en caso de tendencia alcista) de la vela.
Por último, dos parámetros de color —BullishColor y BearishColor— permiten controlar por completo el tono de estos marcadores, lo que permite al usuario adaptarlos al tema de su gráfico. Al separar las opciones de visualización de la lógica de detección, el EA mantiene su flexibilidad: los operadores pueden configurarlo en modo de «alerta silenciosa» (sin objetos en el gráfico) o en modo completo de «anotaciones en el gráfico», según sus preferencias personales o las necesidades de rendimiento del sistema.
//--- Inputs: Label/Arrow & Color Customization enum LabelType {None=0, Short, Full}; input LabelType LblType = Full; // Label type (None/Short/Full) input bool PlotArrow = true; // Draw arrow on signal input int ArrowOffsetPoints = 10; // Offset (in points) above/below candles input color BullishColor = clrLime; // Color for bullish signals input color BearishColor = clrRed; // Color for bearish signals
Inmediatamente después de la sección de entrada, el EA declara dos variables globales. La primera, datetime lastBarTime, almacena la marca de tiempo de la barra procesada más recientemente. Esta variable es fundamental: en cada tick, el EA compara la hora de inicio de la barra actual (iTime(Symbol(),Period(),0)) con lastBarTime. Si difieren, significa que se ha formado una nueva vela, y solo entonces el EA ejecuta su rutina de detección. Sin esta comprobación, la lógica se ejecutaría en cada tick de una barra en formación, lo que provocaría múltiples señales en una misma vela e introduciría lo que en trading se conoce comúnmente como repainting.
La segunda variable global, int MAHandle, contiene el identificador devuelto por iMA(...) cada vez que se crea una media móvil integrada. Al inicializarlo con INVALID_HANDLE, el código puede comprobar posteriormente si existe realmente un identificador válido antes de intentar leer de él.
//--- Globals datetime lastBarTime = 0; // Timestamp of the last processed bar int MAHandle = INVALID_HANDLE; // Handle for built‐in MA indicator
La función OnInit() es la rutina de configuración del EA. Cuando se conecta el EA por primera vez, o cada vez que alguien modifica una entrada, la plataforma llama a OnInit(). Lo primero que hay que hacer aquí es validar MALength. Dado que MQL5 trata cualquier valor de entrada como una constante tras la compilación, no podemos limitarnos a reasignar un valor seguro; en su lugar, el código comprueba if(MALength < 1), muestra un mensaje de error y devuelve INIT_FAILED si el usuario ha especificado un periodo no válido. Esta programación defensiva garantiza que el EA nunca intente calcular una media móvil con un período de cero o negativo, lo que, de lo contrario, provocaría errores en tiempo de ejecución o resultados sin sentido. A continuación, se establece lastBarTime en la hora de inicio de la barra actual, lo que inicia la lógica del controlador de ticks para que no se active inmediatamente en la barra en la que se ha añadido.
int OnInit() { // Validate MALength (cannot assign to an input directly) if(MALength < 1) { Print("ERROR: MALength must be at least 1. Current value = ", MALength); return(INIT_FAILED); } // Initialize timing so we only run when a new bar closes lastBarTime = iTime(Symbol(), Period(), 0); // … (rest of OnInit follows) … return(INIT_SUCCEEDED); }
A continuación, dentro de la función OnInit(), el EA determina si es necesario crear un identificador de media móvil integrada. Si el usuario ha seleccionado cualquiera de los cuatro tipos de media móvil estándar (SMA, EMA, LWMA o RMA) y tiene activadas las opciones UseMAFilter o ShowMA, el código llama a iMA(Symbol(), Period(), MALength, 0, (ENUM_MA_METHOD)MAType, PRICE_CLOSE) para crear un identificador de indicador. Si esa llamada falla, el EA muestra un error de diagnóstico y interrumpe la inicialización. Si la operación se realiza con éxito, comprueba si (ShowMA == true) y, en caso afirmativo, llama a ChartIndicatorAdd(0, 0, MAHandle) para trazar la media móvil en el gráfico. Al finalizar la inicialización, un mensaje —«EA de barrido de liquidez inicializado correctamente»— confirma que se cumplen todos los requisitos previos (período válido, identificador de MA) y que el EA está listo para ejecutarse.
// Create MA handle if using a built-in MA (SMA, EMA, LWMA, RMA) if((MAType != VWMA && MAType != HMA) && (UseMAFilter || ShowMA)) { ENUM_MA_METHOD method = (ENUM_MA_METHOD)MAType; MAHandle = iMA(Symbol(), Period(), MALength, 0, method, PRICE_CLOSE); if(MAHandle == INVALID_HANDLE) { Print("Failed to create MA handle (type=", EnumToString(MAType), ", length=", MALength, ")"); return(INIT_FAILED); } if(ShowMA) ChartIndicatorAdd(0, 0, MAHandle); }
Cuando el usuario retira el EA del gráfico o cierra MetaTrader, se ejecuta la función OnDeinit(). Su única función es la gestión interna: si se ha establecido MAHandle (es decir, no es INVALID_HANDLE), llama a IndicatorRelease(MAHandle) para liberar la memoria y eliminar cualquier referencia residual. De este modo, el EA evita las fugas de recursos, una práctica recomendada fundamental siempre que se crean identificadores de indicadores en tiempo de ejecución. Aunque la versión moderna de MetaTrader gestiona parte de esto de forma automática, liberar explícitamente los identificadores evita que los gráficos se saturen en sesiones prolongadas o cuando se vuelve a conectar con frecuencia el EA para optimizar los parámetros.
void OnDeinit(const int reason) { if(MAHandle != INVALID_HANDLE) IndicatorRelease(MAHandle); }
La función OnTick() es el corazón del EA; MetaTrader la activa cada vez que se recibe un tick de precio. Sin embargo, dado que solo queremos comprobar si se han producido «liquidity sweeps» una vez por cada vela completada, OnTick() recupera primero la hora de la última barra mediante iTime(Symbol(),Period(),0) y la compara con lastBarTime. Si coinciden, significa que el EA sigue dentro de la misma barra, por lo que no ocurre nada.
Solo cuando aparece una nueva vela (es decir, iTime(...) != lastBarTime), el código invoca DetectLiquiditySweep(1), pasando un desplazamiento de 1 para comparar la vela que acaba de cerrarse con la anterior. Inmediatamente después, lastBarTime se actualiza con el nuevo valor, lo que garantiza que el EA no vuelva a detectar nada hasta que se cierre la barra siguiente. Este enfoque disciplinado garantiza una —y solo una— señal por barra, lo que elimina el ruido y evita que se produzcan múltiples alertas en una misma vela.
void OnTick() { // Retrieve the current bar’s start time datetime current = iTime(Symbol(), Period(), 0); // If the bar start time changed, call the detection routine once if(current != lastBarTime) { DetectLiquiditySweep(1); lastBarTime = current; } }
La rutina principal, DetectLiquiditySweep(int shift), implementa la lógica que distingue a este EA de los marcadores de patrones más sencillos. En primer lugar, garantiza que haya suficientes datos históricos para calcular cualquier media móvil personalizada. Al calcular requiredBars = shift + MALength y comprobar si (Bars(Symbol(),Period()) <= requiredBars) return;, el código evita la indexación fuera de rango. Si, por ejemplo, solo hay 15 barras en el gráfico, pero el usuario ha establecido MALength=20, el EA no intentará calcular la media móvil ni comprobar patrones, ya que no dispone de un historial suficiente. Esta cláusula de protección demuestra una prevención minuciosa de errores, lo cual es esencial para que los EA sean robustos.
void DetectLiquiditySweep(int shift) { // Ensure there are at least (shift + MALength) bars of history int requiredBars = shift + MALength; if(Bars(Symbol(), Period()) <= requiredBars) { // Not enough bars to compute custom MA or compare prices return; } // … (next steps in the function) … }
Una vez superada la comprobación del historial, DetectLiquiditySweep lee ocho valores de precio —apertura, máximo, mínimo y cierre— tanto de la barra «actual» completada (índice = shift) como de la barra «anterior» (índice = shift + 1). Estos valores se almacenan en ocho valores de tipo «double» independientes: o, c, h, l para la barra actual, y o1, c1, h1, l1 para la barra anterior. Con estos datos, el EA puede evaluar si se ha producido un barrido de liquidez.
Es importante destacar que el código también calcula dos indicadores booleanos —bullCC (cambio de color alcista) y bearCC (cambio de color bajista)— simplemente comprobando si el cierre de la barra actual está por encima de su apertura, mientras que el cierre de la barra anterior estaba por debajo de su apertura, y viceversa. Estas señales entran en juego más adelante si se activa ColorChangeOnly, lo que garantiza que el EA solo señale los barridos que coincidan con un cambio de color de la vela.
//--- Bar data for the current completed candle (index = shift) double o = iOpen(Symbol(), Period(), shift); double c = iClose(Symbol(), Period(), shift); double h = iHigh(Symbol(), Period(), shift); double l = iLow(Symbol(), Period(), shift); //--- Bar data for the prior candle (index = shift + 1) double o1 = iOpen(Symbol(), Period(), shift + 1); double c1 = iClose(Symbol(), Period(), shift + 1); double h1 = iHigh(Symbol(), Period(), shift + 1); double l1 = iLow(Symbol(), Period(), shift + 1);
En la siguiente sección se implementan las definiciones de barrido «LessStrict» frente a «Strict». Si el usuario ha seleccionado LessStrict, se indica un barrido alcista cuando la barra actual cierra por encima de su propia apertura (c > o), su mínimo se sitúa por debajo del mínimo de la barra anterior (l < l1), cierra por encima de la apertura de la barra anterior (c > o1) y la barra anterior no era un doji (c1 != o1). Un «sweep» bajista es simétrico: el cierre actual es inferior a la apertura actual, el máximo actual es superior al máximo anterior, el cierre actual es inferior a la apertura anterior y la barra anterior no es un doji.
Si, por el contrario, el usuario seleccionara la opción «Estricta», el EA endurecería las condiciones al exigir que el cierre actual supere el máximo de la barra anterior (en el caso alcista) o que caiga por debajo del mínimo de la barra anterior (en el caso bajista). Esta variante «estricta» reduce las señales falsas de alerta en retrocesos de poca envergadura, lo que obliga a que se produzca una ruptura más clara del nivel de soporte o de resistencia. Al explicar ambos modos, el EA enseña a los lectores cómo pequeños ajustes en la lógica pueden modificar de forma significativa la frecuencia y la calidad de la señal.
// Compute color-change flags bool bullCC = (c > o && c1 < o1); bool bearCC = (c < o && c1 > o1); // Liquidity sweep flags (LessStrict or Strict) bool bullSweep, bearSweep; if(SignalStrict == LessStrict) { bullSweep = (c > o && l < l1 && c > o1 && c1 != o1); bearSweep = (c < o && h > h1 && c < o1 && c1 != o1); } else // Strict { bullSweep = (c > o && l < l1 && c > h1 && c1 != o1); bearSweep = (c < o && h > h1 && c < l1 && c1 != o1); }
Tras las comprobaciones de barrido sin procesar, el EA aplica, de forma opcional, el filtro de cambio de color. Si ColorChangeOnly es verdadero, se establece bullSweep &= bullCC; bearSweep &= bearCC;. De hecho, cualquier movimiento que no coincida con un cambio de color de la vela se descarta inmediatamente. Muchos operadores consideran que un cambio de color (por ejemplo, una vela roja tras una vela verde) confirma un impulso de reversión, por lo que, al limitar los barridos a los casos en los que se cumple esa condición, el EA reduce la probabilidad de marcar un barrido que carezca de un rechazo visible. Esta sencilla operación de «AND» bit a bit combina de forma elegante dos señales complementarias —la estructura de precios y la psicología de las velas— en un único filtro.
// If only color-change sweeps are desired, AND‐combine with the raw sweep flags if(ColorChangeOnly) { bullSweep &= bullCC; // must also be a bullish color reversal bearSweep &= bearCC; // must also be a bearish color reversal }
Con las condiciones de barrido sin procesar y el filtro de color opcional activados, el EA examina a continuación el filtro de media móvil si UseMAFilter es verdadero. Declara la variable doble maValue = 0,0; y a continuación calcula la media móvil de una de estas tres formas. Si el usuario selecciona «VWMA», se invoca la función auxiliar personalizada CalcVWMA(shift). Si se ha seleccionado «HMA», se llama a CalcHMA(shift). De lo contrario, asume un tipo de media móvil (MA) integrada (SMA, EMA, LWMA o RMA) y ejecuta CopyBuffer(MAHandle, 0, shift, 1, buf) para recuperar un valor de la media móvil. Si CopyBuffer no devuelve exactamente un resultado, el EA simplemente sale y omite toda la lógica posterior en esa barra.
Una vez que se conoce maValue, una condición booleana comprueba si PriceAboveMA ? (c > maValue) : (c < maValue). En la práctica, si PriceAboveMA es verdadero, el EA solo conserva los barridos alcistas que cierran por encima de la media móvil (descartando los bajistas al establecer bearSweep = falso), mientras que si PriceAboveMA es falso, solo se mantienen los barridos bajistas que cierran por debajo de la media móvil. Este filtro garantiza que los barridos de liquidez se analicen en el contexto de la tendencia: los movimientos alcistas solo son relevantes si el precio se encuentra por encima de la media móvil, y los bajistas, solo si el precio se encuentra por debajo.
if(UseMAFilter) { double maValue = 0.0; // Compute MA value at the same 'shift' if(MAType == VWMA) maValue = CalcVWMA(shift); else if(MAType == HMA) maValue = CalcHMA(shift); else { // Built‐in MA handle (SMA, EMA, LWMA, RMA) double buf[]; if(CopyBuffer(MAHandle, 0, shift, 1, buf) != 1) return; // no valid MA data available maValue = buf[0]; } // Only allow bullish sweeps above MA or bearish sweeps below MA bool cond = PriceAboveMA ? (c > maValue) : (c < maValue); bullSweep &= cond; bearSweep &= !cond; }
Por fin, una vez aplicados todos los filtros, DetectLiquiditySweep utiliza dos bloques «if» para comprobar si (bullSweep) y si (bearSweep). Si bullSweep es verdadero, llama a DrawSignal(shift, true) para colocar un marcador alcista en el gráfico y escribe un mensaje en el registro de expertos del tipo «Se ha detectado un barrido alcista a las [hora], precio=[cierre]». Si bearSweep es verdadero, hace lo mismo con DrawSignal(shift, false) y el mensaje correspondiente.
En cualquier caso, siempre que uno de ellos se haya ejecutado, el código finalmente llama a Alert(«Se ha detectado un «liquidity sweep» en », Symbol(), « », EnumToString(Period()));, lo que hace que aparezca una alerta en pantalla. Esta distinción entre «marcar en el gráfico» y «enviar una alerta» permite a cada operador decidir si prefiere únicamente notificaciones sonoras o emergentes, o si también desea una referencia visual permanente en el propio gráfico.
// If a bullish sweep remains true after all filters, draw and log it if(bullSweep) { DrawSignal(shift, true); PrintFormat("Bullish sweep detected at %s, price=%.5f", TimeToString(iTime(Symbol(),Period(),shift)), c); } // If a bearish sweep remains true after all filters, draw and log it if(bearSweep) { DrawSignal(shift, false); PrintFormat("Bearish sweep detected at %s, price=%.5f", TimeToString(iTime(Symbol(),Period(),shift)), c); } // In either case, fire a pop‐up alert if(bullSweep || bearSweep) { Alert("Liquidity Sweep detected on ", Symbol(), " ", EnumToString(Period())); }
La función DrawSignal(int shift, bool bullish) se encarga exclusivamente de colocar los objetos del gráfico. En primer lugar, lee la marca de tiempo de la barra t = iTime(Symbol(),Period(),shift) y calcula un precio de visualización restando del mínimo ArrowOffsetPoints * _Point (para una flecha alcista) o sumando al máximo el mismo desplazamiento (para una flecha bajista). Al seleccionar _Point, se garantiza que el desplazamiento se mida en incrementos de precio reales, independientemente de si el instrumento es, por ejemplo, el EURUSD (incrementos de 0,0001) o el USDJPY (incrementos de 0,01). A continuación, genera un nombre de objeto único mediante StringFormat(«LS_%I64u», (long)t). Dado que (long)t es un entero de 64 bits que refleja la hora exacta de apertura de la barra, nunca hay dos barras que generen el mismo nombre de objeto.
Antes de dibujar nada, llama a ObjectFind(0, name) y elimina cualquier objeto preexistente con ese nombre; esto evita el desorden si se vuelve a adjuntar el EA o si, al actualizar el gráfico, se repite la misma detección. Por último, si PlotArrow es verdadero, ObjectCreate(...) dibuja una flecha de color (hacia arriba si la tendencia es alcista o hacia abajo si es bajista) en el precio calculado. Si PlotArrow es «false» pero LblType no es «None», la función dibuja en su lugar una etiqueta con texto breve o completo (por ejemplo, «BS» frente a «Bull Sweep»). Al aislar todo el código de los objetos del gráfico en una sola rutina, el EA mantiene su lógica de detección separada de sus anotaciones visuales, lo cual es un rasgo característico de un diseño limpio y modular.
void DrawSignal(int shift, bool bullish) { // Get the exact bar start time for this sweep datetime t = iTime(Symbol(), Period(), shift); // Determine the on‐chart Y‐coordinate: offset a few points above/below the candle double price = bullish ? (iLow(Symbol(), Period(), shift) - ArrowOffsetPoints * _Point) : (iHigh(Symbol(), Period(), shift) + ArrowOffsetPoints * _Point); // Compose a unique name using the 64-bit timestamp string name = StringFormat("LS_%I64u", (long)t); // If an object with that name already exists, delete it first if(ObjectFind(0, name) >= 0) ObjectDelete(0, name); // (Next lines will choose arrow vs. text drawing) }
En segundo plano, dos funciones auxiliares calculan medias móviles no estándar. CalcVWMA(int shift) implementa una media móvil ponderada por volumen típica. Inicializa dos variables de agregado —el numerador y el denominador— en 0,0. A continuación, para cada índice de barras comprendido entre el turno y turno + MALength – 1, lee el precio de cierre price = iClose(...) y el volumen por tick vol = iVolume(...) (almacenado como un entero de 64 bits). Al convertir explícitamente «vol» a «double» al calcular «numerador += precio * (double)vol» y «denominador += (double)vol», el código evita cualquier advertencia sobre conversiones involuntarias de entero a «double».
Una vez finalizado el bucle, devuelve el numerador dividido por el denominador si el denominador es mayor que 0,0; en caso contrario, devuelve 0,0 de forma segura para evitar la división por cero. Por lo tanto, cada punto de la VWMA es precisamente la suma de (precio × volumen) de las últimas MALength barras, dividida por la suma del volumen; exactamente lo que los operadores esperan de una media ponderada por volumen.
double CalcVWMA(int shift) { double numerator = 0.0; double denominator = 0.0; // Loop over 'MALength' bars, starting at 'shift' for(int i = shift; i < shift + MALength; i++) { double price = iClose(Symbol(), Period(), i); long vol = iVolume(Symbol(), Period(), i); // Accumulate (price × volume) and sum of volume numerator += price * (double)vol; denominator += (double)vol; } // Return VWMA = sum(price×volume) / sum(volume), or 0 if volume = 0 return (denominator > 0.0) ? (numerator / denominator) : 0.0; }
Por último, CalcHMA(int shift) calcula la media móvil de Hull, una media ponderada en dos pasos diseñada para reducir el retraso. En primer lugar, establece half = MALength / 2 y recorre un bucle desde i = shift hasta i < shift + half, asignando a cada barra un peso decreciente desde half hasta 1. Se acumula una suma ponderada w1 y un total de ponderación sw1, y finalmente se divide w1 entre sw1 para obtener una media móvil ponderada de medio período. En segundo lugar, recorre el bucle desde i = shift hasta i < shift + MALength, asignando ponderaciones desde MALength hasta 1, acumulando w2 y sw2, y calculando w2/sw2 para obtener una media móvil ponderada de período completo.
El valor final de HMA es 2 * w1 – w2. De hecho, duplicar la media móvil de medio período y restarle la media móvil de período completo aumenta la capacidad de respuesta de la media móvil ante las variaciones recientes de los precios, lo que muchos operadores consideran más adecuado para los mercados dinámicos. Al igual que con VWMA, el EA evita que el historial sea insuficiente al no llamar nunca a CalcHMA a menos que Bars(Symbol(),Period()) > shift + MALength.
//+------------------------------------------------------------------+ //| Compute Hull Moving Average (HMA) | //+------------------------------------------------------------------+ double CalcHMA(int shift) { int half = MALength / 2; double w1 = 0.0, sw1 = 0.0; double w2 = 0.0, sw2 = 0.0; // 1) Weighted MA over half period for(int i = shift; i < shift + half; i++) { double p = iClose(Symbol(), Period(), i); int weight = half - (i - shift); w1 += p * weight; sw1 += weight; } w1 = (sw1 > 0.0) ? (w1 / sw1) : 0.0; // 2) Weighted MA over full period for(int i = shift; i < shift + MALength; i++) { double p = iClose(Symbol(), Period(), i); int weight = MALength - (i - shift); w2 += p * weight; sw2 += weight; } w2 = (sw2 > 0.0) ? (w2 / sw2) : 0.0; // 3) Final HMA value = 2 * (MA over half) – (MA over full) return 2.0 * w1 - w2; }
Pruebas y resultados
Para evaluar y comprender el rendimiento de nuestra herramienta, hemos llevado a cabo una serie de pruebas exhaustivas. Este proceso consistió en aplicar la herramienta a datos históricos mediante pruebas retrospectivas, así como en evaluar su rendimiento inmediato en condiciones reales de mercado. A continuación, presentamos una ilustración de los resultados obtenidos en estas pruebas.

Figura 3: Pruebas en tiempo real con el par GBPUSD.
Este gráfico ilustra de forma eficaz la capacidad de la herramienta para identificar barridos de liquidez, poniendo de relieve un claro movimiento bajista seguido de uno alcista, lo que indica su potencial para detectar con precisión los cambios de tendencia del mercado y la actividad institucional. Este gráfico ilustra de forma eficaz la capacidad de la herramienta para identificar barridos de liquidez, poniendo de relieve un claro movimiento bajista seguido de uno alcista, lo que indica su potencial para detectar con precisión los cambios de tendencia del mercado y la actividad institucional.

Figura 4: Pruebas en tiempo real en el Step Index.
El resultado anterior demuestra la eficacia de la herramienta a la hora de identificar barridos de liquidez, captando los cambios de tendencia clave del mercado mediante indicadores visuales claros, lo que puede facilitar la toma de decisiones estratégicas. Este resultado demuestra la eficacia de la herramienta a la hora de identificar barridos de liquidez, captando los cambios de tendencia clave del mercado mediante indicadores visuales claros, lo que puede facilitar la toma de decisiones estratégicas.

Figura 5: Backtesting del EURUSD.
Este gráfico de backtest muestra la capacidad de la herramienta para detectar con precisión patrones de barrido de liquidez en los datos históricos del mercado. Al analizar la evolución histórica de los precios, se ponen de relieve los casos en los que el precio ha traspasado temporalmente los niveles de soporte o resistencia antes de dar marcha atrás, lo que indica una posible actividad institucional. Estos puntos identificados pueden servir como indicios valiosos para anticipar futuros cambios de tendencia en el mercado o la continuación de las mismas. El gráfico confirma la eficacia del mecanismo de detección y refuerza la confianza en su aplicación en situaciones de negociación real.
Conclusión
Este EA identifica de forma consistente los barridos de liquidez tanto en las pruebas retrospectivas históricas como en la simulación en tiempo real, marcando cada movimiento válido con una flecha justo en el punto en el que el precio atravesó —y posteriormente recuperó— un mínimo o máximo oscilante anterior. En diversas configuraciones de filtros MA, las señales se alinearon bien con la continuación de la tendencia posterior, lo que demuestra una gran fiabilidad en condiciones reales de mercado. Ya sea que se revise su desempeño pasado o se observe su funcionamiento en tiempo real, la capacidad del EA para detectar las «cazas de stop» al estilo institucional ha sido validada de manera consistente. Utilice esta información para integrar la detección de barridos en su propia estrategia, sabiendo que el código ha demostrado su precisión y capacidad de respuesta tanto en entornos de simulación como en tiempo real.
Traducción del inglés realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/en/articles/18379
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.
Simulación de mercado: Position View (XI)
De novato a experto: Desmitificando los niveles ocultos de retroceso de Fibonacci
Del básico al intermedio: Colas, listas y árboles (IV)
Características del Wizard MQL5 que debe conocer (Parte 68): Uso de patrones de TRIX y Williams Percent Range con una red de núcleo coseno
- 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