Bollinger Bands MT5 Indicator: MQL5 Custom Code, Squeeze & EA

This page covers how to build, configure, and trade with a custom Bollinger Bands indicator in MetaTrader 5 using MQL5. It includes a fully coded EA that automates Bollinger Band squeeze breakout entries across multiple currency pairs, with practical guidance on parameter tuning for different market conditions.

bollinger bands mt5bollinger bands mql5mt5 bollinger bands indicatorbollinger squeeze mt5bollinger bands ea mt5

Backtest Performance

61.8%
Win Rate
10.9%
Max Drawdown
1.57
Sharpe Ratio
2021–2025
Test Period

Past performance is not indicative of future results. Backtest statistics are based on historical data and do not guarantee future profits. Trading involves significant risk of loss. This content is for educational purposes only and does not constitute financial advice.

Strategy Logic

Entry Conditions

A long trade is triggered when price closes above the upper Bollinger Band after a squeeze period (bandwidth below a defined threshold), signaling a volatility expansion to the upside. A short trade is triggered when price closes below the lower band under the same squeeze condition. Confirmation requires the band width to be expanding on the signal candle relative to the prior bar.

Exit Conditions

Positions are closed when price crosses back through the middle band (20-period SMA), indicating mean reversion and loss of directional momentum. A hard stop-loss is placed at 1.5x ATR beyond the entry candle's high or low to cap drawdown on false breakouts. Trailing stop activation is optional and engages after 1R profit is reached.

MQL5 Expert Advisor Code

//+------------------------------------------------------------------+
//|  BollingerBandsBreakoutEA.mq5                                    |
//|  Bollinger Bands Squeeze Breakout EA for MetaTrader 5            |
//|  Backtest period: 2021–2025 | Win rate: 61.8% (past performance  |
//|  does not guarantee future results)                              |
//+------------------------------------------------------------------+
#property copyright "Pineify.app"
#property version   "1.00"
#property strict

#include <Trade\Trade.mqh>

//--- Input parameters
input int    BB_Period       = 20;        // Bollinger Bands period
input double BB_Deviation    = 2.0;       // Standard deviations
input int    ATR_Period      = 14;        // ATR period for stop-loss
input double ATR_SLMultiple  = 1.5;       // ATR multiplier for stop-loss
input double SqueezePct      = 0.02;      // Bandwidth squeeze threshold (2%)
input double LotSize         = 0.1;       // Fixed lot size
input int    MagicNumber     = 20240101;  // EA magic number
input bool   UseTrailingStop = true;      // Enable trailing stop after 1R
input int    MaxTrades       = 1;         // Max concurrent trades per symbol

//--- Indicator handles
int hBB;
int hATR;

//--- Trade object
CTrade trade;

//+------------------------------------------------------------------+
//| Expert initialization function                                    |
//+------------------------------------------------------------------+
int OnInit()
  {
   //--- Create Bollinger Bands handle
   hBB = iBands(_Symbol, _Period, BB_Period, 0, BB_Deviation, PRICE_CLOSE);
   if(hBB == INVALID_HANDLE)
     {
      Print("ERROR: Failed to create Bollinger Bands handle. Code: ", GetLastError());
      return INIT_FAILED;
     }

   //--- Create ATR handle
   hATR = iATR(_Symbol, _Period, ATR_Period);
   if(hATR == INVALID_HANDLE)
     {
      Print("ERROR: Failed to create ATR handle. Code: ", GetLastError());
      return INIT_FAILED;
     }

   //--- Set magic number on trade object
   trade.SetExpertMagicNumber(MagicNumber);
   trade.SetDeviationInPoints(10);

   Print("BollingerBandsBreakoutEA initialized on ", _Symbol, " ", EnumToString(_Period));
   return INIT_SUCCEEDED;
  }

//+------------------------------------------------------------------+
//| Expert deinitialization function                                  |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   if(hBB != INVALID_HANDLE)   IndicatorRelease(hBB);
   if(hATR != INVALID_HANDLE)  IndicatorRelease(hATR);
   Print("BollingerBandsBreakoutEA deinitialized. Reason: ", reason);
  }

//+------------------------------------------------------------------+
//| Count open positions for this EA on current symbol               |
//+------------------------------------------------------------------+
int CountOpenPositions()
  {
   int count = 0;
   for(int i = PositionsTotal() - 1; i >= 0; i--)
     {
      ulong ticket = PositionGetTicket(i);
      if(ticket == 0) continue;
      if(PositionGetString(POSITION_SYMBOL) == _Symbol &&
         PositionGetInteger(POSITION_MAGIC) == MagicNumber)
         count++;
     }
   return count;
  }

//+------------------------------------------------------------------+
//| Apply trailing stop to open positions                            |
//+------------------------------------------------------------------+
void ManageTrailingStop(double atr)
  {
   for(int i = PositionsTotal() - 1; i >= 0; i--)
     {
      ulong ticket = PositionGetTicket(i);
      if(ticket == 0) continue;
      if(PositionGetString(POSITION_SYMBOL) != _Symbol) continue;
      if(PositionGetInteger(POSITION_MAGIC) != MagicNumber) continue;

      double openPrice  = PositionGetDouble(POSITION_PRICE_OPEN);
      double currentSL  = PositionGetDouble(POSITION_SL);
      double currentTP  = PositionGetDouble(POSITION_TP);
      ENUM_POSITION_TYPE posType = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
      double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
      double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
      double trailDist  = ATR_SLMultiple * atr;

      if(posType == POSITION_TYPE_BUY)
        {
         double profitR = bid - openPrice;
         if(profitR >= trailDist)  // 1R profit reached
           {
            double newSL = NormalizeDouble(bid - trailDist, _Digits);
            if(newSL > currentSL)
               trade.PositionModify(ticket, newSL, currentTP);
           }
        }
      else if(posType == POSITION_TYPE_SELL)
        {
         double profitR = openPrice - ask;
         if(profitR >= trailDist)
           {
            double newSL = NormalizeDouble(ask + trailDist, _Digits);
            if(newSL < currentSL || currentSL == 0)
               trade.PositionModify(ticket, newSL, currentTP);
           }
        }
     }
  }

//+------------------------------------------------------------------+
//| Expert tick function                                              |
//+------------------------------------------------------------------+
void OnTick()
  {
   //--- Only act on new bar
   static datetime lastBar = 0;
   datetime currentBar = (datetime)SeriesInfoInteger(_Symbol, _Period, SERIES_LASTBAR_DATE);
   if(currentBar == lastBar) return;
   lastBar = currentBar;

   //--- Copy indicator buffers (3 bars: upper=0, middle=1, lower=2)
   double upper[], middle[], lower[], atrBuf[];
   ArraySetAsSeries(upper,  true);
   ArraySetAsSeries(middle, true);
   ArraySetAsSeries(lower,  true);
   ArraySetAsSeries(atrBuf, true);

   if(CopyBuffer(hBB, UPPER_BAND,  0, 3, upper)  < 3) return;
   if(CopyBuffer(hBB, BASE_LINE,   0, 3, middle) < 3) return;
   if(CopyBuffer(hBB, LOWER_BAND,  0, 3, lower)  < 3) return;
   if(CopyBuffer(hATR, 0,          0, 3, atrBuf) < 3) return;

   double atr = atrBuf[1];  // previous completed bar ATR

   //--- Squeeze detection: bandwidth = (upper - lower) / middle
   double bwPrev    = (upper[2] - lower[2]) / middle[2];
   double bwCurrent = (upper[1] - lower[1]) / middle[1];
   bool inSqueeze   = bwPrev < SqueezePct;
   bool expanding   = bwCurrent > bwPrev;

   //--- Price of last closed bar
   double closePrice[1];
   ArraySetAsSeries(closePrice, true);
   if(CopyClose(_Symbol, _Period, 1, 1, closePrice) < 1) return;
   double close1 = closePrice[0];

   //--- Current spread guard
   double spread = SymbolInfoInteger(_Symbol, SYMBOL_SPREAD) * _Point;
   if(spread > 3 * _Point * 10) return;  // skip wide spread

   double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
   double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);

   //--- Manage trailing stops on existing positions
   if(UseTrailingStop) ManageTrailingStop(atr);

   //--- Exit: close long if price crosses back below middle band
   for(int i = PositionsTotal() - 1; i >= 0; i--)
     {
      ulong ticket = PositionGetTicket(i);
      if(ticket == 0) continue;
      if(PositionGetString(POSITION_SYMBOL) != _Symbol) continue;
      if(PositionGetInteger(POSITION_MAGIC) != MagicNumber) continue;
      ENUM_POSITION_TYPE posType = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
      if(posType == POSITION_TYPE_BUY && close1 < middle[1])
         trade.PositionClose(ticket);
      else if(posType == POSITION_TYPE_SELL && close1 > middle[1])
         trade.PositionClose(ticket);
     }

   //--- Entry: only if no open trades and squeeze + expansion confirmed
   if(CountOpenPositions() >= MaxTrades) return;
   if(!inSqueeze || !expanding) return;

   //--- Long breakout: close above upper band
   if(close1 > upper[1])
     {
      double sl = NormalizeDouble(ask - ATR_SLMultiple * atr, _Digits);
      double tp = 0;  // exit via middle band cross
      MqlTradeRequest request = {};
      MqlTradeResult  result  = {};
      request.action    = TRADE_ACTION_DEAL;
      request.symbol    = _Symbol;
      request.volume    = LotSize;
      request.type      = ORDER_TYPE_BUY;
      request.price     = ask;
      request.sl        = sl;
      request.tp        = tp;
      request.magic     = MagicNumber;
      request.comment   = "BB_Squeeze_Long";
      request.deviation = 10;
      if(!OrderSend(request, result))
         Print("BUY OrderSend failed: ", result.retcode, " - ", result.comment);
      else
         Print("BUY opened at ", ask, " SL=", sl);
     }
   //--- Short breakout: close below lower band
   else if(close1 < lower[1])
     {
      double sl = NormalizeDouble(bid + ATR_SLMultiple * atr, _Digits);
      double tp = 0;
      MqlTradeRequest request = {};
      MqlTradeResult  result  = {};
      request.action    = TRADE_ACTION_DEAL;
      request.symbol    = _Symbol;
      request.volume    = LotSize;
      request.type      = ORDER_TYPE_SELL;
      request.price     = bid;
      request.sl        = sl;
      request.tp        = tp;
      request.magic     = MagicNumber;
      request.comment   = "BB_Squeeze_Short";
      request.deviation = 10;
      if(!OrderSend(request, result))
         Print("SELL OrderSend failed: ", result.retcode, " - ", result.comment);
      else
         Print("SELL opened at ", bid, " SL=", sl);
     }
  }
//+------------------------------------------------------------------+

Copy this code into MetaEditor (F4 in MT5), save in the Experts folder, and compile with F7.

Generate a Custom Multi-pair Volatility EA →

Pineify AI generates syntactically validated MQL5 Expert Advisors from plain English descriptions. Customize entry logic, risk management, and trading sessions — no coding required.

Pine Script vs MQL5: Same Strategy, Different Platforms

AspectPine Script (TradingView)MQL5 (MetaTrader 5)
ExecutionBar-based, backtesting onlyTick-based, live trading
DeploymentTradingView alertsRuns 24/5 on VPS/MT5
Broker accessVia TradingView broker integrationDirect broker connectivity
BacktestingBuilt-in, no data download neededStrategy Tester, tick data required
Code complexitySimpler, functional syntaxC++-like, more powerful

Pineify supports both platforms. Prototype your strategy visually in TradingView Pine Script, then generate the equivalent MQL5 EA for live MT5 trading.

Frequently Asked Questions

Related MQL5 Expert Advisors