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



1. Copy the code into MetaEditor (F4 in MT4)
2. Click Compile (F7) - ensure zero errors
3. Attach EA to a chart (best for EURUSD, GBPUSD, USDJPY)
4. Adjust parameters in Inputs tab
5. 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.*