Control flow
Control flow decides what runs on each bar. A wrun indicator is AssemblyScript,
so the constructs are the typed C-style ones: if/else, the ? : ternary,
switch, for, while, do/while, break and continue.
Everything here
runs inside onBar(), which the host calls once per bar as it walks the loaded
history. The bar loop belongs to the host: your code sees one bar per call, and
a loop inside it walks memory you own, never history you do not have.
Module-level variables are where state lives between calls.
| Construct | Use it for |
|---|---|
if / else | decision logic; the condition must be a bool |
? : | one inline choice; both branches share a type |
switch | mapping a discrete code (a mode param) to a value or a branch |
for | a known iteration count: a ring buffer, a cell block, a bounded scan |
while, do/while | a condition-driven search with a bound you can prove |
break, continue | leaving a loop early, skipping an iteration |
if / else
Branch on any boolean condition; the else is optional and else if chains
as usual. The condition must be a real bool: if (value) on a number is a
compile error (AS200), so compare (if (value > 0.0), if (!isNaN(value))).
if (close > open) {
direction = 1.0; // up bar
} else if (close < open) {
direction = -1.0; // down bar
} else {
direction = 0.0; // doji
}A value from the last bar, such as the previous close, is a module-level
variable the module kept from the last call, not close[1]
(Execution model).
Ternary expressions
For a single inline choice, condition ? a : b is cleaner than a full
if. Both branches must have the same type: write 1.0 : 0.0 for an
f64, not 1 : 0, or the compiler infers an integer and refuses the
assignment. It is the idiomatic way to pick a value or to suppress a
draw, since NaN written to an output draws nothing:
// Pick a price based on the bar's direction.
const anchor = close > open ? high : low;
// Write the low only on signal bars; NaN draws nothing (a shape_where gate is the declared form).
const signal = crossed == 1 ? low : NaN;switch
switch matches an integer against case labels with a default
fallback, and falls through without break exactly as JavaScript does.
Use it to map a discrete code (a mode param) to a value or a branch.
switch (mode) {
case 0:
picked = close;
break;
case 1:
picked = high;
break;
default:
picked = low;
}for loops
Use a C-style for when you need a fixed number of iterations: summing a
window, scanning buckets, accumulating a value. The form is
for (let i = 0; i < end; i++) { ... }: the counter is an i32 (i++ and
i += 1 both work), so cast it when it meets an f64. Loop bodies work with
locals and with the module's buffers. A StaticArray<f64> sized from a
param's max in onStart() or at module start is the usual target, and
unchecked(ring[i]) skips the bounds check once the index is provably inside.
// Sum the last closes by hand, from a ring buffer the module keeps; count is how many slots are filled.
let total = 0.0;
for (let i = 0; i < count; i++) {
total += unchecked(ring[i]);
}A celled input's block is the other common loop target:
for (let i = 0; i + 3 < n; i += 4) walks [low, high, buy, sell] tuples
(Order flow).
There is no history array to loop over (close[n] does not exist): a wrun
indicator has no timeseries. A window is a History you push() once per
bar and read with ago(n)
(Stats, history and lists), or by hand a
StaticArray<f64> ring the module fills one bar at a time, sized once from
the param's max (Collections). The ring
is the window, and it is yours to size and index.
while and do/while
while (condition) { ... } and do { ... } while (condition). Use them
when the iteration count depends on what you find, and make the bound
explicit: a search back through a ring stops at the ring's size whatever
the data says. Here above is a ring of booleans (was the close above
its average on that bar) and slot(back) maps "bars back" to a ring
index.
let back = 0;
while (back < count && !unchecked(above[slot(back)])) {
back += 1;
}A counted for is clearer and safer when you know the bound. Reach for
while only for genuine search-until-found logic, and make sure the
condition can end.
break and continue
break leaves the loop; continue skips to the next iteration. Both
work in for, while, and do/while, and both are the idiomatic way
to stop a scan the moment it has its answer.
Branches in one module
A for loop, an if/else branch, a ternary, and a switch, folded
into one value, with a bar counter the module keeps itself (there is no
barIndex global). The if/else, ternary, switch and for snippets
above appear verbatim inside onBar().
param("mode", 0, { min: 0, max: 2, description: "0 rotate by bar, 1 high, 2 low: which price the switch picks" });
param("window", 5, { min: 2, max: 50, description: "Closes summed by the for loop" });
output("direction", line, lower, { description: "+1 up bar, -1 down bar, 0 doji" });
output("anchor", line, lower, { description: "The high on up bars, the low otherwise" });
output("signal", none, lower, { description: "The low on bullish-cross bars, NaN elsewhere" });
output("total", line, lower, { description: "The last closes summed by hand" });
output("picked", line, lower, { description: "The price the switch picked" });
output("folded", line, lower, { description: "All four results in one number" });
const MAX_WINDOW = 50;
const ring = new StaticArray<f64>(MAX_WINDOW);
let n: i32 = 5;
let cursor: i32 = 0;
let count: i32 = 0;
let modeParam: i32 = 0;
let barIndex: i32 = 0;
let sma = new Sma(5);
const cross = new Cross();
function onStart(): void {
modeParam = i32(p_mode());
n = i32(p_window());
sma = new Sma(n);
}
function onBar(): void {
const close = bar.close();
const open = bar.open();
const high = bar.high();
const low = bar.low();
unchecked((ring[cursor] = close));
cursor = (cursor + 1) % n;
if (count < n) count += 1;
const crossed = cross.update(close, sma.update(close));
let direction = 0.0;
if (close > open) {
direction = 1.0; // up bar
} else if (close < open) {
direction = -1.0; // down bar
} else {
direction = 0.0; // doji
}
// Pick a price based on the bar's direction.
const anchor = close > open ? high : low;
// Write the low only on signal bars; NaN draws nothing (a shape_where gate is the declared form).
const signal = crossed == 1 ? low : NaN;
// Sum the last closes by hand, from a ring buffer the module keeps; count is how many slots are filled.
let total = 0.0;
for (let i = 0; i < count; i++) {
total += unchecked(ring[i]);
}
// A per-bar mode: the param, or the bar index modulo 3 when the param is 0.
const mode = modeParam == 0 ? barIndex % 3 : modeParam;
let picked = NaN;
switch (mode) {
case 0:
picked = close;
break;
case 1:
picked = high;
break;
default:
picked = low;
}
barIndex += 1;
out_direction(direction);
out_anchor(anchor);
out_signal(signal);
out_total(total);
out_picked(picked);
out_folded(anchor + picked / 100.0 + total);
}Loops in one module
A search-until-found: how many bars back the close last traded above its
average, found with a while over a ring buffer, plus a for that sums
the ring, a for with continue that counts up bars while skipping doji
bars, and a do/while with break that finds the first bar back whose
range exceeds the current one. The for and while snippets above appear
verbatim inside onBar().
param("period", 20, { min: 2, max: 200, description: "Average window and search depth" });
output("direction", line, lower, { color: "#94a3b8", description: "+1 when the close rose, -1 when it fell, 0 flat" });
output("close_sum", line, lower, { color: "#7c3aed", description: "The window's closes summed by the for loop" });
output("bars_since_above", line, lower, { color: "#2563eb", description: "Bars back to the last close above the average (0 = this bar), NaN if none in the window" });
output("bars_above", line, lower, { color: "#16a34a", description: "Up bars in the window, doji bars skipped" });
output("wider_back", line, lower, { color: "#f97316", description: "Bars back to the first bar with a wider range than this one, NaN if none" });
const MAX_PERIOD = 200;
const ring = new StaticArray<f64>(MAX_PERIOD);
const opens = new StaticArray<f64>(MAX_PERIOD);
const ranges = new StaticArray<f64>(MAX_PERIOD);
const above = new StaticArray<bool>(MAX_PERIOD);
let size: i32 = 20;
let sma = new Sma(20);
let cursor: i32 = 0;
let count: i32 = 0;
let prevClose: f64 = NaN;
// The ring slot `back` bars behind the newest write.
function slot(back: i32): i32 {
return (cursor - 1 - back + size) % size;
}
function onStart(): void {
size = i32(p_period());
sma = new Sma(size);
}
function onBar(): void {
const close = bar.close();
const open = bar.open();
const average = sma.update(close);
// if / else: the bar's direction against the previous close the module kept.
let direction = 0.0;
if (close > prevClose) {
direction = 1.0;
} else if (close < prevClose) {
direction = -1.0;
}
prevClose = close;
unchecked((ring[cursor] = close));
unchecked((opens[cursor] = open));
unchecked((ranges[cursor] = bar.high() - bar.low()));
unchecked((above[cursor] = !isNaN(average) && close > average));
cursor = (cursor + 1) % size;
if (count < size) count += 1;
if (isNaN(average)) return;
// for: sum the filled slots of the ring.
let total = 0.0;
for (let i = 0; i < count; i++) {
total += unchecked(ring[i]);
}
// while: search back until a bar above the average is found, or the window runs out.
let back = 0;
while (back < count && !unchecked(above[slot(back)])) {
back += 1;
}
const barsSinceAbove = back < count ? f64(back) : NaN;
// for with continue: count up bars, skipping doji bars.
let ups = 0;
for (let i = 0; i < count; i++) {
const c = unchecked(ring[i]);
const o = unchecked(opens[i]);
if (c == o) continue;
if (c > o) ups += 1;
}
// do/while with break: the first bar back whose range exceeds this one.
const current = unchecked(ranges[slot(0)]);
let probe = 1;
let widerBack = NaN;
if (count > 1) {
do {
if (unchecked(ranges[slot(probe)]) > current) {
widerBack = f64(probe);
break;
}
probe += 1;
} while (probe < count);
}
out_direction(direction);
out_close_sum(total);
out_bars_since_above(barsSinceAbove);
out_bars_above(f64(ups));
out_wider_back(widerBack);
}Every loop here is bounded by count, which is bounded by size, which
is bounded by the param's declared max: the module allocates its rings
once for MAX_PERIOD and never grows.
Loop limits
A wrun indicator has no iteration counter: the chart bounds the whole
evaluation with an execution timeout (20 s per run). A module that overruns
it is stopped and its worker replaced, so a runaway loop, or a while whose
condition never becomes false, fails the run loudly rather than hanging the
chart. In practice a loop bounded by a param's declared max never
approaches the timeout; keep loop bounds tied to declared ranges and the
module stays cheap on a long history
(Execution model).
Loops run once per bar over the loaded history and again each time a live update re-evaluates the forming bar (at most about once a second). A scan of a 200-slot ring is cheap; a scan inside a scan is not, so cache a statistic once per bar and reuse it.
What to keep in mind
- Per-bar execution.
onBar()re-runs on every bar. To carry a value across bars, keep it in a module-levellet. AletinsideonBar(), a loop or a branch is local to that block and gone when the call returns. - Typed branches. Every branch of a ternary and every assignment
agrees on a type;
f64values get float literals (0.0,1.0), counts and indexes arei32.