Summary: Gold Breakout Engine EA is an MQL4 expert advisor for XAUUSD that identifies volatility compression breakouts with Order Block trend confirmation and ATR-based dynamic grid recovery. Suitable for M15 timeframe.
Gold Breakout Engine EA is designed specifically for XAUUSD, combining three core modules: volatility breakout detection, Order Block trend filtering, and ATR-based grid recovery. The EA first identifies volatility compression patterns (narrowing ranges), then confirms the breakout direction using a simplified Order Block concept (last swing high/low structure), and finally manages trade series with an ATR-adaptive grid that closes all positions when profit targets are met. This multi-layer filtering reduces false breakouts while the controlled grid structure helps recover drawdowns without infinite risk.
Recommended Timeframe: M15
Trading Logic:
1. Volatility Compression: Detect when current ATR(14) drops below 0.65x average ATR of last 20 bars.
2. Breakout Direction: When price closes above the highest high of compression period → potential buy; below lowest low → potential sell.
3. Order Block Filter: Confirm that the last swing structure supports the breakout direction (price has broken a previous key level).
4. Grid Execution: First trade with fixed stop loss (ATR × 1.2), then add grid levels at ATR-distance intervals with 1.5x lot multiplier (max 3 levels).
5. Exit: Close all positions when total floating profit reaches 1% of account balance or individual TP hits.
```mql4
//+------------------------------------------------------------------+
//| GoldBreakoutEngineEA.mq4 |
//+------------------------------------------------------------------+
#property copyright ""
#property link ""
#property version "1.00"
#property strict
//--- input parameters with comments
input double BaseLotSize = 0.01; // Base lot size for first trade
input int ATRPeriod = 14; // ATR period for volatility detection
input double CompressionRatio = 0.65; // Volatility compression ratio (current ATR / avg ATR)
input int LookbackBars = 20; // Lookback bars for compression detection
input int OBLookback = 10; // Order Block lookback (swing detection)
input double GridDistanceATR = 1.2; // Grid distance as multiple of ATR
input double GridLotMultiplier = 1.5; // Lot multiplier for each grid level
input int MaxGridLevels = 3; // Maximum grid levels (0 = disable grid)
input double TPPercent = 1.0; // Take profit percentage of balance (close all)
input double StopLossATR = 1.2; // Stop loss as multiple of ATR
input int MagicNumber = 202601; // Unique EA identifier
input int MaxSpread = 35; // Maximum allowed spread in points
input double DailyLossLimit = 5.0; // Daily loss limit as percentage
input bool UseFridayClose = true; // Close trades before Friday 20:00 GMT
//--- global variables
double dailyStartBalance = 0;
datetime lastBarTime = 0;
bool fridayCloseExecuted = false;
double compressionHigh = 0;
double compressionLow = 0;
bool compressionActive = false;
datetime compressionStartTime = 0;
//+------------------------------------------------------------------+
//| Expert initialization function |
//+------------------------------------------------------------------+
int OnInit()
{
dailyStartBalance = AccountBalance();
lastBarTime = 0;
fridayCloseExecuted = false;
compressionHigh = 0;
compressionLow = 0;
compressionActive = false;
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Expert deinitialization function |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
Comment("");
}
//+------------------------------------------------------------------+
//| Calculate average ATR over lookback period |
//+------------------------------------------------------------------+
double GetAvgATR()
{
double sum = 0;
for(int i = 1; i <= LookbackBars; i++)
sum += iATR(Symbol(), PERIOD_M15, ATRPeriod, i);
return sum / LookbackBars;
}
//+------------------------------------------------------------------+
//| Detect volatility compression pattern |
//+------------------------------------------------------------------+
bool DetectCompression()
{
double currentATR = iATR(Symbol(), PERIOD_M15, ATRPeriod, 1);
double avgATR = GetAvgATR();
if(avgATR <= 0) return false;
bool isCompressed = (currentATR < avgATR * CompressionRatio);
if(isCompressed && !compressionActive)
{
// Start new compression tracking
compressionActive = true;
compressionStartTime = Time[0];
// Find highest high and lowest low during compression
compressionHigh = iHigh(Symbol(), PERIOD_M15, iHighest(Symbol(), PERIOD_M15, MODE_HIGH, 5, 1));
compressionLow = iLow(Symbol(), PERIOD_M15, iLowest(Symbol(), PERIOD_M15, MODE_LOW, 5, 1));
}
else if(!isCompressed && compressionActive)
{
// Compression ended, check breakout
compressionActive = false;
double close1 = iClose(Symbol(), PERIOD_M15, 1);
double high1 = iHigh(Symbol(), PERIOD_M15, 1);
double low1 = iLow(Symbol(), PERIOD_M15, 1);
// Bullish breakout: close above compression high
if(close1 > compressionHigh)
return true;
// Bearish breakout: close below compression low
else if(close1 < compressionLow)
return true;
}
return false;
}
//+------------------------------------------------------------------+
//| Simplified Order Block trend check (swing structure) |
//+------------------------------------------------------------------+
int CheckOrderBlockTrend()
{
double highs[];
double lows[];
ArrayResize(highs, OBLookback);
ArrayResize(lows, OBLookback);
for(int i = 0; i < OBLookback; i++)
{
highs[i] = iHigh(Symbol(), PERIOD_M15, i);
lows[i] = iLow(Symbol(), PERIOD_M15, i);
}
// Find recent swing high and low
double recentHigh = highs[ArrayMaximum(highs, 0, OBLookback/2)];
double recentLow = lows[ArrayMinimum(lows, 0, OBLookback/2)];
double currentPrice = iClose(Symbol(), PERIOD_M15, 1);
// Bullish: price broke above recent swing high
if(currentPrice > recentHigh)
return 1;
// Bearish: price broke below recent swing low
else if(currentPrice < recentLow)
return -1;
return 0;
}
//+------------------------------------------------------------------+
//| Check if current position series exists |
//+------------------------------------------------------------------+
bool HasActiveSeries()
{
for(int i = OrdersTotal()-1; i >= 0; i--)
{
if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
{
if(OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber)
return true;
}
}
return false;
}
//+------------------------------------------------------------------+
//| Count current grid levels (positions in series) |
//+------------------------------------------------------------------+
int CountGridLevels()
{
int count = 0;
for(int i = OrdersTotal()-1; i >= 0; i--)
{
if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
{
if(OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber)
count++;
}
}
return count;
}
//+------------------------------------------------------------------+
//| Get current direction of the position series (1=buy, -1=sell) |
//+------------------------------------------------------------------+
int GetSeriesDirection()
{
for(int i = OrdersTotal()-1; i >= 0; i--)
{
if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
{
if(OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber)
{
if(OrderType() == OP_BUY) return 1;
if(OrderType() == OP_SELL) return -1;
}
}
}
return 0;
}
//+------------------------------------------------------------------+
//| Get last order price in the series |
//+------------------------------------------------------------------+
double GetLastOrderPrice()
{
datetime latestTime = 0;
double latestPrice = 0;
for(int i = OrdersTotal()-1; i >= 0; i--)
{
if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
{
if(OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber)
{
if(OrderOpenTime() > latestTime)
{
latestTime = OrderOpenTime();
latestPrice = OrderOpenPrice();
}
}
}
}
return latestPrice;
}
//+------------------------------------------------------------------+
//| Calculate total floating profit in dollars |
//+------------------------------------------------------------------+
double GetTotalFloatingProfit()
{
double total = 0;
for(int i = OrdersTotal()-1; i >= 0; i--)
{
if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
{
if(OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber)
total += OrderProfit() + OrderSwap() + OrderCommission();
}
}
return total;
}
//+------------------------------------------------------------------+
//| Close all positions in the series |
//+------------------------------------------------------------------+
void CloseAllSeriesOrders()
{
for(int i = OrdersTotal()-1; i >= 0; i--)
{
if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
{
if(OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber)
{
if(OrderType() == OP_BUY)
OrderClose(OrderTicket(), OrderLots(), Bid, 5, clrNONE);
else if(OrderType() == OP_SELL)
OrderClose(OrderTicket(), OrderLots(), Ask, 5, clrNONE);
}
}
}
}
//+------------------------------------------------------------------+
//| Add next grid level |
//+------------------------------------------------------------------+
void AddGridLevel(int direction, double distance)
{
int currentLevels = CountGridLevels();
if(currentLevels >= MaxGridLevels) return;
double lot = BaseLotSize * MathPow(GridLotMultiplier, currentLevels);
if(lot > 1.0) lot = 1.0;
double atr = iATR(Symbol(), PERIOD_M15, ATRPeriod, 1);
double slDistance = atr * StopLossATR;
int cmd = -1;
double price = 0, sl = 0, tp = 0;
if(direction == 1) // Buy series
{
cmd = OP_BUY;
price = Ask;
sl = price - slDistance;
}
else if(direction == -1) // Sell series
{
cmd = OP_SELL;
price = Bid;
sl = price + slDistance;
}
if(cmd != -1)
{
int ticket = OrderSend(Symbol(), cmd, lot, price, 5, sl, 0, "Grid Level", MagicNumber, 0, clrNONE);
if(ticket < 0)
Print("Grid level add failed: ", GetLastError());
}
}
//+------------------------------------------------------------------+
//| Expert tick function |
//+------------------------------------------------------------------+
void OnTick()
{
// Daily equity protection
double currentEquity = AccountEquity();
double lossPercent = (dailyStartBalance - currentEquity) / dailyStartBalance * 100;
if(lossPercent >= DailyLossLimit)
{
Comment("Daily loss limit reached. No new trades.");
return;
}
// Friday close before weekend
if(UseFridayClose && !fridayCloseExecuted)
{
datetime currentTime = TimeCurrent();
if(TimeDayOfWeek(currentTime) == 5 && TimeHour(currentTime) >= 20)
{
CloseAllSeriesOrders();
fridayCloseExecuted = true;
return;
}
if(TimeDayOfWeek(currentTime) != 5)
fridayCloseExecuted = false;
}
// Spread filter
if(MarketInfo(Symbol(), MODE_SPREAD) > MaxSpread)
{
Comment("Spread too high: ", MarketInfo(Symbol(), MODE_SPREAD));
return;
}
// New bar logic (M15)
if(Time[0] == lastBarTime)
return;
lastBarTime = Time[0];
// Check if we have an active position series
bool hasSeries = HasActiveSeries();
if(hasSeries)
{
// Manage existing series: check profit target
double totalProfit = GetTotalFloatingProfit();
double targetProfit = AccountBalance() * TPPercent / 100;
if(totalProfit >= targetProfit)
{
CloseAllSeriesOrders();
Print("Profit target reached: ", totalProfit);
return;
}
// Check if we need to add next grid level
int currentLevels = CountGridLevels();
if(currentLevels > 0 && currentLevels < MaxGridLevels)
{
int direction = GetSeriesDirection();
double lastPrice = GetLastOrderPrice();
double atr = iATR(Symbol(), PERIOD_M15, ATRPeriod, 1);
double gridDistance = atr * GridDistanceATR;
double currentPrice = (direction == 1) ? Bid : Ask;
double priceDiff = MathAbs(currentPrice - lastPrice);
if(priceDiff >= gridDistance)
{
AddGridLevel(direction, gridDistance);
}
}
}
else
{
// No active series: look for new setup
// Step 1: Detect breakout from volatility compression
bool breakoutDetected = DetectCompression();
if(!breakoutDetected)
{
Comment("No breakout detected");
return;
}
// Step 2: Confirm Order Block trend direction
int obTrend = CheckOrderBlockTrend();
if(obTrend == 0)
{
Comment("No clear Order Block direction");
return;
}
// Step 3: Determine breakout direction from compression
double close1 = iClose(Symbol(), PERIOD_M15, 1);
int breakoutDir = 0;
if(close1 > compressionHigh) breakoutDir = 1;
else if(close1 < compressionLow) breakoutDir = -1;
// Step 4: Align breakout with OB trend
if(breakoutDir != obTrend)
{
Comment("Breakout and OB trend misaligned");
return;
}
// Step 5: Open first trade
double atr = iATR(Symbol(), PERIOD_M15, ATRPeriod, 1);
double slDistance = atr * StopLossATR;
double lot = BaseLotSize;
int cmd = -1;
double price = 0, sl = 0;
if(breakoutDir == 1) // Long
{
cmd = OP_BUY;
price = Ask;
sl = price - slDistance;
}
else if(breakoutDir == -1) // Short
{
cmd = OP_SELL;
price = Bid;
sl = price + slDistance;
}
if(cmd != -1)
{
int ticket = OrderSend(Symbol(), cmd, lot, price, 5, sl, 0, "Breakout Entry", MagicNumber, 0, clrNONE);
if(ticket < 0)
Print("First order failed: ", GetLastError());
else
Print("Breakout trade opened. Direction: ", cmd==OP_BUY?"BUY":"SELL");
}
}
}
//+------------------------------------------------------------------+
```
Reference: Original MQL4 code inspired by institutional SMC concepts and volatility breakout strategies .
Disclaimer: Gold trading involves substantial risk due to high volatility. This EA is provided as-is without any guarantee of profit. Test thoroughly on a demo account for at least 2 months before live deployment. Past performance does not guarantee future results.