Samples: statistics and tools
Sample programs for statistics and everyday tools. Z-score, percent rank, correlation, day range, position size, round numbers, weekly range and more.
These programs measure how unusual a price or a bar is, or draw the reference levels and helper numbers traders use every day. The programs that read days, weeks or sessions make most sense on an intraday chart.
Z-score#
How many standard deviations the close is from its average.
// Z-score: how many standard deviations the close is from its average.
// Values beyond 2 or -2 are rare under a normal spread of prices.
indicator "Z-score"
edition = 1
display = pane
input length: int = 20, name = "Length", min = 2
let average = tech.sma(close, length)
let deviation = tech.stdev(close, length)
let z = if deviation > 0 then (close - average) / deviation else 0
plot(z, name = "Z-score", style = histogram, color = if z >= 0 then color.green else color.red)
level(2, name = "Plus two", color = color.gray, style = dashed)
level(-2, name = "Minus two", color = color.gray, style = dashed)What it teaches: tech.stdev, a guarded division, and a histogram that changes colour with its sign.
Percent rank#
The share of recent closes that were lower than this one, and the same rank for the size of the bar.
// Percent rank: the share of the last `length` closes that were lower than this one, from 0 to 100.
// The same idea on the true range shows whether this bar moved more than usual.
indicator "Percent rank"
edition = 1
display = pane
input length: int = 100, name = "Length", min = 2
let close_rank = tech.percent_rank(close, length)
let range_rank = tech.percent_rank(tech.tr(true), length)
plot(close_rank, name = "Rank of the close", color = color.blue)
plot(range_rank, name = "Rank of the true range", color = color.orange)
level(80, name = "High", color = color.gray, style = dashed)
level(20, name = "Low", color = color.gray, style = dashed)What it teaches: tech.percent_rank, and tech.tr(true), the true range of the bar.
Change and volume correlation#
Whether big volume tends to come with up moves or with down moves.
// Rolling correlation of the bar's change and its volume, from -1 to 1.
// Near 1 big volume comes with up moves; near -1 it comes with down moves; near 0 there is no link.
indicator "Change and volume correlation"
edition = 1
display = pane
input length: int = 50, name = "Length", min = 5
let change = close - close[1]
let link = tech.correlation(change, volume, length = length)
plot(link, name = "Correlation", color = color.purple, width = 2)
level(0, name = "Zero", color = color.gray)What it teaches: tech.correlation. Its third argument is written by name (length = length): the call takes only its first two arguments by position, and the editor refuses a third one written without its name (E3161).
Day range used#
How much of an average day the price has travelled so far today, in percent.
// How much of an average day the price has travelled so far today.
// The average is the mean range of the last three finished days; 100 means today is already as wide as an average day.
indicator "Day range used"
edition = 1
display = pane
let today = current(day).high - current(day).low
let average_day = (prior(day, 1).high - prior(day, 1).low + (prior(day, 2).high - prior(day, 2).low) + (prior(
day,
3,
).high - prior(day, 3).low)) / 3
let used = 100 * today / average_day
plot(used, name = "Range used %", style = area, color = color.fade(color.blue, 60))
level(100, name = "An average day", color = color.orange, style = dashed)What it teaches: current(day) for today so far and prior(day, n) for the days before. On a daily chart prior(day) stops the script with E-PERIOD-TF, so use it on an intraday chart.
ATR position size#
How many units keep the loss of a trade at a share of the account when the stop is placed a number of ATRs away. The numbers are written in a table on the last bar.
// A position size helper: with a stop placed `mult` ATRs away, how many units keep the loss at `risk` of the account.
// It is arithmetic only, written in a table on the last bar; the script places no order.
indicator "ATR position size"
edition = 1
display = price
input account: float = 10000, name = "Account", min = 1
input risk: float = 1.0, name = "Risk per trade %", min = 0.1, max = 10, step = 0.1
input mult: float = 1.5, name = "Stop in ATRs", min = 0.5, step = 0.5
let atr = tech.atr(14)
let stop_distance = atr * mult
let money_at_risk = account * risk / 100
let units = money_at_risk / stop_distance
if bar.is_last
let t = table.new(bottom_right, 2, 3, background = color.fade(color.black, 70))
table.cell(t, 0, 0, "Stop distance", text_color = color.silver)
table.cell(t, 1, 0, f"{stop_distance:price}", text_color = color.white)
table.cell(t, 0, 1, "Money at risk", text_color = color.silver)
table.cell(t, 1, 1, f"{money_at_risk:.2f}", text_color = color.white)
table.cell(t, 0, 2, "Units", text_color = color.silver)
table.cell(t, 1, 2, f"{units:.2f}", text_color = color.white)
plot(close - stop_distance, name = "Long stop", color = color.red, style = step)What it teaches: arithmetic with inputs, table.new and table.cell, and f-texts that write a price with the symbol's decimals (:price) or a number with two decimals (:.2f). It is a calculator only: the script places no order, and the size it shows is not a recommendation.
Round numbers#
The nearest round numbers above and below the close, for a step you choose.
// The nearest round numbers above and below the close, for a step you choose (for example 10 on gold, 0.01 on a currency pair).
indicator "Round numbers"
edition = 1
display = price
input step: float = 10.0, name = "Step", min = 0.0001
let below = math.floor(close / step) * step
let above = below + step
plot(above, name = "Round above", color = color.green, style = step_break)
plot(below, name = "Round below", color = color.red, style = step_break)What it teaches: math.floor to round down to a step, and the step_break style, which draws flat steps.
Last week's range#
Last week's high, low and middle, drawn through the current week.
// Last week's high and low, and the middle between them, drawn through the whole current week.
indicator "Last week's range"
edition = 1
display = price
let week_high = prior(week).high
let week_low = prior(week).low
plot(week_high, name = "Last week's high", color = color.green, style = step_break)
plot(week_low, name = "Last week's low", color = color.red, style = step_break)
plot((week_high + week_low) / 2, name = "Middle", color = color.gray, style = step_break)What it teaches: prior(week), read from the chart's own bars. It works on any chart whose bars are shorter than a week.
Session average price#
A volume-weighted average price of the London session, built by hand.
// A volume-weighted average price of the London session, built by hand with `state`:
// the sums start again on the first bar of the session and grow on every bar inside it.
indicator "Session average price"
edition = 1
display = price
session london = 08:00 .. 16:30 @ "Europe/London"
state price_volume = 0.0
state total_volume = 0.0
let typical = (high + low + close) / 3
if london.start
price_volume = typical * volume
total_volume = volume
else if london.active
price_volume += typical * volume
total_volume += volume
let average = if london.active and total_volume > 0 then price_volume / total_volume else na
plot(average, name = "Session average", color = color.orange, width = 2, style = line_break)What it teaches: two state sums that start again with london.start and grow while london.active, and the line_break style, which leaves a gap where the value is na (outside the session).
Regression line#
A straight line fitted to the last closes, green while it rises and red while it falls.
// A straight line fitted to the last `length` closes (least squares), and its slope per bar.
// tech.linreg gives the value of the fitted line on the current bar; the slope is the change of that value from the bar before.
indicator "Regression line"
edition = 1
display = price
input length: int = 50, name = "Length", min = 2
let fitted = tech.linreg(close, length, offset = 0)
let slope = fitted - fitted[1]
plot(fitted, name = "Regression", color = if slope >= 0 then color.green else color.red, width = 2)What it teaches: tech.linreg with its offset written by name, and the slope as the change of the fitted value from one bar to the next.
Market dashboard#
Trend, momentum, volatility and volume in one small table.
// A dashboard of several readings, written once on the last bar: trend, momentum, volatility and volume.
indicator "Market dashboard"
edition = 1
display = price
let trend_up = close > tech.ema(close, 50)
let rsi = tech.rsi(close, 14)
let atr_percent = 100 * tech.atr(14) / close
let volume_ratio = volume / tech.sma(volume, 20)
if bar.is_last
let t = table.new(
top_right,
2,
4,
background = color.fade(color.black, 70),
border_color = color.gray,
border_width = 1,
)
table.cell(t, 0, 0, "Trend", text_color = color.silver)
table.cell(
t,
1,
0,
if trend_up then "above EMA 50" else "below EMA 50",
text_color = if trend_up then color.green else color.red,
)
table.cell(t, 0, 1, "RSI 14", text_color = color.silver)
table.cell(t, 1, 1, f"{rsi:.1f?'n/a'}", text_color = color.white)
table.cell(t, 0, 2, "ATR %", text_color = color.silver)
table.cell(t, 1, 2, f"{atr_percent:.2f?'n/a'}", text_color = color.white)
table.cell(t, 0, 3, "Volume / average", text_color = color.silver)
table.cell(t, 1, 3, f"{volume_ratio:.2f?'n/a'}", text_color = color.white)
plot(close, name = "Close", color = color.gray)What it teaches: several readings worked out on every bar and shown once on the last bar, a cell whose text and colour depend on a condition, and the ?'n/a' fallback of an f-text for a value that is na.