Edgoro
Pine Script v6 → MQL5 custom indicator

ta.pivothigh and ta.pivotlow in MQL5: confirmed swing points

Short answer

ta.pivothigh(high, left, right) returns a bar's high once right more bars have closed without going above it, so right bars late. MQL5 has no pivot function: on each closed bar, test whether the bar right bars back is higher than the left bars before it and the right bars after it, and write its high into the buffer at that older bar, which is what Pine's offset = -rightBars draws.

Pineta.pivothighta.pivotlowplotshape offsetna

MQL5DRAW_ARROWPlotIndexSetIntegerSetIndexBufferiCustom

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

The Pine Script v6 code

An overlay indicator that marks pivot highs and lows with 5 bars on each side, drawn back on the pivot bar.

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("Pivot highs and lows", overlay = true)

leftBars = input.int(5, "Bars on the left", minval = 1)
rightBars = input.int(5, "Bars on the right", minval = 1)

// The pivot's price, returned rightBars bars AFTER the pivot bar (na on every other bar)
ph = ta.pivothigh(high, leftBars, rightBars)
pl = ta.pivotlow(low, leftBars, rightBars)

// offset = -rightBars draws the shape back on the pivot bar itself
plotshape(not na(ph), "Pivot high", shape.triangledown, location.abovebar, color.red, offset = -rightBars, size = size.tiny)
plotshape(not na(pl), "Pivot low", shape.triangleup, location.belowbar, color.teal, offset = -rightBars, size = size.tiny)

The MQL5 program

A custom indicator drawing the same pivots, confirmed on closed bars only. It only draws; an EA can read its buffers with iCustom().

Save it as MQL5/Indicators/ta-pivothigh.mq5 and compile it in MetaEditor (F7): 0 errors, 0 warnings.

MQL5
//+------------------------------------------------------------------+
//| ta.pivothigh / ta.pivotlow in MQL5                               |
//| A pivot is confirmed InpRight closed bars later, then drawn on   |
//| the pivot bar itself (Pine's offset = -rightBars).               |
//| Custom indicator: save in MQL5/Indicators and compile (F7).      |
//+------------------------------------------------------------------+
#property indicator_chart_window
#property indicator_buffers 2
#property indicator_plots   2

#property indicator_label1  "Pivot high"
#property indicator_type1   DRAW_ARROW
#property indicator_color1  clrRed
#property indicator_label2  "Pivot low"
#property indicator_type2   DRAW_ARROW
#property indicator_color2  clrTeal

input int InpLeft  = 5; // Bars on the left
input int InpRight = 5; // Bars on the right

double HighBuf[], LowBuf[];

int OnInit()
  {
   if(InpLeft < 1 || InpRight < 1)
      return INIT_PARAMETERS_INCORRECT;
   SetIndexBuffer(0, HighBuf, INDICATOR_DATA);
   SetIndexBuffer(1, LowBuf, INDICATOR_DATA);
   PlotIndexSetInteger(0, PLOT_ARROW, 234);        // down arrow above pivot highs
   PlotIndexSetInteger(1, PLOT_ARROW, 233);        // up arrow below pivot lows
   PlotIndexSetInteger(0, PLOT_ARROW_SHIFT, -12);
   PlotIndexSetInteger(1, PLOT_ARROW_SHIFT, 12);
   PlotIndexSetDouble(0, PLOT_EMPTY_VALUE, EMPTY_VALUE);
   PlotIndexSetDouble(1, PLOT_EMPTY_VALUE, EMPTY_VALUE);
   return INIT_SUCCEEDED;
  }

// Bar c is a pivot high if its high is strictly above the InpLeft highs before it and the InpRight highs after it
bool IsPivotHigh(const double &h[], const int c)
  {
   for(int j = 1; j <= InpLeft; j++)
      if(h[c - j] >= h[c])
         return false;
   for(int j = 1; j <= InpRight; j++)
      if(h[c + j] >= h[c])
         return false;
   return true;
  }

bool IsPivotLow(const double &l[], const int c)
  {
   for(int j = 1; j <= InpLeft; j++)
      if(l[c - j] <= l[c])
         return false;
   for(int j = 1; j <= InpRight; j++)
      if(l[c + j] <= l[c])
         return false;
   return true;
  }

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[])
  {
   int start = prev_calculated > 0 ? prev_calculated - 1 : 0;
   for(int i = start; i < rates_total; i++)
     {
      HighBuf[i] = EMPTY_VALUE;  // na until a later bar confirms a pivot here
      LowBuf[i]  = EMPTY_VALUE;
     }

   // Bar i confirms (or not) the candidate c = i - InpRight. Only closed bars confirm,
   // so the forming bar (rates_total - 1) never makes an arrow appear and then vanish
   for(int i = MathMax(start, InpLeft + InpRight); i < rates_total - 1; i++)
     {
      int c = i - InpRight;
      if(IsPivotHigh(high, c))
         HighBuf[c] = high[c];
      if(IsPivotLow(low, c))
         LowBuf[c] = low[c];
     }
   return rates_total;
  }
//+------------------------------------------------------------------+

Line by line

Pine Script v6
ph = ta.pivothigh(high, leftBars, rightBars)
MQL5
int c = i - InpRight;          // the candidate pivot bar
if(IsPivotHigh(high, c))
   HighBuf[c] = high[c];

Bar i is the confirming bar and c the candidate, right bars earlier. IsPivotHigh() compares bar c with the left bars before it and the right bars after it.

Pine Script v6
// the test inside ta.pivothigh
MQL5
for(int j = 1; j <= InpLeft; j++)
   if(h[c - j] >= h[c])
      return false;
for(int j = 1; j <= InpRight; j++)
   if(h[c + j] >= h[c])
      return false;

Strictly higher than every neighbour. TradingView's reference doesn't document how equal highs are treated, so on flat tops the two versions can disagree.

Pine Script v6
plotshape(not na(ph), "Pivot high", shape.triangledown,
  location.abovebar, color.red, offset = -rightBars)
MQL5
HighBuf[c] = high[c];  // written on the pivot bar itself

Pine computes the value on the confirming bar and shifts the drawing back; MQL5 simply writes the older element of the buffer.

Pine Script v6
// Pine also confirms on the real-time bar
MQL5
for(int i = MathMax(start, InpLeft + InpRight); i < rates_total - 1; i++)

Only closed bars confirm here. The forming bar's high can still change, so a pivot confirmed on it could disappear before the bar closes.

Pine Script v6
// na on every other bar
MQL5
HighBuf[i] = EMPTY_VALUE;
LowBuf[i]  = EMPTY_VALUE;

Each recalculated bar starts with no pivot; a later bar may fill it in.

Pitfalls

  • It is late by design. A pivot exists only right bars after its high. A backtest that acts at the pivot bar trades on information it didn't have yet, which is why pivot indicators look perfect on history.
  • Reading pivots from an EA. With iCustom(), the newest right + 1 elements of the buffers are always EMPTY_VALUE. Loop back from shift right + 1 to find the most recent pivot, and remember that it became known only right bars after the shift it sits on. See reading a custom indicator with iCustom.
  • Equal highs. This program needs a strictly higher high on both sides. If TradingView marks a flat top that this version doesn't, or the other way round, that is the reason.
  • Different bars, different pivots. A one-tick difference in a high, or bars cut at other times (H4 and above in server time), moves or removes pivots on one platform only.
  • Index range. The candidate needs left bars before it and right after it; starting the loop earlier reads before index 0 and stops the indicator with array out of range.

Checking that both give the same values

  1. Same symbol, timeframe and inputs on both platforms. List the last ten pivot highs on each, with their bar times and prices.
  2. The lists should match wherever the highs match; look for differences around equal highs and where the feeds differ by a tick.
  3. In real time, a new arrow should appear on both after the right-th bar closes (TradingView may show it earlier, during that bar).

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.