The script
An average daily range built from intraday bars: one value per completed day goes into an array, and today's range is shown as a percentage of the average. Paste it into a new indicator in the Pine Editor, then save and add it to the chart.
//@version=6
indicator("Average daily range (arrays)", overlay = false)
days = input.int(14, "Days in the average", minval = 1, maxval = 250)
// One value per completed day, oldest first
var array<float> ranges = array.new<float>()
var float dayHigh = na
var float dayLow = na
bool newDay = timeframe.change("D")
if newDay
// Store the day that just ended, then start the new one
if not na(dayHigh)
ranges.push(dayHigh - dayLow)
if ranges.size() > days
ranges.shift()
dayHigh := high
dayLow := low
else
dayHigh := math.max(nz(dayHigh, high), high)
dayLow := math.min(nz(dayLow, low), low)
bool ready = ranges.size() > 0
float adr = ready ? ranges.avg() : na
float medRange = ready ? ranges.median() : na
float widest = ready ? ranges.max() : na
float todayPct = adr > 0 ? (dayHigh - dayLow) / adr * 100 : na
plot(todayPct, "Today's range, % of ADR", todayPct >= 100 ? color.orange : color.teal, style = plot.style_columns)
hline(100, "100% of ADR", color.gray, hline.style_dashed)
plot(adr, "ADR", display = display.data_window)
plot(medRange, "Median daily range", display = display.data_window)
plot(widest, "Widest day in the window", display = display.data_window)
plot(ranges.size(), "Days stored", display = display.data_window)How it works
var array<float> ranges = array.new<float>()creates an empty array of floats once. Withoutvarit would be a new, empty array on every bar.- One value per day.
timeframe.change("D")is true on the first bar of each day. Only then is the finished day's range pushed, so the array holds days, not bars. A moving average of bars liketa.sma(high - low, 14)couldn't do that on an intraday chart. - The window.
push()adds at the end andshift()removes the first, oldest element once there are more thandays. - Reading it.
avg(),median()andmax()summarize the whole array in one call.readyguards them on the first day, before anything is stored. - Today so far.
dayHigh - dayLowis the range of the current, unfinished day, compared with the average of finished days. The columns turn orange once today has moved more than an average day. ranges.size()in the Data Window shows how many days are in the average; it stops growing atdays.
Pitfalls
- An array without var.
array<float> ranges = array.new<float>()builds a new empty array on every bar, so it never holds more than one value and the average is just the last range. - Reading an empty array.
ranges.get(0),ranges.first()orranges.last()on an empty array stops the script with Index is out of bounds. Checksize()first, asreadydoes. - Loops from 0 to size() - 1. On an empty array,
for i = 0 to ranges.size() - 1runs from 0 to -1, and Pine counts down when the end is below the start, so the loop runs twice andget(0)fails. Usefor r in ranges, or check the size first. - Sorting the array you shift.
ranges.sort()reorders the values in place, soshift()would then remove the smallest value, not the oldest. Sort a copy:array<float> sorted = ranges.copy(). - Arrays that only grow. An array that receives a value every bar and is never trimmed keeps growing toward the 100,000-element limit, and returning arrays from
request.security()copies them on every bar (Memory limits exceeded). Trim withshift().
Variations
Walk through the array. for r in ranges reads each element in order, here to count the stored days wider than the average, and percentile_nearest_rank() gives a percentile without sorting anything:
// How many of the stored days were wider than the average
int wideDays = 0
for r in ranges
if r > adr
wideDays += 1
// The range that 80% of the stored days did not exceed
float p80 = ready ? ranges.percentile_nearest_rank(80) : na
plot(wideDays, "Days wider than the ADR", display = display.data_window)
plot(p80, "80th percentile range", display = display.data_window)
Index and value together. for [i, r] in ranges gives the position too: i = 0 is the oldest day and i = ranges.size() - 1 the most recent. The loops example covers the other loop forms.
Questions
How do I get the last element of an array in Pine Script?
Call a.last(), or a.get(a.size() - 1). Both fail on an empty array, so check a.size() > 0 first. a.first() and a.get(0) return the oldest element of an array filled with push().
Can I use [1] to get an array's values from the previous bar?
Not for a var array: it's the same array on every bar, so ranges[1] points to the same object, with today's contents. To keep past states, store the values you need in separate variables or in another array.
What is the maximum size of an array in Pine Script?
100,000 elements. Long before that, large arrays slow the script down and use memory, so keep them trimmed to the window you need with shift() or pop().
Related pages
Every script on this page was checked with Edgoro Pine Check (Pine Script v6 rules). Pine Check is a static checker, not TradingView's compiler: TradingView compiles the script when you save it in the Pine Editor. The scripts are examples to learn from, not trading systems. 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.