Skip to content
NEXORA Money & AI

How to Develop a Bollinger Bands Breakout and Retest Indicator

October 3, 2026 · OYIN ADESOLA · 6 min read · Updated October 12, 2026
Cover graphic for the article: How to Develop a Bollinger Bands Breakout and Retest Indicator

Last checked: 12 October 2026. This guide draws on the MQL5 reference for iBands and CopyBuffer. The rule is a teaching example and has not been backtested for this article. Test on a demo account.

A Bollinger Bands breakout happens when a candle closes outside the upper or lower band. A retest happens when price comes back to the band or the middle line and holds. Many traders prefer to wait for the retest rather than chase the first move. This guide turns that idea into a small state machine you can code in MQL5.

Define the events precisely

Words like “breakout” and “retest” are vague until you convert them into conditions on closed candles:

  • Breakout up: a candle closes above the upper band.
  • Retest up: within the next N candles, a candle’s low trades down to or below the upper band (or the middle line, if you choose that version) but the candle still closes above the band.
  • Failure: a candle closes back inside the bands before any valid retest, which cancels the setup.
  • The downside is the mirror image.

Every word in those definitions maps to a comparison between a closed candle’s values and the band values on the same candle. That is what makes the logic testable.

Summary graphic listing the main sections of: How to Develop a Bollinger Bands Breakout and Retest Indicator

Why a state machine

The indicator has to remember what happened on earlier bars: a breakout occurred two candles ago, and we are waiting. A state machine stores that memory in a small number of named states: IDLE, WAIT_RETEST_UP, WAIT_RETEST_DOWN. Each new closed bar can move the state forward, back to IDLE or fire a signal.

The indicator

This indicator draws an up arrow when a valid upward retest is confirmed and a down arrow for a valid downward retest. It processes bars in order and stores arrows in buffers, so past arrows never move.

#property indicator_chart_window
#property indicator_buffers 2
#property indicator_plots   2
#property indicator_label1  "Retest Buy"
#property indicator_type1   DRAW_ARROW
#property indicator_color1  clrLimeGreen
#property indicator_label2  "Retest Sell"
#property indicator_type2   DRAW_ARROW
#property indicator_color2  clrTomato

input int    InpPeriod   = 20;
input double InpDev      = 2.0;
input int    InpMaxWait  = 5;     // candles allowed for the retest

double BuyBuf[], SellBuf[];
int    hBB = INVALID_HANDLE;

int OnInit()
  {
   SetIndexBuffer(0, BuyBuf,  INDICATOR_DATA);
   SetIndexBuffer(1, SellBuf, INDICATOR_DATA);
   PlotIndexSetInteger(0, PLOT_ARROW, 233);   // up arrow
   PlotIndexSetInteger(1, PLOT_ARROW, 234);   // down arrow
   PlotIndexSetDouble(0, PLOT_EMPTY_VALUE, EMPTY_VALUE);
   PlotIndexSetDouble(1, PLOT_EMPTY_VALUE, EMPTY_VALUE);
   hBB = iBands(_Symbol, _Period, InpPeriod, 0, InpDev, PRICE_CLOSE);
   return(hBB == INVALID_HANDLE ? INIT_FAILED : INIT_SUCCEEDED);
  }

void OnDeinit(const int reason) { IndicatorRelease(hBB); }

int OnCalculate(const int rates_total, const int prev_calculated,
                const datetime &time[], const double &open[],
                const double &high[], const double &low[],
                const double &close[], const long &tick_volume[],
                const long &volume[], const int &spread[])
  {
   if(rates_total < InpPeriod + 10) return(0);

   double up[], lo[];
   if(CopyBuffer(hBB, 1, 0, rates_total, up) != rates_total) return(0);
   if(CopyBuffer(hBB, 2, 0, rates_total, lo) != rates_total) return(0);
   ArraySetAsSeries(up, false);
   ArraySetAsSeries(lo, false);

   int start = (prev_calculated > 1) ? prev_calculated - 1 : InpPeriod;
   int last  = rates_total - 1;                       // forming bar: never evaluated

   // rebuild state by scanning a short window before 'start'
   int from = MathMax(InpPeriod, start - InpMaxWait - 2);
   int state = 0, waited = 0;                         // 0 idle, 1 wait up, -1 wait down

   for(int i = from; i < last; i++)
     {
      if(i >= start) { BuyBuf[i] = EMPTY_VALUE; SellBuf[i] = EMPTY_VALUE; }

      if(state == 0)
        {
         if(close[i] > up[i]) { state = 1;  waited = 0; }
         else if(close[i] < lo[i]) { state = -1; waited = 0; }
         continue;
        }
      waited++;
      if(state == 1)
        {
         if(close[i] <= up[i]) { state = 0; }                       // failed
         else if(low[i] <= up[i])                                   // touched band, held
           {
            if(i >= start) BuyBuf[i] = low[i] - 10 * _Point;
            state = 0;
           }
         else if(waited >= InpMaxWait) state = 0;
        }
      else
        {
         if(close[i] >= lo[i]) { state = 0; }
         else if(high[i] >= lo[i])
           {
            if(i >= start) SellBuf[i] = high[i] + 10 * _Point;
            state = 0;
           }
         else if(waited >= InpMaxWait) state = 0;
        }
     }
   BuyBuf[last] = EMPTY_VALUE;
   SellBuf[last] = EMPTY_VALUE;
   return(rates_total);
  }

Understanding the design

The loop stops one bar before the newest, so the forming candle is never evaluated and no arrow can appear and vanish within a candle. Because OnCalculate may be called with only a few new bars, the code re-scans a short window before the starting bar to rebuild the state. Without that, a breakout that happened just before the window would be forgotten.

The BuyBuf and SellBuf arrays are filled with EMPTY_VALUE where there is no arrow. Setting PLOT_EMPTY_VALUE tells the terminal not to draw those points.

Testing it properly

  1. Attach it to a chart and scroll back. Check each arrow by eye: was there a close outside the band before it, and a touch followed by a close outside?
  2. Watch it live on a demo chart for several days. Arrows should appear only after candles close and should never disappear.
  3. Count how many arrows were followed by continuation. A handful of examples proves nothing, so test across many months and several pairs before drawing conclusions.

Honest limits

  • Bollinger Bands widen when price moves, so a strong trend can keep price outside the band for many candles with no retest at all.
  • In sideways markets, closes outside the band often fail. The ranging filter article in this series helps with that.
  • The arrow marks a pattern, not a prediction.

Installing and adjusting it

Create a custom indicator in MetaEditor named BBRetest, paste the code and compile. After attaching it, the Inputs tab lets you change the Bollinger period and deviation, and the number of candles you allow for the retest. Standard Bollinger settings are a period of 20 and a deviation of 2, but they are conventions rather than laws, and some traders prefer a deviation of 2.5 for fewer signals. Change one input at a time so you can see what each does.

Arrow codes and offsets

PLOT_ARROW takes a Wingdings character code. Code 233 draws an upward arrow and 234 a downward one in the standard Wingdings font. The arrows are placed ten points below the candle’s low or above its high so they do not sit on the candle itself. On instruments with a very small point size, ten points may be too tight; try a multiple of ATR for the offset instead.

What a good manual check looks like

Pick three recent arrows and walk through them by eye. For each, find the candle that closed outside the band, count the candles until the arrow, and confirm that none of the candles in between closed back inside the band. Then ask what price did next, but do not draw conclusions from three cases. The goal of this check is to prove the code does what the definition says, not to prove the pattern makes money.

What we could not confirm

  • Any profitability of the retest rule. We did not run a backtest.
  • The best period, deviation or wait length for your market.

Frequently asked questions

Should I retest the band or the middle line? Both are used. The middle line retest is deeper and less frequent. Try each and compare.

Why arrows in buffers instead of objects? Buffers are tied to bars and cleaned up by the terminal, and they work with iCustom if you later build an Expert Advisor.

Key takeaways

Convert vocabulary into closed-candle comparisons, use a state machine for multi-bar patterns, never evaluate the forming bar and verify arrows against the chart by hand.

Sources checked

  • MQL5 Reference, iBands, CopyBuffer, PlotIndexSetInteger and PLOT_ARROW, mql5.com

Educational content only — not financial advice. Income and savings results vary and are never guaranteed. How we write

Enjoyed this story?

Get the best stories delivered weekly.

Leave a Reply

Your email address will not be published. Required fields are marked *

Share with