Volume spike detector
Flag bars whose volume blows past its trailing average, scored as a z-score against a rolling mean and standard deviation.
This recipe spots unusual volume. Instead of a fixed "alert above 1M" threshold that means nothing across different symbols, it measures how far the current bar's volume sits above its own recent average, in standard deviations. A reading of 3 means "three sigma above normal" whatever the symbol or timeframe. It is the cleanest possible introduction to the indicator loop: read one input, fold it into one class, write a few outputs.
The wrun indicator
param("lookback", 50, { min: 10, max: 300, description: "Bars of history that define normal volume" });
param("threshold", 2.5, { min: 1, max: 6, description: "Z-score that counts as a spike" });
input("volume", ohlcv.volume); // the first input is the bar grid: the chart's own candles
output("z", line, lower, { color: "#64748b", width: 1, description: "Volume z-score" });
output("threshold", line, lower, { color: "#ef4444", width: 1 });
output("spike", shape, lower, { color: "#ef4444", shape_where: "is_spike" });
output("is_spike", none);
let zscore = new Zscore(50);
let threshold: f64 = 2.5;
function onStart(): void {
zscore = new Zscore(i32(p_lookback()));
threshold = p_threshold();
}
function onBar(): void {
const z = zscore.update(bar.volume());
if (isNaN(z)) return;
out_z(z);
out_threshold(threshold);
out_spike(z);
out_is_spike(z >= threshold ? 1.0 : 0.0);
}How it works
The data. One input("volume", ohlcv.volume) names the chart's own volume as the first input, the grid the run walks, and bar.volume() reads it in onBar(), one value per bar. That is the only feed the detector needs, and onBar() sees this bar's value and nothing else.
The score. A z-score answers "how surprising is this number?" You need two reference points: where volume usually sits, and how much it normally wobbles. Zscore is both reference points in one class: update(x) folds one bar into its window and returns (x - mean) / stdev over that window, or 0 when the window is flat. Because it is a ratio, it is comparable across any symbol or timeframe. A whale print on BTC and a thin altcoin both light up at the same number.
The warm-up guard. Until lookback bars have loaded, the class returns NaN, and onBar() returns before writing anything: every output stays NaN on that bar, and a NaN is not drawn, so the pane stays empty there instead of drawing noise (Execution model).
The output. z plots as a line in its own pane (lower). threshold is a line too, written to the same value on every bar, so you can see at a glance how close any bar is to firing. spike is a shape output whose value is the z-score (where the mark sits) and whose shape_where gate is the data-only is_spike output: the mark draws only on bars where the gate is 1. Decisions are numbers here, and the sheet turns them into looks.
Design notes
- The mark is two outputs: its position (
spike) and the decision that gates it (is_spike). An output cannot gate itself, so the gate is its ownnoneoutput. - The threshold line is an output that never changes; its value is the param, written every bar.
- Settings take any value inside
min..max; thedescriptionis the label the settings dialog shows beside the field. - Every output has a value on every bar. After a Run the editor's Console prompt reads any of them (
last 20 z,is_spike), and once the indicator is published,zcan carry an alert set from the chart.
Customize it
- Sensitivity.
thresholdis the main knob. Drop it toward1.5to catch milder bursts, raise it toward4to keep only genuine anomalies. The red line moves with it. - Memory.
lookbacksets how much history "normal" is measured against. A short window (20) reacts to recent conditions and treats a busy session as the new baseline; a long window (200) compares against a calmer, broader average and flags more. - Two-sided. As written, only high-volume bars fire because the gate is
z >= threshold. To catch unusually quiet bars too, add a second gate output (z <= -threshold) and a secondshapeoutput gated by it. - Color and size. The circle and line colors are hex strings on the declarations.
widthon the line output resizes it;render.shapepicks a different mark kind than the chart's default (Plotting). - Turn it into an alert. Publish it, add it to a chart, and set an alert from the chart on
zwith Crosses above at 3. OpenMarket's alerts engine evaluates it in the cloud with the overlay's settings; Once per bar lets it fire again on later spikes, at most once per bar (Alerts).
Run it
- In the editor's Explorer, press New indicator and pick Blank indicator: the tab holds the
//@lang=wrun-tsline alone. - Paste the indicator block above under that line and press Run.
- At the editor's Console prompt, type
last 20 zto read the score of the last 20 bars.
Concepts used
- Series functions and TA library for
Zscoreover a trailing window - Setting kinds for the
lookbackandthresholdparams - Missing values (NaN) for the
NaNwarm-up rule - Plotting and Cards, frames and panels for the
lineoutputs and the gatedshape