Estratégia de trading "Captura de Liquidez" (Liquidity Grab)
A estratégia de trading captura de liquidez é um componente-chave do Smart Money Concepts (SMC), que visa identificar e aproveitar as ações dos participantes institucionais no mercado. Ela envolve mirar áreas de alta liquidez, como zonas de suporte ou resistência, onde ordens de grande volume podem provocar um movimento de preço antes que o mercado retome sua tendência. Este artigo explica em detalhes o conceito de captura de liquidez e descreve o processo de desenvolvimento de um EA para a estratégia de captura de liquidez em MQL5.
Visão geral da estratégia: Conceitos e táticas-chave
A estratégia de captura de liquidez foca no uso de áreas de alta liquidez, como níveis de suporte e resistência, onde traders institucionais frequentemente manipulam os preços para acionar stop-losses e aumentar a volatilidade. Essa estratégia utiliza a dinâmica geral do mercado para antecipar essas mudanças e lucrar com elas.
Conceitos principais
- Caça a stop-loss: Estimular os preços a acionar ordens de stop-loss, o que desencadeia compras ou vendas em cascata.
- Layering e spoofing: Uso de ordens falsas para enganar traders sobre a direção do mercado.
- Ordens iceberg: Ocultação de operações grandes dividindo-as em partes menores e visíveis.
- Ignição de momentum (Momentum Ignition): Criação de impulso artificial para atrair outros traders antes da reversão.
- Manipulação de níveis de suporte e resistência: Uso de níveis de preço chave para uma reação previsível.
- Pontos de preço psicológicos: Uso de números redondos para influenciar o comportamento.
A manipulação de mercado envolve influenciar deliberadamente o preço ou o volume de um título financeiro para criar condições de negociação enganosas. Embora a maioria dos traders institucionais siga padrões legais e éticos, alguns podem recorrer a manipulações para atingir objetivos estratégicos específicos. A seguir, um breve resumo de por que e como isso ocorre:
Motivação:
- Maximizar o lucro aproveitando as oscilações de preço de curto prazo e arbitragem.
- Ocultar as intenções de trading dos concorrentes.
- Executar operações de grande porte com influência mínima sobre o mercado.
Tática:
- Acionar stop-losses para gerar liquidez ou elevar os preços.
- Gerenciar o fluxo de ordens por meio de ordens iceberg ou spoofing.
- Mirar suporte, resistência ou níveis de preço psicológicos.
Compreender essa dinâmica permite aos traders antecipar o comportamento institucional e integrar esses dados em ferramentas automatizadas para elaborar estratégias mais eficazes.
Buscamos lucrar com oscilações temporárias de preço provocadas pelo acúmulo de stop-losses e pelas ações de grandes participantes de mercado. Ao identificar áreas de liquidez, os traders procuram entrar no mercado em momentos ótimos antes de o preço reverter e retomar a tendência predominante, obtendo oportunidades favoráveis em termos de risco-retorno.
A seguir, um esquema aproximado de como isso deve se parecer.

Desenvolvimento da estratégia
Eu proponho seguir a ordem abaixo ao escrever o código do EA:
- Considerar as funções necessárias para esta estratégia e definir como encapsulá-las em diferentes componentes.
- Codificar cada função por sua vez, declarando as variáveis globais associadas ou inicializando os respectivos handles à medida que avançamos.
- Após implementar cada função, revise-a e pense em como conectá-las, por exemplo, passando parâmetros ou chamando funções a partir de outras funções.
- Por fim, passe para OnTick() e desenvolva a lógica usando as funções descritas nas etapas anteriores.
Primeiro, tentamos quantificar as regras. O SMC é negociado principalmente por traders discricionários, pois há inúmeros nuances que não se prestam à quantificação. É objetivamente difícil definir as características exatas que uma manipulação deve possuir. Uma abordagem é analisar a variação do volume no fluxo de ordens, mas os dados de volume fornecidos pelas corretoras são frequentemente pouco confiáveis. Em mercados como forex, as transações não são centralizadas e, para bolsas centralizadas como as de futuros, a maioria das corretoras fornece dados de seus provedores de liquidez, e não dados centralizados. Um método mais simples e passível de otimização é a análise técnica; é a abordagem que usaremos neste artigo. Para quantificar as regras, e para simplificar, dividiremos a estratégia nos seguintes segmentos:
- O padrão rejection candle formado em um nível-chave, onde o nível-chave é definido como o ponto mais alto ou mais baixo dentro de um período de retrospectiva.
- Após a rejection candle, o preço se inverte e rompe o nível-chave no lado oposto, com um período de retrospectiva mais curto.
- Por fim, se o movimento geral coincidir com a tendência mais ampla, o que é indicado pela posição do preço em relação à média móvel, entramos na operação com stop-loss e take-profit fixos.
Observe que poderíamos adicionar mais regras para imitar melhor as características de manipulação de mercado, mas é desejável que a estratégia permaneça o mais simples possível para evitar overfitting.
Em seguida, codificamos as funções correspondentes. São funções necessárias para executar ordens após o cálculo de take-profit e stop-loss, bem como para rastrear os tickets das ordens.
//+------------------------------------------------------------------+ //| 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(); }
Essas duas funções determinam e retornam o ponto mais alto ou mais baixo dentro de um intervalo especificado, garantindo que esse ponto corresponda a um nível-chave, verificando a presença de suporte ou resistência que provoquem reversão a partir desse nível.
//+------------------------------------------------------------------+ //| 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; }
A função findhigh() assegura a detecção de rejeição em um ponto de máxima, verificando se o ponto mais alto dentro do intervalo especificado ocorre na barra atual (i) e se esse mesmo ponto mais alto também coincide com o ponto máximo dentro de um intervalo maior (duas vezes o período de retrospectiva). Isso indica uma rejeição, pois o preço não conseguiu romper acima após alcançar esse nível. Se o valor for true, o valor máximo é retornado como um nível-chave potencial. A função findlow() segue simplesmente a lógica inversa.
Essas duas funções determinam se a última vela fechada é um indicativo de rejection candle em um nível-chave, o que pode ser interpretado como um comportamento 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; }
Uma vela demonstra um padrão de rejeição quando seu pavio é significativamente maior que seu corpo e a direção do movimento da vela se inverte em relação à anterior.
Usando as duas funções anteriores, elas foram criadas para realizar um loop dentro de um período específico de retrospectiva a fim de detectar qualquer comportamento de captura de liquidez, o que será usado para verificar se tal comportamento ocorreu antes que surja um sinal de reversão e rompimento.
//+------------------------------------------------------------------+ //| 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 obter os dados do valor atual da média móvel, primeiro inicializamos o handle na função 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; }
Em seguida, é possível acessar facilmente o valor da média móvel criando um array de buffer e copiando o valor do handle para o array de buffer da seguinte forma:
double ma[]; if (CopyBuffer(handleMa, 0, 1, 1, ma) <= 0) { Print("Failed to copy MA data. Error: ", GetLastError()); return; }
Por fim, passamos para OnTick() para aplicar a lógica de trading ao programa, utilizando as funções que definimos. Isso garante que o sinal seja calculado apenas quando um novo candle se formar, verificando se o candle atual é diferente do último candle fechado salvo. Isso economiza poder computacional e garante transações mais suaves.
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { int bars = iBars(_Symbol, PERIOD_CURRENT); if (barsTotal != bars) { barsTotal = bars;
Depois, aplicamos simplesmente a condição do sinal da seguinte maneira:
if(WasRejectionDown()&&bid<ma[0]&&bid<findlow(CandlesBeforeBreakout)) executeSell(); else if(WasRejectionUp()&&ask>ma[0]&&ask>findhigh(CandlesBeforeBreakout)) executeBuy();
Após essa etapa, tentamos compilar o programa e abrir o visualizador de backtesting para verificar se o EA está funcionando corretamente.
No visualizador de backtesting, uma execução típica se pareceria com o seguinte:

Recomendações
Embora tenhamos concluído a ideia principal da estratégia, tenho algumas sugestões para implementar este EA no mercado real:
1. As manipulações de mercado ocorrem rapidamente, portanto, o ideal é operar essa estratégia em intraday, usando timeframes como 5 ou 15 minutos. Timeframes mais baixos podem ser mais suscetíveis a falsos sinais, enquanto timeframes mais altos podem reagir de forma muito lenta às manipulações de mercado.
2. As manipulações de mercado geralmente acontecem em períodos de alta volatilidade, como durante as sessões de Nova York e Londres no mercado Forex ou nos horários de abertura e fechamento do mercado de ações. Recomenda-se implementar uma função que limite as operações a essas horas específicas, como mostrado abaixo:
//+------------------------------------------------------------------+ //| 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. Se o preço estiver consolidando ao redor de níveis-chave, isso pode gerar várias operações consecutivas em ambas as direções. Para garantir que apenas uma operação seja executada por vez, adicionamos mais um critério: ambos os tickets das posições devem estar definidos como 0, indicando que não há posições abertas para esse EA. Redefinimos esses valores para 0 escrevendo essas linhas na função OnTick().
if(buypos>0&&(!PositionSelectByTicket(buypos)|| PositionGetInteger(POSITION_MAGIC) != Magic)){ buypos = 0; } if(sellpos>0&&(!PositionSelectByTicket(sellpos)|| PositionGetInteger(POSITION_MAGIC) != Magic)){ sellpos = 0; }
Atualizamos nosso código-fonte para incluir as modificações recém-adicionadas.
//+------------------------------------------------------------------+ //| 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; } } }
Backtesting
Neste artigo, utilizaremos este EA no par GBPUSD em um timeframe de 5 minutos.
A seguir estão as configurações de parâmetros que decidimos usar para este EA:

Observações importantes:
- Para definir o take-profit e o stop-loss, escolhemos um valor razoável em pontos, baseado na volatilidade intradiária. Como essa estratégia essencialmente opera a favor da tendência, é recomendável que a relação risco-retorno seja superior a 1.
- DistanceRange é o período de retrospectiva usado para buscar níveis-chave a fim de identificar sinais de captura de liquidez.
- De forma semelhante, CandlesBeforeBreakout é o período de retrospectiva usado para buscar níveis-chave com o objetivo de identificar sinais de rompimento.
- A proporção entre o pavio e o corpo pode ser ajustada para um valor que o trader considere suficiente para ilustrar o padrão de rejeição.
- O cronograma do dia de trading é baseado no horário do servidor da sua corretora. No caso da minha corretora (GMT+0), o período de maior volatilidade na sessão de Forex de Nova York é das 13:00 às 19:00.
Agora vamos realizar o backtest de 2020.11.1 até 2024.11.1:



A estratégia apresentou um desempenho sólido nos últimos 4 anos.
Conclusão
Neste artigo, apresentamos pela primeira vez o conceito de captura de liquidez e a motivação que o fundamenta. Em seguida, fornecemos um guia passo a passo para a criação de um EA para essa estratégia, partindo do zero. Depois, oferecemos recomendações adicionais para otimizar o EA. Por fim, demonstramos seu potencial de lucratividade ao longo de um período de quatro anos de backtesting, que incluiu mais de 200 operações.
Esperamos que você considere esta estratégia útil e que ela o inspire a aprimorá-la, seja desenvolvendo estratégias semelhantes ou otimizando seus parâmetros. O arquivo correspondente ao EA está anexado abaixo. Sinta-se à vontade para baixá-lo e experimentá-lo.
Traduzido do Inglês pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/en/articles/16518
Aviso: Todos os direitos sobre esses materiais pertencem à MetaQuotes Ltd. É proibida a reimpressão total ou parcial.
Esse artigo foi escrito por um usuário do site e reflete seu ponto de vista pessoal. A MetaQuotes Ltd. não se responsabiliza pela precisão das informações apresentadas nem pelas possíveis consequências decorrentes do uso das soluções, estratégias ou recomendações descritas.
Caminhe em novos trilhos: Personalize indicadores no MQL5
Implementação do algoritmo criptográfico SHA-256 do zero em MQL5
Está chegando o novo MetaTrader 5 e MQL5
A Arte de Registrar Logs (Parte 3): Explorando os handlers para armazenamento de logs
- Aplicativos de negociação gratuitos
- 8 000+ sinais para cópia
- Notícias econômicas para análise dos mercados financeiros
Você concorda com a política do site e com os termos de uso
Obrigado pelo código, o artigo foi muito bem escrito e bem montado, o código é muito útil, obrigado. É interessante ver que os retornos dos traders da SMC nas mídias sociais são muito diferentes. Vou revisar as transações e tentar um trailing stop e um trailing tp ou algum Fibonacci nas faixas externas
Obrigado por seu comentário! Sim, eu vejo os traders da SMC nas mídias sociais. Em geral, acho que eles não concordam muito com a estratégia em termos de captura de liquidez. Alguns procuram duas falsificações em vez de uma, e outros analisam o volume de negociação. Em geral, suas ações envolvem algumas discrepâncias entre si, o que dificulta a avaliação da validade de suas estratégias. No entanto, estou ansioso para ver seus resultados experimentando com trailing sl/tp e intervalos de Fibonacci.