Control flow decides what runs on each bar. An Indicator is AssemblyScript,
so the toolkit is the familiar typed C-style one: if/else branches,
for loops, the ? : ternary, and switch. Everything here runs inside
state() (or finalize()), which the host calls once per bar as it walks
the loaded history; that is the whole per-bar execution model, and it is
why a kScript (legacy) script and an Indicator read almost the same in
their bodies while differing in where the state lives.
if / else
Branch on any boolean condition; the else is optional. Conditions are
real bool values: if (value) on a number is a compile error
(AS200), so compare (if (close > open), 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
}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;for loops
Use a C-style for when you need a fixed number of iterations: summing a
window, scanning buckets, accumulating a value. The loop index is an
i32; cast it when it meets an f64.
// 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]);
}There is no history array to loop over (close[n] does not exist): a
window is a StaticArray<f64> ring the module fills one bar at a time,
sized once from the param's max (Collections).
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;
}Everything together
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 four snippets above appear verbatim inside
state().
import { input, line, lower, none, ohlcv, output, param } from "./sdk/declare";
import { in_close, in_high, in_low, in_open } from "./gen/inputs";
import { emitRow, out_anchor, out_direction, out_folded, out_picked, out_signal, out_total } from "./gen/outputs";
import { p_mode, p_window } from "./gen/params";
import { Cross, Sma } from "./sdk/ta";
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" });
input("close", ohlcv.close);
input("open", ohlcv.open);
input("high", ohlcv.high);
input("low", ohlcv.low);
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();
let direction: f64 = 0.0;
let anchorValue: f64 = NaN;
let signalValue: f64 = NaN;
let totalValue: f64 = 0.0;
let picked: f64 = NaN;
export function init(): void {
modeParam = i32(p_mode());
n = i32(p_window());
sma = new Sma(n);
}
export function state(): i32 {
const close = in_close();
const open = in_open();
const high = in_high();
const low = in_low();
unchecked((ring[cursor] = close));
cursor = (cursor + 1) % n;
if (count < n) count += 1;
const crossed = cross.update(close, sma.update(close));
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;
switch (mode) {
case 0:
picked = close;
break;
case 1:
picked = high;
break;
default:
picked = low;
}
anchorValue = anchor;
signalValue = signal;
totalValue = total;
barIndex += 1;
return 1;
}
export function finalize(): void {
out_direction(direction);
out_anchor(anchorValue);
out_signal(signalValue);
out_total(totalValue);
out_picked(picked);
out_folded(anchorValue + picked / 100.0 + totalValue);
emitRow();
}
export function reset(): void {
cursor = 0;
count = 0;
barIndex = 0;
sma.reset();
cross.reset();
direction = 0.0;
anchorValue = NaN;
signalValue = NaN;
totalValue = 0.0;
picked = NaN;
}Loop limits
kScript capped a script at one million loop iterations. An Indicator has
no iteration counter: the host bounds the WHOLE evaluation with an
execution timeout, and a module that overruns it is stopped and its
worker discarded, so a runaway loop fails the run rather than hanging the
chart. In practice a loop bounded by a param's declared max never
approaches either limit; keep loop bounds tied to declared ranges and
the module stays cheap on a long history
(Execution model).
What to keep in mind
- Per-bar execution.
state()re-runs on every bar. To carry a value across bars, keep it in a module-levellet(the port ofpersist); a local declared insidestate()is gone when the call returns, the port of a plainvar. whileexists but use it sparingly. A countedforis clearer and safer when you know the bound. Reach forwhileonly for genuine search-until-found logic (Loops), and make sure the condition can end.- 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. reset()restores what the branches touched. The host replays the forming bar throughreset(); a module-level variable a branch set on a previous tick andreset()forgot is a stale read.