Complete MQL5 guide & code

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

Use the built-in iBands indicator from an MQL5 Expert Advisor and inspect an educational squeeze-breakout rule. This page supplies EA source, not a custom plotted indicator: save it under MQL5/Experts. The example demonstrates buffers, completed-bar conditions and exit requests, without a verified profitability claim.

Implementation details & mechanics

Core implementation pattern

The example compares normalized bandwidth on bars 2 and 1. A possible entry requires the older width below the chosen threshold, a wider latest completed bar, and a close beyond the corresponding outer band. The 2% squeeze setting is an illustrative rule rather than evidence of a high-probability move. Fixed lots and the 30-point spread guard must be evaluated for the instrument.

Error handling & safety rules

Matching positions are submitted for closure when the last completed close is on the opposite side of the middle band; this is a level check, not necessarily a newly detected cross. Initial SL is measured from the requested ask or bid, not from a candle high or low. Optional trailing uses the current ATR distance, not stored initial risk. Gap risk and rejected requests remain possible, and the spread guard can skip management on a bar.

MQL5 Code Implementation

//+------------------------------------------------------------------+
//|  BollingerBandsBreakoutEA.mq5                                    |
//|  Bollinger Bands Squeeze Breakout EA for MetaTrader 5            |
//|  Educational example; no verified performance 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 3 bars: iBands middle=0, upper=1, lower=2
   double upper[], middle[], lower[], atrBuf[];
   ArraySetAsSeries(upper,  true);
   ArraySetAsSeries(middle, true);
   ArraySetAsSeries(lower,  true);
   ArraySetAsSeries(atrBuf, true);

   if(CopyBuffer(hBB, 1,  0, 3, upper)  < 3) return;
   if(CopyBuffer(hBB, 0,   0, 3, middle) < 3) return;
   if(CopyBuffer(hBB, 2,  0, 3, lower)  < 3) return;
   if(CopyBuffer(hATR, 0,          0, 3, atrBuf) < 3) return;

   double atr = atrBuf[1];  // previous completed bar ATR
   if(atr<=0 || middle[1]==0 || middle[2]==0) return;

   //--- 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];
   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) ||
         (result.retcode!=TRADE_RETCODE_DONE && result.retcode!=TRADE_RETCODE_DONE_PARTIAL))
         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) ||
         (result.retcode!=TRADE_RETCODE_DONE && result.retcode!=TRADE_RETCODE_DONE_PARTIAL))
         Print("SELL OrderSend failed: ", result.retcode, " - ", result.comment);
      else
         Print("SELL opened at ", bid, " SL=", sl);
     }
  }
//+------------------------------------------------------------------+

Copy this code into MetaEditor, save it in your MQL5 folder (Experts, Scripts, or Include), and compile with F7.

Generate custom MQL5 code with Pineify AI

Describe the trade execution, indicator calculations, risk management, or data processing logic you need. Pineify generates editable MQL5 source code. Review its logic, request a syntax check, and compile and test the final file in MetaEditor.

Pine Script vs MQL5: Same Strategy, Different Platforms

AspectPine Script (TradingView)MQL5 (MetaTrader 5)
ExecutionSeries evaluated on chart updatesEvent handlers with explicit buffers
DeploymentRuns in TradingView with alertsRuns in the MT5 terminal or on a VPS
Broker accessStrategy orders are simulated; live automation needs a separate integrationDirect broker connectivity
BacktestingStrategy Tester for strategy scriptsStrategy Tester for Expert Advisors
Code complexitySimpler, functional syntaxC++-like, more powerful

The calculation can be implemented on either platform, but the runtime model and buffer APIs differ. Read the Pine Script reference guide before porting logic between them.

Frequently Asked Questions

Related MQL5 pages

Technical references

Risk and testing note

These educational examples require independent compilation and testing. A compile pass or an illustrative setting does not establish execution correctness or profitability. Any reported performance must be supported by reproducible data and test settings; past results do not predict future returns. Trading involves significant risk of loss. This content is not financial advice.