Разработка индикатора снятия ликвидности STF Liquidity Sweep на языке MQL5
Введение
Я до сих пор помню, как пристально вглядывался в ценовые графики, снова и снова задавая себе вопросы, с которыми рано или поздно сталкивается каждый трейдер: почему цена часто выбивает стоп-лоссы, прежде чем двинуться в ожидаемом направлении? Что на самом деле двигает рынок? Почему при бычьих движениях цена часто сначала снижается, а затем резко растёт?
Чем внимательнее я наблюдал за поведением рынка, тем яснее становилось, что ценовые колебания не были полностью случайными. За многими из этих, казалось бы, обманчивых ходов стояла одна и та же сущность — ликвидность — понятие, которое находит широкий отклик как у современных трейдеров, так и у разработчиков 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; }
- Анализ локальных экстремумов
//+------------------------------------------------------------------+ //| 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); }
Пояснение:
При обнаружении новой свечи основной рабочий процесс выполняется в следующем порядке:- Динамическое обнаружение снятия ликвидности
- Проверяет, не присутствует ли данный уровень в массивах sweeps или violates.
- Устанавливает флаг состояния обнаружения в значение true.
- Сохраняет событие снятия ликвидности в массиве sweeps.
- Отображает линию тренда и текстовые аннотации на графике.
- Определяет и назначает следующий значимый локальный экстремум в качестве новой цели ликвидности.
- Недействительность уровня ликвидности после пробоя
Логика обработки пробитых уровней учитывает случаи, когда уровень ликвидности становится недействительным без подтверждённого снятия. Это гарантирует, что модель не будет повторно обращаться к устаревшим зонам ликвидности.
- Помечает текущий уровень ликвидности как обработанный (swept = true).
- Сохраняет уровень в массиве violates, чтобы предотвратить повторную обработку.
- Переходит к следующей допустимой структуре.
- Поиск новых точек разворота
Если на текущей свече не произошло снятия ликвидности или нарушения уровня, алгоритм ищет новые локальные максимумы и минимумы колебаний относительно сложившейся рыночной структуры. Это гарантирует, что активный ориентир ликвидности остается актуальным и соответствует динамике цен.
- Обеспечивает соответствие системы мониторинга ликвидности меняющейся структуре рынка.
- Динамически обновляет buySideLiquidity и sellSideLiquidity.
- Сбрасывает значение параметра swept = false, чтобы вновь включить обнаружение будущих событий на новом уровне.
Тестирование
После успешной компиляции на экране должен появиться вывод индикатора, как показано ниже:

Рис. 5. Индикатор Liquidity Sweep Sentinel (STF)
Заключение:
На этом мы завершаем рассмотрение концепции индикатора Liquidity Sweep: это динамический индикатор с проверяемой логикой Liquidity Sweep Sentinel, разработанный для MetaTrader 5. Система преобразует концепции Smart Money в структурированную модель, основанную на наборе правил, способную в режиме реального времени выявлять и отслеживать поведение рынка, определяемое ликвидностью.
Индикатор позволяет решить следующие задачи:
- Выявляет как снятие ликвидности тенью одной свечи, так и формации снятия ликвидности двумя свечами независимо от продолжительности их формирования.
- Динамически отслеживает уровни ликвидности со стороны покупателей и продавцов с использованием логики рыночной структуры, основанной на ценовых колебаниях.
- Обеспечивает историческую целостность за счет структурированной обработки памяти, предотвращает повторное обнаружение снятия ликвидности с помощью специальных массивов для учёта снятых и пробитых уровней.
- Отфильтровывает слабые ценовые сигналы с помощью проверки соотношения теней свечей, чтобы гарантировать, чтобы только выраженные отскоки классифицировались как подтверждённые снятия ликвидности.
- Постоянно адаптируется к меняющейся структуре рынка за счет обновления точек разворота в режиме реального времени, обеспечивая актуальность показателей ликвидности.
Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/22140
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.
Метаразметка классических стратегий (Часть 1): Фильтрация сделок по RSI и определение размера позиций
Кэширование метаданных символов и расписаний торговых сессий в MQL5: Устранение лишних вызовов SymbolInfo в мультисимвольных советниках
Фундаментальные модели для торговли (Часть II): Декодирование, авторегрессия и точный KV-кэш
Фундаментальные модели для торговли (Часть I): Нативная реализация Kronos средствами MQL5
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования
Строки не отображаются. Проверьте, пожалуйста, код. Спасибо.
@Nazmul Hasan
Добрый день
Индикатор разработан таким образом, что начинает работу только при формировании новой свечи. Это сделано намеренно и помогает избежать ненужной нагрузки на систему. После добавления индикатора на график он просто ожидает формирования новой свечи — после чего индикатор начнет проверку на наличие скачков ликвидности. Приведенный ниже GIF-файл демонстрирует это поведение и показывает тестирование индикатора в Тестере стратегий MT5.
LIQUIDITY SWEEP SENTINEL — ТЕСТ