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.
Backtest Performance
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
| Aspect | Pine Script (TradingView) | MQL5 (MetaTrader 5) |
|---|---|---|
| Execution | Bar-based, backtesting only | Tick-based, live trading |
| Deployment | TradingView alerts | Runs 24/5 on VPS/MT5 |
| Broker access | Via TradingView broker integration | Direct broker connectivity |
| Backtesting | Built-in, no data download needed | Strategy Tester, tick data required |
| Code complexity | Simpler, functional syntax | C++-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.