You are currently viewing the resource titled "Automating Liquidity Sweep on Break of Structure (BoS) Forex Trading Strategy in MQL5". This page provides detailed information about the resource, including its content, attached files, and recent discussions. Feel free to explore, download available files if logged in, and join the community conversation below!
Banner Image

Introduction


Hey there, buckle up for a brand-new thrill: crafting a Liquidity Sweep on Break of Structure (BoS) system that'll have you trading like a sly fox in the forex henhouse. 🦊📈This savvy setup scouts swing highs and lows over your chosen lookback length, tags 'em as HH (higher high), HL (higher low), LH (lower high), or LL (lower low) to spot those game-changing BoS moments—think HH breakthroughs in uptrends or LL plunges in downtrends. It then hunts for liquidity sweeps: those cheeky wicks that poke beyond the swing but snap back inside on a bullish or bearish candle (sneaky market makers getting swept!). Trades fire buys on Sell Side Liquidity (SSL) sweeps during bullish BoS or sells on Buy Side Liquidity (BSL) in bearish ones, with dynamic stops, trade caps, opposite-closing smarts, and eye-candy visuals like icons, labels, rectangles, dashed lines, arrows, and even adaptive fonts for that pro polish. It's like giving your chart a makeover while it makes you money! 💄💰

We'll break it down bite by bite: First, decoding the Liquidity Sweep on BoS strategy (spoiler: it's all about outsmarting the big players). Then, hands-on MQL5 implementation—code snippets, tips, and tweaks. Next, backtesting to test its mettle (does it shine or flop?). And finally, a wrap-up with optimization ideas to turbocharge your version. By the end, you'll wield a ready-to-roll MQL5 powerhouse for snagging BoS liquidity plays, complete with visuals and risk reins. Coffee ready? Let's splash into this liquidity pool! ☕🌊


Understanding the Liquidity Sweep on Break of Structure (BoS) Strategy


Let's unpack the Liquidity Sweep on Break of Structure (BoS)—a slick price action ploy that's like spotting market makers playing hide-and-seek with traders' stops, combining trend-spotting via swing points with those sneaky sweeps past them to hoard liquidity before flipping the script. We scout nearby bars to crown swing highs (taller than their left/right buddies) and lows (shorter), then slap labels based on the previous champ: highs get HH (higher high, "I'm the new boss!") or LH (lower high, "not quite there"), while lows earn HL (higher low, "bouncing back") or LL (lower low, "diving deeper"). BoS kicks in with an HH breakout in uptrends (bullish "let's keep climbing!") or LL smash in downtrends (bearish "down we go!"), yelling "structure shattered—trend evolving!"; a sweep seals the deal when price teases a wick beyond the swing (SSL dipping under a low in uptrends, BSL poking over a high in downtrends) but snaps back inside on a bullish or bearish candle, trapping eager beavers and hinting at the real reversal party. 🕵️‍♂️📉

Our blueprint? Hunt swings across your input length, tag 'em with HH/HL/LH/LL to lock in that BoS trend vibe, sniff out sweeps on BoS where wicks wander wild but closes cozy up directional-style, then trade buys on SSL in bullish BoS or sells on BSL in bearish with smart dynamic levels, a max trades cap to avoid overcommitting (party pooper prevention!), auto-closing opposites for clean slates, and blingy visuals like icons/labels on swings (name tags for highs/lows), rectangles hugging sweeps (highlighting the trap zone), dashed lines marking BoS breaks (dotted drama!), arrows pointing entries (follow the leader!), and adaptive fonts that resize on zooms (no squinting needed). Liquidity sweeps play nice with any setup—we picked BoS for its no-nonsense charm, but swap it for imbalance gigs or whatever tickles your strategy fancy. In a nutshell, here's a snazzy visual breakdown of our goals—picture this! 🎨🚀


Implementation in MQL5


Alright, let's fire up this MQL5 masterpiece! 🚀 Crack open MetaEditor (your gateway to trading bot glory), hop into the Navigator panel like a digital explorer, sniff out the Experts folder, smash that "New" button with enthusiasm, and breeze through the prompts to birth your shiny new file. Once it's alive and kicking in the editor, we'll jazz it up by declaring some input parameters (those tweakable dials for user fun) and global variables (the backstage crew that keeps the show running smooth across the program). Think of it as equipping your EA with brains and brawn—ready for the trading tango! 😄🛠️

//+------------------------------------------------------------------+
//|                                       BOS Liquidity Sweep EA.mq5 |
//|                           Copyright 2025, Allan Munene Mutiiria. |
//|                                   https://t.me/Forex_Algo_Trader |
//+------------------------------------------------------------------+
#property copyright "Copyright 2025, Allan Munene Mutiiria."
#property link      "https://t.me/Forex_Algo_Trader"
#property version   "1.00"

#include <Trade/Trade.mqh>

//+------------------------------------------------------------------+
//| Global Variables                                                 |
//+------------------------------------------------------------------+
CTrade obj_Trade;                                                 //--- Trade object

//+------------------------------------------------------------------+
//| Input Parameters                                                 |
//+------------------------------------------------------------------+
input group "EA GENERAL SETTINGS"
input int    SwingLength       = 5;                               // Swing Length in Bars (left/right check)
input double LotSize           = 0.01;                            // Fixed lot size
input double SL_Buffer_Pips    = 10.0;                            // SL buffer in pips below/above sweep
input double RiskRewardRatio   = 2.0;                             // Take profit multiplier (e.g., 2:1 RR)
input int    MaxTrades         = 1;                               // Max open trades
input long   MagicNumber       = 12345;                           // Unique magic number
input group "VISUALIZATION SETTINGS"
input color  clr_Bullish       = clrBlue;                         // Bullish Color (HH/HL)
input color  clr_Bearish       = clrRed;                          // Bearish Color (LL/LH)
input color  clr_SSL_Rect      = clrLightBlue;                    // SSL Sweep Rectangle Color
input color  clr_BSL_Rect      = clrLightCoral;                   // BSL Sweep Rectangle Color
input color  clr_SSL_Line      = clrBlue;                         // SSL Sweep Line Color
input color  clr_BSL_Line      = clrRed;                          // BSL Sweep Line Color
input color  clr_BullBOS       = clrGreen;                        // Bullish BOS Line Color
input color  clr_BearBOS       = clrMaroon;                       // Bearish BOS Line Color
input int    LineWidth         = 2;                               // Line Width
input bool   PrintLogs         = true;                            // Print Statements

//+------------------------------------------------------------------+
//| Global Variables Continued                                       |
//+------------------------------------------------------------------+
static double   current_swing_high = -1.0, current_swing_low = -1.0; //--- Current swing high and low
static datetime swing_high_time = 0, swing_low_time = 0;          //--- Swing high and low times
int    MarketTrend = 0;                                           //--- Market trend (1: Bullish BOS, -1: Bearish BOS, 0: Neutral)
int    OpenTrades = 0;                                            //--- Open trades count
int    current_font_size = 10;                                    //--- Current font size
int    object_code = 174;                                         //--- Wingdings arrow code for swings
int    buy_arrow_code = 233;                                      //--- Wingdings up arrow for buy
int    sell_arrow_code = 234;                                     //--- Wingdings down arrow for sell
string ObjPrefix = "BOSLiqSweep_";                                //--- Object prefix

Let's launch this implementation with a bang by roping in the trade library via #include —your golden ticket to the CTrade class, mastering all things orders and positions like a backstage pass to the market's VIP lounge. We crown "obj_Trade" as our global CTrade superstar to juggle those trading ops with flair. Then, we herd the input parameters into tidy groups for the properties dialog: Under "EA GENERAL SETTINGS," we've got "SwingLength" dialing the bar count for left/right swing peeks (spot those peaks and valleys!), "LotSize" locking in fixed lots for bets, "SL_Buffer_Pips" adding a safety cushion below/above sweeps for stop-loss (because nobody likes nasty surprises), "RiskRewardRatio" multiplying take-profits for that sweet reward chase, "MaxTrades" capping open positions to prevent portfolio pile-ups, and "MagicNumber" tagging trades like a secret handshake. Shifting to "VISUALIZATION SETTINGS," color your world with "clr_Bullish" in blue for HH/HL highs (upward vibes!), "clr_Bearish" in red for LL/LH lows (downward drama), "clr_SSL_Rect" in light blue for SSL rectangles (sell-side traps highlighted), "clr_BSL_Rect" in light coral for BSL (buy-side sneaky spots), "clr_SSL_Line" in blue for SSL lines, "clr_BSL_Line" in red for BSL, "clr_BullBOS" in green for bullish BoS breaks (go team bull!), "clr_BearBOS" in maroon for bearish (red alert!), "LineWidth" tweaking line thickness for visibility, and "PrintLogs" flipping the switch on logging for those debug giggles. It's like dressing your EA in a custom suit—functional, fabulous, and ready to impress! 🎩📊🚀

INPUT PARAMETERS

Then, let's pile on more global variables—like stocking your EA's pantry with essentials for the trading feast! We've got static "current_swing_high" and "current_swing_low" kicking off at -1.0 to stalk those latest swing peaks and valleys (ghost values until the real deal shows up), static "swing_high_time" and "swing_low_time" to timestamp their arrivals (because timing is everything in markets!), "MarketTrend" set as 1 for bullish BoS romps, -1 for bearish plunges, or 0 for neutral "meh" moments, "OpenTrades" to tally live positions (crowd control!), "current_font_size" starting at 10 for adaptive text sizing (no more squinty eyes on zooms), "object_code" as 174 for those Wingdings swing icons (fancy symbols ahoy!), "buy_arrow_code" as 233 for buy arrows (upward thrust!), "sell_arrow_code" as 234 for sell (downward dive!), and "ObjPrefix" as "BOSLiqSweep_" to brand all objects uniquely (no name clashes in chart land). With this arsenal prepped, we're set to spark the program logic in the OnInit event handler—starting by zapping any lingering objects to banish clutter (fresh canvas vibes!). But hold the horses; we'll whip up some helper functions first to make the magic smoother. 😄🧰🚀

//+------------------------------------------------------------------+
//| Update font sizes                                                |
//+------------------------------------------------------------------+
void UpdateFontSizes() {
   long scale = 0;                                                //--- Init scale
   if (ChartGetInteger(0, CHART_SCALE, 0, scale)) {               //--- Get scale
      current_font_size = (int)(7 + scale * 0.7);                 //--- Calculate font size
      if (current_font_size < 6) current_font_size = 6;           //--- Set minimum font size
      if (current_font_size > 15) current_font_size = 15;         //--- Set maximum font size
      for (int i = ObjectsTotal(0, -1, -1) - 1; i >= 0; i--) {    //--- Iterate objects reverse
         string name = ObjectName(0, i, -1, -1);                  //--- Get object name
         long type = ObjectGetInteger(0, name, OBJPROP_TYPE);     //--- Get object type
         if (type == OBJ_TEXT) {                                  //--- Check text type
            ObjectSetInteger(0, name, OBJPROP_FONTSIZE, current_font_size); //--- Set font size
         }
      }
      ChartRedraw(0);                                             //--- Redraw chart
   }
}

//+------------------------------------------------------------------+
//| Delete objects by prefix                                         |
//+------------------------------------------------------------------+
void DeleteObjectsByPrefix(string prefix) {
   int total = ObjectsTotal(0, 0, -1);                            //--- Get total objects
   for (int i = total - 1; i >= 0; i--) {                         //--- Iterate reverse
      string name = ObjectName(0, i, 0, -1);                      //--- Get name
      if (StringFind(name, prefix) == 0) {                        //--- Check prefix
         ObjectDelete(0, name);                                   //--- Delete object
      }
   }
}

//+------------------------------------------------------------------+
//| Count open trades                                                |
//+------------------------------------------------------------------+
int CountOpenTrades() {
   int count = 0;                                                 //--- Init count
   for (int i = PositionsTotal() - 1; i >= 0; i--) {              //--- Iterate reverse
      ulong ticket = PositionGetTicket(i);                        //--- Get ticket
      if (PositionSelectByTicket(ticket)) {                       //--- Select position
         if (PositionGetString(POSITION_SYMBOL) == _Symbol && PositionGetInteger(POSITION_MAGIC) == MagicNumber) { //--- Check symbol and magic
            count++;                                              //--- Increment count
         }
      }
   }
   return count;                                                  //--- Return count
}
Here, we implement the "UpdateFontSizes" function to dynamically adjust the size of text objects on the chart based on the current zoom level, ensuring everything stays readable and easy on the eyes 👀✨. We begin by initializing "scale" to 0, then retrieve the chart’s scale value using "ChartGetInteger" with "CHART_SCALE". If the call succeeds (win 🎯), we calculate "current_font_size" as 7 plus 70% of the scale, carefully limiting it between 6 and 15—because even fonts need boundaries 😄. Next, we loop backward through all chart objects using "ObjectsTotal", specifying -1 to include all windows and object types. For each object, we fetch its name with "ObjectName" and determine its type using "ObjectGetInteger" with "OBJPROP_TYPE". When an "OBJ_TEXT" object is found, we update its font size via "ObjectSetInteger" and "OBJPROP_FONTSIZE", then redraw the chart so the changes show up instantly ✨📊.
We then define the "DeleteObjectsByPrefix" function, which acts as a cleanup assistant 🧹. This function removes all chart objects that match a specified prefix. We retrieve the total number of objects on the main chart across all types, then loop backward through them. For each object, we get its name and check whether it starts with the given prefix using "StringFind", where a return value of 0 confirms a match ✅. When a match is found, the object is removed using "ObjectDelete". Next, we create the "CountOpenTrades" function to count the number of currently open positions associated with this program 📈. We initialize "count" to 0, then loop backward through "PositionsTotal". For each position, we retrieve the ticket using "PositionGetTicket" and select it via "PositionSelectByTicket". If the position matches our symbol using "PositionGetString" with "POSITION_SYMBOL", and also matches our magic number using "PositionGetInteger" with "POSITION_MAGIC", we increment "count". Once the loop completes, the function returns the final total. Finally, we mention that additional helper functions for visualization will be defined next 🎨📉, setting the stage for even clearer and more engaging chart displays.
//+------------------------------------------------------------------+
//| Draw swing point with label                                      |
//+------------------------------------------------------------------+
void DrawSwingPoint(string objName, datetime time, double price, int arrCode, color clr, int direction, string label) {
   UpdateFontSizes();                                             //--- Update font sizes
   objName = ObjPrefix + label + TimeToString(time);              //--- Set obj name
   if (ObjectFind(0, objName) < 0) {                              //--- Check no object
      string iconName = objName + "_icon";                        //--- Icon name
      ObjectCreate(0, iconName, OBJ_TEXT, 0, time, price);        //--- Create icon
      ObjectSetString(0, iconName, OBJPROP_FONT, "Wingdings");    //--- Set font
      ObjectSetInteger(0, iconName, OBJPROP_FONTSIZE, current_font_size); //--- Set font size
      ObjectSetString(0, iconName, OBJPROP_TEXT, CharToString((uchar)arrCode)); //--- Set text
      ObjectSetInteger(0, iconName, OBJPROP_COLOR, clr);          //--- Set color
      ObjectSetInteger(0, iconName, OBJPROP_ANCHOR, ANCHOR_RIGHT); //--- Set anchor
      string txtName = objName + "_txt";                          //--- Text name
      ObjectCreate(0, txtName, OBJ_TEXT, 0, time, price);         //--- Create text
      ObjectSetString(0, txtName, OBJPROP_FONT, "Arial");         //--- Set font
      ObjectSetInteger(0, txtName, OBJPROP_COLOR, clr);           //--- Set color
      ObjectSetInteger(0, txtName, OBJPROP_FONTSIZE, current_font_size); //--- Set font size
      ObjectSetInteger(0, txtName, OBJPROP_ANCHOR, ANCHOR_LEFT);  //--- Set anchor
      ObjectSetString(0, txtName, OBJPROP_TEXT, label);           //--- Set text
   }
   ChartRedraw(0);                                                //--- Redraw chart
}

//+------------------------------------------------------------------+
//| Draw sweep rectangle (no text)                                   |
//+------------------------------------------------------------------+
void DrawSweepRectangle(string objName, datetime time, double level, double extremum, color clr, bool is_ssl) {
   UpdateFontSizes();                                             //--- Update font sizes
   objName = ObjPrefix + objName + TimeToString(time, TIME_SECONDS); //--- Set obj name
   if (ObjectFind(0, objName) < 0) {                              //--- Check no object
      double top = MathMax(level, extremum);                      //--- Calc top
      double bottom = MathMin(level, extremum);                   //--- Calc bottom
      datetime end_time = time + PeriodSeconds(_Period);          //--- Calc end time
      ObjectCreate(0, objName, OBJ_RECTANGLE, 0, time, top, end_time, bottom); //--- Create rectangle
      ObjectSetInteger(0, objName, OBJPROP_COLOR, clr);           //--- Set color
      ObjectSetInteger(0, objName, OBJPROP_BACK, true);           //--- Set back
      ObjectSetInteger(0, objName, OBJPROP_FILL, true);           //--- Set fill
      ObjectSetInteger(0, objName, OBJPROP_STYLE, STYLE_SOLID);   //--- Set style
      // No text inside rectangle to reduce clutter
   }
   ChartRedraw(0);                                                //--- Redraw chart
}

//+------------------------------------------------------------------+
//| Draw horizontal dashed break level                               |
//+------------------------------------------------------------------+
void DrawBreakLevel(string objName, datetime time1, double price, datetime time2, double price2, color clr, int direction, string label) {
   UpdateFontSizes();                                             //--- Update font sizes
   objName = ObjPrefix + objName + label + TimeToString(time2, TIME_SECONDS); //--- Set obj name
   if (ObjectFind(0, objName) < 0) {                              //--- Check no object
      ObjectCreate(0, objName, OBJ_TREND, 0, time1, price, time2, price); //--- Create trend line
      ObjectSetInteger(0, objName, OBJPROP_COLOR, clr);           //--- Set color
      ObjectSetInteger(0, objName, OBJPROP_WIDTH, LineWidth);     //--- Set width
      ObjectSetInteger(0, objName, OBJPROP_STYLE, STYLE_DASH);    //--- Set style
      ObjectSetInteger(0, objName, OBJPROP_RAY_RIGHT, false);     //--- Set no ray right
      string txt = label + " Sweep";                              //--- Set text
      string txtName = objName + "_txt";                          //--- Text name
      ObjectCreate(0, txtName, OBJ_TEXT, 0, time2, price);        //--- Create text
      ObjectSetInteger(0, txtName, OBJPROP_COLOR, clr);           //--- Set color
      ObjectSetInteger(0, txtName, OBJPROP_FONTSIZE, current_font_size); //--- Set font size
      if (direction > 0) {                                        //--- Check positive
         ObjectSetInteger(0, txtName, OBJPROP_ANCHOR, ANCHOR_RIGHT_UPPER); //--- Set anchor
         ObjectSetString(0, txtName, OBJPROP_TEXT, " " + txt);    //--- Set text
      } else {                                                    //--- Negative
         ObjectSetInteger(0, txtName, OBJPROP_ANCHOR, ANCHOR_RIGHT_LOWER); //--- Set anchor
         ObjectSetString(0, txtName, OBJPROP_TEXT, " " + txt);    //--- Set text
      }
   }
   ChartRedraw(0);                                                //--- Redraw chart
}

//+------------------------------------------------------------------+
//| Draw entry arrow with Wingdings                                  |
//+------------------------------------------------------------------+
void DrawEntryArrow(datetime time, double price, bool is_buy) {
   UpdateFontSizes();                                             //--- Update font sizes
   string objName = ObjPrefix + "Entry_" + TimeToString(time, TIME_SECONDS); //--- Set obj name
   if (ObjectFind(0, objName) < 0) {                              //--- Check no object
      int arrCode = is_buy ? buy_arrow_code : sell_arrow_code;    //--- Set arrow code
      color arrow_color = is_buy ? clrBlue : clrRed;              //--- Set color
      ObjectCreate(0, objName, OBJ_TEXT, 0, time, price);         //--- Create text
      ObjectSetString(0, objName, OBJPROP_FONT, "Wingdings");     //--- Set font
      ObjectSetInteger(0, objName, OBJPROP_FONTSIZE, current_font_size); //--- Set font size
      ObjectSetString(0, objName, OBJPROP_TEXT, CharToString((uchar)arrCode)); //--- Set text
      ObjectSetInteger(0, objName, OBJPROP_COLOR, arrow_color);   //--- Set color
      ObjectSetInteger(0, objName, OBJPROP_ANCHOR, is_buy ? ANCHOR_UPPER : ANCHOR_LOWER); //--- Set anchor
   }
   ChartRedraw(0);                                                //--- Redraw chart
}
First, we define the "DrawSwingPoint" function to visually mark detected swing points on the chart using a clear icon and an accompanying label 🎯📍. We start by calling "UpdateFontSizes" to make sure the current text sizing matches the zoom level. A unique object name is then formed by combining "ObjPrefix", the label, and the time string. If no object exists (checked using "ObjectFind"), we create a Wingdings icon as an "OBJ_TEXT" object with the suffix "_icon", positioned at the specified time and price. Its font is set to Wingdings, the size to "current_font_size", the text to the character pulled from "arrCode" using "CharToString", the color to "clr", and the anchor aligned to the right. Next, we create the text label itself with the suffix "_txt" as another "OBJ_TEXT" object, using the Arial font, the same color and size, and a left anchor. The displayed text is set to "label", and the chart is refreshed using "ChartRedraw" so everything appears instantly ✨. Then, we implement the "DrawSweepRectangle" function to draw a filled rectangle that highlights the sweep area without adding internal text—keeping the chart clean and uncluttered 🧼📊. We call "UpdateFontSizes", generate the object name using "ObjPrefix" and the time seconds string, and check for existence. If no object is found, we calculate the top as the maximum of the level and extremum, and the bottom as the minimum. The end time is set to the current time plus one bar period. We then create an "OBJ_RECTANGLE" spanning from the start time at the top to the end time at the bottom, set its color to "clr", enable background drawing, enable fill, apply a solid style, and redraw the chart. After that, we create the "DrawBreakLevel" function to mark BOS breaks using a horizontal dashed line paired with text 📏💥. We call "UpdateFontSizes", form the object name from "ObjPrefix", the label, and the time plus two seconds string, and check for existence. If none is found, we create an "OBJ_TREND" object from time 1 at the given price to time 2 at the same price, producing a horizontal line. We set its color to "clr", width to "LineWidth", style to dash, and disable the right ray. We then add a text label as "OBJ_TEXT" at time 2 and the price, using the suffix "_txt", set the color to "clr", size to "current_font_size", and anchor it as right-upper for positive direction or right-lower for negative direction, before setting the text. Finally, we define the "DrawEntryArrow" function to place a Wingdings arrow that visually indicates trade entries 🚀📉. We call "UpdateFontSizes", build the object name using "ObjPrefix + "Entry_" + time seconds string", and check for existence. If no object is found, we choose "arrCode" as "buy_arrow_code" for buy entries or "sell_arrow_code" for sell entries, and set the color to blue for buys or red for sells. We then create an "OBJ_TEXT" object at the specified time and price, set the font to Wingdings, size to "current_font_size", text to the character derived from "arrCode", apply the chosen color, anchor it as upper for buys or lower for sells, and redraw the chart. We can now continue with the initialization phase to handle cleanup and verify the input variables before execution ✅🛠️.
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit() {
   obj_Trade.SetExpertMagicNumber(MagicNumber);                   //--- Set magic number for trade object
   if (SwingLength < 1 || LotSize <= 0 || SL_Buffer_Pips < 0 || RiskRewardRatio < 1.0 || MaxTrades < 1) { //--- Check invalid inputs
      Print("Invalid input parameters.");                         //--- Log invalid parameters
      return(INIT_PARAMETERS_INCORRECT);                          //--- Return incorrect parameters
   }
   DeleteObjectsByPrefix(ObjPrefix);                              //--- Delete objects by prefix
   UpdateFontSizes();                                             //--- Update font sizes
   Print("EA Initialized Successfully.");                         //--- Log initialization success
   return(INIT_SUCCEEDED);                                        //--- Return success
}

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason) {
   DeleteObjectsByPrefix(ObjPrefix);                              //--- Delete objects by prefix
   Print("EA Deinitialized.");                                    //--- Log deinitialization
}
In the "OnInit" event handler—executed when the program is attached to a chart or freshly loaded—we begin by setting the input "MagicNumber" on "obj_Trade" using "SetExpertMagicNumber", allowing the program to clearly recognize and manage its own trades 🧠📌. We then move on to validating the key inputs. If "SwingLength" is less than 1, "LotSize" is non-positive, "SL_Buffer_Pips" is negative, "RiskRewardRatio" falls below 1.0, or "MaxTrades" is less than 1, we log "Invalid input parameters" using "Print" and immediately return "INIT_PARAMETERS_INCORRECT"—no shortcuts, no surprises 🚫. If all inputs pass validation, we call "DeleteObjectsByPrefix" to clear any existing visual elements, then call "UpdateFontSizes" to establish the initial text sizing. We log "EA Initialized Successfully" for confirmation ✅ and return "INIT_SUCCEEDED", signaling that everything is ready to roll. In the "OnDeinit" event handler—triggered when the program is removed from the chart or the terminal is closing—we once again call "DeleteObjectsByPrefix" to remove all chart objects matching our prefix, ensuring a clean exit with no visual leftovers 🧹✨. With initialization and cleanup handled, we can now move on to the "OnTick" event handler, where the real heavy lifting begins 💪📈. We start by detecting swings and breaks, laying the groundwork for the core trading logic.
//+------------------------------------------------------------------+
//| Detect swings and BOS                                            |
//+------------------------------------------------------------------+
void DetectSwingsAndBOS() {
   int curr_bar = SwingLength;                                    //--- Set current bar
   bool isSwingHigh = true, isSwingLow = true;                    //--- Init swing flags
   for (int j = 1; j <= SwingLength; j++) {                       //--- Iterate length
      int right_index = curr_bar - j;                             //--- Calc right index (newer)
      int left_index = curr_bar + j;                              //--- Calc left index (older)
      if (iHigh(_Symbol, _Period, curr_bar) <= iHigh(_Symbol, _Period, right_index) || iHigh(_Symbol, _Period, curr_bar) < iHigh(_Symbol, _Period, left_index)) { //--- Check not high
         isSwingHigh = false;                                     //--- Set not high
      }
      if (iLow(_Symbol, _Period, curr_bar) >= iLow(_Symbol, _Period, right_index) || iLow(_Symbol, _Period, curr_bar) > iLow(_Symbol, _Period, left_index)) { //--- Check not low
         isSwingLow = false;                                      //--- Set not low
      }
   }
   if (isSwingHigh) {                                             //--- Check swing high
      double new_high = iHigh(_Symbol, _Period, curr_bar);        //--- Get new high
      string label = "H";                                         //--- Init label
      color clr = clr_Bullish;                                    //--- Set color
      if (current_swing_high > 0) {                               //--- Check existing high
         if (new_high > current_swing_high) {                     //--- Check higher
            label = "HH";                                         //--- Set HH
            MarketTrend = 1;                                      //--- Set bullish trend
            if (PrintLogs) Print("Bullish BOS Detected");         //--- Log bullish BOS
            datetime break_time = FindBreakTime(swing_high_time, current_swing_high, true); //--- Find break time
            if (break_time > 0) DrawBreakLevel("Bull_BOS_", swing_high_time, current_swing_high, break_time, current_swing_high, clr_BullBOS, -1, "Bullish BOS"); //--- Draw BOS
         } else {                                                 //--- Lower
            label = "LH";                                         //--- Set LH
            clr = clr_Bearish;                                    //--- Set bearish color
         }
      }
      if (PrintLogs) Print("SWING HIGH @ BAR INDEX ", curr_bar, " of High: ", new_high, " Label: ", label); //--- Log high
      DrawSwingPoint(TimeToString(iTime(_Symbol, _Period, curr_bar)), iTime(_Symbol, _Period, curr_bar), new_high, object_code, clr, -1, label); //--- Draw high point
      current_swing_high = new_high;                              //--- Update high
      swing_high_time = iTime(_Symbol, _Period, curr_bar);        //--- Update high time
   }
   if (isSwingLow) {                                              //--- Check swing low
      double new_low = iLow(_Symbol, _Period, curr_bar);          //--- Get new low
      string label = "L";                                         //--- Init label
      color clr = clr_Bearish;                                    //--- Set color
      if (current_swing_low > 0) {                                //--- Check existing low
         if (new_low < current_swing_low) {                       //--- Check lower
            label = "LL";                                         //--- Set LL
            MarketTrend = -1;                                     //--- Set bearish trend
            if (PrintLogs) Print("Bearish BOS Detected");         //--- Log bearish BOS
            datetime break_time = FindBreakTime(swing_low_time, current_swing_low, false); //--- Find break time
            if (break_time > 0) DrawBreakLevel("Bear_BOS_", swing_low_time, current_swing_low, break_time, current_swing_low, clr_BearBOS, 1, "Bearish BOS"); //--- Draw BOS
         } else {                                                 //--- Higher
            label = "HL";                                         //--- Set HL
            clr = clr_Bullish;                                    //--- Set bullish color
         }
      }
      if (PrintLogs) Print("SWING LOW @ BAR INDEX ", curr_bar, " of Low: ", new_low, " Label: ", label); //--- Log low
      DrawSwingPoint(TimeToString(iTime(_Symbol, _Period, curr_bar)), iTime(_Symbol, _Period, curr_bar), new_low, object_code, clr, 1, label); //--- Draw low point
      current_swing_low = new_low;                                //--- Update low
      swing_low_time = iTime(_Symbol, _Period, curr_bar);         //--- Update low time
   }
}

//+------------------------------------------------------------------+
//| Find break candle time (based on close)                          |
//+------------------------------------------------------------------+
datetime FindBreakTime(datetime prev_time, double prev_level, bool is_high_break) {
   int prev_shift = iBarShift(_Symbol, _Period, prev_time);       //--- Get prev shift
   if (prev_shift < 0) return 0;                                  //--- Return invalid
   for (int i = prev_shift - 1; i >= 0; i--) {                    //--- Iterate reverse
      if (is_high_break) {                                        //--- Check high break
         if (iClose(_Symbol, _Period, i) > prev_level) return iTime(_Symbol, _Period, i); //--- Return time if break
      } else {                                                    //--- Low break
         if (iClose(_Symbol, _Period, i) < prev_level) return iTime(_Symbol, _Period, i); //--- Return time if break
      }
   }
   return 0;                                                      //--- Return no break
}
To house the detection logic, we define the "DetectSwingsAndBOS" function, which identifies swing points and detects breaks of structure on each new bar, updating both the trend direction and the chart visuals accordingly 🔍📈. We begin by setting "curr_bar" to "SwingLength", marking it as the target bar for scanning, and initialize "isSwingHigh" and "isSwingLow" to true. We then loop from 1 to "SwingLength". For each iteration j, we calculate "right_index" as "curr_bar - j", representing newer bars, and "left_index" as "curr_bar + j", representing older bars. For swing highs, we check that the current bar’s high is strictly higher than both the right and left highs—if any of these conditions fail, "isSwingHigh" is set to false ❌. We apply the same logic to lows, setting "isSwingLow" to false if the current bar’s low is not strictly lower. If "isSwingHigh" remains true, we store the high value in "new_high", initialize the label as "H", and set the color to "clr_Bullish". If a previous "current_swing_high" exists, we compare the two. If the new high is higher, we update the label to "HH", set "MarketTrend" to 1 (bullish), and—if "PrintLogs" is true—log "Bullish BOS Detected" 🐂. We then locate the break time using "FindBreakTime" with "swing_high_time", "current_swing_high", and true to indicate a high break. If a valid time is returned, we call "DrawBreakLevel" using the prefix "Bull_BOS_", the relevant times, the level, "clr_BullBOS", direction -1, and the text "Bullish BOS". If the new high is lower than the previous one, we label it "LH" and switch the color to "clr_Bearish". When "PrintLogs" is enabled, we log the swing, then call "DrawSwingPoint" with the time string, time, price, "object_code", color, direction -1, and the label. Finally, we update "current_swing_high" and "swing_high_time". The same logic is mirrored for swing lows, ensuring consistent detection on both sides 🔄. To support BOS detection, we implement the "FindBreakTime" function, which locates the first bar that closes beyond a previous swing level ⏱️. We retrieve the bar shift for "prev_time" using "iBarShift", returning 0 immediately if the result is invalid. We then loop backward from "prev_shift - 1" down to 0. For high breaks, if the close price exceeds "prev_level", we return that bar’s time using "iTime". For low breaks, we return the time when the close falls below "prev_level". If no break is found, the function returns 0. With this function in place, we can now perform precise, per-bar detection by calling it as shown below, keeping the structure logic sharp and responsive 🧠📊.
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick() {
   static bool isNewBar = false;                                  //--- New bar flag
   int currBars = iBars(_Symbol, _Period);                        //--- Get current bars
   static int prevBars = currBars;                                //--- Previous bars
   if (prevBars == currBars) {                                    //--- Check same bars
      isNewBar = false;                                           //--- Set not new bar
   } else if (prevBars != currBars) {                             //--- Check new bars
      isNewBar = true;                                            //--- Set new bar
      prevBars = currBars;                                        //--- Update previous bars
   }
   if (!isNewBar) return;                                         //--- Return if not new bar
   OpenTrades = CountOpenTrades();                                //--- Count open trades
   if (OpenTrades >= MaxTrades) return;                           //--- Return if max trades reached
   DetectSwingsAndBOS();                                          //--- Detect swings and BOS
}
Here, in the "OnTick" event handler—which runs on every price tick and handles the core logic—we use a static "isNewBar" flag alongside "prevBars" to detect bar changes ⏱️📊. We first retrieve the total number of bars using "iBars" and store it in "currBars". This value is then compared with "prevBars": if the count is unchanged, "isNewBar" is set to false; if the count has increased, "isNewBar" is set to true and "prevBars" is updated accordingly. If "isNewBar" is false, we return early—no new bar, no extra work 🚫. When a new bar is detected, we update "OpenTrades" by calling "CountOpenTrades". If the number of open trades is at or above "MaxTrades", we return immediately to prevent any new entries from being placed. If trading is still allowed, we then invoke "DetectSwingsAndBOS" to scan the market for swing points and breaks of structure, keeping the analysis up to date 🔍📈. Upon compilation, we get the following outcome: Bearish Sweep Setup:
BEARISH SWEEP SETUP
Bullish Sweep Setup:
BULLISH SWEEP SETUP
With the detection done, we now need to trade on the liquidity sweeps. We will house the logic in a function for modularity.
//+------------------------------------------------------------------+
//| Detect and trade sweep on BOS                                    |
//+------------------------------------------------------------------+
void DetectAndTradeSweepOnBOS() {
   if (MarketTrend == 0) return;                                  //--- Return if neutral
   double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);            //--- Get bid
   double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);            //--- Get ask
   // Bullish BOS + SSL Sweep for Buy
   if (MarketTrend == 1 && current_swing_low > 0.0 && iLow(_Symbol, _Period, 1) < current_swing_low && iClose(_Symbol, _Period, 1) > current_swing_low && iClose(_Symbol, _Period, 1) > iOpen(_Symbol, _Period, 1)) { //--- Check bullish sweep
      if (PrintLogs) Print("Bullish BOS + SSL Sweep Detected");   //--- Log sweep
      double sweep_low = iLow(_Symbol, _Period, 1);               //--- Get sweep low
      datetime sweep_time = iTime(_Symbol, _Period, 1);           //--- Get sweep time
      DrawSweepRectangle("SSL_Rect_", sweep_time, current_swing_low, sweep_low, clr_SSL_Rect, true); //--- Draw SSL rect
      DrawBreakLevel("SSL_Line_", swing_low_time, current_swing_low, sweep_time, current_swing_low, clr_SSL_Line, 1, "SSL"); //--- Draw SSL line
      CloseOpposite(true);                                        //--- Close opposite
      double sl = NormalizeDouble(sweep_low - SL_Buffer_Pips * _Point, _Digits); //--- Calc SL
      double entry = ask;                                         //--- Set entry
      double risk = entry - sl;                                   //--- Calc risk
      double tp = NormalizeDouble(entry + risk * RiskRewardRatio, _Digits); //--- Calc TP
      obj_Trade.Buy(LotSize, _Symbol, entry, sl, tp, "BOS SSL Buy"); //--- Open buy
      if (obj_Trade.ResultRetcode() == TRADE_RETCODE_DONE) {      //--- Check success
         DrawEntryArrow(sweep_time, iLow(_Symbol,_Period, 1), true);                 //--- Draw buy arrow
         MarketTrend = 0;                                         //--- Reset trend
      } else Print("Buy order failed: ", obj_Trade.ResultRetcodeDescription()); //--- Log failure
   }
   // Bearish BOS + BSL Sweep for Sell
   if (MarketTrend == -1 && current_swing_high > 0.0 && iHigh(_Symbol, _Period, 1) > current_swing_high && iClose(_Symbol, _Period, 1) < current_swing_high && iClose(_Symbol, _Period, 1) < iOpen(_Symbol, _Period, 1)) { //--- Check bearish sweep
      if (PrintLogs) Print("Bearish BOS + BSL Sweep Detected");   //--- Log sweep
      double sweep_high = iHigh(_Symbol, _Period, 1);             //--- Get sweep high
      datetime sweep_time = iTime(_Symbol, _Period, 1);           //--- Get sweep time
      DrawSweepRectangle("BSL_Rect_", sweep_time, current_swing_high, sweep_high, clr_BSL_Rect, false); //--- Draw BSL rect
      DrawBreakLevel("BSL_Line_", swing_high_time, current_swing_high, sweep_time, current_swing_high, clr_BSL_Line, -1, "BSL"); //--- Draw BSL line
      CloseOpposite(false);                                       //--- Close opposite
      double sl = NormalizeDouble(sweep_high + SL_Buffer_Pips * _Point, _Digits); //--- Calc SL
      double entry = bid;                                         //--- Set entry
      double risk = sl - entry;                                   //--- Calc risk
      double tp = NormalizeDouble(entry - risk * RiskRewardRatio, _Digits); //--- Calc TP
      obj_Trade.Sell(LotSize, _Symbol, entry, sl, tp, "BOS BSL Sell"); //--- Open sell
      if (obj_Trade.ResultRetcode() == TRADE_RETCODE_DONE) {      //--- Check success
         DrawEntryArrow(sweep_time, iHigh(_Symbol,_Period,1), false);                //--- Draw sell arrow
         MarketTrend = 0;                                         //--- Reset trend
      } else Print("Sell order failed: ", obj_Trade.ResultRetcodeDescription()); //--- Log failure
   }
}
Here, we define the "DetectAndTradeSweepOnBOS" function to identify liquidity sweeps that occur after a break of structure and to execute trades accordingly, while also updating visuals and managing any existing positions 🎯📉. We begin with an early return if "MarketTrend" is 0, indicating neutral conditions with no active BOS—nothing to do yet 🚫. Next, we retrieve the current bid and ask prices using "SymbolInfoDouble" with "SYMBOL_BID" and "SYMBOL_ASK". For a bullish BOS scenario ("MarketTrend == 1") with a valid "current_swing_low" greater than 0.0, we check for an SSL sweep. This occurs when the previous bar’s low (via "iLow" at shift 1) dips below "current_swing_low", but its close (via "iClose" at shift 1) finishes back above that level and above the open (via "iOpen" at shift 1), confirming a bullish candle that traps short sellers 😈📈. When this condition is met, we log "Bullish BOS + SSL Sweep Detected" if "PrintLogs" is enabled, capture the sweep low and its time using "iLow" and "iTime" at shift 1, and call "DrawSweepRectangle" with the prefix "SSL_Rect_", the time, "current_swing_low", the sweep low, "clr_SSL_Rect", and true to indicate an SSL sweep. We then mirror this logic for a bearish BOS ("MarketTrend == -1") with a valid "current_swing_high". In this case, we check whether the previous bar’s high exceeded "current_swing_high", but closed back below it and below the open—confirming a bearish candle that traps long positions 🐻📉. If detected, we log "Bearish BOS + BSL Sweep Detected", capture the sweep high and time, draw the sweep rectangle using "BSL_Rect_", "clr_BSL_Rect", and false for BSL, and draw the break level using "BSL_Line_", "clr_BSL_Line", direction -1, and the label "BSL". We then call "CloseOpposite" with false to close any buy positions, calculate the stop-loss above the sweep high plus the buffer, set the entry to the bid price, compute risk as stop-loss minus entry, and calculate take-profit as entry minus risk multiplied by the ratio. A sell trade is opened using "obj_Trade.Sell" with the comment "BOS BSL Sell". If the trade is successful, we draw an entry arrow with false for sell and reset the trend; if not, we log the failure for visibility ⚠️. With this function in place, we simply call it inside the tick handler, and we get the following outcome.
LIQUIDITY SWEEP ON BOS TEST GIF
From the visualization, we can see that we detect, trade, and manage the liquidity sweep setups, hence achieving our objectives. The thing that remains is backtesting the program, and that is handled in the next section.

Backtesting

After thorough backtesting, we have the following results. Backtest graph:
GRAPH
Backtest report:
REPORT

Conclusion


In conclusion, we’ve built a Liquidity Sweep on Break of Structure (BoS) system in MQL5 that blends structure, liquidity, and clean visuals into one smart trading approach 🧠📈. The system detects swings over the selected input length and labels those swing points to establish the prevailing trend. It then watches for liquidity sweeps—specifically, a wick pushing beyond a swing level followed by a close back inside a strong directional candle 🎣✨. Trading logic is straightforward and disciplined: buys are triggered on Sell Side Liquidity (SSL) sweeps during a bullish BOS, while sells are executed on Buy Side Liquidity (BSL) sweeps during a bearish BOS. Trade management is handled dynamically, with calculated entries, stop-losses, and take-profits, a maximum trades limit to prevent overexposure, and automatic closing of opposite positions for consistency and control 🛡️📊. On the visualization side, the chart stays both informative and readable. Swing points are marked with icons, breaks of structure are shown with dashed lines, sweep zones are highlighted using filled rectangles, entries are clearly indicated with arrows, and font sizes adapt automatically as you zoom—so nothing gets lost in translation 👀✨.
Disclaimer: This article is for educational purposes only. Trading carries significant financial risks, and market volatility may result in losses. Thorough backtesting and careful risk management are essential before deploying this program in live markets ⚠️💼.
With this Liquidity Sweep on Break of Structure strategy, you now have the tools to spot manipulative wicks after BOS and trade high-quality reversal setups with confidence. You’re well-positioned for further optimization and refinement on your trading journey—happy trading! 🚀📉😄

Attached Files

Disclaimer: The ideas and strategies presented in this resource are solely those of the author and are intended for informational and educational purposes only. They do not constitute financial advice, and past performance is not indicative of future results. All materials, including but not limited to text, images, files, and any downloadable content, are protected by copyright and intellectual property laws and are the exclusive property of Forex Algo-Trader or its licensors. Reproduction, distribution, modification, or commercial use of these materials without prior written consent from Forex Algo-Trader is strictly prohibited and may result in legal action. Users are advised to exercise extreme caution, perform thorough independent research, and consult with qualified financial professionals before implementing any trading strategies or decisions based on this resource, as trading in financial markets involves significant risk of loss.

Recent Comments

Go to discussion to Comment or View other Comments

No comments yet. Be the first to comment!

Banner
🤖 Building a ChatGPT AI Trade Brain in MQL5
What’s the point of an AI trading assistant that talks but never acts? In this article, we transfo...
2026-05-15 16:50:14
Banner
Grid Scalper MA MT5 EA Review (2026): The Forever-Free MetaTrader 5 Robot
Grid Scalper MA MT5 EA Review (2026): The forever-free MetaTrader 5 robot that's quietly earning a 4...
2026-05-14 19:35:18
Banner
Supercharging Your TPO Market Profile With Volume Intelligence
You've got a TPO market profile that tracks where price spent its time — now imagine giving it a f...
2026-04-03 16:05:42