Calls that keep a state
Some calls remember earlier bars. Each place where such a call is written has its own state, and the state moves only when the script reaches that place.
Most calls forget: math.abs(x) gives the same answer for the same x, whatever happened before. A few calls remember. tech.sma(close, 20) needs the last twenty closes, tech.crosses_above(a, b) needs the previous bar, and math.random() needs to know which number comes next. This page calls them calls that keep a state. The reference marks each of them in its Effects line: "keeps a state for each place it is written (a call site) and advances it when execution reaches the call".
The whole rule fits in one sentence. A call that keeps a state has one state for each place it is written, and the state moves exactly when the script reaches that place on a bar. If the script does not reach the place on a bar, the state stays as it was.
What keeps a state#
| Keeps a state | Does not |
|---|---|
Every function of tech except tech.tr: averages, bands, oscillators, highs and lows over a window, crossings, pivots (60 of the 61 functions) | The prices and bar values: close, bar.index, bar.time |
rises(condition) and falls(condition) | close[1], and the history of any plain name: the engine already keeps the series, so reading it moves nothing |
tf.changed(tf), math.random(...) | Arithmetic, math.* other than math.random, text.*, color.* |
data.series(...) and data.intrabars(...), which read another timeframe | The methods of an array, a map or a matrix, and the objects of the chart |
The history of anything that is not a plain name: (high - low)[1] | The members of a declared session and prior(...) and current(...) (see below) |
A function of yours that holds a state, or that calls one of the left column | A function of yours that does neither |
A state name of your own is different: it is a value you read and write yourself, in plain statements. Variables shows let, mut and state.
One state for each place#
The state belongs to the place in the text where the call is written, not to the function that is called. Two lines that both call tech.ema are two moving averages with two memories. The same goes for a function of yours: the memory is not in the function but in the place that calls it.
// Every place where a call that keeps a state is written has a state of its own. smooth() is one function called twice, with two different series,
// and each call keeps its own running average: the difference to the same average written out directly is zero on every bar.
indicator "One state for each call"
edition = 1
display = pane
fn smooth(source: float, length: int): float = tech.ema(source, length)
let from_close = smooth(close, 10)
let from_open = smooth(open, 10)
plot(from_close - tech.ema(close, 10), name = "Close, function minus direct", color = color.green)
plot(from_open - tech.ema(open, 10), name = "Open, function minus direct", color = color.red)
plot(from_close - from_open, name = "Close average minus open average", color = color.gray)The first two plots are zero because smooth(close, 10) and tech.ema(close, 10) each advance their own state on every bar with the same input. If the two calls of smooth shared one memory, the average of the opens and the average of the closes would be mixed up.
Because the state is tied to a place, the place has to be one that runs a fixed number of times on a bar. That is why a call that keeps a state may not stand in a loop. All the passes of the loop would be the same place and would share one memory, so the engine refuses it with E3301:
// Mistake: a request for another timeframe keeps a state per call site, so it may not stand in a loop (E3301).
indicator "Request in a loop"
edition = 1
display = pane
mut total = 0.0
for i in 0 to 2
total += data.series("60", close, symbol = symbol.id)
plot(total, name = "Total")The same holds for a function of yours that keeps a state and is called inside a loop. Make the call once before the loop and use its value inside, as the corrected program does:
// The fix for E3301: make the request once, outside the loop, and use its value inside.
indicator "Request outside the loop"
edition = 1
display = pane
let hourly = data.series("60", close, symbol = symbol.id)
mut total = 0.0
for i in 0 to 2
total += hourly
plot(total, name = "Total")Moving only when it is reached#
A place that the script reaches on every bar gives the state every bar, and that is what a call at the top of the script does. A place inside a block that runs on some bars gives the state only those bars. The state does not advance on the bars where the block is skipped, and it does not go back to zero either: it waits where it was.
The checker tells you when it sees this. A call that keeps a state inside an if, else or match block is allowed and is a warning, W3302:
// Warning W3302: a call that keeps a state stands in an `if` block. It is allowed, but its state moves only on the bars where the block runs.
// Here the average of the up bars is smoothed over the up bars only, which is rarely what the line says.
indicator "Stateful call in a block"
edition = 1
display = price
mut smoothed: float = na
if close > open
smoothed = tech.ema(close, 5)
plot(smoothed, name = "Smoothed")Sometimes that is exactly what you want: an average of the up bars only. Often it is not, and the usual fix is to run the call on every bar above the block and let the block choose what to keep:
// The fix for W3302: run the call on every bar above the block, and let the block only choose what to keep.
indicator "Stateful call above the block"
edition = 1
display = price
let smoothed_all = tech.ema(close, 5)
mut smoothed: float = na
if close > open
smoothed = smoothed_all
plot(smoothed, name = "Smoothed")Where a call may stand#
| Where the call is written | What the language does |
|---|---|
At the top level of the script, or on the right of a let | Reached on every bar. No message. |
In the body of an if or else block, or in an arm of a match | Allowed. The warning W3302 says the state moves only where that code runs. |
Directly in the right side of and, or or ??, or in an arm of if ... then ... else | Refused: E3302. The side may not run, so the state would move on some bars only. See Lazy operators. |
In a for or while loop, directly or through a function of yours | Refused: E3301. The passes would share one state. |
| In a function of yours | Depends on the place that calls the function: each call of the function is a place with its own states. |
The checker reports W3302 and E3302. E3301 comes from the engine when it compiles the script for a run, not from the checker's own pass.
Reads that look like calls and keep none#
Some values are computed by the engine once for every bar, whether or not the script reaches the line that reads them. They have no state of their own for the line, so they may stand anywhere, including the right side of and:
- the members of a declared session (
london.active,london.highand the others): Trading day and sessions; prior(day).high,current(week).openand the other period fields;clock.reached(...), which depends on the time of the bar and the one before it, andbar.time_in(...), which depends on the time of the bar.
// A trading session is two times and a zone. `state session` keeps a value that restarts at the start of each session,
// so this draws the high of the London session so far, and the high of the New York session so far.
indicator "Session highs"
edition = 1
display = price
session london = 08:00 .. 16:30 @ "Europe/London"
session newyork = 09:30 .. 16:00 @ "America/New_York"
state session london london_high = high
state session newyork newyork_high = high
if london.active
london_high = math.max(london_high, high)
if newyork.active
newyork_high = math.max(newyork_high, high)
plot(london_high, name = "London high so far", color = color.green)
plot(newyork_high, name = "New York high so far", color = color.orange)What it costs#
A call that keeps a state holds memory for as long as the script runs, but only as much as it reads back. Its memory follows the number of bars it reads (at most 8,192) and never the length of the chart. The memory is counted in the ledger of Limits and budgets, and every call spends steps from the budget of 500,000 for a bar.
See also: Lazy operators, E3302 and W3302, Choices and loops, Functions, Examples: calls that keep a state.