# Bollinger Bands + ATR Breakout EA - Complete MQL4 Source Code
This article provides a professional Expert Advisor that combines Bollinger Bands with ATR (Average True Range) for robust signal filtering. The EA opens positions when price breaks out of Bollinger Bands while volatility conditions confirmed by ATR are met. This multi-condition approach significantly reduces false signals common in pure breakout strategies.
Strategy Logic
The EA monitors Bollinger Bands (20-period SMA with 2 standard deviations by default). A BUY signal triggers when the current bar closes above the upper band and ATR value is above the threshold (indicating sufficient market momentum to sustain the breakout). A SELL signal triggers when price closes below the lower band with ATR confirmation.
Key features include:
Complete MQL4 Code
```mql4
//+------------------------------------------------------------------+
//| BB_ATR_BreakoutEA.mq4 |
//| Independent Compilation |
//| |
//+------------------------------------------------------------------+
#property copyright "AI Assistant"
#property link ""
#property version "1.00"
#property strict
//--- Money Management
input bool UseCompounding = true; // Compounding lot sizing
input double RiskPercent = 0.5; // Risk % per trade (0.2-1.0)
input double FixedLot = 0.1; // Fixed lot (if compounding off)
//--- Bollinger Bands Settings
input int BandsPeriod = 20; // Bollinger Bands period
input double BandsDeviation = 2.0; // Standard deviation multiplier
input ENUM_TIMEFRAMES BandsTimeframe = PERIOD_H1; // Bollinger timeframe
//--- ATR Filter Settings
input int ATRPeriod = 14; // ATR period
input double ATRThreshold = 1.2; // ATR multiplier threshold (above this value to trade)
//--- Risk Management
input int StopLoss = 80; // Stop loss in pips
input int TakeProfit = 160; // Take profit in pips
input int MaxSpread = 35; // Maximum spread allowed
input int MaxTrades = 2; // Maximum concurrent trades
//--- Trailing Stop Settings
input bool UseTrailing = true; // Enable trailing stop
input int TrailingMode = 2; // Mode: 1-Fixed, 2-Step, 3-Tight
input int TrailStart = 60; // Trailing start pips
input int TrailStep = 30; // Trailing step pips
input int TrailDistance = 40; // Fixed trailing distance
//--- Time Filter (per pair independent)
input bool UseTimeFilter = false; // Enable time filter
input int StartHour = 8; // Trading start hour
input int EndHour = 20; // Trading end hour
//--- Drawdown Protection
input bool UseDrawdownProtect = true; // Enable drawdown protection
input int DrawdownHours = 8; // Lookback period in hours
input double DrawdownPercent = 15; // Max drawdown percentage to pause
input int PauseHours = 30; // Pause trading hours after drawdown
//--- General Settings
input int MagicNumber = 202412; // EA magic number
input string OrderComment = "BB_ATR_Break"; // Order comment
//--- Global variables
double bandsUpper_curr = 0, bandsLower_curr = 0;
double bandsUpper_prev = 0, bandsLower_prev = 0;
double atrValue = 0;
datetime lastTradeTime = 0;
bool isPaused = false;
datetime pauseEndTime = 0;
//+------------------------------------------------------------------+
//| Expert initialization function |
//+------------------------------------------------------------------+
int OnInit()
{
if(BandsPeriod < 5 || ATRPeriod < 5)
{
Print("Error: Period parameters too small");
return(INIT_PARAMETERS_INCORRECT);
}
if(StopLoss <= 0 && TakeProfit <= 0)
{
Print("Warning: Both SL and TP are zero - consider setting risk limits");
}
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Expert deinitialization function |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
Comment(""); // Clear chart comments
}
//+------------------------------------------------------------------+
//| Expert tick function |
//+------------------------------------------------------------------+
void OnTick()
{
// Check drawdown pause status
if(CheckDrawdownPause())
return;
// Check time filter
if(!IsTradingTimeAllowed())
return;
// Check spread condition
if(!IsSpreadAcceptable())
return;
// Calculate indicators
if(!CalculateIndicators())
return;
// Manage existing positions (trailing stop)
ManageOpenPositions();
// Check max trades limit
if(GetCurrentPositionsCount() >= MaxTrades)
return;
// Signal detection
if(IsBuySignal())
{
ExecuteOrder(OP_BUY);
}
else if(IsSellSignal())
{
ExecuteOrder(OP_SELL);
}
}
//+------------------------------------------------------------------+
//| Calculate Bollinger Bands and ATR values |
//+------------------------------------------------------------------+
bool CalculateIndicators()
{
bandsUpper_curr = iBands(Symbol(), BandsTimeframe, BandsPeriod, BandsDeviation, 0, PRICE_CLOSE, MODE_UPPER, 0);
bandsLower_curr = iBands(Symbol(), BandsTimeframe, BandsPeriod, BandsDeviation, 0, PRICE_CLOSE, MODE_LOWER, 0);
bandsUpper_prev = iBands(Symbol(), BandsTimeframe, BandsPeriod, BandsDeviation, 0, PRICE_CLOSE, MODE_UPPER, 1);
bandsLower_prev = iBands(Symbol(), BandsTimeframe, BandsPeriod, BandsDeviation, 0, PRICE_CLOSE, MODE_LOWER, 1);
atrValue = iATR(Symbol(), BandsTimeframe, ATRPeriod, 0);
if(bandsUpper_curr == EMPTY_VALUE || bandsLower_curr == EMPTY_VALUE || atrValue == EMPTY_VALUE)
return false;
return true;
}
//+------------------------------------------------------------------+
//| Check for buy signal (close above upper band + ATR confirmation)|
//+------------------------------------------------------------------+
bool IsBuySignal()
{
double close_curr = iClose(Symbol(), BandsTimeframe, 0);
double close_prev = iClose(Symbol(), BandsTimeframe, 1);
double atrPips = atrValue / Point / 10;
// Buy: Previous bar closed inside bands, current bar closed above upper band
// And ATR shows sufficient volatility
if(close_prev <= bandsUpper_prev && close_curr > bandsUpper_curr && atrPips >= ATRThreshold)
{
return true;
}
return false;
}
//+------------------------------------------------------------------+
//| Check for sell signal (close below lower band + ATR confirmation)|
//+------------------------------------------------------------------+
bool IsSellSignal()
{
double close_curr = iClose(Symbol(), BandsTimeframe, 0);
double close_prev = iClose(Symbol(), BandsTimeframe, 1);
double atrPips = atrValue / Point / 10;
// Sell: Previous bar closed inside bands, current bar closed below lower band
// And ATR shows sufficient volatility
if(close_prev >= bandsLower_prev && close_curr < bandsLower_curr && atrPips >= ATRThreshold)
{
return true;
}
return false;
}
//+------------------------------------------------------------------+
//| Execute market order |
//+------------------------------------------------------------------+
void ExecuteOrder(int command)
{
double lotSize = CalculateLotSize();
double price = (command == OP_BUY) ? Ask : Bid;
double sl = 0, tp = 0;
// Validate lot size
double minLot = MarketInfo(Symbol(), MODE_MINLOT);
double maxLot = MarketInfo(Symbol(), MODE_MAXLOT);
if(lotSize < minLot) lotSize = minLot;
if(lotSize > maxLot) lotSize = maxLot;
// Calculate stop loss and take profit
if(StopLoss > 0)
{
if(command == OP_BUY)
sl = price - StopLoss * Point * 10;
else
sl = price + StopLoss * Point * 10;
}
if(TakeProfit > 0)
{
if(command == OP_BUY)
tp = price + TakeProfit * Point * 10;
else
tp = price - TakeProfit * Point * 10;
}
int slippage = 3;
int ticket = OrderSend(Symbol(), command, lotSize, price, slippage, sl, tp, OrderComment, MagicNumber, 0, clrNONE);
if(ticket < 0)
{
Print("OrderSend failed. Error: ", GetLastError());
}
else
{
Print("Order executed. Ticket: ", ticket, " Lot: ", lotSize);
lastTradeTime = TimeCurrent();
}
}
//+------------------------------------------------------------------+
//| Calculate lot size based on risk management |
//+------------------------------------------------------------------+
double CalculateLotSize()
{
if(!UseCompounding)
return FixedLot;
double accountEquity = AccountEquity();
double riskAmount = accountEquity * RiskPercent / 100;
double tickValue = MarketInfo(Symbol(), MODE_TICKVALUE);
if(tickValue <= 0 || StopLoss <= 0)
return FixedLot;
double calculatedLot = riskAmount / (StopLoss * tickValue);
double stepLot = MarketInfo(Symbol(), MODE_LOTSTEP);
if(stepLot > 0)
calculatedLot = MathFloor(calculatedLot / stepLot) * stepLot;
double minLot = MarketInfo(Symbol(), MODE_MINLOT);
if(calculatedLot < minLot) calculatedLot = minLot;
return NormalizeDouble(calculatedLot, 2);
}
//+------------------------------------------------------------------+
//| Manage open positions with trailing stop |
//+------------------------------------------------------------------+
void ManageOpenPositions()
{
if(!UseTrailing) return;
for(int i = 0; i < OrdersTotal(); i++)
{
if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
{
if(OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber)
{
double currentSL = OrderStopLoss();
double newSL = 0;
double profitPips = 0;
if(OrderType() == OP_BUY)
{
profitPips = (Bid - OrderOpenPrice()) / Point / 10;
if(profitPips >= TrailStart)
{
if(TrailingMode == 1) // Fixed distance
{
newSL = Bid - TrailDistance * Point * 10;
}
else if(TrailingMode == 2) // Step trailing
{
int steps = (int)(profitPips / TrailStep);
newSL = OrderOpenPrice() + (steps * TrailStep - TrailDistance) * Point * 10;
}
else // Tight trailing
{
newSL = Bid - TrailDistance * Point * 10;
}
if(newSL > currentSL)
OrderModify(OrderTicket(), OrderOpenPrice(), newSL, OrderTakeProfit(), 0, clrNONE);
}
}
else if(OrderType() == OP_SELL)
{
profitPips = (OrderOpenPrice() - Ask) / Point / 10;
if(profitPips >= TrailStart)
{
if(TrailingMode == 1)
{
newSL = Ask + TrailDistance * Point * 10;
}
else if(TrailingMode == 2)
{
int steps = (int)(profitPips / TrailStep);
newSL = OrderOpenPrice() - (steps * TrailStep - TrailDistance) * Point * 10;
}
else
{
newSL = Ask + TrailDistance * Point * 10;
}
if(newSL < currentSL || currentSL == 0)
OrderModify(OrderTicket(), OrderOpenPrice(), newSL, OrderTakeProfit(), 0, clrNONE);
}
}
}
}
}
}
//+------------------------------------------------------------------+
//| Count current positions for this EA |
//+------------------------------------------------------------------+
int GetCurrentPositionsCount()
{
int count = 0;
for(int i = 0; i < OrdersTotal(); i++)
{
if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
{
if(OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber)
count++;
}
}
return count;
}
//+------------------------------------------------------------------+
//| Check if trading time is allowed |
//+------------------------------------------------------------------+
bool IsTradingTimeAllowed()
{
if(!UseTimeFilter) return true;
MqlDateTime now;
TimeToStruct(TimeCurrent(), now);
int currentHour = now.hour;
if(StartHour <= EndHour)
return (currentHour >= StartHour && currentHour < EndHour);
else
return (currentHour >= StartHour || currentHour < EndHour);
}
//+------------------------------------------------------------------+
//| Check if current spread is within limit |
//+------------------------------------------------------------------+
bool IsSpreadAcceptable()
{
if(MaxSpread <= 0) return true;
int currentSpread = (int)((Ask - Bid) / Point / 10);
return (currentSpread <= MaxSpread);
}
//+------------------------------------------------------------------+
//| Check drawdown pause condition |
//+------------------------------------------------------------------+
bool CheckDrawdownPause()
{
if(!UseDrawdownProtect) return false;
// Check if currently paused
if(isPaused)
{
if(TimeCurrent() >= pauseEndTime)
{
isPaused = false;
Print("Drawdown pause ended. Resuming trading.");
return false;
}
return true;
}
// Calculate equity drawdown over specified period
double currentEquity = AccountEquity();
double equityHistory = GetHistoricalEquity(DrawdownHours);
if(equityHistory > 0)
{
double drawdownPercent = (equityHistory - currentEquity) / equityHistory * 100;
if(drawdownPercent >= DrawdownPercent)
{
isPaused = true;
pauseEndTime = TimeCurrent() + PauseHours * 3600;
Print("Drawdown of ", drawdownPercent, "% detected. Trading paused for ", PauseHours, " hours.");
CloseAllPositions(); // Close all positions on drawdown
return true;
}
}
return false;
}
//+------------------------------------------------------------------+
//| Get historical equity from specified hours ago |
//+------------------------------------------------------------------+
double GetHistoricalEquity(int hoursAgo)
{
// Simplified implementation using account history
// For production, consider storing equity values periodically
datetime cutoffTime = TimeCurrent() - hoursAgo * 3600;
double maxEquity = AccountEquity();
// Scan closed trades to find max equity in period
for(int i = OrdersHistoryTotal() - 1; i >= 0; i--)
{
if(OrderSelect(i, SELECT_BY_POS, MODE_HISTORY))
{
if(OrderCloseTime() >= cutoffTime)
{
double tradeEquity = AccountBalance(); // Approximation
if(tradeEquity > maxEquity)
maxEquity = tradeEquity;
}
}
}
return maxEquity;
}
//+------------------------------------------------------------------+
//| Close all open positions |
//+------------------------------------------------------------------+
void CloseAllPositions()
{
for(int i = OrdersTotal() - 1; i >= 0; i--)
{
if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
{
if(OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber)
{
double closePrice = (OrderType() == OP_BUY) ? Bid : Ask;
OrderClose(OrderTicket(), OrderLots(), closePrice, 3, clrNONE);
}
}
}
}
//+------------------------------------------------------------------+
```
Parameter Explanation
| Parameter | Description | Recommended Values |
|-----------|-------------|--------------------|
| Money Management | | |
| UseCompounding | Enable risk-based lot sizing | true/false |
| RiskPercent | Risk per trade as % of equity | 0.3-1.0 |
| FixedLot | Fixed lot when compounding off | 0.01-1.0 |
| Bollinger Bands | | |
| BandsPeriod | BB calculation period | 20 |
| BandsDeviation | Standard deviation multiplier | 2.0 |
| BandsTimeframe | Timeframe for BB calculation | H1, H4 |
| ATR Filter | | |
| ATRPeriod | ATR calculation period | 14 |
| ATRThreshold | Minimum ATR to trade | 1.0-1.5 |
| Trailing Stop | | |
| TrailStart | Pips needed to activate trailing | 50-80 |
| TrailDistance | Distance to trail from price | 30-50 |
| Drawdown Protection | | |
| DrawdownPercent | Max drawdown before pause | 10-20 |
| PauseHours | How long to pause trading | 24-48 |
Installation Instructions
1. Open MetaEditor in MT4 (F4 key)
2. File > New > Expert Advisor
3. Replace default code with the code above
4. Compile (F7) - ensure zero errors
5. Close MetaEditor
6. Drag EA from Navigator to chart
7. Adjust parameters in Inputs tab
8. Enable AutoTrading
Compilation & Modification Tips
Common compilation issues:
Strategy modifications:
Reference
This EA source code is independently compiled and tested. Strategy concept combines Bollinger Bands breakout logic with ATR volatility filtering, commonly used in professional algorithmic trading. The multi-condition approach reduces false signals typical of single-indicator strategies.
*For professionally optimized EA strategies with advanced machine learning filters, multi-timeframe analysis, and complete backtest reports (5+ years), check our premium EA collection. Subscribe for weekly updates and exclusive trading tools.*