Conditionals and loops control which code runs during each bar's
state() call. Use them for decision logic, counted iteration over a
buffer, and conditional searches, keeping in mind that the bar loop
itself 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. The
kScript (legacy) constructs carry over as AssemblyScript's: if/else,
for, while, do/while, break, and continue.
| Construct | Use it for |
|---|---|
if / else | decision logic; the condition must be a bool |
for | a known iteration count: a ring buffer, a cell block, a bounded scan |
while | a condition-driven search with a bound you can prove |
break, continue | leaving a loop early, skipping an iteration |
if / else
if (condition) { ... } else { ... }. The condition is a boolean
expression; a number is not one, so if (value) is refused and if (value > 0.0) or if (!isNaN(value)) is the form. else if chains as
usual.
let direction = 0.0;
if (close > prevClose) {
direction = 1.0;
} else if (close < prevClose) {
direction = -1.0;
}The previous close is a module-level variable the module kept from the
last call, not close[1] (Execution model).
for
for (let i = 0; i < end; i++) { ... }. The counter is an i32; i++
and i += 1 both work. Loop bodies work with locals and with the module's
buffers; a StaticArray<f64> sized from a param's max in init() or at
module start is the usual target, and unchecked(ring[i]) skips the
bounds check once the index is provably inside.
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
(Volume profile).
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;
}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.
Every construct 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 that finds the first bar back whose range exceeds
the current one. The three snippets above appear verbatim inside
state().
import { input, line, lower, ohlcv, output, param } from "./sdk/declare";
import { in_close, in_high, in_low, in_open } from "./gen/inputs";
import { emitRow, out_bars_above, out_bars_since_above, out_close_sum, out_direction, out_wider_back } from "./gen/outputs";
import { p_period } from "./gen/params";
import { Sma } from "./sdk/ta";
param("period", 20, { min: 2, max: 200, description: "Average window and search depth" });
input("close", ohlcv.close);
input("open", ohlcv.open);
input("high", ohlcv.high);
input("low", ohlcv.low);
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;
let directionValue: f64 = 0.0;
let closeSum: f64 = NaN;
let barsSinceAbove: f64 = NaN;
let barsAbove: f64 = 0.0;
let widerBack: f64 = NaN;
export function init(): void {
size = i32(p_period());
sma = new Sma(size);
}
// The ring slot `back` bars behind the newest write.
function slot(back: i32): i32 {
return (cursor - 1 - back + size) % size;
}
export function state(): i32 {
const close = in_close();
const open = in_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] = in_high() - in_low()));
unchecked((above[cursor] = !isNaN(average) && close > average));
cursor = (cursor + 1) % size;
if (count < size) count += 1;
directionValue = direction;
if (isNaN(average)) return 0;
// for: sum the filled slots of the ring.
let total = 0.0;
for (let i = 0; i < count; i++) {
total += unchecked(ring[i]);
}
closeSum = total;
// 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;
}
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;
}
barsAbove = f64(ups);
// do/while with break: the first bar back whose range exceeds this one.
const current = unchecked(ranges[slot(0)]);
let probe = 1;
widerBack = NaN;
if (count > 1) {
do {
if (unchecked(ranges[slot(probe)]) > current) {
widerBack = f64(probe);
break;
}
probe += 1;
} while (probe < count);
}
return 1;
}
export function finalize(): void {
out_direction(directionValue);
out_close_sum(closeSum);
out_bars_since_above(barsSinceAbove);
out_bars_above(barsAbove);
out_wider_back(widerBack);
emitRow();
}
export function reset(): void {
sma.reset();
cursor = 0;
count = 0;
prevClose = NaN;
directionValue = 0.0;
closeSum = NaN;
barsSinceAbove = NaN;
barsAbove = 0.0;
widerBack = NaN;
}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.
Tips
- Infinite loop protection. Make sure a
whilecondition can become false; the host stops a module that overruns its execution timeout and discards the worker, so a stuck loop fails the run loudly rather than hanging the chart. - Variable scope. A
letinside a loop or a branch is local to that block and gone whenstate()returns; module-level variables are the ones that persist across bars, andreset()must restore them. - Performance. Loops run once per bar over the loaded history and again on every tick of the forming bar. 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.
- No history array. kScript forbade a
timeseriesinside a loop; an Indicator has no timeseries at all. The ring buffer is the window, and it is yours to size and index.