Edgoro
Pine Script v6 → MQL5 custom indicator

Pine plot(), fill() and plotshape() in MQL5: indicator buffers

Short answer

Every plot() becomes an indicator buffer: declare the counts with #property indicator_buffers and indicator_plots, bind an array with SetIndexBuffer(), pick a drawing type (DRAW_LINE, DRAW_COLOR_LINE, DRAW_ARROW, DRAW_FILLING) and write one value per bar. fill() needs two buffers of its own and a changing color needs a color-index buffer.

Pineplotfillplotshapeta.bbcolor.new

MQL5SetIndexBufferPlotIndexSetIntegerDRAW_COLOR_LINEDRAW_FILLINGiBands

  • MQL5: compiled in MetaEditor, 0 errors, 0 warnings
  • Pine: checked by Edgoro Pine Check, not run in TradingView

The Pine Script v6 code

Bollinger Bands with a basis line that changes color with its slope, a fill between the bands and a dot over bars that close above the upper band.

Checked by Edgoro Pine Check with no errors and no warnings. Pine Check is a static checker, not TradingView's compiler, and this script was not run in TradingView.

Pine Script v6
//@version=6
indicator("Bollinger Bands: plot, fill and plotshape", overlay = true)

len = input.int(20, "Length", minval = 1)
mult = input.float(2.0, "Standard deviations", minval = 0.1, step = 0.1)

[basis, upper, lower] = ta.bb(close, len, mult)

// a color that changes bar by bar
plot(basis, "Basis", basis > basis[1] ? color.teal : color.red, 2)
p1 = plot(upper, "Upper", color.gray)
p2 = plot(lower, "Lower", color.gray)
fill(p1, p2, color.new(color.gray, 90), "Band fill")
plotshape(close > upper, "Close above upper band", shape.circle, location.abovebar, color.red, size = size.tiny)

The MQL5 program

A custom indicator drawing the same bands, colored basis, fill and dots, from the built-in iBands. It only draws.

Save it as MQL5/Indicators/plot-to-indicator-buffers.mq5 and compile it in MetaEditor (F7): 0 errors, 0 warnings.

MQL5
//+------------------------------------------------------------------+
//| plot(), fill() and plotshape() as MQL5 indicator buffers         |
//| Bollinger Bands from the built-in iBands handle.                 |
//| Custom indicator: save in MQL5/Indicators and compile (F7).      |
//+------------------------------------------------------------------+
#property indicator_chart_window
#property indicator_buffers 7
#property indicator_plots   5

// Plot 1 - fill(p1, p2): DRAW_FILLING uses two buffers. Drawn first so the lines sit on top
#property indicator_label1  "Band fill"
#property indicator_type1   DRAW_FILLING
#property indicator_color1  clrWhiteSmoke,clrWhiteSmoke

// Plot 2 - plot(basis, color = cond ? teal : red): DRAW_COLOR_LINE = value buffer + color index buffer
#property indicator_label2  "Basis"
#property indicator_type2   DRAW_COLOR_LINE
#property indicator_color2  clrTeal,clrRed
#property indicator_width2  2

// Plots 3 and 4 - plot(upper), plot(lower)
#property indicator_label3  "Upper"
#property indicator_type3   DRAW_LINE
#property indicator_color3  clrGray
#property indicator_label4  "Lower"
#property indicator_type4   DRAW_LINE
#property indicator_color4  clrGray

// Plot 5 - plotshape(close > upper, shape.circle, location.abovebar)
#property indicator_label5  "Close above upper band"
#property indicator_type5   DRAW_ARROW
#property indicator_color5  clrRed

input int    InpLen  = 20;  // Length
input double InpMult = 2.0; // Standard deviations

double FillUpBuf[], FillLoBuf[], BasisBuf[], BasisColorBuf[], UpperBuf[], LowerBuf[], SignalBuf[];
int    bandsHandle = INVALID_HANDLE;

int OnInit()
  {
   if(InpLen < 2 || InpMult <= 0.0)
      return INIT_PARAMETERS_INCORRECT;
   // Buffer order follows the plots: 2 for the filling, 2 for the color line, then 1 each
   SetIndexBuffer(0, FillUpBuf, INDICATOR_DATA);
   SetIndexBuffer(1, FillLoBuf, INDICATOR_DATA);
   SetIndexBuffer(2, BasisBuf, INDICATOR_DATA);
   SetIndexBuffer(3, BasisColorBuf, INDICATOR_COLOR_INDEX);
   SetIndexBuffer(4, UpperBuf, INDICATOR_DATA);
   SetIndexBuffer(5, LowerBuf, INDICATOR_DATA);
   SetIndexBuffer(6, SignalBuf, INDICATOR_DATA);

   PlotIndexSetInteger(4, PLOT_ARROW, 159);        // Wingdings dot, like shape.circle
   PlotIndexSetInteger(4, PLOT_ARROW_SHIFT, -10);  // above the bar
   for(int p = 0; p < 5; p++)
     {
      PlotIndexSetDouble(p, PLOT_EMPTY_VALUE, EMPTY_VALUE);
      PlotIndexSetInteger(p, PLOT_DRAW_BEGIN, InpLen);
     }

   // ta.bb(close, len, mult): middle = SMA, bands = SMA +/- mult x population standard deviation
   bandsHandle = iBands(_Symbol, _Period, InpLen, 0, InpMult, PRICE_CLOSE);
   if(bandsHandle == INVALID_HANDLE)
     {
      Print("Could not create the iBands handle, error ", GetLastError());
      return INIT_FAILED;
     }
   return INIT_SUCCEEDED;
  }

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

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(BarsCalculated(bandsHandle) < rates_total)
      return prev_calculated;

   int start = prev_calculated > 0 ? prev_calculated - 1 : 0;
   int count = rates_total - start;

   // iBands buffers: 0 = BASE_LINE, 1 = UPPER_BAND, 2 = LOWER_BAND (oldest first in these arrays)
   double mid[], up[], lo[];
   if(CopyBuffer(bandsHandle, BASE_LINE, 0, count, mid) != count ||
      CopyBuffer(bandsHandle, UPPER_BAND, 0, count, up) != count ||
      CopyBuffer(bandsHandle, LOWER_BAND, 0, count, lo) != count)
      return prev_calculated;

   for(int k = 0; k < count; k++)
     {
      int i = start + k;
      BasisBuf[i]  = mid[k];
      UpperBuf[i]  = up[k];
      LowerBuf[i]  = lo[k];
      FillUpBuf[i] = up[k];
      FillLoBuf[i] = lo[k];
      // color = basis > basis[1] ? teal : red  ->  index 0 or 1 of indicator_color2
      BasisColorBuf[i] = (i > 0 && BasisBuf[i] > BasisBuf[i - 1]) ? 0.0 : 1.0;
      // plotshape(close > upper): a value where the shape goes, EMPTY_VALUE elsewhere
      SignalBuf[i] = close[i] > up[k] ? high[i] : EMPTY_VALUE;
     }
   return rates_total;
  }
//+------------------------------------------------------------------+

Line by line

Pine Script v6
plot(basis, "Basis", basis > basis[1] ? color.teal : color.red, 2)
MQL5
#property indicator_type2   DRAW_COLOR_LINE
#property indicator_color2  clrTeal,clrRed
...
BasisColorBuf[i] = (i > 0 && BasisBuf[i] > BasisBuf[i - 1]) ? 0.0 : 1.0;

The colors are listed once in the property; a second buffer holds, for each bar, the number of the color to use (0 for teal, 1 for red).

Pine Script v6
p1 = plot(upper, "Upper", color.gray)
p2 = plot(lower, "Lower", color.gray)
fill(p1, p2, color.new(color.gray, 90), "Band fill")
MQL5
#property indicator_type1   DRAW_FILLING
#property indicator_color1  clrWhiteSmoke,clrWhiteSmoke
...
FillUpBuf[i] = up[k];
FillLoBuf[i] = lo[k];

DRAW_FILLING takes two buffers of its own (copies of the bands) and fills between them. It is declared first so the lines are drawn over it. There is no transparency, hence a pale color.

Pine Script v6
plotshape(close > upper, "Close above upper band",
  shape.circle, location.abovebar, color.red, size = size.tiny)
MQL5
PlotIndexSetInteger(4, PLOT_ARROW, 159);
PlotIndexSetInteger(4, PLOT_ARROW_SHIFT, -10);
SignalBuf[i] = close[i] > up[k] ? high[i] : EMPTY_VALUE;

The condition becomes a price where the symbol is drawn, or EMPTY_VALUE for none. 159 is a dot in the Wingdings font; a negative shift moves it up, above the high.

Pine Script v6
[basis, upper, lower] = ta.bb(close, len, mult)
MQL5
bandsHandle = iBands(_Symbol, _Period, InpLen, 0, InpMult, PRICE_CLOSE);
...
CopyBuffer(bandsHandle, BASE_LINE, 0, count, mid);
CopyBuffer(bandsHandle, UPPER_BAND, 0, count, up);
CopyBuffer(bandsHandle, LOWER_BAND, 0, count, lo);

iBands() computes the same bands as ta.bb(): a simple average plus or minus the population standard deviation. Its three buffers have named numbers.

Pine Script v6
// each plot() is one series
MQL5
#property indicator_buffers 7
#property indicator_plots   5
SetIndexBuffer(0, FillUpBuf, INDICATOR_DATA);
SetIndexBuffer(1, FillLoBuf, INDICATOR_DATA);
SetIndexBuffer(2, BasisBuf, INDICATOR_DATA);
SetIndexBuffer(3, BasisColorBuf, INDICATOR_COLOR_INDEX);

Buffers are bound in plot order, and a plot's extra buffers (the filling's second one, a color index) come right after its first. Seven buffers make five plots here.

Pine Script v6
plot(upper, "Upper", color.gray)
MQL5
#property indicator_label3  "Upper"
#property indicator_type3   DRAW_LINE
#property indicator_color3  clrGray

The label is the name shown in the Data Window and the tooltip, like a plot's title.

Pitfalls

  • Counts that don't match. indicator_buffers counts every array (7 here) and indicator_plots the things drawn (5). With too few buffers declared, SetIndexBuffer() fails at run time and plots stay empty, though the code compiles.
  • No transparency. color.new(color.gray, 90) has no MetaTrader 5 equivalent: DRAW_FILLING is opaque and covers the candles unless the chart's "Chart on foreground" option is on. Choose a pale color.
  • Unwritten elements. A new bar's buffer element holds no meaningful value until you write one. Write every buffer for every bar you calculate, EMPTY_VALUE included, or stray lines and dots appear.
  • Color numbers are doubles. The color-index buffer holds 0, 1, 2… as doubles, and the order of colors in indicator_color decides what each number means.
  • Expert Advisors have no buffers. An EA draws with chart objects (ObjectCreate()), as in drawing objects on the chart, or adds an indicator to its chart with ChartIndicatorAdd().

Checking that both give the same values

  1. Read Basis, Upper and Lower in both Data Windows on the same bars: with the same closes they agree to the displayed digits.
  2. Count the dots over the last 100 bars on each chart; a difference points to bars whose close is within a tick of the upper band.
  3. The basis should change color on the same bars on both.

These programs show one technique each; they are not trading systems, and none of this is a recommendation to trade. Trading involves substantial risk of loss and is not suitable for every investor. Edgoro is a software tool, not a broker or financial adviser: nothing it produces is investment advice, and past or backtested performance does not guarantee future results.

The same topic in each language

All MQL5 errors

Rather not port it by hand?

Edgoro can generate the MQL5 version for you from a plain-English description of the rules, and compiles it in MetaEditor before you get it. Test it on a demo account: a clean build isn't proof that a strategy works.