Summary: Professional MT4 EA implementing Bollinger Bands mean reversion with ATR volatility confirmation. Features dynamic take profit, breakeven stop, spread control, and risk percentage position sizing. Complete source code included.




Bollinger Bands Mean Reversion EA - Complete MQL4 Source Code



This article provides a fully functional Expert Advisor based on the mean reversion principle using Bollinger Bands. Unlike trend-following strategies, this EA trades counter-trend: it sells when price touches the upper band and buys when price touches the lower band, expecting price to revert to the middle band .

Strategy Logic



The EA uses Bollinger Bands with configurable period and deviation. When price touches or exceeds the lower band, it opens a BUY position. When price touches or exceeds the upper band, it opens a SELL position. An ATR filter confirms sufficient market volatility before entry, avoiding false signals in low-volatility sideways markets .

Complete MQL4 Code



``mql4
//+------------------------------------------------------------------+
//| BB_MeanReversionEA.mq4 |
//| Independent Compilation |
//| |
//+------------------------------------------------------------------+
#property copyright "AI Assistant"
#property link ""
#property version "1.00"
#property strict

//--- Input Parameters
input double LotSize = 0.1; // Fixed lot size (if RiskPercent=0)
input double RiskPercent = 1.0; // Risk % of free margin (0=use fixed lot)
input int BandsPeriod = 20; // Bollinger Bands period
input double BandsDeviation = 2.0; // Bollinger Bands deviation
input int ATRPeriod = 14; // ATR period for volatility filter
input double MinATR = 10.0; // Minimum ATR value in points (0=disable)
input int StopLoss = 60; // Stop loss in pips
input int TakeProfit = 120; // Take profit in pips
input bool UseDynamicTP = true; // Dynamic TP = ATR multiplier
input double TPMultiplier = 2.5; // TP multiplier for ATR (if dynamic)
input int BreakEvenPips = 30; // Pips to breakeven (0=disable)
input int MaxSpread = 30; // Maximum allowed spread in pips
input int Slippage = 10; // Maximum slippage
input int MagicNumber = 202412; // EA magic number
input bool CloseOpposite = true; // Close opposite positions on new signal

//--- Global variables
double upperBand = 0, lowerBand = 0, middleBand = 0;
double atrValue = 0;
int pointMultiplier = 10; // For 5-digit brokers

//+------------------------------------------------------------------+
//| Expert initialization function |
//+------------------------------------------------------------------+
int OnInit()
{
// Check for 4-digit vs 5-digit broker
if(Digits == 3 || Digits == 5)
pointMultiplier = 10;
else if(Digits == 2 || Digits == 4)
pointMultiplier = 1;

if(BandsPeriod < 2)
{
Print("Error: Bands period must be at least 2");
return(INIT_PARAMETERS_INCORRECT);
}
return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| Expert deinitialization function |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
}

//+------------------------------------------------------------------+
//| Expert tick function |
//+------------------------------------------------------------------+
void OnTick()
{
// Spread check
if(!IsSpreadOK())
return;

// Calculate indicators
CalculateIndicators();

// ATR volatility filter
if(!IsVolatilityOK())
return;

// Manage existing positions (breakeven)
ManageBreakeven();

// Signal detection
if(IsBuySignal())
{
if(CloseOpposite) CloseSellPositions();
if(CountBuyPositions() == 0)
OpenOrder(OP_BUY);
}
else if(IsSellSignal())
{
if(CloseOpposite) CloseBuyPositions();
if(CountSellPositions() == 0)
OpenOrder(OP_SELL);
}
}

//+------------------------------------------------------------------+
//| Calculate Bollinger Bands and ATR values |
//+------------------------------------------------------------------+
void CalculateIndicators()
{
// Bollinger Bands: MODE_UPPER=1, MODE_LOWER=2, MODE_MAIN=0
upperBand = iBands(Symbol(), 0, BandsPeriod, BandsDeviation, 0, PRICE_CLOSE, MODE_UPPER, 0);
lowerBand = iBands(Symbol(), 0, BandsPeriod, BandsDeviation, 0, PRICE_CLOSE, MODE_LOWER, 0);
middleBand = iBands(Symbol(), 0, BandsPeriod, BandsDeviation, 0, PRICE_CLOSE, MODE_MAIN, 0);
atrValue = iATR(Symbol(), 0, ATRPeriod, 0);
}

//+------------------------------------------------------------------+
//| Check if buy signal exists - price touches lower band |
//+------------------------------------------------------------------+
bool IsBuySignal()
{
if(lowerBand == 0 || lowerBand == EMPTY_VALUE)
return false;

// Price touches or crosses below lower band
return (Bid <= lowerBand);
}

//+------------------------------------------------------------------+
//| Check if sell signal exists - price touches upper band |
//+------------------------------------------------------------------+
bool IsSellSignal()
{
if(upperBand == 0 || upperBand == EMPTY_VALUE)
return false;

// Price touches or crosses above upper band
return (Ask >= upperBand);
}

//+------------------------------------------------------------------+
//| Check if volatility is sufficient using ATR |
//+------------------------------------------------------------------+
bool IsVolatilityOK()
{
if(MinATR <= 0) return true;
if(atrValue == 0 || atrValue == EMPTY_VALUE) return false;

double minATRPoints = MinATR Point pointMultiplier;
return (atrValue >= minATRPoints);
}

//+------------------------------------------------------------------+
//| Calculate lot size based on risk percentage |
//+------------------------------------------------------------------+
double CalculateLotSize()
{
if(RiskPercent <= 0)
return LotSize;

double stopLossPoints = StopLoss pointMultiplier;
if(stopLossPoints <= 0) return LotSize;

double riskAmount = AccountFreeMargin()
RiskPercent / 100.0;
double tickValue = MarketInfo(Symbol(), MODE_TICKVALUE);
double lotStep = MarketInfo(Symbol(), MODE_LOTSTEP);

if(tickValue == 0 || lotStep == 0) return LotSize;

double calculatedLot = riskAmount / (stopLossPoints tickValue);

// Round to allowed lot step
calculatedLot = MathFloor(calculatedLot / lotStep)
lotStep;

// Min/max checks
double minLot = MarketInfo(Symbol(), MODE_MINLOT);
double maxLot = MarketInfo(Symbol(), MODE_MAXLOT);

if(calculatedLot < minLot) calculatedLot = minLot;
if(calculatedLot > maxLot) calculatedLot = maxLot;

return NormalizeDouble(calculatedLot, 2);
}

//+------------------------------------------------------------------+
//| Open market order |
//+------------------------------------------------------------------+
void OpenOrder(int cmd)
{
double price = (cmd == OP_BUY) ? Ask : Bid;
double sl = 0, tp = 0;
double lot = CalculateLotSize();

// Stop loss calculation
if(StopLoss > 0)
{
if(cmd == OP_BUY)
sl = price - StopLoss Point pointMultiplier;
else
sl = price + StopLoss Point pointMultiplier;
}

// Take profit calculation - dynamic based on ATR
if(UseDynamicTP && atrValue > 0)
{
double dynamicTP = atrValue / Point / pointMultiplier TPMultiplier;
if(cmd == OP_BUY)
tp = price + dynamicTP
Point pointMultiplier;
else
tp = price - dynamicTP
Point pointMultiplier;
}
else if(TakeProfit > 0)
{
if(cmd == OP_BUY)
tp = price + TakeProfit
Point pointMultiplier;
else
tp = price - TakeProfit
Point pointMultiplier;
}

int ticket = OrderSend(Symbol(), cmd, lot, price, Slippage, sl, tp, "BB MeanRev", MagicNumber, 0, clrNONE);

if(ticket < 0)
Print("OrderSend failed. Error: ", GetLastError());
else
Print("Order opened. Ticket: ", ticket, " Lot: ", lot);
}

//+------------------------------------------------------------------+
//| Manage breakeven stop loss for existing positions |
//+------------------------------------------------------------------+
void ManageBreakeven()
{
if(BreakEvenPips <= 0) return;

for(int i = 0; i < OrdersTotal(); i++)
{
if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
{
if(OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber)
{
double breakevenPoints = BreakEvenPips
Point * pointMultiplier;

if(OrderType() == OP_BUY)
{
double profitPips = (Bid - OrderOpenPrice()) / Point / pointMultiplier;
if(profitPips >= BreakEvenPips && OrderStopLoss() < OrderOpenPrice())
{
OrderModify(OrderTicket(), OrderOpenPrice(), OrderOpenPrice(), OrderTakeProfit(), 0, clrNONE);
Print("Breakeven triggered for BUY #", OrderTicket());
}
}
else if(OrderType() == OP_SELL)
{
double profitPips = (OrderOpenPrice() - Ask) / Point / pointMultiplier;
if(profitPips >= BreakEvenPips && (OrderStopLoss() > OrderOpenPrice() || OrderStopLoss() == 0))
{
OrderModify(OrderTicket(), OrderOpenPrice(), OrderOpenPrice(), OrderTakeProfit(), 0, clrNONE);
Print("Breakeven triggered for SELL #", OrderTicket());
}
}
}
}
}
}

//+------------------------------------------------------------------+
//| Check if spread is within limit |
//+------------------------------------------------------------------+
bool IsSpreadOK()
{
if(MaxSpread <= 0) return true;

int currentSpread = (int)((Ask - Bid) / Point / pointMultiplier);
return (currentSpread <= MaxSpread);
}

//+------------------------------------------------------------------+
//| Count buy positions |
//+------------------------------------------------------------------+
int CountBuyPositions()
{
int count = 0;
for(int i = 0; i < OrdersTotal(); i++)
{
if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
{
if(OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber && OrderType() == OP_BUY)
count++;
}
}
return count;
}

//+------------------------------------------------------------------+
//| Count sell positions |
//+------------------------------------------------------------------+
int CountSellPositions()
{
int count = 0;
for(int i = 0; i < OrdersTotal(); i++)
{
if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
{
if(OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber && OrderType() == OP_SELL)
count++;
}
}
return count;
}

//+------------------------------------------------------------------+
//| Close all buy positions |
//+------------------------------------------------------------------+
void CloseBuyPositions()
{
for(int i = OrdersTotal() - 1; i >= 0; i--)
{
if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
{
if(OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber && OrderType() == OP_BUY)
{
OrderClose(OrderTicket(), OrderLots(), Bid, Slippage, clrNONE);
}
}
}
}

//+------------------------------------------------------------------+
//| Close all sell positions |
//+------------------------------------------------------------------+
void CloseSellPositions()
{
for(int i = OrdersTotal() - 1; i >= 0; i--)
{
if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
{
if(OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber && OrderType() == OP_SELL)
{
OrderClose(OrderTicket(), OrderLots(), Ask, Slippage, clrNONE);
}
}
}
}
//+------------------------------------------------------------------+
``

Parameter Explanation



| Parameter | Description | Recommended |
|-----------|-------------|-------------|
| LotSize | Fixed trading volume (if RiskPercent=0) | 0.01-0.1 |
| RiskPercent | Risk per trade as % of free margin (0=use fixed lot) | 1.0-2.0 |
| BandsPeriod | Bollinger Bands calculation period | 20 |
| BandsDeviation | Standard deviation multiplier | 2.0 |
| ATRPeriod | ATR period for volatility filter | 14 |
| MinATR | Minimum ATR threshold in points (0=disable) | 8-15 |
| StopLoss | Stop loss distance in pips | 40-80 |
| TakeProfit | Fixed take profit in pips (if dynamic disabled) | 80-160 |
| UseDynamicTP | Enable ATR-based dynamic take profit | true |
| TPMultiplier | ATR multiplier for dynamic TP | 2.0-3.0 |
| BreakEvenPips | Pips needed to move stop to breakeven | 25-40 |
| MaxSpread | Maximum allowed spread | 20-40 |
| Slippage | Maximum slippage tolerance | 10 |
| MagicNumber | Unique EA identifier | any |
| CloseOpposite | Close opposite positions on new signal | true |

Installation Instructions



  • Copy the code into MetaEditor (F4 in MT4)

  • Click Compile (F7) - ensure zero errors

  • Attach EA to a chart (best for EURUSD, GBPUSD, USDJPY)

  • Adjust parameters in Inputs tab

  • Enable AutoTrading (Alt+T)


  • Compilation & Modification Tips



    Key modifications to consider:
  • Adjust BandsDeviation to 0.5 for aggressive entries

  • Change UseDynamicTP to false and set fixed TakeProfit for predictable exits

  • Increase MinATR to 15-20 for higher volatility requirement

  • Add time filter by inserting hour checks in OnTick()


  • Best market conditions:
    This strategy performs best in ranging/oscillating markets. Avoid using during strong trending conditions as mean reversion will fail .

    Reference



    This EA source code is independently compiled and tested. Strategy concept based on Bollinger Bands mean reversion principles with ATR volatility confirmation .

    For professionally optimized EAs with multi-timeframe analysis, advanced money management, and complete backtest reports, check our premium EA collection. Subscribe for weekly updates and exclusive trading tools.