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:

  • <strong>RSI Filter</strong> - Prevents trading in overbought/oversold zones

  • <strong>ADX Filter</strong> - Ensures sufficient trend strength (ADX > threshold)

  • <strong>Probability Confirmation</strong> - Uses statistical probability based on Z-score calculation

  • <strong>Volume Confirmation</strong> - 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



  • Open MetaEditor in MT5 (press F4)

  • Create a new Expert Advisor (File > New > Expert Advisor)

  • Replace all default code with the code above

  • Press Compile (F7) - ensure 0 errors

  • Attach EA to a chart in MT5

  • Adjust parameters in the Inputs tab

  • 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.