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.
//@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.
//+------------------------------------------------------------------+
//| 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
ph = ta.pivothigh(high, leftBars, rightBars)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.
// the test inside ta.pivothighfor(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.
plotshape(not na(ph), "Pivot high", shape.triangledown,
location.abovebar, color.red, offset = -rightBars)HighBuf[c] = high[c]; // written on the pivot bar itselfPine computes the value on the confirming bar and shifts the drawing back; MQL5 simply writes the older element of the buffer.
// Pine also confirms on the real-time barfor(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.
// na on every other barHighBuf[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
rightbars 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 newestright + 1elements of the buffers are alwaysEMPTY_VALUE. Loop back from shiftright + 1to find the most recent pivot, and remember that it became known onlyrightbars 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
leftbars before it andrightafter 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
- Same symbol, timeframe and inputs on both platforms. List the last ten pivot highs on each, with their bar times and prices.
- The lists should match wherever the highs match; look for differences around equal highs and where the feeds differ by a tick.
- 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.