Complete .mq5 source code

Bollinger Bands H4 EA MQL5: Volatility Breakout Trading System

Study an H4 Bollinger Bands breakout EA using completed-bar closes, band-width comparison and an ATR filter. The example shows indicator handles, position filtering and initial/trailing stop requests. Its settings are illustrative; no reproducible performance study or verified reduction in false signals is supplied.

Strategy logic

Entry conditions

Read the iBands buffers as 0 for the middle line, 1 for the upper band and 2 for the lower band. On a new H4 bar, the sample compares the previous close with the outer bands and checks whether normalized width exceeds the median of the preceding lookback bars. ATR is a separate price-unit threshold. It requests at most one matching position per direction; validate broker volume rules and account mode before use.

Exit conditions

The code requests closure on an opposite breakout, not on a middle-band touch. Initial SL uses an ATR multiple plus the current spread, while TP uses an ATR multiple from requested entry. Trailing and maximum-holding checks run on the H4 update after data and ATR filters, so skipped checks or rejected requests can delay exits. The holding limit measures elapsed time in H4-sized intervals rather than counting actual market bars.

MQL5 Expert Advisor Code

//+------------------------------------------------------------------+
//| BollingerBandsH4_EA.mq5                                           |
//| Bollinger Bands H4 Volatility Breakout EA — MT5, MQL5 5.x         |
//| Educational purposes only. Test before live use.                   |
//+------------------------------------------------------------------+
#property copyright "Pineify Example"
#property version   "1.00"
#property strict

#include <Trade/Trade.mqh>
#include <Trade/PositionInfo.mqh>
#include <Trade/SymbolInfo.mqh>

//--- Input parameters
input int      BB_Period         = 20;        // BB period
input double   BB_Deviation      = 2.0;       // BB deviation
input int      ATR_Period        = 14;        // ATR period
input double   ATR_SL_Mult       = 2.0;       // Stop-loss ATR mult
input double   ATR_TP_Mult       = 4.0;       // Take-profit ATR mult
input double   ATR_Trail_Mult    = 1.5;       // Trailing activation
input double   ATR_Min_Filter    = 0.0010;    // Min ATR to enter
input double   LotSize           = 0.10;      // Fixed lot size
input ulong    MagicNumber       = 20240007;  // EA magic number
input int      MaxHoldingBars    = 30;        // Max H4 bars (5 days)
input int      BandwidthLookback = 20;        // Bandwidth median lookback

//--- Globals
int    handleBB, handleATR;
double bbUpperBuf[], bbMiddleBuf[], bbLowerBuf[], atrBuf[];
CTrade         m_trade;
CPositionInfo  m_position;
CSymbolInfo    m_symbol;
datetime lastBarTime = 0;

//+------------------------------------------------------------------+
//| Expert initialization                                             |
//+------------------------------------------------------------------+
int OnInit()
  {
   if(BB_Period<2 || ATR_Period<1 || BandwidthLookback<2 || BB_Deviation<=0) return INIT_PARAMETERS_INCORRECT;
   m_symbol.Name(_Symbol);
   m_symbol.Refresh();
   m_trade.SetExpertMagicNumber(MagicNumber);
   m_trade.SetDeviationInPoints(10);
   m_trade.SetTypeFillingBySymbol(_Symbol);
   m_trade.SetAsyncMode(false);

   handleBB  = iBands(_Symbol, PERIOD_H4, BB_Period, 0, BB_Deviation, PRICE_CLOSE);
   handleATR = iATR(_Symbol, PERIOD_H4, ATR_Period);
   if(handleBB == INVALID_HANDLE || handleATR == INVALID_HANDLE)
     { Print("ERROR: Failed to create indicator handles."); return INIT_FAILED; }

   ArraySetAsSeries(bbUpperBuf, true);
   ArraySetAsSeries(bbMiddleBuf, true);
   ArraySetAsSeries(bbLowerBuf, true);
   ArraySetAsSeries(atrBuf, true);
   Print("BB_H4_EA initialized on ", _Symbol);
   return INIT_SUCCEEDED;
  }

//+------------------------------------------------------------------+
//| Expert deinitialization                                           |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   IndicatorRelease(handleBB);
   IndicatorRelease(handleATR);
  }

//+------------------------------------------------------------------+
//| Expert tick                                                       |
//+------------------------------------------------------------------+
void OnTick()
  {
   // --- New bar detection on H4 ---
   datetime currentBarTime = iTime(_Symbol, PERIOD_H4, 0);
   if(currentBarTime == lastBarTime) return;
   lastBarTime = currentBarTime;

   // --- Copy indicator data ---
   if(CopyBuffer(handleBB, 1, 0, BandwidthLookback + 3, bbUpperBuf) < BandwidthLookback + 3) return;
   if(CopyBuffer(handleBB, 0, 0, BandwidthLookback + 3, bbMiddleBuf) < BandwidthLookback + 3) return;
   if(CopyBuffer(handleBB, 2, 0, BandwidthLookback + 3, bbLowerBuf)  < BandwidthLookback + 3) return;
   if(CopyBuffer(handleATR, 0, 0, 2, atrBuf) < 2) return;

   // --- Use previous closed bar for signal ---
   double closePrev  = iClose(_Symbol, PERIOD_H4, 1);
   double upperPrev  = bbUpperBuf[1];
   double middlePrev = bbMiddleBuf[1];
   double lowerPrev  = bbLowerBuf[1];
   double atrVal     = atrBuf[1];
   if(atrVal<=0 || atrVal < ATR_Min_Filter || middlePrev==0) return;

   // --- Bandwidth expansion check ---
   double bwCurrent = (upperPrev - lowerPrev) / middlePrev;
   double bwArray[];
   ArrayResize(bwArray, BandwidthLookback);
   for(int i = 2; i <= BandwidthLookback+1; i++)
     { double u = bbUpperBuf[i], m = bbMiddleBuf[i], l = bbLowerBuf[i];
       if(m==0) return;
       bwArray[i - 2] = (u - l) / m; }
   ArraySort(bwArray);
   int mid=BandwidthLookback/2;
   double median=(BandwidthLookback%2==0) ? (bwArray[mid-1]+bwArray[mid])/2.0 : bwArray[mid];
   bool expanding = bwCurrent > median;

   // --- Signal detection ---
   bool longSignal  = (closePrev > upperPrev) && expanding;
   bool shortSignal = (closePrev < lowerPrev) && expanding;

   // --- Position management ---
   int longCount  = CountPositionsByType(POSITION_TYPE_BUY);
   int shortCount = CountPositionsByType(POSITION_TYPE_SELL);
   if(longSignal && shortCount > 0) CloseAllPositionsByType(POSITION_TYPE_SELL);
   if(shortSignal && longCount > 0) CloseAllPositionsByType(POSITION_TYPE_BUY);
   if(longSignal  && longCount == 0 && CountPositionsByType(POSITION_TYPE_SELL)==0) OpenMarketOrder(ORDER_TYPE_BUY, atrVal);
   if(shortSignal && shortCount == 0 && CountPositionsByType(POSITION_TYPE_BUY)==0) OpenMarketOrder(ORDER_TYPE_SELL, atrVal);

   // --- Risk management ---
   ManageTrailingStops(atrVal);
   EnforceMaxHolding();
  }

//+------------------------------------------------------------------+
//| Open market order with SL and TP                                  |
//+------------------------------------------------------------------+
void OpenMarketOrder(ENUM_ORDER_TYPE orderType, double atr)
  {
   double price, sl, tp;
   double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
   double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
   double spread = ask - bid;

   if(orderType == ORDER_TYPE_BUY)
     { price = ask;
       sl = NormalizeDouble(price - atr * ATR_SL_Mult - spread, _Digits);
       tp = NormalizeDouble(price + atr * ATR_TP_Mult, _Digits); }
   else
     { price = bid;
       sl = NormalizeDouble(price + atr * ATR_SL_Mult + spread, _Digits);
       tp = NormalizeDouble(price - atr * ATR_TP_Mult, _Digits); }

   // Validate minimum stop distance
   double slDist = MathAbs(price - sl);
   double minDist = SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL) * _Point;
   if(slDist < minDist)
     { Print("SL distance below broker minimum."); return; }

   if(m_trade.PositionOpen(_Symbol, orderType, LotSize, price, sl, tp, "BB_H4_EA") &&
      (m_trade.ResultRetcode()==TRADE_RETCODE_DONE || m_trade.ResultRetcode()==TRADE_RETCODE_DONE_PARTIAL))
      Print("Opened: ", EnumToString(orderType), " ", price, " SL=", sl, " TP=", tp);
   else
      Print("Open failed: ", m_trade.ResultRetcode());
  }

//+------------------------------------------------------------------+
//| Count positions by type                                           |
//+------------------------------------------------------------------+
int CountPositionsByType(ENUM_POSITION_TYPE posType)
  {
   int count = 0;
   for(int i = PositionsTotal() - 1; i >= 0; i--)
     { if(!m_position.SelectByIndex(i)) continue;
       if(m_position.Symbol() != _Symbol) continue;
       if(m_position.Magic() != MagicNumber) continue;
       if(m_position.PositionType() == posType) count++; }
   return count;
  }

//+------------------------------------------------------------------+
//| Close all positions of a given type                               |
//+------------------------------------------------------------------+
void CloseAllPositionsByType(ENUM_POSITION_TYPE posType)
  {
   for(int i = PositionsTotal() - 1; i >= 0; i--)
     { if(!m_position.SelectByIndex(i)) continue;
       if(m_position.Symbol() != _Symbol) continue;
       if(m_position.Magic() != MagicNumber) continue;
       if(m_position.PositionType() != posType) continue;
       m_trade.PositionClose(m_position.Ticket()); }
  }

//+------------------------------------------------------------------+
//| ATR-based trailing stop                                           |
//+------------------------------------------------------------------+
void ManageTrailingStops(double atr)
  {
   double trailDist = atr * ATR_Trail_Mult;
   for(int i = PositionsTotal() - 1; i >= 0; i--)
     { if(!m_position.SelectByIndex(i)) continue;
       if(m_position.Symbol() != _Symbol) continue;
       if(m_position.Magic() != MagicNumber) continue;

       double openPrice = m_position.PriceOpen();
       double currentSL = m_position.StopLoss();
       double currentTP = m_position.TakeProfit();
       ENUM_POSITION_TYPE pType = m_position.PositionType();

       if(pType == POSITION_TYPE_BUY)
         { double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
           if(bid < openPrice + trailDist) continue;
           double newSL = NormalizeDouble(bid - trailDist, _Digits);
           if(newSL > currentSL)
              m_trade.PositionModify(m_position.Ticket(), newSL, currentTP); }
       else if(pType == POSITION_TYPE_SELL)
         { double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
           if(ask > openPrice - trailDist) continue;
           double newSL = NormalizeDouble(ask + trailDist, _Digits);
           if(newSL < currentSL || currentSL == 0)
              m_trade.PositionModify(m_position.Ticket(), newSL, currentTP); } }
  }

//+------------------------------------------------------------------+
//| Time-based position close                                         |
//+------------------------------------------------------------------+
void EnforceMaxHolding()
  {
   datetime currentTime = iTime(_Symbol, PERIOD_H4, 0);
   for(int i = PositionsTotal() - 1; i >= 0; i--)
     { if(!m_position.SelectByIndex(i)) continue;
       if(m_position.Symbol() != _Symbol) continue;
       if(m_position.Magic() != MagicNumber) continue;
       datetime openTime = (datetime)m_position.Time();
       int barsHeld = (int)((currentTime - openTime) / PeriodSeconds(PERIOD_H4));
       if(barsHeld >= MaxHoldingBars)
          m_trade.PositionClose(m_position.Ticket()); }
  }
//+------------------------------------------------------------------+

Copy this code into MetaEditor, save it in the MQL5/Experts folder, and compile with F7.

Generate a custom Multi-pair volatility EA

Describe the entry, exit, position, order, and risk rules you want. Pineify generates editable MQL5 source code that you can inspect and compile in MetaEditor.

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.