---
title: Volume Spike Detector
description: >-
  Flag bars whose volume blows past its trailing average, scored as a z-score
  against a rolling mean and standard deviation.
---

<div class="flex gap-3 mb-6">
  <span class="inline-flex items-center gap-1.5 px-3 py-1 rounded-full bg-green-50 text-green-600 text-sm font-medium">
    Beginner
  </span>
  <span class="inline-flex items-center gap-1.5 px-3 py-1 rounded-full bg-gray-100 text-gray-600 text-sm font-medium">
    4 min read
  </span>
</div>

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.

```javascript title="scripts/probes/cookbook/volume_spike.ks" lines wrap
//@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 `threshold` slider is the main knob. Drop it toward `1.5` to catch milder bursts, raise it toward `4` to keep only genuine anomalies. The red `hline` moves with it, so the chart always shows where the bar is.
- **Memory.** `lookback` sets 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 on `z <= -threshold`.
- **Turn it into an alert.** Swap the marker for an [`alert()`](/kscript/functions/alerts) call inside the `if (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](/kscript/functions/series-functions) for `sma` and `stddev` over a trailing window
- [Typed inputs](/kscript/functions/typed-inputs) for the `number` lookback and the `slider` threshold
- [na and scalar types](/kscript/core-concepts/na-and-scalar-types) for the `isnum` warm-up guard
- [Plotting](/kscript/functions/plotting) for `plotLine`, `hline`, and `plotShape`
