Named streams
Multi-output indicators like Bollinger bands, MACD, and the stochastic produce several lines at once.
In a wrun indicator each stream is an
output of its own, and the computation behind them is a class whose
fields you read by name after one update(): bands.upper,
macd.signal, stoch.k. No positional guessing: every stream is read by
name.
Introduction
Some indicators produce more than one line. Bollinger bands give you an upper band, a basis, and a lower band. MACD gives a MACD line, a signal line, and a histogram. A wrun indicator declares one output per line, and a class computes all of them from one input per bar:
output("bb_upper", line, overlay, { color: "#2563eb" });
output("bb_basis", line, overlay, { color: "#64748b" });
output("bb_lower", line, overlay, { color: "#2563eb" });
const bands = new Bb(20, 2.0); // in onBar(): bands.update(close)
out_bb_upper(bands.upper); // then read the fields by name
out_bb_basis(bands.basis);
out_bb_lower(bands.lower);bands.update(close) folds one bar; bands.upper, bands.basis, and
bands.lower are its three lines afterwards. You name what you want. There
is no "is the upper band index 0 or index 2?" guesswork, and a typo like
bands.upperr is a compile error rather than a silently wrong line.
How it works
A multi-output computation is a class with one update(x) method and one
typed field per stream. update returns the primary stream (the MACD line,
the basis) so the class also reads like a single-output one, and the other
streams sit on the instance. Each field is an ordinary f64, so it flows
straight into math, other classes, conditions, and outputs:
const m = new Macd(12, 26, 9);
m.update(close);
const risingMomentum = m.hist > prevHist; // compare bars (prevHist is remembered)
const smoothSignal = signalEma.update(m.signal); // feed a stream into another classThe kit ships every class, single-stream and multi-stream alike, and a
file uses them by name with nothing to import. Two rules keep a module
honest: construct in onStart() (allocation once), and update() once
per bar.
The named streams
The multi-stream classes, and the fields each one carries:
| Class | Fields |
|---|---|
Bb | .basis, .upper, .lower |
Keltner | .basis, .upper, .lower |
Donchian | .basis, .upper, .lower |
Macd | .macd, .signal, .hist |
Stoch, Stochastic | .k, .d |
Supertrend | .line, .direction |
Adx | .adx, .plusDi, .minusDi |
Ichimoku | .tenkan, .kijun, .senkouA, .senkouB, .chikou |
Every class and its conventions: TA library.
A few worth calling out:
Stochsplits into the fast%K(.k) and its smoothed%D(.d). The classic crossover isCross.update(stoch.k, stoch.d).Supertrendcarries the trailing stop level as.lineand the trend side as.direction(1or-1). Use.directionas acolor_byindex or a gate; draw.line.Macd.histis the MACD line minus the signal line, ready for ahistogramoutput.
Reading every stream at once
Two shipped classes and all six of their streams as outputs. Macd is
the fast, slow and signal EMAs folded into one object; Bb is the window
mean and its population standard deviation. Every stream is an output of
its own, so once you publish the indicator an alert set from the chart can
follow any of them (Alerts).
param("fast", 12, { min: 1, max: 200, description: "MACD fast EMA" });
param("slow", 26, { min: 2, max: 400, description: "MACD slow EMA" });
param("signal", 9, { min: 1, max: 200, description: "MACD signal EMA" });
param("bb_period", 20, { min: 2, max: 400, description: "Bollinger window" });
param("bb_mult", 2, { min: 0.5, max: 4, description: "Bollinger width in standard deviations" });
output("bb_upper", line, overlay, { color: "#2563eb", width: 2, description: "Upper Bollinger band" });
output("bb_basis", line, overlay, { color: "#64748b", width: 2, description: "Bollinger basis (the moving average)" });
output("bb_lower", line, overlay, { color: "#dc2626", width: 2, description: "Lower Bollinger band" });
output("macd", line, lower, { color: "#1d4ed8", width: 2, description: "MACD line" });
output("signal", line, lower, { color: "#ea580c", width: 2, description: "Signal line" });
output("hist", histogram, lower, { color: "#15803d", description: "MACD minus signal" });
// One update() per bar; the streams are fields you read by name afterwards.
let macd = new Macd(12, 26, 9);
let bands = new Bb(20, 2.0);
function onStart(): void {
macd = new Macd(i32(p_fast()), i32(p_slow()), i32(p_signal()));
bands = new Bb(i32(p_bb_period()), p_bb_mult());
}
function onBar(): void {
const close = bar.close();
macd.update(close);
bands.update(close);
// Nothing is written until the slowest stream is warm: the MACD signal line.
if (isNaN(macd.signal) || isNaN(bands.basis)) return;
out_bb_upper(bands.upper);
out_bb_basis(bands.basis);
out_bb_lower(bands.lower);
out_macd(macd.macd);
out_signal(macd.signal);
out_hist(macd.hist);
}What to expect: the three bands start drawing once the 20-bar window
fills and track together. The MACD streams warm later: the MACD line needs
the slow EMA (26 bars), the signal line nine MACD values beyond that, so
nothing is drawn until the 34th bar and all six lines then start in
lockstep.
Bars where one stream is finite and another is not are handled in the
classes (NaN stays NaN), never in onBar().
A composite class as one value
The class instance is one value: pass macd to a helper that
takes a Macd, keep an array of them for several settings, or read a
single field inline. What you cannot do is index a stream's history
(m.hist[1]): remember the previous value in a module-level variable,
exactly as for any other number (core-variables.md).
Single-output classes like Rsi, Ema, and Sma return their one value
from update() and have no stream fields, so you write them straight to an
output: out_rsi(rsi.update(close)). Named streams are only for the
multi-line indicators above. The full catalog is the
TA library.