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 kScript loop: load a series, compute a number per bar, draw it.
//@version=2
define(title="Volume Spike Detector", position="offchart", axis=true)
var lookback = input(name="lookback", type="number", defaultValue=50, label="Lookback", constraints={min: 10, max: 300, step: 10})
var threshold = input(name="threshold", type="slider", defaultValue=2.5, label="Z-Score Threshold", constraints={min: 1, max: 6, step: 0.1})
timeseries d = ohlcv(symbol=currentSymbol, exchange=currentExchange)
// Z-score of volume against its trailing mean and standard deviation.
timeseries vMean = sma(source=d.volume, period=lookback)
timeseries vStd = stddev(d.volume, lookback)
timeseries z = (isnum(vStd[0]) && vStd[0] > 0) ? (d.volume[0] - vMean[0]) / vStd[0] : 0
plotLine(value=z, colors=["#64748b"], width=1, label=["Volume z-score"], desc=["how many standard deviations volume sits above its trailing mean"])
hline(value=threshold, color="#ef4444", width=1)
// Flag bars whose volume z-score clears the threshold.
var isSpike = isnum(z[0]) && z[0] >= threshold
if (isSpike) {
plotShape(value=z[0], shape="circle", width=6, colors=["#ef4444"], fill=true, label=["Spike"], desc=["volume anomaly above the threshold"])
}How it works
The data. One ohlcv series gives the script the chart's own candles, and from it the volume member. That is the only input the detector needs.
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. sma(d.volume, lookback) is the trailing mean, stddev(d.volume, lookback) is the trailing spread. Subtract the mean from the current bar's volume and divide by the spread, and you get the distance from normal measured in standard deviations. 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 z-score.
The na guard. Until lookback bars have loaded, the standard deviation is undefined, and dividing by it would produce garbage. The ternary checks isnum(vStd[0]) && vStd[0] > 0 first and falls back to 0, so the warm-up region reads as a flat, quiet line instead of noise.
The output. The z-score plots as a line in an off-chart pane. hline draws the threshold so you can see at a glance how close any bar is to firing. When a bar clears the line, plotShape drops a circle right on it, turning the abstract score into an obvious marker.
Customize it
- Sensitivity. The
thresholdslider is the main knob. Drop it toward1.5to catch milder bursts, raise it toward4to keep only genuine anomalies. The redhlinemoves with it, so the chart always shows where the bar is. - 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. - One-sided vs two-sided. As written, only high-volume bars fire because the spike condition is
z >= threshold. To catch unusually quiet bars too, add a second marker onz <= -threshold. - Turn it into an alert. Swap the marker for an
alert()call inside theif (isSpike)block to get notified the moment volume goes abnormal, instead of watching the pane. - Color and size. The circle and line colors are plain hex strings. Resize the marker with
width, or color it by intensity if you want bigger spikes to stand out more.
Concepts used
- Series functions for
smaandstddevover a trailing window - Typed inputs for the
numberlookback and thesliderthreshold - na and scalar types for the
isnumwarm-up guard - Plotting for
plotLine,hline, andplotShape