English
preview
Разработка индикатора снятия ликвидности  STF Liquidity Sweep на языке MQL5

Разработка индикатора снятия ликвидности STF Liquidity Sweep на языке MQL5

MetaTrader 5 — Примеры |
70 3
Chukwubuikem Okeke
Chukwubuikem Okeke

Введение

Я до сих пор помню, как пристально вглядывался в ценовые графики, снова и снова задавая себе вопросы, с которыми рано или поздно сталкивается каждый трейдер: почему цена часто выбивает стоп-лоссы, прежде чем двинуться в ожидаемом направлении? Что на самом деле двигает рынок? Почему при бычьих движениях цена часто сначала снижается, а затем резко растёт?

Чем внимательнее я наблюдал за поведением рынка, тем яснее становилось, что ценовые колебания не были полностью случайными. За многими из этих, казалось бы, обманчивых ходов стояла одна и та же сущность — ликвидность — понятие, которое находит широкий отклик как у современных трейдеров, так и у разработчиков MQL5. Это объясняло, почему цена часто пробивает максимумы или минимумы, прежде чем развернуться с сильным импульсом. То, что поначалу казалось случайной манипуляцией рынком, постепенно проявило себя как структурированный процесс сбора ликвидности, осуществляемый институциональными участниками рынка.

Однако одно дело — понять эту концепцию как трейдер, а совсем другое — реализовать ее на языке MQL5. Как и многие разработчики, я обнаружил, что большинство индикаторов снятия ликвидности используют либо простую логику, основанную на свечах с тенями, либо фиксированное временное окно для выявления таких колебаний. Эти подходы могут оказаться эффективными при определённых рыночных условиях. Однако они часто пропускают ситуации со сбором ликвидности, которые возникают в ходе длительных или нестандартных ценовых формаций. В результате многие такие ситуации либо выявляются слишком поздно, либо остаются незамеченными вовсе.

В статье рассматривается данная проблема через создание на MQL5 индикатора Dynamic Single-Timeframe (STF) Liquidity Sweep, способного выявлять снятия ликвидности независимо от того, сколько времени занимает формирование сигнала. Индикатор не ограничивается анализом статичных теней свечей, а использует следующие элементы:

  • Динамическое обнаружение снятия ликвидности
  • Проверка соотношения теней свечи
  • Обнаружение снятия ликвидности двумя свечами (паттерн поглощения)
  • Правило проверки: не более одного снятия ликвидности на каждом уровне
  • Признание снятия ликвидности недействительным после нарушения

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


Понимание концепции

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

В рамках концепции Smart Money Concepts (SMC) принято считать, что ликвидность движет рынком. Идея основана на убеждении, что цена постоянно стремится к зонам, где наблюдается значительная концентрация ордеров. Поскольку институциональным участникам требуются значительные ордера противоположного направления для эффективного открытия или закрытия позиций без создания чрезмерного ценового дисбаланса, рынки зачастую ориентируются на эти источники ликвидности.


Виды ликвидности в концепции умных денег

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

Ликвидность, как правило, подразделяется на две основные формы:

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

Ликвидность со стороны покупателей

Рис. 1. Ликвидность со стороны покупателей

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

Ликвидность со стороны продавцов

Рис. 2. Ликвидность со стороны продавцов

Цена часто движется к этим зонам, прежде чем сформировать основное направление движения — это явление широко известно как снятие ликвидности.

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

  • Wick Sweep (тень свечи): возникает, когда цена на одной свече резко пробивает ключевую зону ликвидности вверх или вниз, что на короткое время приводит к срабатыванию пассивных стоп-лоссов и ордеров на прорыв, после чего цена быстро возвращается в пределы диапазона. Этот тип снятия ликвидности характеризуется длинными тенями свечей, или хвостами отторжения, указывающими на мгновенное поглощение ликвидности и сильное отторжение ценовых уровней в зоне снятия ликвидности.

Снятие ликвидности тенью свечи

Рис. 3. Снятие ликвидности тенью свечи

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

Снятие ликвидности двумя свечами

Рис. 4. Снятие ликвидности двумя свечами


Реализация на MQL5

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

Директивы компилятора

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

//+------------------------------------------------------------------+
//|                               Liquidity Sweep Sentinel (STF).mq5 |
//|                                             © 2026, ChukwuBuikem |
//|                             https://www.mql5.com/en/users/bikeen |
//+------------------------------------------------------------------+
#property copyright "© 2026, ChukwuBuikem"
#property link      "https://www.mql5.com/en/users/bikeen"
#property version   "1.00"
#property indicator_chart_window
#property  indicator_plots 0

#include <ChartObjects\ChartObjectsTxtControls.mqh>
#include <ChartObjects\ChartObjectsLines.mqh>

#define PROG_NAME "Liquidity Sweep Sentinel (STF)"
#define TRENDLINE PROG_NAME + "_Trendline"
#define TEXT PROG_NAME + "_Text"
#define CHART_ID ChartID()
#define MIN_WICK_RATIO 45

Пользовательская структура данных

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

//--- Custom data structure
struct st_LiquiditySweep
  {
   //---+
   double            swingPrice;  //Swing point price
   datetime          swingTime;   //Swing point timestamp
   bool              swept;       //Swing point sweep flag
   // constructor
                     st_LiquiditySweep(): swingPrice(EMPTY_VALUE),
                     swingTime(LONG_MIN), swept(false) {}
  };

Пояснение:

Структура содержит основные свойства уровня-кандидата для снятия ликвидности.
  • swingPrice: Содержит значение цены выявленного локального экстремума.
  • swingTime: Записывает временную метку, в момент которой сформировался локальный экстремум.
  • swept: логический флаг, используемый для отслеживания того, была ли уже снята ликвидность на анализируемом уровне.
  • Конструктор: инициализирует все члены структуры безопасными значениями по умолчанию, чтобы предотвратить неопределённое поведение во время выполнения.

Настройки индикатора

Следующие входные параметры определяют настраиваемое поведение и внешний вид индикатора. Сделав эти значения входными переменными, трейдеры могут динамически настраивать чувствительность обнаружения колебаний и графическое отображение зон ликвидности прямо с панели настроек индикатора.
//--- Indicator settings
input int rightLeftBars = 3;                //Pivot strength (bars on each side)
input color buyLiquidityColor = clrRed;     //Color for Buy-side liquidity
input color sellLiquidityColor = clrPurple; //Color for Sell-side liquidity

Пояснение:

  • rightLeftBars: Определяет силу разворота, устанавливая количество соседних баров, необходимых как слева, так и справа от свечи для подтверждения локального максимума или минимума.
  • buyLiquidityColor и sellLiquidityColor: Определяют цвета, используемые для визуального отображения на графике зон ликвидности со стороны покупателей и со стороны продавцов, соответственно.

Глобальные переменные состояния

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

//--- Global state variables
CChartObjectText text;
CChartObjectTrend trendLine;
int barIndex = -1;
st_LiquiditySweep buySideLiquidity, sellSideLiquidity;
st_LiquiditySweep sweeps[], violates[];

Пояснение:

  • text: Экземпляр класса CChartObjectText, используемый для создания текстовых объектов, предназначенных для отображения примечаний, связанных с ликвидностью, непосредственно на графике.
  • trendLine: экземпляр класса CChartObjectTrend из стандартной библиотеки, используемый для визуального обозначения уровней снятия ликвидности.
  • barIndex: Хранит индекс текущего анализируемого бара в обработчике события OnCalculate.
  • buySideLiquidity и sellSideLiquidity: экземпляры структуры st_LiquiditySweep, используемые для хранения и управления текущими уровнями ликвидности на стороне покупателей и на стороне продавцов.
  • sweeps[] и violates[]: динамические массивы, представляющие экземпляры структуры st_LiquiditySweep, которые используются для хранения ранее снятых уровней и ранее нарушенных точек колебания, соответственно. Эти массивы обеспечивают соблюдение правила: одно снятие ликвидности на уровень, предотвращая повторное обнаружение колебаний на одном и том же уровне ликвидности, предотвращая повторное обнаружение снятия ликвидности на одном уровне и ложные сигналы после его пробоя.

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

Вспомогательные функции

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

  • Обнаружение новой свечи

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

//+------------------------------------------------------------------+
//|                  New candle detection                            |
//+------------------------------------------------------------------+
bool isNewCandle(const datetime newOpenTime)
  {
//---
   static datetime lastOpenTime = LONG_MIN;
   if(lastOpenTime == LONG_MIN)
     {
      lastOpenTime = newOpenTime;
      return false;
     }
   if(lastOpenTime != newOpenTime)
     {
      lastOpenTime = newOpenTime;
      return true;
     }
   return false;
  }
  • Анализ локальных экстремумов
Следующие вспомогательные функции реализуют механизм обнаружения локальных экстремумов на основе правил с использованием модели сравнения баров слева и справа. Свеча считается локальным экстремумом только в том случае, если её максимум выше максимумов соседних свечей или её минимум ниже их минимумов в пределах настраиваемого диапазона баров слева и справа (rightLeftBars). Затем с помощью дополнительных вспомогательных функций определяется следующий достоверный локальный максимум или минимум после заданной начальной точки.
//+------------------------------------------------------------------+
//|                Swing high detection                              |
//+------------------------------------------------------------------+
bool isSwingHigh(const int index, const MqlRates &rates[])
  {
//---
   int totalBars = ArraySize(rates);
//--- Index boundary validation
   if(index < rightLeftBars)
      return false;
   if(index >= totalBars - rightLeftBars)
      return false;

   for(int w = 1; w <= rightLeftBars && (index - w) >= 1; w++)
     {
      //--- Look right
      if(rates[index].high < rates[index - w].high)
         return false;
      //--- Look left
      if(rates[index].high < rates[index + w].high)
         return false;
     }
   return true;
  }
//+------------------------------------------------------------------+
//|                 Swing low detection                              |
//+------------------------------------------------------------------+
bool isSwingLow(const int index, const MqlRates &rates[])
  {
//---
   int totalBars = ArraySize(rates);
//--- Index boundary validation
   if(index < rightLeftBars)
      return false;
   if(index >= totalBars - rightLeftBars)
      return false;

   for(int w = 1; w <= rightLeftBars && (index - w) >= 1; w++)
     {
      if(index - w < 1)
         return false;
      //--- Look right
      if(rates[index].low > rates[index - w].low)
         return false;
      //--- Look left
      if(rates[index].low > rates[index + w].low)
         return false;
     }
   return true;
  }
//+------------------------------------------------------------------+
//|            Obtain the index of next swing high                   |
//+------------------------------------------------------------------+
int getNextSwingHighIndex(const int startIndex, const double minPrice)
  {
//---
   MqlRates rates[] = {};
   ArraySetAsSeries(rates, true);
   int bars = Bars(_Symbol, PERIOD_CURRENT);
   if(CopyRates(_Symbol, PERIOD_CURRENT, 0, bars, rates) < bars)
      return -1;

   for(int w = startIndex + 1; w <= bars - (rightLeftBars + 1); w++)
     {
      if(isSwingHigh(w, rates))
         //--- High must be higher than minimum price
         if(rates[w].high > minPrice)
            return w;
     }
   return -1;// Invalid index
  }
//+------------------------------------------------------------------+
//|            Obtain the index of next swing low                    |
//+------------------------------------------------------------------+
int getNextSwingLowIndex(const int startIndex, const double maxPrice)
  {
//---
   MqlRates rates[] = {};
   ArraySetAsSeries(rates, true);
   int bars = Bars(_Symbol, PERIOD_CURRENT);

   if(CopyRates(_Symbol, PERIOD_CURRENT, 0, bars, rates) < bars)
      return -1;

   for(int w = startIndex + 1; w <= bars - (rightLeftBars + 1); w++)
     {
      if(isSwingLow(w, rates))
         //--- Low must be lower than Maximum price
         if(rates[w].low < maxPrice)
            return w;
     }
   return -1;// Invalid index
  }

Пояснение:

  • isSwingHigh(): Определяет, является ли свеча с указанным индексом локальным максимумом в рамках колебания.
  • isSwingLow(): Определяет, является ли свеча с указанным индексом локальным минимумом в рамках колебания.
  • getNextSwingHighIndex(): Выполняет поиск следующего допустимого локального максимума колебания после указанного начального индекса. Учитываются только максимумы колебаний, цена которых превышает заданный минимальный порог.
  • getNextSwingHighIndex(): Выполняет поиск следующего допустимого локального минимума колебания после указанного начального индекса. Эта функция повторяет логику определения локальных максимумов, но ориентирована на медвежье движение структуры рынка и выявляет последующие локальные минимумы. Действительными считаются только минимумы, расположенные ниже указанного порогового значения.
В совокупности эти функции образуют динамическую систему поиска свинг-уровней, которая выявляет как исторические, так и вновь сформировавшиеся максимумы и минимумы. Благодаря локальной проверке свечей и сканированию индекса вперед система может эффективно выявлять структурные точки разворота, адаптируясь при этом к постоянно меняющемуся ценовому движению.
  • Обнаружение паттерна поглощения

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

//+------------------------------------------------------------------+
//|              Bullish engulfing detection                         |
//+------------------------------------------------------------------+
bool isBullishEngulfing(const double currOpen, const double currClose,
                        const double prevOpen, const double prevClose)
  {
//---
   return (prevClose < prevOpen && currClose > currOpen
           && currOpen <= prevClose && currClose > prevOpen);
  }
//+------------------------------------------------------------------+
//|              Bearish engulfing detection                         |
//+------------------------------------------------------------------+
bool isBearishEngulfing(const double currOpen, const double currClose,
                        const double prevOpen, const double prevClose)
  {
//---
   return (prevClose > prevOpen && currClose < currOpen &&
           currOpen >= prevClose && currClose < prevOpen);
  }

  • Проверка соотношения теней свечи

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

//+------------------------------------------------------------------+
//|             Upper wick ratio validation                          |
//+------------------------------------------------------------------+
int upperWickRatio(const double open, const double high,
                   const double low, const double close)
  {
//---
   double candleRange = high - low;
   double ratio = (high - MathMax(open, close)) / candleRange;
   ratio = NormalizeDouble(ratio, 2);

   return (int)(ratio * 100);
  }
//+------------------------------------------------------------------+
//|                Lower wick ratio validation                       |
//+------------------------------------------------------------------+
int lowerWickRatio(const double open, const double high,
                   const double low, const double close)
  {
//---
   double candleRange = high - low;
   double ratio = (MathMin(open, close) - low) / candleRange;
   ratio = NormalizeDouble(ratio, 2);

   return (int)(ratio * 100);
  }
  • Управление массивами

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

//+------------------------------------------------------------------+
//|                     Storage helper                               |
//+------------------------------------------------------------------+
bool append(const st_LiquiditySweep &value, st_LiquiditySweep &destArr[])
  {
//---
   if(ArrayResize(destArr, ArraySize(destArr) + 1) == -1)
      return false;

   destArr[ArraySize(destArr) - 1] = value;

   return true;
  }
//+------------------------------------------------------------------+
//|                        Search helper                             |
//+------------------------------------------------------------------+
bool isExist(const datetime swingTime, const st_LiquiditySweep &searchArr[])
  {
//---
   for(int w = ArraySize(searchArr) - 1; w >= 0; w--)
     {
      if(searchArr[w].swingTime == swingTime)
        {
         return true;
        }
     }
   return false;
  }

Пояснение:

  • append(): Безопасно добавляет новый экземпляр st_LiquiditySweep в динамический массив.
  • isExist(): Эта функция проверяет, существует ли указанная временная метка в массиве хранимых экземпляров ликвидности.

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

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

//+------------------------------------------------------------------+
//|                   Trendline creation                             |
//+------------------------------------------------------------------+
void drawTrendline(const string objName, const datetime time1,
                   const double price1, const datetime time2,
                   const double price2, const color clr,
                   const int width, const string tooltip)
  {
//---
   if(trendLine.Create(CHART_ID, objName, 0, time1, price1, time2, price2))
     {
      trendLine.Color(clr);
      trendLine.Width(width);
      trendLine.Tooltip(tooltip);
      trendLine.SetInteger(OBJPROP_HIDDEN, true);
     }
  }
//+------------------------------------------------------------------+
//|                     Text creation                                |
//+------------------------------------------------------------------+
void createText(const string objName, const datetime time,
                const double price, const color clr, const string display,
                const int fontSize = 10, const string font = "Arial")
  {
//---
   if(text.Create(CHART_ID, objName, 0, time, price))
     {
      text.Color(clr);
      text.Font(font);
      text.FontSize(fontSize);
      text.Tooltip(display);
      text.SetString(OBJPROP_TEXT, display);
      text.SetInteger(OBJPROP_HIDDEN, true);
     }
  }

Инициализация и очистка

На этапе инициализации устанавливается базовая структура рынка, необходимая для обнаружения снятия ликвидности. Во время выполнения функции OnInit() индикатор проводит анализ исторических данных с целью выявления начальных максимумов и минимумов колебаний, которые, соответственно, выше или ниже значений недавних закрытых свечей. Кроме того, логика очистки в функции OnDeinit() обеспечивает правильное удаление всех графических объектов, связанных с индикатором, для поддержания целостности графика.

//+------------------------------------------------------------------+
//|                   Initialization function                        |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   MqlRates rates[];
   ArraySetAsSeries(rates, true);
   if(CopyRates(_Symbol, PERIOD_CURRENT, 0, iBars(_Symbol, PERIOD_CURRENT), rates) > 0)
     {
      if((barIndex = getNextSwingHighIndex(rightLeftBars, iHigh(_Symbol, PERIOD_CURRENT, 1))) != -1)
        {
         buySideLiquidity.swept = false;
         buySideLiquidity.swingPrice = rates[barIndex].high;
         buySideLiquidity.swingTime = rates[barIndex].time;
        }
      if((barIndex = getNextSwingLowIndex(rightLeftBars, iLow(_Symbol, PERIOD_CURRENT, 1))) != -1)
        {
         sellSideLiquidity.swept = false;
         sellSideLiquidity.swingPrice = rates[barIndex].low;
         sellSideLiquidity.swingTime = rates[barIndex].time;
        }
     }
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//|                   Deinitialization function                      |
//+------------------------------------------------------------------+
void OnDeinit(const int32_t reason)
  {
//--- Clear chart
   ObjectsDeleteAll(CHART_ID, PROG_NAME);
   ChartRedraw(CHART_ID);
  }

Основной механизм (OnCalculate)

Функция OnCalculate() выступает в качестве основного механизма выполнения индикатора, обеспечивая анализ ликвидности в режиме реального времени, обнаружение снятия ликвидности и обновление структуры рынка. Чтобы сократить вычислительную нагрузку, логика запускается только при формировании новой свечи. Это обеспечивает значительное снижение нагрузки во время выполнения при одновременном сохранении точности отслеживания структуры рынка.
//+------------------------------------------------------------------+
//|               Core Engine (OnCalculate)                          |
//+------------------------------------------------------------------+
int OnCalculate(const int32_t rates_total,
                const int32_t prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int32_t &spread[])
  {
//---
   if(isNewCandle(time[rates_total - 1]))
     {
      ArraySetAsSeries(open, true);
      ArraySetAsSeries(high, true);
      ArraySetAsSeries(low, true);
      ArraySetAsSeries(close, true);
      ArraySetAsSeries(time, true);
      //--- SWEEP DETECTION
      //--- Buy-side liquidity sweeps: Wick and Dual-candle sweep
      if((!buySideLiquidity.swept &&  high[1] >= buySideLiquidity.swingPrice &&
          close[1] < buySideLiquidity.swingPrice && open[1] < buySideLiquidity.swingPrice &&
          upperWickRatio(open[1], high[1], low[1], close[1]) >= MIN_WICK_RATIO) ||
         (!buySideLiquidity.swept &&  close[2] > buySideLiquidity.swingPrice &&
          close[1] < buySideLiquidity.swingPrice && isBearishEngulfing(open[1], close[1], open[2], close[2])))
        {
         if(!isExist(buySideLiquidity.swingTime, sweeps) && !isExist(buySideLiquidity.swingTime, violates))
           {
            buySideLiquidity.swept = true;
            //--- Append to sweep array
            if(!append(buySideLiquidity, sweeps))
              {
               Print("[RUNTIME]: Failed to append Buy-side LQ to sweep array.");
              }
            //--- Mark level with trendline and text
            drawTrendline(TRENDLINE + "Buy-side", buySideLiquidity.swingTime, buySideLiquidity.swingPrice, time[1],
                          buySideLiquidity.swingPrice, buyLiquidityColor, 2, "Buy-side LQ");
            createText(TEXT + "Buy-side", buySideLiquidity.swingTime + PeriodSeconds(),
                       buySideLiquidity.swingPrice + (20 * _Point), buyLiquidityColor, "Buy-side LQ");
            ChartRedraw(CHART_ID);
           }
         //--- Scan for older swing high (next liquidity target)
         if((barIndex = getNextSwingHighIndex(iBarShift(_Symbol, PERIOD_CURRENT, buySideLiquidity.swingTime),
                                              buySideLiquidity.swingPrice)) != -1)
           {
            buySideLiquidity.swingPrice = high[barIndex];
            buySideLiquidity.swingTime = time[barIndex];
            buySideLiquidity.swept = false;
           }
         return rates_total;
        }
      //--- Sell-side liquidity sweeps: Wick and Dual-candle sweep
      if((!sellSideLiquidity.swept && low[1] <= sellSideLiquidity.swingPrice &&
          close[1] > sellSideLiquidity.swingPrice && open[1] > sellSideLiquidity.swingPrice &&
          lowerWickRatio(open[1], high[1], low[1], close[1]) >= MIN_WICK_RATIO) ||
         (!sellSideLiquidity.swept && close[2] < sellSideLiquidity.swingPrice &&
          close[1] > sellSideLiquidity.swingPrice && isBullishEngulfing(open[1], close[1], open[2], close[2])))
        {
         if(!isExist(sellSideLiquidity.swingTime, sweeps) && !isExist(sellSideLiquidity.swingTime, violates))
           {
            sellSideLiquidity.swept = true;
            //--- Append to sweep array
            if(!append(sellSideLiquidity, sweeps))
              {
               Print("[RUNTIME]: Failed to append Sell-side LQ to sweep array.");
              }
            //--- Mark level with trendline and text
            drawTrendline(TRENDLINE + "Sell-Side", sellSideLiquidity.swingTime, sellSideLiquidity.swingPrice, time[1],
                          sellSideLiquidity.swingPrice, sellLiquidityColor, 2, "Sell-side LQ");
            createText(TEXT + "Sell-Side", sellSideLiquidity.swingTime + PeriodSeconds(),
                       sellSideLiquidity.swingPrice - (10 * _Point), sellLiquidityColor, "Sell-side LQ");
            ChartRedraw(CHART_ID);
           }
         //--- Scan for older swing low (next liquidity target)
         if((barIndex = getNextSwingLowIndex(iBarShift(_Symbol, PERIOD_CURRENT, sellSideLiquidity.swingTime),
                                             sellSideLiquidity.swingPrice)) != -1)
           {
            sellSideLiquidity.swingPrice = low[barIndex];
            sellSideLiquidity.swingTime = time[barIndex];
            sellSideLiquidity.swept = false;
           }
         return rates_total;
        }
      //--- SWEEP VIOLATION
      //--- Buy-side liquidity violation
      if((!buySideLiquidity.swept && close[2] > buySideLiquidity.swingPrice && close[1] > buySideLiquidity.swingPrice) ||
         (!buySideLiquidity.swept &&  close[2] > buySideLiquidity.swingPrice && close[1] < buySideLiquidity.swingPrice &&
          !isBearishEngulfing(open[1], close[1], open[2], close[2])) ||
         (!buySideLiquidity.swept && close[1] > buySideLiquidity.swingPrice))
        {
         buySideLiquidity.swept = true;
         //--- Append to violate array
         if(!append(buySideLiquidity, violates))
           {
            Print("[RUNTIME]: Failed to append Buy-side LQ to violate array.");
           }
         //--- Scan for older swing high (next liquidity target)
         if((barIndex = getNextSwingHighIndex(iBarShift(_Symbol, PERIOD_CURRENT, buySideLiquidity.swingTime),
                                              buySideLiquidity.swingPrice)) != -1)
           {
            //--- Update buy side liquidity structure
            buySideLiquidity.swingPrice = high[barIndex];
            buySideLiquidity.swingTime = time[barIndex];
            buySideLiquidity.swept = false;
           }
         return rates_total;
        }
      //--- Sell-side liquidity violation
      if((!sellSideLiquidity.swept && close[2] < sellSideLiquidity.swingPrice && close[1] < sellSideLiquidity.swingPrice) ||
         (!sellSideLiquidity.swept &&  close[2] < sellSideLiquidity.swingPrice && close[1] > buySideLiquidity.swingPrice &&
          !isBullishEngulfing(open[1], close[1], open[2], close[2])) ||
         (!sellSideLiquidity.swept && close[1] < sellSideLiquidity.swingPrice))
        {
         sellSideLiquidity.swept = true;
         //--- Append to violate array
         if(!append(sellSideLiquidity, violates))
           {
            Print("[RUNTIME]: Failed to append Sell-side LQ to violate array.");
           }
         //--- Scan for older swing low (next liquidity target)
         if((barIndex = getNextSwingLowIndex(iBarShift(_Symbol, PERIOD_CURRENT, sellSideLiquidity.swingTime)
                                             , sellSideLiquidity.swingPrice)) != -1)
           {
            //--- Update sell side liquidity structure
            sellSideLiquidity.swingPrice = low[barIndex];
            sellSideLiquidity.swingTime = time[barIndex];
            sellSideLiquidity.swept = false;
           }
         return rates_total;
        }
      //--- NEWER SWING POINT SEARCH
      //--- Scan for swing highs closer to market price
      if((barIndex = getNextSwingHighIndex(rightLeftBars, iHigh(_Symbol, PERIOD_CURRENT, 1)))
         != -1 && time[barIndex] > buySideLiquidity.swingTime)
        {
         //--- Update buy side liquidity structure
         buySideLiquidity.swingPrice = high[barIndex];
         buySideLiquidity.swingTime = time[barIndex];
         buySideLiquidity.swept = false;
         return rates_total;
        }
      //--- Scan for swing lows closer to market price
      if((barIndex = getNextSwingLowIndex(rightLeftBars, iLow(_Symbol, PERIOD_CURRENT, 1)))
         != -1 && time[barIndex] > sellSideLiquidity.swingTime)
        {
         //--- Update sell side liquidity structure
         sellSideLiquidity.swingPrice = low[barIndex];
         sellSideLiquidity.swingTime = time[barIndex];
         sellSideLiquidity.swept = false;
         return rates_total;
        }
     }
   return(rates_total);
  }

Пояснение:

При обнаружении новой свечи основной рабочий процесс выполняется в следующем порядке:

  • Динамическое обнаружение снятия ликвидности
Логика обнаружения снятия ликвидности выявляет корректные случаи снятия ликвидности на стороне покупателей и продавцов, используя как модель отторжения уровня тенью одной свечи, так и модель подтверждения поглощением из двух свечей. После подтверждения снятия ликвидности система последовательно выполняет следующие действия:
  1. Проверяет, не присутствует ли данный уровень в массивах sweeps или violates.
  2. Устанавливает флаг состояния обнаружения в значение true.
  3. Сохраняет событие снятия ликвидности в массиве sweeps.
  4. Отображает линию тренда и текстовые аннотации на графике.
  5. Определяет и назначает следующий значимый локальный экстремум в качестве новой цели ликвидности.

  • Недействительность уровня ликвидности после пробоя

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

  1. Помечает текущий уровень ликвидности как обработанный (swept = true).
  2. Сохраняет уровень в массиве violates, чтобы предотвратить повторную обработку.
  3. Переходит к следующей допустимой структуре.

  • Поиск новых точек разворота

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

  1. Обеспечивает соответствие системы мониторинга ликвидности меняющейся структуре рынка.
  2. Динамически обновляет buySideLiquidity и sellSideLiquidity.
  3. Сбрасывает значение параметра swept = false, чтобы вновь включить обнаружение будущих событий на новом уровне.


Тестирование

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

Система мониторинга снятия ликвидности (STF)

Рис. 5. Индикатор Liquidity Sweep Sentinel (STF)


Заключение:

На этом мы завершаем рассмотрение концепции индикатора Liquidity Sweep: это динамический индикатор с проверяемой логикой Liquidity Sweep Sentinel, разработанный для MetaTrader 5. Система преобразует концепции Smart Money в структурированную модель, основанную на наборе правил, способную в режиме реального времени выявлять и отслеживать поведение рынка, определяемое ликвидностью.

Индикатор позволяет решить следующие задачи:

  • Выявляет как снятие ликвидности тенью одной свечи, так и формации снятия ликвидности двумя свечами независимо от продолжительности их формирования.
  • Динамически отслеживает уровни ликвидности со стороны покупателей и продавцов с использованием логики рыночной структуры, основанной на ценовых колебаниях.
  • Обеспечивает историческую целостность за счет структурированной обработки памяти, предотвращает повторное обнаружение снятия ликвидности с помощью специальных массивов для учёта снятых и пробитых уровней.
  • Отфильтровывает слабые ценовые сигналы с помощью проверки соотношения теней свечей, чтобы гарантировать, чтобы только выраженные отскоки классифицировались как подтверждённые снятия ликвидности.
  • Постоянно адаптируется к меняющейся структуре рынка за счет обновления точек разворота в режиме реального времени, обеспечивая актуальность показателей ликвидности.
В совокупности эта концептуальная основа создает структурированную и расширяемую базу для анализа ликвидности в MQL5, соединяя теоретические концепции умных денег с практической алгоритмической реализацией.

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

Прикрепленные файлы |
Последние комментарии | Перейти к обсуждению на форуме трейдеров (3)
Aarthy Rajanbabu
Aarthy Rajanbabu | 31 мая 2026 в 05:01

Строки не отображаются. Проверьте, пожалуйста, код. Спасибо.

Nazmul Hasan
Nazmul Hasan | 12 июл. 2026 в 08:56
Возможно, нужно что-то изменить. На графике ничего не отображается.
Chukwubuikem Okeke
Chukwubuikem Okeke | 13 июл. 2026 в 09:19

Добрый день

Индикатор разработан таким образом, что начинает работу только при формировании новой свечи. Это сделано намеренно и помогает избежать ненужной нагрузки на систему. После добавления индикатора на график он просто ожидает формирования новой свечи — после чего индикатор начнет проверку на наличие скачков ликвидности. Приведенный ниже GIF-файл демонстрирует это поведение и показывает тестирование индикатора в Тестере стратегий MT5.



LIQUIDITY SWEEP SENTINEL — ТЕСТ

LIQUIDITY SWEEP SENTINEL — ТЕСТ

Метаразметка классических стратегий (Часть 1): Фильтрация сделок по RSI и определение размера позиций Метаразметка классических стратегий (Часть 1): Фильтрация сделок по RSI и определение размера позиций
В условиях тренда RSI накапливает убытки, поскольку генерирует сигнал при каждом пересечении порога независимо от рыночного режима. Вторичный классификатор на основе случайного леса, обученный на 12 контекстных признаках — наклоне импульса RSI, скорости тренда EMA50, нормированном по ATR отклонении цены от трендовой средней и девяти других, — фильтрует исходные сигналы и масштабирует размер позиции в зависимости от уверенности классификатора на данных EURUSD H1. В результатах сравниваются исходный RSI, RSI с метафильтрацией и RSI с адаптивным размером позиции за 16-месячный период вне обучающей выборки с анализом показателей по отдельным сделкам и просадок.
Кэширование метаданных символов и расписаний торговых сессий в MQL5: Устранение лишних вызовов SymbolInfo в мультисимвольных советниках Кэширование метаданных символов и расписаний торговых сессий в MQL5: Устранение лишних вызовов SymbolInfo в мультисимвольных советниках
В этой статье представлен CSymbolMetaCache — программный слой на MQL5, который при запуске советника заранее загружает спецификации контрактов и расписания торговых сессий отслеживаемых символов, а затем возвращает их значения через типизированные геттеры из кэша в памяти. В статье объясняется, какие свойства можно безопасно кэшировать, а какие являются динамическими, включая полудинамическую стоимость тика для кросс-валютных пар. Также реализуется функция IsMarketOpen(), которая определяет, открыта ли сессия, используя кэшированные данные. Тест производительности показывает, насколько сокращается задержка при работе с набором из двадцати символов.
Фундаментальные модели для торговли (Часть II): Декодирование, авторегрессия и точный KV-кэш Фундаментальные модели для торговли (Часть II): Декодирование, авторегрессия и точный KV-кэш
Мы завершаем нативную реализацию Kronos на MQL5: создаем декодировщик, этапы decode_s1 и decode_s2 предиктора с подводными камнями перекрестного внимания и авторегрессионный цикл, формирующий прогноз на несколько баров. Затем мы проводим профилирование и ускоряем реализацию примерно в 4,5 раза с помощью точного KV-кэша и предварительно транспонированных весов, сверяя результаты каждого этапа с результатами PyTorch.
Фундаментальные модели для торговли (Часть I): Нативная реализация Kronos средствами MQL5 Фундаментальные модели для торговли (Часть I): Нативная реализация Kronos средствами MQL5
Kronos — предварительно обученная модель-трансформер, которая моделирует бары OHLCV так же, как языковая модель предсказывает слова. Мы с нуля реализуем ее токенизатор/кодировщик и блок трансформера на нативном MQL5, экспортируем веса в плоские файлы .bin и полностью исключаем Python из этапа выполнения модели. В части 1 реализованы предобработка, токенизация BSQ и средства побитной проверки результатов относительно PyTorch, что позволяет уверенно запускать кодировщик внутри MetaTrader 5.