English Русский 中文 Deutsch 日本語 Português
preview
La estrategia comercial de captura de liquidez

La estrategia comercial de captura de liquidez

MetaTrader 5Ejemplos |
355 4
Zhuo Kai Chen
Zhuo Kai Chen

La estrategia de negociación basada en la captura de liquidez es un componente clave de Smart Money Concepts (SMC), que busca identificar y explotar las acciones de los actores institucionales en el mercado. Implica apuntar a áreas de alta liquidez, como zonas de soporte o resistencia, donde las órdenes grandes pueden desencadenar movimientos de precios antes de que el mercado reanude su tendencia. Este artículo explica en detalle el concepto de «liquidity grab» (captura de liquidez) y describe el proceso de desarrollo de la estrategia de negociación basada en la captura de liquidez en MQL5.


Resumen de la estrategia: conceptos clave y tácticas

La estrategia de captura de liquidez se centra en explotar áreas de alta liquidez, como los niveles de soporte y resistencia, donde los operadores institucionales suelen manipular los precios para activar las órdenes stop-loss y provocar volatilidad. Esta estrategia aprovecha la dinámica común del mercado para anticiparse a estos movimientos y sacarles partido.

Conceptos fundamentales

  1. Cacería de órdenes stop-loss: Impulsar los precios para activar órdenes stop-loss, provocando una cascada de compras o ventas.
  2. Layering y Spoofing: Utilizar órdenes falsas para engañar a los operadores sobre la dirección del mercado.
  3. Órdenes iceberg: Ocultar operaciones de gran volumen dividiéndolas en partes más pequeñas y visibles.
  4. Encendido por impulso: Crear un impulso artificial para atraer a otros operadores antes de revertir la tendencia.
  5. Manipulación del soporte y la resistencia: Aprovechar los niveles clave de precios para obtener reacciones predecibles.
  6. Puntos de precio psicológicos: Aprovechar los números redondos para influir en el comportamiento.

La manipulación del mercado consiste en influir intencionadamente en el precio o el volumen de un valor para crear condiciones comerciales engañosas. Aunque la mayoría de los operadores institucionales se adhieren a las normas legales y éticas, algunos pueden incurrir en prácticas manipuladoras para alcanzar objetivos estratégicos específicos. A continuación, se ofrece una breve descripción general de por qué y cómo ocurre esto:

Motivaciones:

  • Maximice las ganancias mediante movimientos de precios a corto plazo y arbitraje.
  • Ocultar las intenciones comerciales a los competidores.
  • Realice operaciones de gran volumen con un impacto mínimo en el mercado.

Tácticas:

  • Activar las órdenes stop-loss para crear liquidez o impulsar los precios.
  • Controlar el flujo de órdenes con órdenes iceberg o spoofing.
  • Apuntar a niveles de soporte, resistencia o precios psicológicos.

Comprender estas dinámicas permite a los operadores anticipar el comportamiento institucional e integrar estos conocimientos en herramientas automatizadas para desarrollar estrategias más eficaces.

Queremos aprovechar los movimientos temporales de los precios provocados por la acumulación de órdenes stop-loss y las acciones de los grandes participantes del mercado. Al identificar las áreas de liquidez, los operadores pretenden entrar en el mercado en los puntos óptimos antes de que el precio se invierta y continúe con la tendencia predominante, lo que ofrece oportunidades favorables en cuanto a la relación riesgo-recompensa.

Aquí hay un diagrama aproximado de cómo debería verse.

Diagrama de ejemplo


Desarrollo de la estrategia

Propongo que adoptemos el siguiente orden cuando codifiquemos un EA:

  1. Considera las funciones necesarias para esta estrategia y determina cómo encapsularlas en diferentes componentes. 
  2. Codifique cada función una por una, declarando las variables globales relacionadas o inicializando los identificadores pertinentes a medida que avanza. 
  3. Después de implementar cada función, revísala y piensa cómo conectarlas, por ejemplo, pasando parámetros o llamando a funciones dentro de otras funciones. 
  4. Por último, vaya a OnTick() y desarrolle la lógica utilizando las funciones de los pasos anteriores.

En primer lugar, intentamos cuantificar las reglas. El SMC es negociado principalmente por operadores discrecionales, ya que hay muchos matices no cuantificables involucrados. Desde un punto de vista objetivo, resulta difícil definir las características exactas que debe tener una manipulación. Un enfoque consiste en analizar el cambio de volumen en el flujo de órdenes, pero los datos de volumen proporcionados por los corredores suelen ser poco fiables. En mercados como el de divisas, las transacciones no están centralizadas, y en el caso de los intercambios centralizados, como los futuros, la mayoría de los corredores proporcionan datos de sus proveedores de liquidez en lugar de datos centralizados. Un método más sencillo y optimizable es el análisis técnico, que es el enfoque que utilizaremos en este artículo. Para cuantificar las reglas, simplificaremos la estrategia en los siguientes segmentos:

  1.  Se formó un patrón de vela de rechazo en un nivel clave, donde el nivel clave se define como el punto más alto o más bajo dentro del período de referencia.
  2.  Tras esta vela de rechazo, el precio se invierte y rompe el nivel clave en el lado opuesto con un periodo de retroceso más corto. 
  3.  Por último, si el movimiento general se alinea con la tendencia más amplia, tal y como indica la posición del precio en relación con la media móvil, entramos en la operación con un stop loss y un take profit fijos.

Tenga en cuenta que podríamos añadir más reglas para imitar mejor las características de la manipulación del mercado, pero es aconsejable mantener la estrategia lo más simple posible para evitar el sobreajuste.

A continuación, codificamos las funciones relacionadas. Estas son funciones esenciales para ejecutar órdenes tras calcular el take profit y el stop loss, y realizar un seguimiento de los tickets de las órdenes.

//+------------------------------------------------------------------+
//| Expert trade transaction handling function                       |
//+------------------------------------------------------------------+
void OnTradeTransaction(const MqlTradeTransaction& trans, const MqlTradeRequest& request, const MqlTradeResult& result) {
    if (trans.type == TRADE_TRANSACTION_ORDER_ADD) {
        COrderInfo order;
        if (order.Select(trans.order)) {
            if (order.Magic() == Magic) {
                if (order.OrderType() == ORDER_TYPE_BUY) {
                    buypos = order.Ticket();
                } else if (order.OrderType() == ORDER_TYPE_SELL) {
                    sellpos = order.Ticket();
                }
            }
        }
    }
}

//+------------------------------------------------------------------+
//| Execute sell trade function                                      |
//+------------------------------------------------------------------+
void executeSell() {
    double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
    bid = NormalizeDouble(bid, _Digits);
    double tp = NormalizeDouble(bid - tpp * _Point, _Digits);
    double sl = NormalizeDouble(bid + slp * _Point, _Digits);
    trade.Sell(lott, _Symbol, bid, sl, tp);
    sellpos = trade.ResultOrder();
}

//+------------------------------------------------------------------+
//| Execute buy trade function                                       |
//+------------------------------------------------------------------+
void executeBuy() {
    double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
    ask = NormalizeDouble(ask, _Digits);
    double tp = NormalizeDouble(ask + tpp * _Point, _Digits);
    double sl = NormalizeDouble(ask - slp * _Point, _Digits);
    trade.Buy(lott, _Symbol, ask, sl, tp);
    buypos = trade.ResultOrder();
}

Estas dos funciones identifican y devuelven el punto más alto o más bajo dentro de un rango determinado, asegurando que este punto se califique como un nivel clave al verificar la presencia de soporte o resistencia que desencadena una reversión desde ese nivel. 

//+------------------------------------------------------------------+
//| find the key level high given a look-back period                 |
//+------------------------------------------------------------------+
double findhigh(int Range = 0)
{
   double highesthigh = 0;
   for (int i = BarsN; i < Range; i++)
   {
      double high = iHigh(_Symbol, PERIOD_CURRENT, i);
      if (i > BarsN && iHighest(_Symbol, PERIOD_CURRENT, MODE_HIGH, BarsN * 2 + 1, i - BarsN) == i)
      //used to make sure there's rejection for this high
      {
         if (high > highesthigh)
         {
            return high;
         }
      }
      highesthigh = MathMax(highesthigh, high);
   }
   return 99999;
}

//+------------------------------------------------------------------+
//| find the key level low given a look-back period                  |
//+------------------------------------------------------------------+
double findlow(int Range = 0)
{
   double lowestlow = DBL_MAX;
   for (int i = BarsN; i < Range; i++)
   {
      double low = iLow(_Symbol, PERIOD_CURRENT, i);
      if (i > BarsN && iLowest(_Symbol, PERIOD_CURRENT, MODE_LOW, BarsN * 2 + 1, i - BarsN) == i)
      {
         if (lowestlow > low)
         {
            return low;
         }
      }
      lowestlow = MathMin(lowestlow, low);
   }
   return -1;
}

La función findhigh() garantiza que haya un rechazo en el punto alto identificado comprobando si el máximo más alto dentro de un rango especificado se produce en la barra actual (i) y si el punto más alto dentro de un rango mayor (el doble del período de retrospectiva) coincide con esa barra. Esto sugiere un rechazo, ya que el precio no pudo superar ese nivel. Si es verdadero, devuelve el valor alto como un nivel clave potencial. La función findlow() es simplemente la lógica inversa.

Estas dos funciones detectan si la última vela cerrada es una vela de rechazo en el nivel clave, lo que puede interpretarse como un comportamiento de captura de liquidez.

//+------------------------------------------------------------------+
//| Check if the market rejected in the upward direction             |
//+------------------------------------------------------------------+
bool IsRejectionUp(int shift=1)
{
   // Get the values of the last candle (shift = 1)
   double open = iOpen(_Symbol,PERIOD_CURRENT, shift);
   double close = iClose(_Symbol,PERIOD_CURRENT, shift);
   double high = iHigh(_Symbol,PERIOD_CURRENT, shift);
   double low = iLow(_Symbol,PERIOD_CURRENT,shift);
   
   // Calculate the body size
   double bodySize = MathAbs(close - open);
   
   // Calculate the lower wick size
   double lowerWickSize = open < close ? open - low : close - low;
   
   // Check if the lower wick is significantly larger than the body
   if (lowerWickSize >= wickToBodyRatio * bodySize&&low<findlow(DistanceRange)&&high>findlow(DistanceRange))
   {
      return true;
   }
   
   return false;
}

//+------------------------------------------------------------------+
//| Check if the market rejected in the downward direction           |
//+------------------------------------------------------------------+
bool IsRejectionDown(int shift = 1)
{
   // Get the values of the last candle (shift = 1)
   double open = iOpen(_Symbol,PERIOD_CURRENT, shift);
   double close = iClose(_Symbol,PERIOD_CURRENT, shift);
   double high = iHigh(_Symbol,PERIOD_CURRENT, shift);
   double low = iLow(_Symbol,PERIOD_CURRENT,shift);
   
   // Calculate the body size
   double bodySize = MathAbs(close - open);
   
   // Calculate the upper wick size
   double upperWickSize = open > close ? high - open : high - close;
   
   // Check if the upper wick is significantly larger than the body
   if (upperWickSize >= wickToBodyRatio * bodySize&&high>findhigh(DistanceRange)&&low<findhigh(DistanceRange))
   {
      return true;
   }
   
   return false;
}

Una vela muestra un patrón de rechazo cuando su mecha es significativamente más grande que su cuerpo, y la dirección de la vela se invierte con respecto a la dirección anterior.

Utilizando las dos últimas funciones, estas se crearon para recorrer un período de retrospectiva específico con el fin de detectar cualquier comportamiento de captura de liquidez, lo que se utilizará para comprobar si dicho comportamiento se produjo antes de observar una señal de reversión y ruptura.

//+------------------------------------------------------------------+
//| check if there were rejection up for the short look-back period  |
//+------------------------------------------------------------------+
bool WasRejectionUp(){
   for(int i=1; i<CandlesBeforeBreakout;i++){
     if(IsRejectionUp(i))
        return true;  
   }
    return false;
}


//+------------------------------------------------------------------+
//| check if there were rejection down for the short look-back period|
//+------------------------------------------------------------------+
bool WasRejectionDown(){
   for(int i=1; i<CandlesBeforeBreakout;i++){
     if(IsRejectionDown(i))
        return true;  
   }
    return false;
}

Para obtener los datos del valor medio móvil actual, primero inicializamos el identificador en la función OnInit().

int handleMa;
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit() {
    trade.SetExpertMagicNumber(Magic);
    handleMa = iMA(_Symbol, PERIOD_CURRENT, MaPeriods, 0, MODE_SMA,PRICE_CLOSE);
    
    if (handleMa == INVALID_HANDLE) {
        Print("Failed to get indicator handles. Error: ", GetLastError());
        return INIT_FAILED;
    }
    return INIT_SUCCEEDED;
}

El valor de la media móvil se puede consultar fácilmente creando una matriz de búfer y copiando el valor del identificador en la matriz de búfer de la siguiente manera:

double ma[];
if (CopyBuffer(handleMa, 0, 1, 1, ma) <= 0) {
            Print("Failed to copy MA data. Error: ", GetLastError());
            return;
}

Por último, pasa a OnTick() para aplicar la lógica de trading al programa utilizando las funciones que has definido. Esto garantiza que solo calculamos la señal cuando se ha formado una nueva barra, comprobando si la barra actual es diferente de la última barra cerrada guardada. Esto ahorra potencia de cálculo y permite transacciones más fluidas.

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick() {
    int bars = iBars(_Symbol, PERIOD_CURRENT);
    if (barsTotal != bars) {
        barsTotal = bars;

A continuación, simplemente aplicamos la condición de señal de la siguiente manera:

if(WasRejectionDown()&&bid<ma[0]&&bid<findlow(CandlesBeforeBreakout))
        executeSell();
else if(WasRejectionUp()&&ask>ma[0]&&ask>findhigh(CandlesBeforeBreakout))
        executeBuy();

Después de este paso, intente compilar el programa y vaya al visualizador de backtest para comprobar si el EA funciona.

En el visualizador de backtest, una entrada típica tendría este aspecto:

Ejemplo de LG


Sugerencias

Aunque ya hemos terminado la idea principal de la estrategia, tengo algunas sugerencias para implementar este EA en el mercado real:

1. Las manipulaciones del mercado se producen rápidamente, por lo que es mejor operar intradía con esta estrategia utilizando marcos temporales de 5 o 15 minutos. Los marcos temporales más cortos pueden ser más susceptibles a señales falsas, mientras que los marcos temporales más largos pueden reaccionar demasiado lentamente a las manipulaciones del mercado.

2. Las manipulaciones del mercado suelen producirse durante periodos de alta volatilidad, como las sesiones de Forex en Nueva York/Londres o las horas de apertura/cierre de los mercados bursátiles. Es recomendable implementar una función que restrinja la negociación a estas horas específicas, como se muestra a continuación:

//+------------------------------------------------------------------+
//| Check if the current time is within the specified trading hours  |
//+------------------------------------------------------------------+
bool IsWithinTradingHours() {
    datetime currentTime = TimeTradeServer();
    MqlDateTime timeStruct;
    TimeToStruct(currentTime, timeStruct);
    int currentHour = timeStruct.hour;

    if (( currentHour >= startHour1 && currentHour < endHour1) ||
        ( currentHour >= startHour2 && currentHour < endHour2))
         {
        return true;
    }
    return false;
}

3. Si el precio se consolida en torno a los niveles clave, puede desencadenar múltiples operaciones consecutivas en ambas direcciones. Para garantizar que solo se ejecute una operación a la vez, añadimos otro criterio: ambos tickets de posición deben estar establecidos en 0, lo que indica que no hay posiciones abiertas para este EA. Los restablecemos a 0 escribiendo estas líneas en la función OnTick().

if(buypos>0&&(!PositionSelectByTicket(buypos)|| PositionGetInteger(POSITION_MAGIC) != Magic)){
 buypos = 0;
 }
if(sellpos>0&&(!PositionSelectByTicket(sellpos)|| PositionGetInteger(POSITION_MAGIC) != Magic)){
 sellpos = 0;
 }

Actualizamos nuestro código original para incorporar los cambios que acabamos de realizar.

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick() {
    int bars = iBars(_Symbol, PERIOD_CURRENT);
    if (barsTotal != bars) {
        barsTotal = bars;

        double ma[];
        
        double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
        double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);

        if (CopyBuffer(handleMa, 0, 1, 1, ma) <= 0) {
            Print("Failed to copy MA data. Error: ", GetLastError());
            return;
        }
        if(WasRejectionDown()&&IsWithinTradingHours()&&sellpos==buypos&&bid<ma[0]&&bid<findlow(CandlesBeforeBreakout))
        executeSell();
        else if(WasRejectionUp()&&IsWithinTradingHours()&&sellpos==buypos&&ask>ma[0]&&ask>findhigh(CandlesBeforeBreakout))
        executeBuy();
        
     if(buypos>0&&(!PositionSelectByTicket(buypos)|| PositionGetInteger(POSITION_MAGIC) != Magic)){
      buypos = 0;
      }
     if(sellpos>0&&(!PositionSelectByTicket(sellpos)|| PositionGetInteger(POSITION_MAGIC) != Magic)){
      sellpos = 0;
      }
    }
}


Prueba retrospectiva

Para este artículo, utilizaremos este EA en GBPUSD en el marco temporal de 5 minutos.

Aquí están los parámetros de configuración que decidimos utilizar para este asesor experto:

Parámetros

Notas importantes:

  • Para el take profit y el stop loss, seleccionamos un punto razonable basado en la volatilidad intradía. Dado que esta estrategia básicamente sigue la tendencia, se recomienda que la relación entre la recompensa y el riesgo sea superior a 1.
  • DistanceRange es el período retrospectivo para buscar niveles clave de señales de captura de liquidez.
  • Del mismo modo, CandlesBeforeBreakout es el período retrospectivo para buscar niveles clave recientes en busca de señales de ruptura.
  • La relación entre la mecha y el cuerpo se puede ajustar a un valor que el operador considere suficiente para ilustrar un patrón de rechazo.
  • El horario de negociación se basa en la hora del servidor de su bróker. Para mi bróker (GMT+0), el periodo volátil de la sesión de Nueva York en el mercado de divisas es de 13:00 a 19:00.

Ahora ejecutemos la prueba retrospectiva desde el 1 de noviembre de 2020 hasta el 1 de noviembre de 2024:

Configuración

Curva

Resultado

La estrategia ha dado buenos resultados durante los últimos cuatro años.


Conclusión

En este artículo, primero presentamos el concepto de «captura de liquidez» y sus motivaciones subyacentes. A continuación, proporcionamos una guía paso a paso sobre cómo crear el asesor experto (Expert Advisor, EA) para esta estrategia desde cero. A continuación, ofrecimos recomendaciones adicionales para optimizar el EA. Por último, demostramos su rentabilidad potencial mediante una prueba retrospectiva de cuatro años, con más de 200 operaciones.

Esperamos que esta estrategia te resulte útil y te inspire para desarrollarla aún más, ya sea creando estrategias similares u optimizando su configuración. El archivo correspondiente al asesor experto se adjunta a continuación. Siéntete libre de descargarlo y experimentar con él.

Traducción del inglés realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/en/articles/16518

linfo2
linfo2 | 28 ene 2025 en 18:27
Gracias por el código , artículo muy bien escrito y muy bien juntos , el código es muy útil gracias . Interesante si ves los comerciantes SMC en los medios de comunicación social los rendimientos muy diferentes . Voy a revisar las transacciones y tratar un trailing stop y un trailing tp o algunos Fibonacci en los rangos externos
Zhuo Kai Chen
Zhuo Kai Chen | 29 ene 2025 en 02:14
linfo2 #:
Gracias por el código , artículo muy bien escrito y muy bien juntos , el código es muy útil gracias . Interesante si ves los comerciantes SMC en los medios de comunicación social los rendimientos muy diferentes . Revisará las transacciones y tratar un trailing stop y un trailing tp o algunos Fibonacci en los rangos externos

Gracias por tu comentario Sí, veo los comerciantes SMC en los medios sociales. En general, creo que no están realmente de acuerdo en la estrategia en términos de agarre de liquidez. Algunos buscan dos fakeouts en lugar de uno, y otros se fijan en el volumen de negociación. En general, sus acciones implican algunas discrepancias de ellos mismos que hacen difícil evaluar la validez de sus estrategias. No obstante, espero sus resultados experimentando con trailing sl/tp y rangos de Fibonacci.

rapidace1
rapidace1 | 2 mar 2025 en 13:07
Este concepto me parece fresco, gracias por compartirlo.
Joab jrs
Joab jrs | 28 dic 2025 en 10:15

La traducción automática ha sido aplicada por un moderador. Por favor, escriba en el idioma de la sección del foro que haya seleccionado.

Hola amigo, acabo de terminar de leer todos tus artículos y son muy interesantes.

Ya estaba desarrollando algo sobre tres de ellos y te los enviaré pronto para que los valides y añadas algo si es necesario.

Sistemas neurosimbólicos en trading algorítmico: Combinación de reglas simbólicas y redes neuronales Sistemas neurosimbólicos en trading algorítmico: Combinación de reglas simbólicas y redes neuronales
El artículo relata la experiencia del desarrollo de un sistema comercial híbrido que combine el análisis técnico clásico con las redes neuronales. El autor describe detalladamente la arquitectura del sistema, desde el análisis básico de patrones y la estructura de la red neuronal hasta los mecanismos de toma de decisiones comerciales, compartiendo código real y observaciones de carácter práctico.
Implementación de los cierres parciales en MQL5 Implementación de los cierres parciales en MQL5
En este artículo se desarrolla una clase para gestionar cierres parciales en MQL5 y se integra dentro de un EA de order blocks. Además, se presentan pruebas de backtest comparando la estrategia con y sin parciales, analizando en qué condiciones su uso puede maximizar y asegurar beneficios. Concluimos que especialmente en estilos de trading orientados a movimientos más amplios, el uso de parciales podría ser beneficioso.
Redes neuronales en el trading: Aprendizaje multitarea basado en el modelo ResNeXt Redes neuronales en el trading: Aprendizaje multitarea basado en el modelo ResNeXt
El marco de aprendizaje multitarea basado en ResNeXt optimiza el análisis de datos financieros considerando su alta dimensionalidad, la no linealidad y las dependencias temporales. El uso de la convolución grupal y cabezas especializadas permite al modelo extraer eficazmente características clave de los datos de origen.
Dominando los registros (Parte 3): Exploración de controladores para guardar registros Dominando los registros (Parte 3): Exploración de controladores para guardar registros
En este artículo, exploraremos el concepto de controladores en la librería de registro, comprenderemos cómo funcionan y crearemos tres implementaciones iniciales: Console, Database y File. Cubriremos todo, desde la estructura básica de los controladores hasta las pruebas prácticas, preparando el terreno para su plena funcionalidad en futuros artículos.