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.
Historical test results
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.
How the Supertrend signal works
Bullish flip
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.
Bearish flip
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 Custom Indicator 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, save it in the MQL5/Indicators folder, and compile with F7.
Change the settings or add your own signal filter
Describe the ATR inputs, alert rules, visual style, or confirmation filter you want. Pineify generates editable MQL5 source code that you can inspect and compile in MetaEditor.
Pine Script vs MQL5: Same Strategy, Different Platforms
| Aspect | Pine Script (TradingView) | MQL5 (MetaTrader 5) |
|---|---|---|
| Execution | Series evaluated on chart updates | Event handlers with explicit buffers |
| Deployment | Runs in TradingView with alerts | Runs in the MT5 terminal or on a VPS |
| Broker access | Via TradingView broker integration | Direct broker connectivity |
| Backtesting | Strategy Tester for strategy scripts | Strategy Tester for Expert Advisors |
| Code complexity | Simpler, functional syntax | C++-like, more powerful |
The calculation can be implemented on either platform, but the runtime model and buffer APIs differ. Read the Pine Script Supertrend reference before porting logic between them.
Frequently Asked Questions
Related MQL5 pages
Risk and testing note
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.