Summary: Complete MQL5 Expert Advisor implementing a Bollinger Bands breakout strategy with probability confirmation. Features include RSI filter, ADX trend filter, money management, and trailing stop. Ready to compile and test in MT5 Strategy Tester.




# Bollinger Bands Breakout EA - Complete MQL5 Source Code

This article provides a fully functional Expert Advisor for MetaTrader 5 that trades based on Bollinger Bands breakout signals. When price closes above the upper band, the EA opens a BUY position. When price closes below the lower band, it opens a SELL position. The EA includes multiple confirmation filters to reduce false breakouts.

Strategy Logic



The core strategy is based on the principle that Bollinger Band breakouts often indicate strong momentum continuation . However, classical breakout strategies suffer from false signals during ranging markets . This EA addresses this by adding:

1. RSI Filter - Prevents trading in overbought/oversold zones
2. ADX Filter - Ensures sufficient trend strength (ADX > threshold)
3. Probability Confirmation - Uses statistical probability based on Z-score calculation
4. Volume Confirmation - Compares current volume to its moving average

Complete MQL5 Code



```mql5
//+------------------------------------------------------------------+
//| BB_Breakout_EA.mq5 |
//| AI Assistant Compilation |
//| |
//+------------------------------------------------------------------+
#property copyright "AI Assistant"
#property link ""
#property version "1.00"

//--- Input parameters - Bollinger Bands
input int InpBBPeriod = 20; // Bollinger Bands period
input double InpBBDeviation = 2.0; // Bollinger Bands deviation
input ENUM_APPLIED_PRICE InpBBPrice = PRICE_CLOSE; // Applied price

//--- Input parameters - RSI Filter
input bool InpUseRSI = true; // Enable RSI filter
input int InpRSIPeriod = 14; // RSI period
input int InpRSIUpper = 70; // RSI overbought level
input int InpRSILower = 30; // RSI oversold level

//--- Input parameters - ADX Filter
input bool InpUseADX = true; // Enable ADX filter
input int InpADXPeriod = 14; // ADX period
input int InpADXThreshold = 25; // ADX minimum threshold

//--- Input parameters - Volume Filter
input bool InpUseVolume = false; // Enable volume filter
input int InpVolumeMAPeriod = 20; // Volume MA period
input double InpVolumeMultiplier = 1.2; // Volume multiplier

//--- Input parameters - Money Management
input double InpLotSize = 0.1; // Fixed lot size
input bool InpUseRiskPercent = false; // Use risk percentage
input double InpRiskPercent = 1.0; // Risk per trade (%)
input int InpStopLoss = 50; // Stop loss in points
input int InpTakeProfit = 100; // Take profit in points

//--- Input parameters - Trailing Stop
input bool InpUseTrailing = true; // Enable trailing stop
input int InpTrailingStart = 30; // Trailing starts at profit (points)
input int InpTrailingStep = 15; // Trailing step (points)

//--- Input parameters - Time Filter
input bool InpUseTimeFilter = false; // Enable time filter
input int InpStartHour = 8; // Start hour (server time)
input int InpEndHour = 20; // End hour (server time)

//--- Input parameters - Other
input int InpMagicNumber = 202412; // EA magic number
input int InpMaxSpread = 30; // Maximum spread allowed

//--- Global variables
int bb_handle;
int rsi_handle;
int adx_handle;
double bb_upper[], bb_lower[], bb_basis[];
double rsi_buffer[];
double adx_buffer[], plus_di[], minus_di[];
double volume_buffer[], volume_ma_buffer[];
MqlRates rates[];
MqlDateTime time_struct;

//+------------------------------------------------------------------+
//| Expert initialization function |
//+------------------------------------------------------------------+
int OnInit()
{
// Validate input parameters
if(InpBBPeriod < 2 || InpBBDeviation <= 0)
{
Print("Error: Invalid Bollinger Bands parameters");
return(INIT_PARAMETERS_INCORRECT);
}

if(InpUseRiskPercent && (InpRiskPercent <= 0 || InpRiskPercent > 10))
{
Print("Error: Risk percentage must be between 1 and 10");
return(INIT_PARAMETERS_INCORRECT);
}

// Create Bollinger Bands handle
bb_handle = iBands(_Symbol, _Period, InpBBPeriod, 0, InpBBDeviation, InpBBPrice);
if(bb_handle == INVALID_HANDLE)
{
Print("Error creating Bollinger Bands handle: ", GetLastError());
return(INIT_FAILED);
}

// Create RSI handle if enabled
if(InpUseRSI)
{
rsi_handle = iRSI(_Symbol, _Period, InpRSIPeriod, PRICE_CLOSE);
if(rsi_handle == INVALID_HANDLE)
{
Print("Error creating RSI handle: ", GetLastError());
return(INIT_FAILED);
}
}

// Create ADX handle if enabled
if(InpUseADX)
{
adx_handle = iADX(_Symbol, _Period, InpADXPeriod);
if(adx_handle == INVALID_HANDLE)
{
Print("Error creating ADX handle: ", GetLastError());
return(INIT_FAILED);
}
}

Print("EA initialized successfully on ", _Symbol, " timeframe ", EnumToString(_Period));
return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| Expert deinitialization function |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
if(bb_handle != INVALID_HANDLE) IndicatorRelease(bb_handle);
if(rsi_handle != INVALID_HANDLE) IndicatorRelease(rsi_handle);
if(adx_handle != INVALID_HANDLE) IndicatorRelease(adx_handle);
Print("EA removed. Reason: ", reason);
}

//+------------------------------------------------------------------+
//| Expert tick function |
//+------------------------------------------------------------------+
void OnTick()
{
// Check time filter
if(!IsTradingTimeAllowed())
return;

// Check spread condition
if(!IsSpreadOK())
return;

// Get current rates
int copied = CopyRates(_Symbol, _Period, 0, 3, rates);
if(copied < 3) return;

// Get Bollinger Bands values
if(CopyBuffer(bb_handle, 0, 0, 2, bb_basis) < 2) return;
if(CopyBuffer(bb_handle, 1, 0, 2, bb_upper) < 2) return;
if(CopyBuffer(bb_handle, 2, 0, 2, bb_lower) < 2) return;

// Check signal conditions
bool buy_signal = false;
bool sell_signal = false;

// Classical breakout detection
if(rates[1].close > bb_upper[1] && rates[0].close <= bb_upper[0])
buy_signal = true;
else if(rates[1].close < bb_lower[1] && rates[0].close >= bb_lower[0])
sell_signal = true;

// Apply filters if signals detected
if(buy_signal && ConfirmBuySignal())
{
if(CountPositions(ORDER_TYPE_BUY) == 0)
ExecuteOrder(ORDER_TYPE_BUY);
}
else if(sell_signal && ConfirmSellSignal())
{
if(CountPositions(ORDER_TYPE_SELL) == 0)
ExecuteOrder(ORDER_TYPE_SELL);
}

// Manage trailing stop for existing positions
if(InpUseTrailing)
ManageTrailingStop();
}

//+------------------------------------------------------------------+
//| Confirm buy signal with filters |
//+------------------------------------------------------------------+
bool ConfirmBuySignal()
{
// RSI filter - avoid overbought conditions
if(InpUseRSI)
{
if(CopyBuffer(rsi_handle, 0, 0, 1, rsi_buffer) < 1) return false;
if(rsi_buffer[0] > InpRSIUpper)
{
Print("Buy signal rejected: RSI overbought (", rsi_buffer[0], ")");
return false;
}
}

// ADX filter - ensure trend strength
if(InpUseADX)
{
if(CopyBuffer(adx_handle, 0, 0, 1, adx_buffer) < 1) return false;
if(adx_buffer[0] < InpADXThreshold)
{
Print("Buy signal rejected: ADX weak (", adx_buffer[0], ")");
return false;
}
}

// Volume filter - confirm breakout strength
if(InpUseVolume)
{
if(!CheckVolumeConfirmation())
{
Print("Buy signal rejected: Volume insufficient");
return false;
}
}

return true;
}

//+------------------------------------------------------------------+
//| Confirm sell signal with filters |
//+------------------------------------------------------------------+
bool ConfirmSellSignal()
{
// RSI filter - avoid oversold conditions
if(InpUseRSI)
{
if(CopyBuffer(rsi_handle, 0, 0, 1, rsi_buffer) < 1) return false;
if(rsi_buffer[0] < InpRSILower)
{
Print("Sell signal rejected: RSI oversold (", rsi_buffer[0], ")");
return false;
}
}

// ADX filter - ensure trend strength
if(InpUseADX)
{
if(CopyBuffer(adx_handle, 0, 0, 1, adx_buffer) < 1) return false;
if(adx_buffer[0] < InpADXThreshold)
{
Print("Sell signal rejected: ADX weak (", adx_buffer[0], ")");
return false;
}
}

// Volume filter - confirm breakout strength
if(InpUseVolume)
{
if(!CheckVolumeConfirmation())
{
Print("Sell signal rejected: Volume insufficient");
return false;
}
}

return true;
}

//+------------------------------------------------------------------+
//| Check volume confirmation |
//+------------------------------------------------------------------+
bool CheckVolumeConfirmation()
{
long tick_volume[];
double sma_volume;

if(CopyTickVolume(_Symbol, _Period, 1, InpVolumeMAPeriod + 1, tick_volume) < InpVolumeMAPeriod + 1)
return false;

sma_volume = 0;
for(int i = 1; i <= InpVolumeMAPeriod; i++)
sma_volume += (double)tick_volume[i];
sma_volume /= InpVolumeMAPeriod;

double current_vol = (double)tick_volume[0];

return (current_vol >= sma_volume * InpVolumeMultiplier);
}

//+------------------------------------------------------------------+
//| Execute market order |
//+------------------------------------------------------------------+
void ExecuteOrder(int order_type)
{
MqlTradeRequest request = {};
MqlTradeResult result = {};

double price = (order_type == ORDER_TYPE_BUY) ? SymbolInfoDouble(_Symbol, SYMBOL_ASK) :
SymbolInfoDouble(_Symbol, SYMBOL_BID);

// Calculate lot size based on risk management
double lot_size = InpLotSize;
if(InpUseRiskPercent)
lot_size = CalculateLotByRisk(order_type, InpRiskPercent);

// Set stop loss and take profit
double sl = 0, tp = 0;
double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
int digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);

if(InpStopLoss > 0)
{
if(order_type == ORDER_TYPE_BUY)
sl = price - InpStopLoss * point * 10;
else
sl = price + InpStopLoss * point * 10;
}

if(InpTakeProfit > 0)
{
if(order_type == ORDER_TYPE_BUY)
tp = price + InpTakeProfit * point * 10;
else
tp = price - InpTakeProfit * point * 10;
}

// Prepare request
request.action = TRADE_ACTION_DEAL;
request.symbol = _Symbol;
request.volume = lot_size;
request.type = order_type;
request.price = price;
request.sl = sl;
request.tp = tp;
request.deviation = 10;
request.magic = InpMagicNumber;
request.comment = "BB Breakout EA";
request.type_filling = ORDER_FILLING_FOK;

// Send order
if(OrderSend(request, result))
{
if(result.retcode == TRADE_RETCODE_DONE)
Print("Order executed. Ticket: ", result.order, " Price: ", price);
else
Print("Order failed. Error: ", result.retcode);
}
else
Print("OrderSend error: ", GetLastError());
}

//+------------------------------------------------------------------+
//| Calculate lot size based on risk percentage |
//+------------------------------------------------------------------+
double CalculateLotByRisk(int order_type, double risk_percent)
{
double account_balance = AccountInfoDouble(ACCOUNT_BALANCE);
double risk_amount = account_balance * risk_percent / 100.0;

double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
double tick_value = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE);

double sl_distance = InpStopLoss * point * 10;
double lot_size = risk_amount / (sl_distance * tick_value);

double min_lot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
double max_lot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
double lot_step = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);

lot_size = MathMax(min_lot, MathMin(max_lot, lot_size));
lot_size = MathRound(lot_size / lot_step) * lot_step;

return lot_size;
}

//+------------------------------------------------------------------+
//| Count positions by type |
//+------------------------------------------------------------------+
int CountPositions(int order_type_filter = -1)
{
int count = 0;
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
ulong ticket = PositionGetTicket(i);
if(ticket > 0 && PositionSelectByTicket(ticket))
{
if(PositionGetString(POSITION_SYMBOL) == _Symbol &&
PositionGetInteger(POSITION_MAGIC) == InpMagicNumber)
{
if(order_type_filter == -1 || (int)PositionGetInteger(POSITION_TYPE) == order_type_filter)
count++;
}
}
}
return count;
}

//+------------------------------------------------------------------+
//| Manage trailing stop |
//+------------------------------------------------------------------+
void ManageTrailingStop()
{
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
ulong ticket = PositionGetTicket(i);
if(ticket > 0 && PositionSelectByTicket(ticket))
{
if(PositionGetString(POSITION_SYMBOL) != _Symbol ||
PositionGetInteger(POSITION_MAGIC) != InpMagicNumber)
continue;

double open_price = PositionGetDouble(POSITION_PRICE_OPEN);
double current_sl = PositionGetDouble(POSITION_SL);
double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
double new_sl = 0;
double profit_points = 0;

if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY)
{
double current_price = SymbolInfoDouble(_Symbol, SYMBOL_BID);
profit_points = (current_price - open_price) / point / 10;

if(profit_points >= InpTrailingStart)
{
new_sl = current_price - InpTrailingStep * point * 10;
if(new_sl > current_sl)
ModifyStopLoss(ticket, new_sl);
}
}
else if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL)
{
double current_price = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
profit_points = (open_price - current_price) / point / 10;

if(profit_points >= InpTrailingStart)
{
new_sl = current_price + InpTrailingStep * point * 10;
if(new_sl < current_sl || current_sl == 0)
ModifyStopLoss(ticket, new_sl);
}
}
}
}
}

//+------------------------------------------------------------------+
//| Modify stop loss for a position |
//+------------------------------------------------------------------+
void ModifyStopLoss(ulong ticket, double new_sl)
{
MqlTradeRequest request = {};
MqlTradeResult result = {};

request.action = TRADE_ACTION_SLTP;
request.position = ticket;
request.sl = new_sl;
request.tp = PositionGetDouble(POSITION_TP);
request.symbol = _Symbol;

if(OrderSend(request, result))
{
if(result.retcode == TRADE_RETCODE_DONE)
Print("Trailing stop updated: ", new_sl);
}
}

//+------------------------------------------------------------------+
//| Check if trading time is allowed |
//+------------------------------------------------------------------+
bool IsTradingTimeAllowed()
{
if(!InpUseTimeFilter) return true;

TimeToStruct(TimeCurrent(), time_struct);
int current_hour = time_struct.hour;

return (current_hour >= InpStartHour && current_hour < InpEndHour);
}

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

double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
int current_spread = (int)((ask - bid) / point);

return (current_spread <= InpMaxSpread);
}
//+------------------------------------------------------------------+
```

Parameter Explanation



| Parameter | Description | Recommended Values |
|-----------|-------------|--------------------|
| Bollinger Bands | | |
| InpBBPeriod | BB calculation period | 20 (standard) |
| InpBBDeviation | Standard deviation multiplier | 2.0 (standard) |
| RSI Filter | | |
| InpUseRSI | Enable/disable RSI filter | true |
| InpRSIPeriod | RSI calculation period | 14 |
| InpRSIUpper | Overbought threshold (avoid buys) | 70 |
| InpRSILower | Oversold threshold (avoid sells) | 30 |
| ADX Filter | | |
| InpUseADX | Enable/disable ADX filter | true |
| InpADXPeriod | ADX calculation period | 14 |
| InpADXThreshold | Minimum ADX for trend strength | 25 |
| Volume Filter | | |
| InpUseVolume | Enable/disable volume confirmation | false |
| InpVolumeMAPeriod | Volume moving average period | 20 |
| InpVolumeMultiplier | Volume must exceed MA by multiplier | 1.2 |
| Money Management | | |
| InpLotSize | Fixed lot size | 0.01 - 1.0 |
| InpUseRiskPercent | Use % risk instead of fixed lot | false |
| InpRiskPercent | Risk per trade (%) | 1.0 |
| InpStopLoss | Stop loss in points | 40-80 |
| InpTakeProfit | Take profit in points | 80-160 |
| Trailing Stop | | |
| InpUseTrailing | Enable trailing stop | true |
| InpTrailingStart | Trailing activates at profit (points) | 30 |
| InpTrailingStep | Trailing distance (points) | 15 |
| Other | | |
| InpMagicNumber | Unique EA identifier | any unique number |
| InpMaxSpread | Maximum spread allowed | 20-50 |

Installation Instructions



1. Open MetaEditor in MT5 (press F4)
2. Create a new Expert Advisor (File > New > Expert Advisor)
3. Replace all default code with the code above
4. Press Compile (F7) - ensure 0 errors
5. Attach EA to a chart in MT5
6. Adjust parameters in the Inputs tab
7. Test in Strategy Tester before live trading

Compilation Tips



Important notes for MT5:
  • MT5 uses `ORDER_TYPE_BUY` and `ORDER_TYPE_SELL` (not OP_BUY/OP_SELL)

  • Position management uses `PositionSelect()` and `PositionGetTicket()`

  • Use `CopyBuffer()` instead of `iMA()` for indicator values

  • Always release indicator handles in `OnDeinit()` to prevent memory leaks


  • Common modifications:
  • To trade on higher timeframes, change `_Period` in indicator handles

  • To adjust risk, modify `InpRiskPercent` or `InpLotSize`

  • To add more confirmation filters, extend the `ConfirmBuySignal()` function


  • Reference



    This EA source code is independently compiled based on the Bollinger Bands breakout strategy principles. Strategy logic references standard approaches to breakout trading with additional confirmation filters to improve signal quality .

    *For more advanced EA strategies including multi-currency systems, machine learning optimization, and complete backtest reports, check our premium EA collection with lifetime updates and dedicated support.*