Building a Dynamic STF Liquidity Sweep Indicator in MQL5
Introduction
I still remember staring at price charts, repeatedly asking myself questions every trader eventually encounters: Why does price often take out stop-losses before moving in the anticipated direction? What truly drives the market? Why do bullish moves often push downward before rallying aggressively?
The deeper I observed market behavior, the clearer it became that price movements were not entirely random. Behind many of these seemingly deceptive moves was a recurring concept—liquidity—a concept that resonates strongly with modern traders and MQL5 developers alike. It explained why price frequently sweeps above highs or below lows before reversing with strong momentum. What initially appeared to be random market manipulation gradually revealed itself as a structured process of liquidity collection performed by institutional participants.
However, understanding the concept as a trader was one thing; implementing it programmatically in MQL5 was an entirely different challenge. Like many developers, I found that most liquidity sweep indicators use either basic wick-based logic or a fixed sweep time window. These approaches can work in some market conditions. However, they often miss liquidity grabs that develop through extended or irregular price formations. Consequently, many valid sweep events are either identified too late or missed entirely.
This article addresses that challenge by building a Dynamic Single-Timeframe (STF) Liquidity Sweep Indicator in MQL5 capable of detecting liquidity sweeps regardless of how long the setup takes to form. Rather than relying solely on static wick logic, the indicator incorporates:
- Dynamic sweep detection
- Wick-ratio validation
- Dual-candle sweep detection (Engulfing pattern)
- One-sweep-per-level validation rule
- Post-violation liquidity sweep invalidation
These mechanisms improve detection accuracy while significantly reducing false signals.
Understanding the Concept
In financial markets, liquidity refers to the availability of buy and sell orders within a particular price region. It represents the pool of resting orders that market participants place around key market structures such as swing highs, swing lows, equal highs and lows, and consolidation ranges. These areas attract stop-loss orders, breakout entries, and pending orders. As a result, price often reacts strongly around them.
Within Smart Money Concepts (SMC), it is commonly said that liquidity drives the market. This idea is based on the belief that price is constantly seeking areas where large concentrations of orders exist. Since institutional participants require significant opposing orders to efficiently enter or exit positions without causing excessive price imbalance, the markets frequently move toward these liquidity pools.
Types of Liquidity in Smart Money Concepts
In SMC, liquidity refers to areas in the market where large clusters of pending orders, stop-losses, or trapped trader positions are concentrated. These areas provide the necessary volume for institutional participants to execute large positions efficiently.
Liquidity is generally categorized into two primary forms:
Buy-Side Liquidity: Refers to the concentration of resting buy orders above the current market price, where breakout buyers place buy-stop pending orders and short sellers position their stop-losses. These liquidity pools are commonly found above swing highs, equal highs, and consolidation ranges (resistance levels).

Fig. 1. Buy-Side Liquidity
Sell-Side Liquidity: Emerges as the concentration of resting sell orders below the current market price, where breakout sellers place sell-stop pending orders and long traders position their stop-losses. These liquidity pools reside at lower price levels such as swing lows, equal lows, and consolidation ranges (support levels).

Fig. 2. Sell-Side Liquidity
Price often seeks out these areas before establishing its true directional move, a phenomenon commonly known as liquidity sweep.Liquidity sweeps generally manifest in two primary forms depending on how price interacts with the targeted liquidity level and how quickly the market reclaims the swept zone:
- Wick Sweep: Occurs when one candle aggressively trades above or below a key liquidity zone, briefly triggering resting stop-losses and breakout orders before rapidly reversing back within range. This type of sweep is characterized by long wicks or rejection tails, indicating immediate absorption of liquidity and strong market rejection from the swept area.

Fig. 3. Wick Sweep
- Dual-Candle Sweep: Consists of a two-candle sequence in which the first candle temporarily breaches a liquidity level, while the following candle retraces and closes back within the swept range. For additional confirmation, the recovery candle is required to form an engulfing pattern, signaling a potential shift in order flow and reinforcing the validity of the liquidity sweep.

Fig. 4. Dual-Candle Sweep
MQL5 Implementation
Having established the foundational concepts of liquidity, along with the visual models used to identify them on price charts, the next step is to translate these ideas into practical implementation. In this section, we model liquidity concepts using native MQL5 constructs. This enables rule-based detection, market-structure analysis, and automated liquidity mapping in MetaTrader 5.
Compiler Directives
We begin by defining the compiler directives, which specify the program metadata and compilation behavior of the MQL5 indicator. This is followed by the inclusion of the required standard library dependencies and the declaration of reusable constants that will be referenced throughout the implementation, improving consistency, readability, and maintainability.//+------------------------------------------------------------------+ //| 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
Custom Data Structure
To efficiently model and track liquidity sweep events, we define a custom data structure using the struct construct provided by 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) {} };
Explanation:
This structure encapsulates the essential properties associated with a liquidity sweep candidate.- swingPrice: Stores the price value of the identified swing point.
- swingTime: Records the timestamp at which the swing point was formed.
- swept: Boolean flag used to track whether the analyzed swing point has already been swept by price.
- Constructor: Initializes all structure members with safe default values to prevent undefined behavior during runtime.
Indicator Settings
The following input parameters define the configurable behavior and visual appearance of the liquidity sweep indicator. By exposing these values as input variables, traders can dynamically customize the sensitivity of swing detection and the graphical representation of liquidity zones directly from the indicator settings panel.//--- 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
Explanation:
- rightLeftBars: Determines the pivot strength by specifying the number of neighboring bars required on both the left and right sides of a candle to validate a swing high or swing low.
- buyLiquidityColor and sellLiquidityColor: Define the drawing colors used to visually represent Buy-side and Sell-side liquidity zones on the chart, respectively.
Global State Variables
With the core data structure in place, we now transition to defining the program’s global variables. These serve as shared instances that persist across the program lifecycle, enabling coordinated access to both data and interface components.
//--- Global state variables CChartObjectText text; CChartObjectTrend trendLine; int barIndex = -1; st_LiquiditySweep buySideLiquidity, sellSideLiquidity; st_LiquiditySweep sweeps[], violates[];
Explanation:
- text: An instance of CChartObjectText, used to create text objects for rendering liquidity-related annotations directly on the chart.
- trendLine: Standard library CChartObjectTrend instance, used to mark liquidity sweep levels visually.
- barIndex: Stores the index of the currently analyzed bar within the OnCalculate event handler.
- buySideLiquidity and sellSideLiquidity: Instances of the st_LiquiditySweep structure, used to store and manage the currently detected Buy-side and Sell-side liquidity level.
- sweeps[] and violates[]: Dynamic array instances of the st_LiquiditySweep structure used to store previously swept and previously violated swing points, respectively. These arrays support the implementation of the “one sweep per level” rule by preventing duplicate sweep detections on the same liquidity level, while also avoiding invalid post-violation sweep detections once a liquidity level has been structurally breached.
This state-management layer forms the foundation for maintaining historical liquidity context during real-time market analysis.
Utility Functions
To improve modularity and maintain clean separation of responsibilities, several utility functions are introduced throughout the implementation. These helper functions encapsulate reusable operations such as candle-state validation, swing analysis, array management, and liquidity sweep event visualization.
- New Candle Detection
Efficient liquidity analysis requires the indicator to distinguish between an actively forming candle and a fully closed candle. The following utility function performs new-candle detection by monitoring changes in the opening timestamp of incoming price bars. This ensures that liquidity calculations and sweep validations are executed only once per completed candle, preventing redundant processing during intra-bar price updates.
//+------------------------------------------------------------------+ //| 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 Analysis
//+------------------------------------------------------------------+ //| 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 }
Explanation:
- isSwingHigh(): Determines whether a candle at a specified index qualifies as swinghigh.
- isSwingLow(): Determines whether a candle at a specified index qualifies as swing low.
- getNextSwingHighIndex(): Searches for the next valid swing high after a specified starting index. Only swing highs whose price exceeds the supplied minimum threshold are accepted.
- getNextSwingLowIndex(): Obtains the index of the next valid swing low after a specified starting position. This function mirrors the swing-high logic but focuses on bearish structural movement by identifying subsequent swing lows. Only lows below the specified threshold are considered valid.
- Engulfing Pattern Detection
To improve confirmation accuracy, the system implements engulfing pattern detection to identify strong momentum shifts in price action. By validating bullish and bearish engulfing formations, the system can better validate potential reversals following liquidity sweeps and reduce weak or misleading signals.
//+------------------------------------------------------------------+ //| 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); }
- Wick-Ratio Validation
To support precise liquidity sweep detection, wick-ratio validation functions are introduced. These utilities quantify the relative size of a candle’s upper and lower wicks as a percentage of the total candle range. This helps identify strong rejection behavior commonly associated with wick-sweeps while filtering out structurally weak candles.
//+------------------------------------------------------------------+ //| 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); }
- Array Management
To maintain an efficient and structured workflow for storing and validating liquidity-related data, a set of helper functions is introduced for dynamic array management. These utilities handle safe insertion of new elements and fast lookup of existing swing points, ensuring data integrity during real-time market analysis.
//+------------------------------------------------------------------+ //| 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; }
Explanation:
- append(): Safely appends a new st_LiquiditySweep instance to a dynamic array.
- isExist(): This function checks whether a given swing timestamp already exists in the stored liquidity sweep array.
- Visualization
These functions utilize standard library instances of CChartObjectTrend and CChartObjectText, enabling simplified and reliable access to graphical object creation and manipulation. By leveraging these standard library classes, the implementation ensures consistent object handling while reducing the complexity typically associated with direct chart object management in 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); } }
Initialization and Cleanup
The initialization phase establishes the baseline market structure required for liquidity sweep detection. During OnInit(), the indicator performs a historical scan to identify initial swing highs and lows that are higher or lower than recent closed candle values, respectively. Additionally, cleanup logic in OnDeinit() ensures proper removal of all graphical objects associated with the indicator to maintain chart integrity.
//+------------------------------------------------------------------+ //| 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); }
Core Engine (OnCalculate)
The OnCalculate() function serves as the core execution engine of the indicator, handling real-time liquidity analysis, sweep detection, and structural updates. To reduce computational overhead, the logic runs only when a new candle forms. This ensures a significant reduction in runtime load while maintaining accurate market structure tracking.//+------------------------------------------------------------------+ //| 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); }
Explanation:
Upon detection of a new candle, the core execution workflow proceeds through the following stages:- Sweep Detection
- Ensures the level is not already present in the sweeps or violates arrays.
- Set the sweep state flag to true.
- Store the sweep in the sweeps array.
- Render trendline and text annotations on the chart.
- Identify and assign the next valid swing point as the new liquidity target.
- Sweep Violation
Sweep violation logic handles cases where a liquidity level is invalidated or structurally broken without a proper sweep confirmation. This ensures the model does not repeatedly reference obsolete liquidity zones.
- Marks the current liquidity level as processed (swept = true).
- Stores the level in the violates array to prevent reprocessing.
- Advances to the next valid swing structure.
- Newer Swing Points Search
In the absence of a sweep or violation event on the current candle, the engine scans for newly formed swing highs or swing lows relative to the prevailing market structure. This ensures that the active liquidity reference remains updated and aligned with evolving price action.
- Ensures liquidity tracking remains aligned with evolving market structure.
- Updates buySideLiquidity and sellSideLiquidity dynamically.
- Resets swept = false to re-enable future detection on the new level.
Testing
After successful compilation with no errors, the indicator output should appear as shown below:

Fig. 5. Liquidity Sweep Sentinel Indicator (STF)
Conclusion
This brings us to the end of the Liquidity Sweep Indicator framework: a verifiable, dynamic Liquidity Sweep Sentinel designed for MetaTrader 5. The system translates Smart Money Concepts into a structured, rule-based execution model capable of identifying and tracking liquidity-driven market behavior in real time.
The indicator framework achieves the following:
- Detects both single-candle wick-based sweeps and dual-candle liquidity sweep formations, regardless of the duration of their development.
- Dynamically tracks Buy-side and Sell-side liquidity levels using swing-based market structure logic.
- Maintains historical integrity through structured memory handling, preventing duplicate sweep detection via dedicated sweep and violation tracking arrays.
- Filters low-quality price action using wick-ratio validation to ensure only strong rejection events are classified as valid liquidity sweeps.
- Continuously adapts to evolving market structure by updating swing points in real time, ensuring liquidity references remain current.
Warning: All rights to these materials are reserved by MetaQuotes Ltd. Copying or reprinting of these materials in whole or in part is prohibited.
This article was written by a user of the site and reflects their personal views. MetaQuotes Ltd is not responsible for the accuracy of the information presented, nor for any consequences resulting from the use of the solutions, strategies or recommendations described.
Covariance Matrix Adaptation Evolution Strategy (CMA-ES)
Integrating AI into 3 Smart Money Concepts (SMC): OB, BOS, and FVG
Meta-Labeling the Classics (Part 1): Filtering and Sizing RSI Trades
News Filtering with MetaTrader 5 Economic Calendar and CSV Fallback
- Free trading apps
- Over 8,000 signals for copying
- Economic news for exploring financial markets
You agree to website policy and terms of use
it is not showing lines. please check the code. Thanks