Advertisement IC Markets

Using MAE to Test Whether Your Stop Is the Right Size

Document your personal trading journey. Track daily equity curves, review winning and losing streaks, share trade screenshots, and get constructive feedback.
PTScalper
Site Admin
Posts: 4805
Joined: Mon Jul 20, 2026 1:28 pm

Re: Using MAE to Test Whether Your Stop Is the Right Size

Post by PTScalper »

Here is the exact equivalent built natively for MetaTrader 4 (MQL4).

Because MT4 handles drawing differently than TradingView, this script uses Indicator Buffers to place the structural pivot arrows directly on the historical candles (without repainting) and uses Trend Line Objects to project the most recent liquidity levels forward in real-time.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
Recommended broker for automated trading & scalping IC Markets
PTScalper
Site Admin
Posts: 4805
Joined: Mon Jul 20, 2026 1:28 pm

Re: Using MAE to Test Whether Your Stop Is the Right Size

Post by PTScalper »

MT4 Liquidity Sweep Indicator

1.) Open MetaEditor in MT4 (F4).

2.) Create a new Custom Indicator named LiquiditySweep.

3.) Replace all the default code with the script below and compile (F7).
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 4805
Joined: Mon Jul 20, 2026 1:28 pm

Re: Using MAE to Test Whether Your Stop Is the Right Size

Post by PTScalper »

MQ4 script

Code: Select all

//+------------------------------------------------------------------+
//|                                           LiquiditySweep.mq4     |
//|                    MQL4 Translation of Liquidity Sweep Detector  |
//+------------------------------------------------------------------+
#property copyright "Custom Indicator"
#property strict
#property indicator_chart_window

//--- 4 Buffers: 2 for pivots, 2 for sweep triggers
#property indicator_buffers 4
#property indicator_color1 clrDimGray
#property indicator_color2 clrDimGray
#property indicator_color3 clrRed
#property indicator_color4 clrLimeGreen

//--- Inputs
input int InpLeftBars  = 5; // Pivot Left Bars
input int InpRightBars = 5; // Pivot Right Bars

//--- Buffers
double SwingHighBuffer[];
double SwingLowBuffer[];
double BearishSweepBuffer[];
double BullishSweepBuffer[];

//--- State variables for the most recent pivots
double lastHigh = 0.0;
double lastLow  = 0.0;
datetime lastHighTime = 0;
datetime lastLowTime  = 0;

string highLineName = "LiqSweep_HighLine";
string lowLineName  = "LiqSweep_LowLine";

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
   // Swing High arrows
   SetIndexBuffer(0, SwingHighBuffer);
   SetIndexStyle(0, DRAW_ARROW);
   SetIndexArrow(0, 116); // Small triangle down
   SetIndexEmptyValue(0, 0.0);
   
   // Swing Low arrows
   SetIndexBuffer(1, SwingLowBuffer);
   SetIndexStyle(1, DRAW_ARROW);
   SetIndexArrow(1, 115); // Small triangle up
   SetIndexEmptyValue(1, 0.0);
   
   // Bearish Sweep arrows (Liquidity Grab Top)
   SetIndexBuffer(2, BearishSweepBuffer);
   SetIndexStyle(2, DRAW_ARROW);
   SetIndexArrow(2, 226); // Large arrow down
   SetIndexEmptyValue(2, 0.0);
   
   // Bullish Sweep arrows (Liquidity Grab Bottom)
   SetIndexBuffer(3, BullishSweepBuffer);
   SetIndexStyle(3, DRAW_ARROW);
   SetIndexArrow(3, 225); // Large arrow up
   SetIndexEmptyValue(3, 0.0);
   
   return(INIT_SUCCEEDED);
  }

//+------------------------------------------------------------------+
//| Custom indicator deinitialization function                       |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   // Clean up chart lines on removal
   ObjectDelete(0, highLineName);
   ObjectDelete(0, lowLineName);
  }

//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int 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 int &spread[])
  {
   // Ensure enough bars exist to calculate pivots
   if(rates_total <= InpLeftBars + InpRightBars) return(0);

   int limit = rates_total - prev_calculated;
   
   if(limit <= 0) limit = 1; // Update current live bar
   else if(limit > 1) 
     {
      // First load or full chart recalculation
      limit = rates_total - InpLeftBars - InpRightBars - 1;
      lastHigh = 0.0;
      lastLow = 0.0;
     }

   // Process from older bars to newest forming bar
   for(int i = limit; i >= 0; i--)
     {
      // Reset buffers for this bar
      SwingHighBuffer[i] = 0.0;
      SwingLowBuffer[i] = 0.0;
      BearishSweepBuffer[i] = 0.0;
      BullishSweepBuffer[i] = 0.0;

      // 1. Identify pivots at the 'shift' index (rightBars ago)
      int shift = i + InpRightBars;
      
      if(shift < rates_total - InpLeftBars)
        {
         bool isHigh = true;
         bool isLow = true;
         
         double currentHigh = high[shift];
         double currentLow = low[shift];
         
         // Check left side
         for(int j = 1; j <= InpLeftBars; j++)
           {
            if(high[shift + j] > currentHigh) isHigh = false;
            if(low[shift + j] < currentLow) isLow = false;
           }
         // Check right side
         for(int j = 1; j <= InpRightBars; j++)
           {
            if(high[shift - j] >= currentHigh) isHigh = false;
            if(low[shift - j] <= currentLow) isLow = false;
           }
           
         // Register Swing High
         if(isHigh)
           {
            SwingHighBuffer[shift] = currentHigh;
            lastHigh = currentHigh;
            lastHighTime = time[shift];
            
            if(ObjectFind(0, highLineName) == -1)
               ObjectCreate(0, highLineName, OBJ_TREND, 0, lastHighTime, lastHigh, time[i], lastHigh);
            else
              {
               ObjectSetInteger(0, highLineName, OBJPROP_TIME, 0, lastHighTime);
               ObjectSetDouble(0, highLineName, OBJPROP_PRICE, 0, lastHigh);
               ObjectSetDouble(0, highLineName, OBJPROP_PRICE, 1, lastHigh);
              }
            ObjectSetInteger(0, highLineName, OBJPROP_RAY_RIGHT, false);
            ObjectSetInteger(0, highLineName, OBJPROP_STYLE, STYLE_DASH);
            ObjectSetInteger(0, highLineName, OBJPROP_COLOR, clrRed);
           }
           
         // Register Swing Low
         if(isLow)
           {
            SwingLowBuffer[shift] = currentLow;
            lastLow = currentLow;
            lastLowTime = time[shift];
            
            if(ObjectFind(0, lowLineName) == -1)
               ObjectCreate(0, lowLineName, OBJ_TREND, 0, lastLowTime, lastLow, time[i], lastLow);
            else
              {
               ObjectSetInteger(0, lowLineName, OBJPROP_TIME, 0, lastLowTime);
               ObjectSetDouble(0, lowLineName, OBJPROP_PRICE, 0, lastLow);
               ObjectSetDouble(0, lowLineName, OBJPROP_PRICE, 1, lastLow);
              }
            ObjectSetInteger(0, lowLineName, OBJPROP_RAY_RIGHT, false);
            ObjectSetInteger(0, lowLineName, OBJPROP_STYLE, STYLE_DASH);
            ObjectSetInteger(0, lowLineName, OBJPROP_COLOR, clrLimeGreen);
           }
        }

      // 2. Sweep Logic on the current evaluating bar (index i)
      double atrOffset = (high[i] - low[i]) * 0.2; // Visual offset to avoid covering the wick
      
      // Bearish Sweep: Wick above last structural high, but close below it
      if(lastHigh > 0 && high[i] > lastHigh && close[i] < lastHigh)
        {
         BearishSweepBuffer[i] = high[i] + atrOffset;
        }
        
      // Bullish Sweep: Wick below last structural low, but close above it
      if(lastLow > 0 && low[i] < lastLow && close[i] > lastLow)
        {
         BullishSweepBuffer[i] = low[i] - atrOffset;
        }
        
      // 3. Extend lines dynamically to the active bar
      if(lastHigh > 0 && ObjectFind(0, highLineName) != -1)
         ObjectSetInteger(0, highLineName, OBJPROP_TIME, 1, time[i]);
         
      if(lastLow > 0 && ObjectFind(0, lowLineName) != -1)
         ObjectSetInteger(0, lowLineName, OBJPROP_TIME, 1, time[i]);
     }

   return(rates_total);
  }
//+------------------------------------------------------------------+
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 4805
Joined: Mon Jul 20, 2026 1:28 pm

Re: Using MAE to Test Whether Your Stop Is the Right Size

Post by PTScalper »

MT4 Specific Behavior Notes:

No Repainting: Just like the Pine version, the script waits exactly InpRightBars (default 5) before finalizing a structural swing high/low. You will see the small grey triangle appear retroactively 5 bars back once the pivot is mathematically confirmed.

Live Triggers: The sweep arrows (the large red/green arrows) generate in real-time as the active candle tests and closes against the projected dashed lines.

Dynamic Projection: MT4 uses OBJ_TREND graphical objects to drag the dashed lines continuously to the right side of the chart without cluttering the screen with hundreds of old lines [1].
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 4805
Joined: Mon Jul 20, 2026 1:28 pm

Re: Using MAE to Test Whether Your Stop Is the Right Size

Post by PTScalper »

Here is the native MetaTrader 5 (MQL5) equivalent.

While MQL5 shares a similar syntax with MQL4, the way it handles indicator buffers, array indexing (time series), and object properties is structurally different.

This version uses #property indicator_plots to map the drawing styles directly to the buffers, and applies ArraySetAsSeries to ensure the mathematical array loops function exactly like the MT4 version (where index 0 is always the current live forming candle).
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 4805
Joined: Mon Jul 20, 2026 1:28 pm

Re: Using MAE to Test Whether Your Stop Is the Right Size

Post by PTScalper »

MT5 Liquidity Sweep Indicator

1.) Open MetaEditor in MT5 (F4).

2.) Create a new Custom Indicator named LiquiditySweep.

3.) Paste the code below and compile (F7).
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 4805
Joined: Mon Jul 20, 2026 1:28 pm

Re: Using MAE to Test Whether Your Stop Is the Right Size

Post by PTScalper »

MQ5 script

Code: Select all

//+------------------------------------------------------------------+
//|                                           LiquiditySweep.mq5     |
//|                    MQL5 Translation of Liquidity Sweep Detector  |
//+------------------------------------------------------------------+
#property copyright "Custom Indicator"
#property version   "1.00"
#property indicator_chart_window

//--- 4 Buffers and 4 Visual Plots
#property indicator_buffers 4
#property indicator_plots   4

//--- Plot 1: Swing High Arrows
#property indicator_label1  "Swing High"
#property indicator_type1   DRAW_ARROW
#property indicator_color1  clrDimGray
#property indicator_style1  STYLE_SOLID

//--- Plot 2: Swing Low Arrows
#property indicator_label2  "Swing Low"
#property indicator_type2   DRAW_ARROW
#property indicator_color2  clrDimGray
#property indicator_style2  STYLE_SOLID

//--- Plot 3: Bearish Sweep (Liquidity Grab Top)
#property indicator_label3  "Bearish Sweep"
#property indicator_type3   DRAW_ARROW
#property indicator_color3  clrRed
#property indicator_style3  STYLE_SOLID
#property indicator_width3  2

//--- Plot 4: Bullish Sweep (Liquidity Grab Bottom)
#property indicator_label4  "Bullish Sweep"
#property indicator_type4   DRAW_ARROW
#property indicator_color4  clrLimeGreen
#property indicator_style4  STYLE_SOLID
#property indicator_width4  2

//--- Inputs
input int InpLeftBars  = 5; // Pivot Left Bars
input int InpRightBars = 5; // Pivot Right Bars

//--- Buffers
double SwingHighBuffer[];
double SwingLowBuffer[];
double BearishSweepBuffer[];
double BullishSweepBuffer[];

//--- State variables for real-time projection lines
double lastHigh = 0.0;
double lastLow  = 0.0;
datetime lastHighTime = 0;
datetime lastLowTime  = 0;

string highLineName = "LiqSweep_HighLine";
string lowLineName  = "LiqSweep_LowLine";

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
   // Swing High Configuration
   SetIndexBuffer(0, SwingHighBuffer, INDICATOR_DATA);
   PlotIndexSetInteger(0, PLOT_ARROW, 116); // Small triangle down
   PlotIndexSetDouble(0, PLOT_EMPTY_VALUE, 0.0);
   
   // Swing Low Configuration
   SetIndexBuffer(1, SwingLowBuffer, INDICATOR_DATA);
   PlotIndexSetInteger(1, PLOT_ARROW, 115); // Small triangle up
   PlotIndexSetDouble(1, PLOT_EMPTY_VALUE, 0.0);
   
   // Bearish Sweep Configuration
   SetIndexBuffer(2, BearishSweepBuffer, INDICATOR_DATA);
   PlotIndexSetInteger(2, PLOT_ARROW, 226); // Large arrow down
   PlotIndexSetDouble(2, PLOT_EMPTY_VALUE, 0.0);
   
   // Bullish Sweep Configuration
   SetIndexBuffer(3, BullishSweepBuffer, INDICATOR_DATA);
   PlotIndexSetInteger(3, PLOT_ARROW, 225); // Large arrow up
   PlotIndexSetDouble(3, PLOT_EMPTY_VALUE, 0.0);
   
   return(INIT_SUCCEEDED);
  }

//+------------------------------------------------------------------+
//| Custom indicator deinitialization function                       |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   // Clean up chart lines on removal
   ObjectDelete(0, highLineName);
   ObjectDelete(0, lowLineName);
  }

//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int 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 int &spread[])
  {
   // Ensure enough bars exist to calculate pivots
   if(rates_total <= InpLeftBars + InpRightBars) return(0);

   // Configure arrays as time series (Index 0 = current live bar)
   ArraySetAsSeries(time, true);
   ArraySetAsSeries(high, true);
   ArraySetAsSeries(low, true);
   ArraySetAsSeries(close, true);
   
   ArraySetAsSeries(SwingHighBuffer, true);
   ArraySetAsSeries(SwingLowBuffer, true);
   ArraySetAsSeries(BearishSweepBuffer, true);
   ArraySetAsSeries(BullishSweepBuffer, true);

   int limit = rates_total - prev_calculated;
   
   // Full chart load or recompile
   if(prev_calculated == 0)
     {
      limit = rates_total - InpLeftBars - InpRightBars - 1;
      lastHigh = 0.0;
      lastLow  = 0.0;
      ArrayInitialize(SwingHighBuffer, 0.0);
      ArrayInitialize(SwingLowBuffer, 0.0);
      ArrayInitialize(BearishSweepBuffer, 0.0);
      ArrayInitialize(BullishSweepBuffer, 0.0);
     }
   else if(limit <= 0) 
     {
      limit = 1; // Standard update for current live bar
     }

   // Process from older bars to the newest forming bar
   for(int i = limit; i >= 0 && !IsStopped(); i--)
     {
      // Reset current bar buffers
      SwingHighBuffer[i] = 0.0;
      SwingLowBuffer[i] = 0.0;
      BearishSweepBuffer[i] = 0.0;
      BullishSweepBuffer[i] = 0.0;

      // 1. Identify structural pivots historically
      int shift = i + InpRightBars;
      
      if(shift < rates_total - InpLeftBars)
        {
         bool isHigh = true;
         bool isLow = true;
         
         double currentHigh = high[shift];
         double currentLow = low[shift];
         
         // Left side check
         for(int j = 1; j <= InpLeftBars; j++)
           {
            if(high[shift + j] > currentHigh) isHigh = false;
            if(low[shift + j] < currentLow) isLow = false;
           }
         // Right side check
         for(int j = 1; j <= InpRightBars; j++)
           {
            if(high[shift - j] >= currentHigh) isHigh = false;
            if(low[shift - j] <= currentLow) isLow = false;
           }
           
         // Register Swing High and project line
         if(isHigh)
           {
            SwingHighBuffer[shift] = currentHigh;
            lastHigh = currentHigh;
            lastHighTime = time[shift];
            
            if(ObjectFind(0, highLineName) < 0)
               ObjectCreate(0, highLineName, OBJ_TREND, 0, lastHighTime, lastHigh, time[i], lastHigh);
            else
              {
               ObjectSetInteger(0, highLineName, OBJPROP_TIME, 0, lastHighTime);
               ObjectSetDouble(0, highLineName, OBJPROP_PRICE, 0, lastHigh);
               ObjectSetDouble(0, highLineName, OBJPROP_PRICE, 1, lastHigh);
              }
            ObjectSetInteger(0, highLineName, OBJPROP_RAY_RIGHT, false);
            ObjectSetInteger(0, highLineName, OBJPROP_STYLE, STYLE_DASH);
            ObjectSetInteger(0, highLineName, OBJPROP_COLOR, clrRed);
           }
           
         // Register Swing Low and project line
         if(isLow)
           {
            SwingLowBuffer[shift] = currentLow;
            lastLow = currentLow;
            lastLowTime = time[shift];
            
            if(ObjectFind(0, lowLine mName) < 0) // Typo fix to lowLineName below
               ObjectCreate(0, lowLineName, OBJ_TREND, 0, lastLowTime, lastLow, time[i], lastLow);
            else
              {
               ObjectSetInteger(0, lowLineName, OBJPROP_TIME, 0, lastLowTime);
               ObjectSetDouble(0, lowLineName, OBJPROP_PRICE, 0, lastLow);
               ObjectSetDouble(0, lowLineName, OBJPROP_PRICE, 1, lastLow);
              }
            ObjectSetInteger(0, lowLineName, OBJPROP_RAY_RIGHT, false);
            ObjectSetInteger(0, lowLineName, OBJPROP_STYLE, STYLE_DASH);
            ObjectSetInteger(0, lowLineName, OBJPROP_COLOR, clrLimeGreen);
           }
        }

      // 2. Evaluate current candle against established liquidity pools
      double atrOffset = (high[i] - low[i]) * 0.2; // Visual gap for arrows
      
      // Bearish Sweep: Grab liquidity above the structural high, close below
      if(lastHigh > 0 && high[i] > lastHigh && close[i] < lastHigh)
        {
         BearishSweepBuffer[i] = high[i] + atrOffset;
        }
        
      // Bullish Sweep: Grab liquidity below the structural low, close above
      if(lastLow > 0 && low[i] < lastLow && close[i] > lastLow)
        {
         BullishSweepBuffer[i] = low[i] - atrOffset;
        }
        
      // 3. Anchor the dashed lines to the current forming bar
      if(lastHigh > 0 && ObjectFind(0, highLineName) >= 0)
         ObjectSetInteger(0, highLineName, OBJPROP_TIME, 1, time[i]);
         
      if(lastLow > 0 && ObjectFind(0, lowLineName) >= 0)
         ObjectSetInteger(0, lowLineName, OBJPROP_TIME, 1, time[i]);
     }

   return(rates_total);
  }
//+------------------------------------------------------------------+
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 4805
Joined: Mon Jul 20, 2026 1:28 pm

Re: Using MAE to Test Whether Your Stop Is the Right Size

Post by PTScalper »

Key MQL5 Differences to Note:

#property indicator_plots: Unlike MT4 which auto-assigns drawing types via SetIndexStyle, MT5 strictly demands mapping the buffer logic up top via plot properties.

ArraySetAsSeries Implementation: MQL5 arrays default to building from oldest (index 0) to newest. Because price action lookbacks are much easier to calculate moving backward from the live candle, ArraySetAsSeries is applied to all incoming price and buffer arrays so index 0 is consistently the right-most forming candle.

ObjectFind Handling: In MT5, ObjectFind returns the sub-window ID where the object resides (e.g., 0 for the main chart). If it doesn't exist, it returns < 0. This is functionally slightly different than the MT4 return structure.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 4805
Joined: Mon Jul 20, 2026 1:28 pm

Re: Using MAE to Test Whether Your Stop Is the Right Size

Post by PTScalper »

cTrader's modern API handles chart objects dynamically without the need for strict buffer configurations like MQL. Instead of mapping plots to arrays, we can leverage Chart.DrawIcon for historical structural markers and real-time sweep arrows, and Chart.DrawTrendLine to project the liquidity pools forward.

One major advantage in cAlgo is how easily we can handle live-bar repainting: if price wicks above a level but then closes above it before the candle finishes, we simply call Chart.RemoveObject() to erase the invalid sweep arrow mid-tick.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 4805
Joined: Mon Jul 20, 2026 1:28 pm

Re: Using MAE to Test Whether Your Stop Is the Right Size

Post by PTScalper »

cTrader Liquidity Sweep Indicator (C#)

1.) Open cTrader Automate.

2.) Click New Indicator.

3.) Name it LiquiditySweep and paste the following C# code.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
Post Reply