English Deutsch 日本語
preview
Торговая стратегия "Захват ликвидности" (Liquidity Grab)

Торговая стратегия "Захват ликвидности" (Liquidity Grab)

MetaTrader 5Примеры |
954 3
Zhuo Kai Chen
Zhuo Kai Chen

Торговая стратегия захвата ликвидности является ключевым компонентом Концепции умных денег (Smart Money Concepts (SMC), которая направлена на выявление и использование действий институциональных игроков на рынке. Она предполагает нацеливание на области с высокой ликвидностью, такие как зоны поддержки или сопротивления, где крупные ордера могут спровоцировать движение цены до того, как рынок возобновит свой тренд. В настоящей статье подробно объясняется концепция захвата ликвидности и описывается процесс разработки советника по торговой стратегии захвата ликвидности на MQL5.


Обзор стратегии: Ключевые концепции и тактика

Стратегия захвата ликвидности фокусируется на использовании областей с высокой ликвидностью, таких как уровни поддержки и сопротивления, где институциональные трейдеры часто манипулируют ценами, чтобы активировать стоп-лоссы и повысить волатильность. Эта стратегия использует общую динамику рынка, чтобы предвидеть эти изменения и извлечь из них выгоду.

Основные понятия

  1. Поиск стоп-лосса: Стимуляция цен к активации стоп-лосс-ордеров, что вызывает каскадные покупки или продажи.
  2. Лееринг и спуфинг: Использование поддельных ордеров для введения трейдеров в заблуждение относительно направления рынка.
  3. Ордера Iceberg: Сокрытие крупных сделок путем разбивки их на более мелкие, видимые части.
  4. Импульс зажигания (Momentum Ignition): Создание искусственного импульса, чтобы заманить других трейдеров до наступления разворота.
  5. Манипулирование уровнями поддержки и сопротивления: Использование ключевых уровней цен для предсказуемой реакции.
  6. Психологические ценовые точки: Использование круглых чисел для влияния на поведение.

Манипулирование рынком предполагает преднамеренное воздействие на цену или объем ценной бумаги с целью создания вводящих в заблуждение условий торговли. В то время как большинство институциональных трейдеров придерживаются правовых и этических стандартов, некоторые могут прибегать к манипуляциям для достижения конкретных стратегических целей. Вот краткий обзор того, почему и как это происходит:

Мотивация:

  • Максимизировать прибыль за счет краткосрочных колебаний цен и арбитража.
  • Скрывать торговые намерения от конкурентов.
  • Осуществляйте крупные сделки с минимальным влиянием рынка.

Тактика:

  • Запуск стоп-лоссов для создания ликвидности или повышения цен.
  • Управление потоком ордеров с помощью ордеров iceberg или спуфинга.
  • Ориентирование на поддержку, сопротивление или психологические ценовые уровни.

Понимание этой динамики позволяет трейдерам предвидеть поведение институтов и интегрировать эти данные в автоматизированные инструменты для разработки более эффективных стратегий.

Мы хотим извлечь выгоду из временных колебаний цен, вызванных накоплением стоп-лоссов и действиями крупных участников рынка. Определяя области ликвидности, трейдеры стремятся войти в рынок в оптимальные моменты до того, как цена развернется и продолжит преобладающий тренд, предлагая благоприятные возможности с точки зрения соотношения риска и прибыли.

Вот приблизительная схема того, как это должно выглядеть.

example diagram


Разработка стратегии

Я предлагаю придерживаться следующего порядка при написании кода советника:

  1. Рассмотрим необходимые функции для этой стратегии и определим, как инкапсулировать их в различные компоненты. 
  2. Кодируем каждую функцию по очереди, объявляя связанные глобальные переменные или инициализируя соответствующие хэндлы по мере выполнения. 
  3. После реализации каждой функции просмотрите ее и подумайте, как их соединить, например, передавая параметры или вызывая функции из других функций. 
  4. Наконец, перейдём к OnTick()  и разработаем логику, используя функции, описанные в предыдущих шагах.

Во-первых, мы пытаемся дать количественную оценку правилам. SMC в основном торгуется дискреционными трейдерами, поскольку существует множество не поддающихся количественной оценке нюансов. Объективно трудно определить точные характеристики, которыми должна обладать манипуляция. Один из подходов заключается в анализе изменения объема потока ордеров, но данные об объеме, предоставляемые брокерами, часто ненадежны. На таких рынках, как форекс, транзакции не централизованы, а для централизованных бирж, таких как фьючерсные, большинство брокеров предоставляют данные от своих поставщиков ликвидности, а не централизованные данные.  Более простым и оптимизируемым методом является технический анализ; это тот подход, который мы и будем использовать в настоящей статье. Чтобы дать количественную оценку правилам, для упрощения мы разделим стратегию на следующие сегменты:

  1.  Паттерн rejection candle, сформированный на ключевом уровне, где ключевой уровень определяется как самая высокая или самая низкая точка в течение периода ретроспективы.
  2.  После rejection candle цена разворачивается и пробивает ключевой уровень на противоположной стороне с более коротким ретроспективным периодом. 
  3.  Наконец, если общее движение совпадает с более широким трендом, на что указывает положение цены относительно скользящей средней, мы вступаем в сделку с фиксированным стоп-лоссом и тейк-профитом.

Обратите внимание, что мы могли бы добавить больше правил, чтобы лучше имитировать особенности манипулирования рынком, но желательно, чтобы стратегия была как можно более простой, чтобы избежать переобучения.

Далее мы кодируем соответствующие функции.  Это необходимые функции для выполнения ордеров после расчета тейк-профита и стоп-лосса, а также для отслеживания тикетов ордеров.

//+------------------------------------------------------------------+
//| 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();
}

Эти две функции определяют и возвращают самую высокую или самую низкую точку в пределах заданного диапазона, обеспечивая, что эта точка соответствует ключевому уровню, проверяя наличие поддержки или сопротивления, которые вызывают разворот от этого уровня. 

//+------------------------------------------------------------------+
//| 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;
}

Функция findhigh() обеспечивает отклонение в определенной точке максимума, проверяя, возникает ли самый высокий максимум в пределах указанного диапазона на текущем баре (i) и совпадает ли самая высокая точка в большем диапазоне (в два раза превышающем период ретроспективного анализа) с этим баром. Это говорит об отклонении, поскольку цена не смогла пробиться выше после достижения этого уровня. Если значение равно true, возвращается высокое значение в качестве потенциального ключевого уровня. Функция findlow() - это просто обратная логика.

Эти две функции определяют, является ли последняя закрытая свеча индикатором rejection candle на ключевом уровне, что можно рассматривать как поведение захвата ликвидности.

//+------------------------------------------------------------------+
//| 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;
}

Свеча демонстрирует паттерн отклонения, когда ее фитиль значительно больше, чем ее тело, и направление движения свечи меняется на противоположное предыдущему.

Используя последние две функции, они были созданы для зацикливания на определенном периоде ретроспективного анализа, чтобы обнаруживать любое поведение захвата ликвидности, что будет использоваться для проверки того, имело ли место такое поведение, прежде чем появится сигнал разворота и пробоя.

//+------------------------------------------------------------------+
//| 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;
}

Чтобы получить данные для текущего значения скользящей средней, сначала инициализируем хэндл в функции 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;
}

Затем можно легко получить доступ к значению скользящей средней, создав буферный массив и скопировав значение хэндла в буферный массив следующим образом:

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

Наконец, перейдем к OnTick() , чтобы применить торговую логику к программе, используя определенные нами функции.  Это гарантирует, что мы рассчитаем сигнал только при формировании нового бара, проверив, отличается ли текущий бар от последнего сохраненного закрытого бара. Это экономит вычислительные мощности и обеспечивает более плавные транзакции.

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

Затем мы просто применяем условие сигнала следующим образом:

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

После этого шага попробуем скомпилировать программу и перейти к визуализатору бэк-тестирования, чтобы проверить, работает ли советник.

В визуализаторе бэк-тестирования типичная запись выглядела бы следующим образом:

LG example


Предложения

Несмотря на то, что мы закончили с основной идеей стратегии, у меня есть несколько предложений по реализации этого советника на реальном рынке:

1. Манипуляции на рынке происходят быстро, поэтому лучше всего торговать по этой стратегии внутри дня, используя такие таймфреймы, как 5 или 15 минут. Более низкие таймфреймы могут быть более восприимчивы к ложным сигналам, в то время как более высокие таймфреймы могут слишком медленно реагировать на рыночные манипуляции.

2. Рыночные манипуляции обычно происходят в периоды высокой волатильности, такие как Нью-Йоркская/Лондонская сессии Форекс или время открытия/закрытия фондового рынка. Рекомендуется реализовать функцию, ограничивающую торговлю в течение этих конкретных часов, как показано ниже:

//+------------------------------------------------------------------+
//| 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.  Если цена консолидируется вокруг ключевых уровней, это может спровоцировать несколько сделок подряд в обоих направлениях. Чтобы обеспечить, что одновременно будет совершена только одна сделка, добавляем еще один критерий: для обоих тикетов позиций должно быть установлено значение 0, указывающее на отсутствие открытых позиций для данного советника. Мы сбрасываем их на 0, записывая эти строки в функцию OnTick().

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

Мы обновляем наш исходный код, чтобы включить в него только что внесенные изменения.

//+------------------------------------------------------------------+
//| 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;
      }
    }
}


Бэк-тестирование

В настоящей статье мы будем использовать этот советник на паре GBPUSD на 5-минутном таймфрейме.

Вот настройки параметров, которые мы решили использовать для этого советника:

параметры

Важные примечания:

  • Для определения тейк-профита и стоп-лосса мы выбираем обоснованную сумму в пунктах, основанную на внутридневной волатильности. Поскольку эта стратегия, по сути, работает в соответствии с трендом, рекомендуется, чтобы соотношение прибыли и риска было больше 1.
  • DistanceRange - это период ретроспективы для поиска ключевых уровней для получения сигналов о захвате ликвидности.
  • Схожим образом, CandlesBeforeBreakout - это период ретроспективы для поиска ключевых уровней для получения сигналов о пробое.
  • Соотношение фитиля к телу может быть скорректировано до значения, которое трейдер сочтет достаточным для иллюстрации паттерна отклонения.
  • График торгового дня основан на серверном времени вашего брокера. Что касается моего брокера (GMT+0), период волатильной сессии форекс в Нью-Йорке составляет с 13:00 до 19:00.

Давайте теперь проведем бэк-тест с 2020.11.1 по 2024.11.1:

установка

curve

результат

Стратегия показала достойный результат за последние 4 года.


Заключение

В настоящей статье мы впервые представили концепцию захвата ликвидности и лежащая в ее основе мотивациая. Затем мы предоставили пошаговое руководство по созданию советника (EA) для этой стратегии с нуля. Далее мы предложили дополнительные рекомендации по оптимизации советника. Наконец, мы продемонстрировали его потенциальную прибыльность в ходе четырехлетнего бэк-тестирования, включавшего более 200 сделок.

Надеемся, что вы найдете эту стратегию полезной и что она вдохновит вас на ее развитие, будь то путем создания аналогичных стратегий или оптимизации ее настроек. Соответствующий файл для советника прикреплен ниже. Не стесняйтесь скачивать его и экспериментировать с ним.

Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/16518

Прикрепленные файлы |
Последние комментарии | Перейти к обсуждению на форуме трейдеров (3)
linfo2
linfo2 | 28 янв. 2025 в 18:27
Спасибо за код, очень хорошо написанная статья и хорошо собраны, код очень полезно спасибо. Интересно, если посмотреть на трейдеров SMC в социальных сетях, то доходность очень отличается. Буду пересматривать сделки и пробовать трейлинг стоп и трейлинг тп или фибоначчи на внешних диапазонах
Zhuo Kai Chen
Zhuo Kai Chen | 29 янв. 2025 в 02:14
linfo2 #:
Спасибо за код, очень хорошо написанная статья и хорошо собраны, код очень полезно спасибо. Интересно, если посмотреть на трейдеров SMC в социальных сетях, то доходность очень отличается. Буду пересматривать сделки и пробовать трейлинг стоп и трейлинг тп или фибоначчи на внешних диапазонах

Спасибо за ваш комментарий! Да, я вижу SMC-трейдеров в социальных сетях. В целом, я думаю, что они не совсем согласны со стратегией в плане захвата ликвидности. Кто-то ищет два фейкаута вместо одного, а кто-то смотрит на объем торгов. В общем, их действия предполагают некоторую свободу действий, что затрудняет оценку правильности их стратегий. Тем не менее, я с нетерпением жду ваших результатов, экспериментируя с трейлингом sl/tp и диапазонами Фибоначчи.

rapidace1
rapidace1 | 2 мар. 2025 в 13:07
Эта концепция кажется мне свежей, спасибо, что поделились.
Нейросети в трейдинге: Адаптивная периодическая сегментация (Окончание) Нейросети в трейдинге: Адаптивная периодическая сегментация (Окончание)
Предлагаем погрузиться в захватывающий мир LightGTS — лёгкого, но мощного фреймворка для прогноза временных рядов, где адаптивная свёртка и RoPE‑кодирование сочетаются с инновационным методами внимания. В нашей статье вы найдёте детальное описание всех компонентов — от создания патчей до сложной смеси экспертов в декодере, готовых к интеграции в MQL5‑проекты. Откройте для себя, как LightGTS выводит автоматическую торговлю на новый уровень!
Алгоритм обратного поиска — Backtracking Search Algorithm (BSA) Алгоритм обратного поиска — Backtracking Search Algorithm (BSA)
Что если алгоритм оптимизации мог бы помнить свои прошлые путешествия и использовать эту память для поиска лучших решений? BSA делает именно это — балансируя между исследованием нового и возвращением к проверенному. В статье раскрываем секреты алгоритма. Простая идея, минимум параметров и стабильный результат.
Искусство ведения логов (Часть 1): Основные понятия и первые шаги в MQL5 Искусство ведения логов (Часть 1): Основные понятия и первые шаги в MQL5
Добро пожаловать в новое приключение! Данная статья открывает специальный цикл, в котором мы будем пошагово создавать библиотеку для манипуляций с журналами, предназначенную для тех, кто занимается разработкой на языке MQL5.
Строим и оптимизируем торговую систему, основанную на объемах торгов (Chaikin Money Flow - CMF) Строим и оптимизируем торговую систему, основанную на объемах торгов (Chaikin Money Flow - CMF)
В настоящей статье мы представим основанный на объемах индикатор денежного потока Чайкина (Chaikin Money Flow, CMF) после того, как узнаем, как его можно построить, рассчитать и использовать. Разберемся как создать пользовательский индикатор. Проанализируем несколько простых стратегий, которые можно использовать и протестируем их, чтобы понять, какая стратегия лучше.