Разработка инструментария для анализа Price Action (Часть 27): Инструмент выявления снятия ликвидности с MA-фильтром
Содержание
Введение
Крупные институциональные участники влияют на рынок не случайно. Их стратегия часто предполагает вывод цены за пределы известных уровней поддержки или сопротивления, чтобы намеренно активировать более мелкие ордера, такие как стоп-лоссы и отложенные заявки. Это кратковременное движение, называемое снятием ликвидности, позволяет им набирать или разгружать крупные позиции по более выгодным ценам, не разворачивая рынок сразу в противоположную сторону.
Для многих розничных трейдеров такие движения выглядят как ловушки. Цена может уйти ниже знакомого минимума, выбить стоп-лоссы, а затем быстро вернуться обратно. Или, наоборот, цена может пробить уровень сопротивления, собрать стопы и затем резко развернуться. Как только крупные игроки поглотят этот приток ликвидности, рынок часто возобновляет прежний тренд с новой силой. Если распознавать такие снятия ликвидности на ранней стадии, можно избежать преждевременного выбивания по стопу и вместо этого занять позицию по направлению институционального потока, а не реагировать на его временные встряски.
В этой статье мы разработаем советник на MQL5, предназначенный для выявления снятия ликвидности по мере его формирования. Советник начинает с анализа свечей, которые уходят ниже или выше предыдущих свинг-уровней, а затем возвращаются обратно и закрываются в прежнем диапазоне, что может указывать на поглощение ликвидности. В него также включены дополнительные фильтры, такие как смена цвета свечи или подтверждение со стороны скользящей средней, чтобы сигналы соответствовали вашему взгляду на рынок. Когда обнаруживается корректный паттерн, советник визуально отмечает его на графике стрелками или метками и подает алерт.
К концу этого руководства у вас будет понятный и наглядный советник, который сразу отмечает случаи снятия ликвидности. Вы узнаете, как советник обнаруживает эти рыночные паттерны, как он использует фильтры для уменьшения числа ложных сигналов и как помогает следовать за действиями "умных денег". С этим инструментом в вашем арсенале вы сможете предугадывать действия институциональных участников до того, как они выбьют ваш стоп-лосс, что даст вам стратегическое преимущество в любых рыночных условиях.
Знакомство со стратегией
Снятие ликвидности (свип) может формироваться двумя способами: либо как бычий свип (ложный пробой ниже уровня поддержки), либо как медвежий свип (ложный пробой выше уровня сопротивления). Основная логика обнаружения сосредоточена в функции DetectLiquiditySweep советника. Ниже приведен пошаговый разбор того, как советник различает эти два случая, а затем – точный фрагмент кода MQL5, который выполняет логические проверки.
Как только закрывается новая свеча, советник вызывает функцию:
DetectLiquiditySweep(1);
Передача shift=1 означает следующее:
- индекс 1 (shift) относится к только что закрытому бару – "текущей" свече, которую мы хотим проверить;
- индекс 2 (shift + 1) относится к бару непосредственно перед ним – "предыдущей" свече;
- внутри DetectLiquiditySweep эти строки получают цены открытия, максимума, минимума и закрытия для обоих баров:
double o = iOpen(Symbol(), Period(), shift); double c = iClose(Symbol(), Period(), shift); double h = iHigh(Symbol(), Period(), shift); double l = iLow(Symbol(), Period(), shift); double o1 = iOpen(Symbol(), Period(), shift + 1); double c1 = iClose(Symbol(), Period(), shift + 1); double h1 = iHigh(Symbol(), Period(), shift + 1); double l1 = iLow(Symbol(), Period(), shift + 1);
- (o, h, l, c) – это цена открытия, цена максимума, цена минимума и цена закрытия нового бара;
- (o1, h1, l1, c1) – те же значения для непосредственно предшествующего бара.
Советник поддерживает два немного различающихся варианта определения, которые задаются параметром SignalStrict:
1. LessStrict (по умолчанию)
Бычий свип:
- новый бар должен закрыться выше, чем открылся – c > o;
- его минимум должен опуститься ниже минимума предыдущего бара – l < l1;
- его цена закрытия также должна быть выше цены открытия предыдущего бара – c > o1 (это не позволяет считать паттерном явную свечу доджи или слишком маленькую ложную свечу);
- предыдущий бар не должен быть свечой доджи – c1 != o1.
- новый бар должен закрыться ниже, чем открылся – c < o;
- его максимум должен уйти выше максимума предыдущего бара – h > h1;
- его цена закрытия также должна быть ниже цены открытия предыдущего бара – c < o1;
- предыдущий бар не должен быть свечой доджи – c1 != o1.
2. Strict
Бычий свип:
То же самое, что и в LessStrict, но шаг 2 здесь строже: условия l < l1 уже недостаточно, дополнительно требуется, чтобы цена закрытия была выше предыдущего максимума (c > h1). Другими словами, новый бар должен сначала уйти ниже, а затем к закрытию не только вернуться обратно, но и подняться выше предыдущего максимума.
Медвежий свип:
Аналогичным образом, новый бар должен после выхода выше предыдущего максимума (h > h1) закрыться ниже предыдущего минимума (c > l1), а не просто слегка проколоть прежний максимум.
Эти логические проверки приведены в блоке кода ниже.
//--- Liquidity sweep logic (LessStrict vs Strict) bool bullSweep, bearSweep; if (SignalStrict == LessStrict) { bullSweep = (c > o && // 1) Bullish candle l < l1 && // 2) Low dipped below previous low c > o1 && // 3) Close above previous open c1 != o1); // 4) Previous bar was not a doji bearSweep = (c < o && // 1) Bearish candle h > h1 && // 2) High spiked above previous high c < o1 && // 3) Close below previous open c1 != o1); // 4) Previous bar was not a doji } else // Strict { bullSweep = (c > o && // 1) Bullish candle l < l1 && // 2) Low dipped below previous low c > h1 && // 3) Close above previous high (stricter) c1 != o1); // 4) Previous bar was not a doji bearSweep = (c < o && // 1) Bearish candle h > h1 && // 2) High spiked above previous high c < l1 && // 3) Close below previous low (stricter) c1 != o1); // 4) Previous bar was not a doji }
- В режиме LessStrict шаг 3 требует только c > o1 (для бычьего сигнала) или c < o1 (для медвежьего).
- В режиме Strict шаг 3 меняется на c > h1 (бычий сигнал) или c < l1 (медвежий).
Если пользователь установил ColorChangeOnly = true, советник требует, чтобы новый бар был противоположного цвета по отношению к предыдущему. А именно:
bool bullCC = (c > o && c1 < o1); // New bar bullish, old bar bearish bool bearCC = (c < o && c1 > o1); // New bar bearish, old bar bullish if (ColorChangeOnly) { bullSweep &= bullCC; // Only a bullish sweep if also a bull‐after‐bear color flip bearSweep &= bearCC; // Only a bearish sweep if also a bear‐after‐bull color flip }
Если ColorChangeOnly имеет значение false, эти две строки ни на что не влияют: и bullSweep, и bearSweep остаются такими, какими их определили предыдущие ценовые проверки.
Как только bullSweep или bearSweep становятся true, то есть проходят ценовые и при необходимости цветовые проверки, советник может дополнительно отфильтровать сигнал по скользящей средней. Это задается параметрами UseMAFilter и PriceAboveMA. По сути:
- рассчитывается одно значение MA для индекса shift, то есть для только что закрытого бара, – либо через встроенный хэндл iMA() (для SMA, EMA, LWMA или RMA), либо с помощью пользовательской функции (CalcVWMA или CalcHMA);
bool cond = PriceAboveMA ? (c > maValue) : (c < maValue);
- если PriceAboveMA == true, советник сохраняет bullSweep только при условии c > maValue и принудительно устанавливает bearSweep = false;
- если PriceAboveMA == false, советник сохраняет bearSweep только при условии c < maValue и принудительно устанавливает bullSweep = false.
if (UseMAFilter) { double maValue = 0.0; if (MAType == VWMA) maValue = CalcVWMA(shift); else if (MAType == HMA) maValue = CalcHMA(shift); else { double buf[]; if (CopyBuffer(MAHandle, 0, shift, 1, buf) != 1) return; // not enough MA data yet maValue = buf[0]; } // Keep only bullish sweeps if price > MA, or only bearish if price < MA bool cond = PriceAboveMA ? (c > maValue) : (c < maValue); bullSweep &= cond; bearSweep &= !cond; }
Итог
Бычий свип:
- Свеча закрывается выше, чем открылась, уходит ниже предыдущего минимума и закрывается выше предыдущей цены открытия (LessStrict) или предыдущего максимума (Strict).
- При желании можно потребовать, чтобы произошла смена цвета с медвежьего на бычий.
- При желании можно потребовать, чтобы цена закрытия была выше скользящей средней.
- Если все проверки пройдены, советник размещает зеленую стрелку вверх (OBJ_ARROW_UP) на несколько пунктов ниже минимума этой свечи.

Рис. 1. Бычий свип
Медвежий свип:
- Свеча закрывается ниже, чем открылась, уходит выше предыдущего максимума и закрывается ниже предыдущей цены открытия (LessStrict) или предыдущего минимума (Strict).
- При желании можно потребовать, чтобы это была смена цвета с бычьего на медвежий.
- При желании можно потребовать, чтобы цена закрытия была ниже скользящей средней.
- Если все проверки пройдены, советник размещает красную стрелку вниз (OBJ_ARROW_DOWN) на несколько пунктов выше максимума этой свечи.

Рис. 2. Медвежий свип
Разбор кода
Советник "Liquidity Sweep with MA filter" предназначен для выявления и визуальной маркировки ложных пробоев, которые часто называют снятием ликвидности, на любом графике в MetaTrader 5. В основе работы советника – отслеживание баров, которые уходят ниже свинг-минимума предыдущего бара или выше его свинг-максимума, а затем закрываются так, что это указывает на попадание ордеров в ловушку. Комбинируя эту логику обнаружения с дополнительным фильтром по скользящей средней, трейдеры могут точнее подстраивать сигналы под общий контекст тренда. В следующих абзацах мы последовательно разберем каждый ключевой компонент советника, объясняя, что делает каждый раздел и почему это важно.
В начале файла советника задаются его метаданные – название, информация об авторских правах, ссылка на автора и версия, – а сразу за ними идет директива #property strict. Эти строки здесь не просто для красоты: они выполняют две задачи. Во-первых, они помогают пользователям, которые просматривают сразу несколько советников, идентифицировать именно данный советник и сразу дают понять, кто автор и где можно найти другие его работы. Во-вторых, благодаря директиве #property strict компилятор применяет более строгую проверку синтаксиса и типов, отлавливая типичные ошибки, такие как необъявленные переменные или несовпадение типов, еще до запуска кода. Практика использования понятных метаданных и строгой компиляции соответствует профессиональным стандартам и обеспечивает как удобство сопровождения, так и надежность.
//+------------------------------------------------------------------+ //| Liquidity Sweep with MA filter| //| Copyright 2025, MetaQuotes Ltd.| //| https://www.mql5.com/ru/users/lynnchris| //+------------------------------------------------------------------+ #property copyright "Copyright 2025, MetaQuotes Ltd." #property link "https://www.mql5.com/ru/users/lynnchris" #property version "1.0" #property strict
Затем советник подключает библиотеку Trade.mqh. Хотя эта версия не открывает реальные сделки автоматически, подключение <Trade\Trade.mqh> сделано с прицелом на будущее: оно делает доступным мощный класс CTrade для любых последующих доработок, которые могут открывать, изменять или закрывать позиции. В контексте учебной статьи это также показывает читателю, что такой советник можно легко превратить в полностью автоматическую торговую систему – достаточно вызывать trade.Buy(...) или trade.Sell(...) внутри логики обнаружения. Показывая это подключение в самом начале, автор сразу наводит читателя на мысль и об анализе, и об исполнении сделок.
#include <Trade\Trade.mqh> После подключения библиотеки советник задает набор входных параметров, которые становятся видны в окне Inputs терминала MetaTrader, когда советник прикреплен к графику. Эти входные параметры делятся на три логические группы. Первая группа – настройки скользящей средней. Она позволяет пользователю включать или выключать MA-фильтр (UseMAFilter), выбирать, отображать ли эту скользящую среднюю на графике (ShowMA), задавать период MA (MALength) и выбирать один из шести типов MA: SMA, EMA, LWMA, VWMA, RMA или HMA. Затем логический параметр PriceAboveMA определяет, должен ли советник требовать, чтобы цена находилась выше MA (для бычьих свипов) или ниже нее (для медвежьих свипов).
На практике этот фильтр гарантирует, что трейдер будет учитывать сигналы снятия ликвидности только по направлению преобладающего тренда, уменьшая число ложных срабатываний, когда рынок идет против него.
//--- Inputs: Moving Average Filter input bool UseMAFilter = false; // Enable Moving Average Filter input bool ShowMA = false; // Show MA on chart input int MALength = 20; // MA period (must be >=1) enum MA_Type {SMA=0, EMA, LWMA, VWMA, RMA, HMA}; input MA_Type MAType = SMA; // Moving Average type input bool PriceAboveMA = true; // Filter: price above MA?
Вторая группа входных параметров определяет, насколько строгие критерии советник использует для выявления свипов. Перечисление SignalStrict предлагает два режима: LessStrict требует только, чтобы минимум нового бара для бычьих сигналов был ниже предыдущего минимума, и наоборот для медвежьих, тогда как Strict требует дополнительного пробоя максимума или минимума предыдущего бара. Благодаря этому флагу читатель видит, как можно настраивать чувствительность: кто-то предпочтет более мягкие критерии, чтобы улавливать больше сигналов, а кто-то – более консервативный подход, который ждет явного пробоя.
Дополнительный логический параметр ColorChangeOnly еще сильнее ужесточает условия, требуя, чтобы цвет нового бара был противоположен цвету предыдущего. Во многих торговых подходах смена цвета сигнализирует о смене моментума, поэтому эта опция подойдет тем, кто хочет отмечать только те развороты, которые сопровождаются явной сменой цвета свечи.
//--- Inputs: Sweep Definition Strictness enum Strictness {LessStrict=0, Strict}; input Strictness SignalStrict = LessStrict; // Signal strictness input bool ColorChangeOnly = false; // Only on color-change candles
Третья группа касается визуальных элементов на графике. Перечисление LabelType позволяет выбрать либо отсутствие метки, либо короткую двухбуквенную метку ("BS" для бычьего свипа или "SS" для медвежьего свипа), либо полную текстовую метку ("Bull Sweep" или "Bear Sweep"). Параметр PlotArrow позволяет выводить стрелку вместо текста или вместе с ним. Чтобы не загромождать график и не накладывать метки на свечи, параметр ArrowOffsetPoints сдвигает каждую стрелку или текстовый объект на заданное число пунктов выше свечи для медвежьего сигнала и ниже нее для бычьего.
Наконец, два цветовых параметра – BullishColor и BearishColor – дают полный контроль над цветом этих маркеров, позволяя подстроить их под тему графика. Благодаря разделению параметров отображения и логики обнаружения советник остается гибким: трейдер может настроить его либо в режиме "silent alert" без объектов на графике, либо в полнофункциональном режиме "chart annotation" – в зависимости от личных предпочтений или требований к производительности системы.
//--- Inputs: Label/Arrow & Color Customization enum LabelType {None=0, Short, Full}; input LabelType LblType = Full; // Label type (None/Short/Full) input bool PlotArrow = true; // Draw arrow on signal input int ArrowOffsetPoints = 10; // Offset (in points) above/below candles input color BullishColor = clrLime; // Color for bullish signals input color BearishColor = clrRed; // Color for bearish signals
Сразу после секции входных параметров советник объявляет две глобальные переменные. Первая из них, datetime lastBarTime, хранит временную метку последнего обработанного бара. Эта переменная играет ключевую роль: на каждом тике советник сравнивает время начала текущего бара (iTime(Symbol(),Period(),0)) с lastBarTime. Если значения различаются, это означает, что сформировалась новая свеча, и только тогда советник запускает процедуру обнаружения. Без этой проверки логика выполнялась бы на каждом тике формирующегося бара, что привело бы к появлению нескольких сигналов на одной свече и к тому, что обычно называют "перерисовкой".
Вторая глобальная переменная, int MAHandle, хранит хэндл индикатора, который возвращает функция iMA(...) при создании встроенной MA. Инициализировав ее значением INVALID_HANDLE, код затем может проверить, действительно ли существует корректный хэндл, прежде чем пытаться читать данные по нему.
//--- Globals datetime lastBarTime = 0; // Timestamp of the last processed bar int MAHandle = INVALID_HANDLE; // Handle for built‐in MA indicator
Функция OnInit() выполняет начальную настройку советника. Когда советник впервые прикрепляют к графику или когда кто-то изменяет входные параметры, платформа вызывает OnInit(). Первым делом здесь проверяется MALength. Поскольку в MQL5 после компиляции любой входной параметр фактически рассматривается как константа, мы не можем просто присвоить ему безопасное значение. Поэтому код осуществляет проверку if(MALength < 1), выводит сообщение об ошибке и возвращает INIT_FAILED, если пользователь задал недопустимый период. Такой защитный подход гарантирует, что советник не будет пытаться вычислять MA по нулевому или отрицательному числу баров, что иначе привело бы к ошибкам времени выполнения или бессмысленным результатам. Затем lastBarTime присваивается время открытия текущего бара, что инициализирует логику обработчика тиков так, чтобы она не сработала сразу на том баре, во время которого советник был прикреплен к графику.
int OnInit() { // Validate MALength (cannot assign to an input directly) if(MALength < 1) { Print("ERROR: MALength must be at least 1. Current value = ", MALength); return(INIT_FAILED); } // Initialize timing so we only run when a new bar closes lastBarTime = iTime(Symbol(), Period(), 0); // … (rest of OnInit follows) … return(INIT_SUCCEEDED); }
Далее внутри OnInit() советник определяет, нужно ли создавать хэндл встроенной MA. Если пользователь выбрал один из четырех стандартных типов MA (SMA, EMA, LWMA или RMA) и включил UseMAFilter, либо ShowMA, код вызывает iMA(Symbol(), Period(), MALength, 0, (ENUM_MA_METHOD)MAType, PRICE_CLOSE), чтобы создать хэндл индикатора. Если этот вызов завершается неудачей, советник выводит диагностическое сообщение об ошибке и прерывает инициализацию. Если все прошло успешно, далее проводится проверка if(ShowMA == true), и, если условие выполняется, вызывается ChartIndicatorAdd(0, 0, MAHandle), чтобы отобразить MA на графике. В конце инициализации сообщение "Liquidity Sweep EA initialized successfully." подтверждает, что все необходимые условия выполнены – период корректен, хэндл MA создан, – и советник готов к работе.
// Create MA handle if using a built-in MA (SMA, EMA, LWMA, RMA) if((MAType != VWMA && MAType != HMA) && (UseMAFilter || ShowMA)) { ENUM_MA_METHOD method = (ENUM_MA_METHOD)MAType; MAHandle = iMA(Symbol(), Period(), MALength, 0, method, PRICE_CLOSE); if(MAHandle == INVALID_HANDLE) { Print("Failed to create MA handle (type=", EnumToString(MAType), ", length=", MALength, ")"); return(INIT_FAILED); } if(ShowMA) ChartIndicatorAdd(0, 0, MAHandle); }
Когда пользователь удаляет советник или закрывает MetaTrader, выполняется функция OnDeinit(). Ее единственная задача – очистка: если MAHandle задан, то есть не равен INVALID_HANDLE, она вызывает IndicatorRelease(MAHandle), чтобы освободить память и убрать оставшиеся ссылки. Тем самым советник избегает утечек ресурсов, что считается важной хорошей практикой всякий раз, когда хэндлы индикаторов создаются во время выполнения. Хотя современный MetaTrader выполняет часть этой работы автоматически, явное освобождение хэндлов помогает избежать накопления лишних объектов на графике в длительных сессиях или при частом повторном подключении советника во время подбора параметров.
void OnDeinit(const int reason) { if(MAHandle != INVALID_HANDLE) IndicatorRelease(MAHandle); }
Функция OnTick() – это рабочий ритм советника: MetaTrader вызывает ее при каждом входящем ценовом тике. Однако, поскольку нам нужно проверять снятие ликвидности только один раз на каждую закрытую свечу, OnTick() сначала получает время последнего бара через iTime(Symbol(),Period(),0) и сравнивает его с lastBarTime. Если значения совпадают, это значит, что советник все еще находится в пределах того же бара, поэтому ничего не происходит.
Только когда появляется новая свеча (iTime(...) != lastBarTime), код вызывает DetectLiquiditySweep(1), передавая shift=1, чтобы сравнить только что закрытую свечу с предыдущей. Сразу после этого lastBarTime обновляется новым значением, гарантируя, что до закрытия следующего бара советник повторно не сработает. Такой дисциплинированный подход гарантирует ровно один сигнал на бар, устраняя шум и предотвращая множественные алерты на одной свече.
void OnTick() { // Retrieve the current bar’s start time datetime current = iTime(Symbol(), Period(), 0); // If the bar start time changed, call the detection routine once if(current != lastBarTime) { DetectLiquiditySweep(1); lastBarTime = current; } }
Основная рабочая функция DetectLiquiditySweep(int shift) реализует логику, которая отличает этот советник от более простых маркеров паттернов. Сначала функция проверяет, хватает ли исторических данных для расчета любых пользовательских скользящих средних. Вычисляя requiredBars = shift + MALength и осуществляя проверку if(Bars(Symbol(),Period()) <= requiredBars) return;, код предотвращает выход индекса за пределы допустимого диапазона. Если на графике, скажем, всего 15 баров, а пользователь задал MALength=20, советник не станет выполнять расчет MA или проверку паттерна, потому что для этого не хватает истории. Это защитное условие показывает внимательное отношение к предотвращению ошибок, что критически важно для надежных советников.
void DetectLiquiditySweep(int shift) { // Ensure there are at least (shift + MALength) bars of history int requiredBars = shift + MALength; if(Bars(Symbol(), Period()) <= requiredBars) { // Not enough bars to compute custom MA or compare prices return; } // … (next steps in the function) … }
После успешной проверки истории DetectLiquiditySweep считывает восемь ценовых значений – цены открытия, максимума, минимума и закрытия как для "текущего" завершенного бара (index = shift), так и для "предыдущего" бара (index = shift + 1). Эти значения сохраняются в восьми отдельных переменных типа double: o, c, h, l для текущего бара и o1, c1, h1, l1 для предыдущего. После этого советник может определить, произошло ли снятие ликвидности.
Важно, что код также вычисляет два логических флага – bullCC (бычья смена цвета свечи) и bearCC (медвежья смена цвета свечи), – просто проверяя, закрылась ли текущая свеча выше открытия, тогда как предыдущая закрылась ниже открытия, и наоборот. Эти флаги используются позже, если включен ColorChangeOnly, и гарантируют, что советник отмечает только те свипы, которые совпадают со сменой цвета свечи.
//--- Bar data for the current completed candle (index = shift) double o = iOpen(Symbol(), Period(), shift); double c = iClose(Symbol(), Period(), shift); double h = iHigh(Symbol(), Period(), shift); double l = iLow(Symbol(), Period(), shift); //--- Bar data for the prior candle (index = shift + 1) double o1 = iOpen(Symbol(), Period(), shift + 1); double c1 = iClose(Symbol(), Period(), shift + 1); double h1 = iHigh(Symbol(), Period(), shift + 1); double l1 = iLow(Symbol(), Period(), shift + 1);
В следующем разделе реализуются определения свипа для режима "LessStrict" и режима "Strict". Если пользователь выбрал LessStrict, бычий свип фиксируется, когда текущий бар закрывается выше своего открытия (c > o), его минимум опускается ниже минимума предыдущего бара (l < l1), закрытие оказывается выше открытия предыдущего бара (c > o1), а предыдущий бар не является свечой доджи (c1 != o1). Медвежий свип строится симметрично: текущее закрытие ниже текущего открытия, текущий максимум выше предыдущего максимума, текущее закрытие ниже предыдущего открытия, а предыдущий бар не является свечой доджи.
Если вместо этого пользователь выбрал Strict, советник ужесточает условия и требует, чтобы текущее закрытие пробивало максимум предыдущего бара для бычьего свипа или опускалось ниже минимума предыдущего бара для медвежьего. Такой строгий вариант уменьшает число ложных сигналов на неглубоких откатах, требуя более убедительного пробоя уровня поддержки или сопротивления. Наличие обоих режимов показывает читателю, как даже небольшие изменения в логике могут заметно влиять на частоту и качество сигналов.
// Compute color-change flags bool bullCC = (c > o && c1 < o1); bool bearCC = (c < o && c1 > o1); // Liquidity sweep flags (LessStrict or Strict) bool bullSweep, bearSweep; if(SignalStrict == LessStrict) { bullSweep = (c > o && l < l1 && c > o1 && c1 != o1); bearSweep = (c < o && h > h1 && c < o1 && c1 != o1); } else // Strict { bullSweep = (c > o && l < l1 && c > h1 && c1 != o1); bearSweep = (c < o && h > h1 && c < l1 && c1 != o1); }
После базовых проверок свипа советник при необходимости применяет фильтр смены цвета свечи. Если ColorChangeOnly имеет значение true, выполняются строки bullSweep &= bullCC; bearSweep &= bearCC;. Иными словами, любой свип, который не совпадает со сменой цвета свечи, сразу отбрасывается. Многие трейдеры рассматривают смену цвета свечи, например красную свечу после зеленой, как подтверждение разворотного импульса, поэтому такой фильтр уменьшает вероятность получения ложного свипа, при котором не произошло явного отторжения уровня. Эта простая побитовая операция AND элегантно объединяет два взаимодополняющих сигнала – ценовую структуру и психологию свечи – в единый фильтр.
// If only color-change sweeps are desired, AND‐combine with the raw sweep flags if(ColorChangeOnly) { bullSweep &= bullCC; // must also be a bullish color reversal bearSweep &= bearCC; // must also be a bearish color reversal }
Когда базовые условия свипа и необязательный цветовой фильтр уже применены, и если UseMAFilter == true, советник переходит к проверке фильтра скользящей средней. Он объявляет переменную double maValue = 0.0; и затем вычисляет MA одним из трех способов. Если пользователь выбрал "VWMA", вызывается пользовательская вспомогательная функция CalcVWMA(shift). Если выбрано "HMA", вызывается CalcHMA(shift). В противном случае предполагается, что используется встроенный тип MA (SMA, EMA, LWMA или RMA), и выполняется CopyBuffer(MAHandle, 0, shift, 1, buf), чтобы получить одно значение MA. Если CopyBuffer не возвращает ровно один результат, советник просто завершает функцию и пропускает всю дальнейшую логику по этому бару.
Когда maValue уже вычислено, логическая переменная cond проверяет условие PriceAboveMA ? (c > maValue) : (c < maValue). На практике, если PriceAboveMA имеет значение true, советник сохраняет только бычьи свипы, закрывшиеся выше MA, и принудительно устанавливает bearSweep = false). Если PriceAboveMA имеет значение false, остаются только медвежьи свипы, закрывшиеся ниже MA. Этот фильтр гарантирует, что снятие ликвидности рассматривается в контексте тренда: бычьи свипы имеют значение только тогда, когда цена находится выше MA, а медвежьи – только когда цена находится ниже нее.
if(UseMAFilter) { double maValue = 0.0; // Compute MA value at the same 'shift' if(MAType == VWMA) maValue = CalcVWMA(shift); else if(MAType == HMA) maValue = CalcHMA(shift); else { // Built‐in MA handle (SMA, EMA, LWMA, RMA) double buf[]; if(CopyBuffer(MAHandle, 0, shift, 1, buf) != 1) return; // no valid MA data available maValue = buf[0]; } // Only allow bullish sweeps above MA or bearish sweeps below MA bool cond = PriceAboveMA ? (c > maValue) : (c < maValue); bullSweep &= cond; bearSweep &= !cond; }
Наконец, после применения всех фильтров DetectLiquiditySweep использует два блока if, чтобы выполнить проверки if(bullSweep) и if(bearSweep). Если bullSweep имеет значение true, вызывается DrawSignal(shift, true), чтобы разместить на графике бычью метку, и в лог на вкладке "Эксперты" записывается сообщение вида "Bullish sweep detected at [time], price=[close]". Если bearSweep имеет значение true, выполняется то же самое с DrawSignal(shift, false) и соответствующим сообщением в логе.
В любом случае, если сработал хотя бы один из них, код затем вызывает Alert("Liquidity Sweep detected on ", Symbol(), " ", EnumToString(Period()));, что выводит на экран всплывающее уведомление. Такое разделение между "рисованием на графике" и "отправкой алерта" позволяет каждому трейдеру самому решить, нужны ли ему только звуковые и всплывающие уведомления или еще и постоянная визуальная отметка прямо на графике.
// If a bullish sweep remains true after all filters, draw and log it if(bullSweep) { DrawSignal(shift, true); PrintFormat("Bullish sweep detected at %s, price=%.5f", TimeToString(iTime(Symbol(),Period(),shift)), c); } // If a bearish sweep remains true after all filters, draw and log it if(bearSweep) { DrawSignal(shift, false); PrintFormat("Bearish sweep detected at %s, price=%.5f", TimeToString(iTime(Symbol(),Period(),shift)), c); } // In either case, fire a pop‐up alert if(bullSweep || bearSweep) { Alert("Liquidity Sweep detected on ", Symbol(), " ", EnumToString(Period())); }
Функция DrawSignal(int shift, bool bullish) отвечает исключительно за размещение объектов на графике. Сначала она считывает временную метку бара t = iTime(Symbol(),Period(),shift) t = [u}iTime(Symbol(),Period(),shift){u] и вычисляет цену отображения: для бычьей стрелки это минимум минус ArrowOffsetPoints * _Point, а для медвежьей – максимум плюс то же смещение. Использование _Point гарантирует, что смещение измеряется в реальных ценовых приращениях независимо от того, идет ли речь, например, о EURUSD с шагом 0.0001 или о USDJPY с шагом 0.01. Затем с помощью StringFormat("LS_%I64u", (long)t) формируется уникальное имя объекта. Поскольку (long)t представляет собой 64-битное целое число, отражающее точное время открытия бара, два разных бара не могут получить одно и то же имя объекта.
Прежде чем что-либо рисовать, функция вызывает ObjectFind(0, name) и удаляет любой уже существующий объект с таким именем – это предотвращает захламление графика, если советник повторно прикрепляют к нему или обновление графика повторяет то же срабатывание. Наконец, если PlotArrow имеет значение true, ObjectCreate(...) рисует цветную стрелку – вверх для бычьего сигнала и вниз для медвежьего – на уровне вычисленной цены. Если PlotArrow имеет значение false, но LblType не равняется None, функция вместо этого рисует короткую или полную текстовую метку, например "BS" вместо "Bull Sweep". Вынося весь код, связанный с объектами графика, в одну процедуру, советник отделяет логику обнаружения от визуальной разметки, что является признаком чистой модульной архитектуры.
void DrawSignal(int shift, bool bullish) { // Get the exact bar start time for this sweep datetime t = iTime(Symbol(), Period(), shift); // Determine the on‐chart Y‐coordinate: offset a few points above/below the candle double price = bullish ? (iLow(Symbol(), Period(), shift) - ArrowOffsetPoints * _Point) : (iHigh(Symbol(), Period(), shift) + ArrowOffsetPoints * _Point); // Compose a unique name using the 64-bit timestamp string name = StringFormat("LS_%I64u", (long)t); // If an object with that name already exists, delete it first if(ObjectFind(0, name) >= 0) ObjectDelete(0, name); // (Next lines will choose arrow vs. text drawing) }
Под капотом две вспомогательные функции вычисляют нестандартные скользящие средние. CalcVWMA(int shift) реализует стандартную скользящую среднюю, взвешенную по объему. Сначала она инициализирует значением 0.0 две накопительные переменные – numerator и denominator. Затем для каждого индекса бара от shift до shift + MALength - 1, считываются цена закрытия price = iClose(...) и тиковый объем vol = iVolume(...), который хранится как 64-битное значение типа long. Явно приводя vol к типу double при вычислении numerator += price * (double)vol и denominator += (double)vol, код избегает предупреждений о неявном преобразовании целочисленного типа в тип double.
После завершения цикла функция возвращает numerator / denominator, если denominator > 0.0; в противном случае безопасно возвращается 0.0, чтобы избежать деления на ноль. Таким образом, каждая точка VWMA представляет собой сумму произведений (price × volume) за последние MALength баров, деленную на сумму объема, – именно этого трейдеры и ожидают от средневзвешенной по объему.
double CalcVWMA(int shift) { double numerator = 0.0; double denominator = 0.0; // Loop over 'MALength' bars, starting at 'shift' for(int i = shift; i < shift + MALength; i++) { double price = iClose(Symbol(), Period(), i); long vol = iVolume(Symbol(), Period(), i); // Accumulate (price × volume) and sum of volume numerator += price * (double)vol; denominator += (double)vol; } // Return VWMA = sum(price×volume) / sum(volume), or 0 if volume = 0 return (denominator > 0.0) ? (numerator / denominator) : 0.0; }
Наконец, CalcHMA(int shift) вычисляет скользящую среднюю Халла – двухэтапную взвешенную среднюю, созданную для уменьшения запаздывания. Сначала функция задает half = MALength / 2, а затем в цикле от i = shift to i < shift + half назначает каждому бару убывающий вес – от half до 1. Далее накапливаются взвешенная сумма w1 и суммарный вес sw1, после чего w1 / sw1 дает взвешенную MA для половинного периода. Во-вторых, она выполняет цикл от i = shift до i < shift + MALength, назначая веса от MALength до 1, накапливая значения w2 и sw2 и вычисляя w2/sw2, чтобы получить взвешенную MA за полный период.
Итоговое значение HMA равно 2 * w1 – w2. Иными словами, удвоение MA за половину периода и вычитание MA за полный период повышает чувствительность скользящей средней к недавним изменениям цены, что многие трейдеры считают более подходящим для динамичных рынков. Как и в случае с VWMA, советник защищается от нехватки исторических данных, вызывая CalcHMA только тогда, когда Bars(Symbol(),Period()) > shift + MALength.
//+------------------------------------------------------------------+ //| Compute Hull Moving Average (HMA) | //+------------------------------------------------------------------+ double CalcHMA(int shift) { int half = MALength / 2; double w1 = 0.0, sw1 = 0.0; double w2 = 0.0, sw2 = 0.0; // 1) Weighted MA over half period for(int i = shift; i < shift + half; i++) { double p = iClose(Symbol(), Period(), i); int weight = half - (i - shift); w1 += p * weight; sw1 += weight; } w1 = (sw1 > 0.0) ? (w1 / sw1) : 0.0; // 2) Weighted MA over full period for(int i = shift; i < shift + MALength; i++) { double p = iClose(Symbol(), Period(), i); int weight = MALength - (i - shift); w2 += p * weight; sw2 += weight; } w2 = (sw2 > 0.0) ? (w2 / sw2) : 0.0; // 3) Final HMA value = 2 * (MA over half) – (MA over full) return 2.0 * w1 - w2; }
Тестирование и результаты
Чтобы оценить работу нашего инструмента и лучше понять его поведение, мы провели комплексное тестирование. Этот процесс включал запуск инструмента на исторических данных в режиме бэктестинга, а также оценку его работы в текущих рыночных условиях. Ниже показаны результаты, полученные в ходе этих тестов.

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

Рис. 4. Тестирование в реальном времени на Step Index
Приведенный выше результат демонстрирует эффективность инструмента в выявлении случаев снятия ликвидности: он выделяет ключевые рыночные развороты четкими визуальными маркерами, что может помочь при принятии торговых решений. Этот результат демонстрирует эффективность инструмента в выявлении свипов: он выделяет ключевые рыночные развороты четкими визуальными маркерами, что может помочь при принятии торговых решений.

Рис. 5. Тестирование на истории EURUSD
Этот график демонстрирует способность инструмента точно выявлять паттерны снятия ликвидности на исторических рыночных данных. Анализируя прошлые движения цены, советник отмечает случаи, когда цена временно пробивала уровни поддержки или сопротивления, а затем разворачивалась, сигнализируя о возможной активности крупных участников. Эти точки могут служить полезными ориентирами при прогнозировании будущих рыночных разворотов или продолжения тренда. Данный график подтверждает эффективность механизма обнаружения и повышает доверие к нему для применения в реальных торговых условиях.
Заключение
Этот советник стабильно выявляет случаи снятия ликвидности как при тестах на истории, так и в симуляции в реальном времени, отмечая каждый подтвержденный свип стрелкой именно в той точке, где цена сначала пробивает предыдущий свинг-минимум или свинг-максимум, а затем возвращается обратно. При разных настройках фильтра MA сигналы хорошо совпадали с последующим продолжением тренда, что говорит о высокой надежности инструмента в реальных рыночных условиях. Независимо от того, анализируете ли вы его прошлые результаты или наблюдаете за его работой в реальном времени, способность советника выявлять "сбор стопов" в институциональном стиле подтверждается достаточно уверенно. Используйте эти выводы, чтобы встроить обнаружение свипов в собственную стратегию, понимая, что этот код уже подтвердил свою точность и скорость реакции как на истории, так и в реальном времени.
Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/18379
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.
Торговые инструменты на MQL5 (Часть 19): Создание интерактивной палитры инструментов графической разметки
Внедрение в MQL5 практических модулей из других языков (Часть 06): Операции файлового ввода-вывода в MQL5, как в Python
Код, слёзы и Algo Forge
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования