Lesson 7: Lists and maps
Keep many values in an array or a map. A rolling window of recent bars, loops over the items, median and percentile, and counts by key.
Lesson 7 of 18. History with [n] gives one value at a time. When you need many values together, for example the last twenty bar ranges to find their middle, keep them in an array (an ordered list) or a map (values stored under keys).
A window of recent values#
indicator "Last closes"
edition = 1
display = price
input size: int = 20, name = "Window size", min = 2, max = 500
state window: array<float> = array<float>()
window.push(close)
if window.size() > size
window.shift()
plot(window.avg(), name = "Average of the window", color = color.orange)
plot(window.max(), name = "Highest close", color = color.green)
plot(window.min(), name = "Lowest close", color = color.red)state window: array<float> = array<float>()makes an empty array of numbers once and keeps it from bar to bar.window.push(close)adds this bar's close at the end. When the window holds more thansizeitems,window.shift()drops the oldest one..size(),.avg(),.max()and.min()read the whole array..at(0)is the oldest item and.last()the newest; indexes count from 0.
Going through the items#
for x in window visits every item in order. This counts how many closes of the window lie above their average:
indicator "Share above the average"
edition = 1
display = pane
input size: int = 20, name = "Window size", min = 2, max = 500
state window: array<float> = array<float>()
window.push(close)
if window.size() > size
window.shift()
let average = window.avg()
mut above = 0
for x in window
if x > average
above += 1
plot(above / window.size() * 100, name = "Closes above the average, %", color = color.purple)
level(50, name = "Half", color = color.gray, style = dashed)mut above = 0 starts again on every bar, so the count is made fresh each time. for (i, x) in window gives the position i as well.
The middle of a window#
An array can sort its items to find a median or a percentile. Here the window holds bar ranges, and the script shows the typical range and a high one:
indicator "Typical range"
edition = 1
display = pane
input size: int = 50, name = "Window size", min = 5, max = 500
state ranges: array<float> = array<float>()
ranges.push(high - low)
if ranges.size() > size
ranges.shift()
plot(high - low, name = "Range", style = columns, color = color.gray)
plot(ranges.median(), name = "Median range", color = color.blue)
plot(ranges.percentile(80), name = "80th percentile", color = color.orange)ranges.median() is the middle value of the window and ranges.percentile(80) the value below which 80 percent of the items lie. A column above the orange line is a bar that is wider than four bars out of five in the window.
Values under keys#
A map stores a value under a key. This one keeps, for every hour of the day, the total range of the bars of that hour and their number, and draws the average range of the current hour:
indicator "Range by hour"
edition = 1
display = pane
state totals: map<int, float> = map<int, float>()
state counts: map<int, int> = map<int, int>()
let hour = clock.hour_of(bar.time, zone = "UTC")
totals.set(hour, (totals.at(hour) ?? 0) + (high - low))
counts.set(hour, (counts.at(hour) ?? 0) + 1)
mut widest_hour = 0
mut widest = 0.0
for key in totals.keys()
let average = totals.at(key) / counts.at(key)
if average > widest
widest = average
widest_hour = key
plot(totals.at(hour) / counts.at(hour), name = "Average range of this hour", color = color.blue)
plot(widest_hour, name = "Hour with the widest bars (UTC)", display = none)totals.set(key, value)stores a value;totals.at(key)reads it, and givesnawhile the key is missing, so?? 0starts a new hour at 0.totals.keys()is an array of the keys, so aforloop can go through them.display = nonekeeps the last plot off the chart: the script still works the value out, but does not draw it.
Use it on an intraday chart: on a daily chart every bar has the same hour.
A mistake to avoid#
Brackets after a name always mean history, never "the item at": window[1] is the whole array as it was one bar ago. The checker refuses it and says to write window.at(1):
indicator "Brackets on an array"
edition = 1
display = price
state window: array<float> = array<float>()
window.push(close)
if window.size() > 10
window.shift()
plot(window[1], name = "Second item")An array or a map holds at most 100,000 items; past that the script stops with E-LIMIT-ITEMS. A window that drops its oldest item with shift() never comes near it. Reading .at(i) outside the array also stops the script, so check .size() first.
Try changing#
- In "Last closes", replace
closewithhigh - lowinwindow.push(...): the lines now describe bar ranges instead of prices (move the script to its own pane withdisplay = pane). - In "Share above the average", change
x > averagetox > close: the line shows the share of the window above the current close. - In "Typical range", try
ranges.percentile(95): the orange line rises and fewer columns cross it. - In "Range by hour", use
zone = "Europe/London": the hours are counted in London time.
Checkpoint#
- You can keep a rolling window of values in a
statearray withpushandshift. - You can go through an array with
for x in ...and read single items with.at(i). - You can find a median and a percentile of a window.
- You can count and sum values by key in a map, and you know that
.at(key)givesnafor a missing key.