Volatility is how a signal moves from one observation to the next.

Take any time series – a price, a sensor reading, a queue length, a heart rate – and draw little arrows from each point to the next one. Each arrow is a vector: it has a size (how far you moved) and a sign or direction (up or down).

Viewed this way, a “volatile” system is just a field of vectors that won’t sit still. Some fields make tiny, jittery moves. Others wander slowly and then lunge. Some clump their big moves together; others sprinkle them evenly. Some only go wild when other systems do.

Once you start watching the arrows instead of the raw values, you realise there are only a few fundamentally different ways motion can misbehave. Everything else is flavour.

Four behaviours, four yardsticks

I wanted a small, geometric catalogue of those behaviours – and one number per series that says “how lively is this thing?” without hiding the structure underneath.

That led to four pillars:

A single Composite Volatility score per series, built from four behaviours: Scale, Shape, Temporal, Correlation – and a weighted Composite.

  • Scale – how big the step vectors are.
  • Shape – how those vectors are distributed relative to a nice bell curve.
  • Temporal – whether big vectors arrive in clusters or are spread out.
  • Correlation – whether one vector field tends to move with another.
  • Composite – a weighted blend into one index number.

All of this runs on log returns of some positive quantity:

$$r_t = \ln\left(\frac{P_t}{P_{t-1}}\right)$$

where P_t is the level at time t (price, load, metric – anything positive). Log returns add over time and behave nicely under scaling, which makes them a good unit for describing motion.

You can imagine sampling your system at a regular interval (say once a minute), computing these log-step vectors, and then building the pillars from that stream of vectors.

Pillar 1: Scale – how big the moves are

Scale is “how much energy is in the moves” once you strip away the weirdness of shape and timing.

We use four classic spread measures on the log-step vectors, then blend them.

Standard deviation of log returns

This is the textbook volatility measure:

$$\sigma = \sqrt{\frac{1}{N-1} \sum_{t=1}^{N} (r_t - \bar r)^2}$$

It tells you “on a typical minute, how far do returns wander from their average?”

Mean absolute deviation (MAD)

A more robust spread measure, less sensitive to outliers than standard deviation:

$$\text{MAD} = \frac{1}{N} \sum_{t=1}^{N} \left|r_t - \bar r\right|$$

This gives you the “typical absolute move” without squaring.

Realised volatility per hour

Here we treat volatility as the square root of accumulated squared returns per unit time:

$$\text{realised\_vol\_per\_hour} = \sqrt{\frac{\sum_{t=1}^{N} r_t^2}{T_{\text{hours}}}}$$

Here \(T_{\text{hours}}\) is the total elapsed time in hours.

We are summing the “energy” in the returns, r_t^2, then normalising by actual clock time, not “number of bars”. If one chain has 10x more ticks but similar price motion per hour, it doesn’t get penalised.

EWMA volatility per hour

Realised volatility treats the last hour and last week equally. Markets don’t.

So we build an Exponentially Weighted Moving Average (EWMA) version by assigning each return a weight that decays over time and computing a weighted realised volatility. Recent moves matter more; old noise fades out.

Huber weighting: ignoring fake micro-volatility

Many real-world feeds throw off a ton of tiny “changes” that are just granularity or noise. If you blindly square every tiny r_t, series with noisy measurements look more volatile than they really are.

We fix this with a Huber-style weight on each return:

  1. Compute a per-series threshold \(\delta = k \cdot \text{median}(|r_t|)\) with \(k = 3.0\).
  2. Define a weight for each return \(w_t = \min\left(\frac{|r_t|}{\delta}, 1.0\right)\).
  3. Use \(w_t \cdot r_t\) instead of \(r_t\) inside realised and EWMA calculations.

Small returns get down-weighted. Genuine big moves keep full weight. This is applied only to the Scale pillar.

After we compute these metrics per series, we median-normalise them across the group and blend:

$$\text{Scale} = 0.4\,\text{realised} + 0.3\,\text{ewma} + 0.2\,\text{stdev} + 0.1\,\text{mad}$$

Scale carries most of the weight in the final composite – it’s the raw size of risk.

Pillar 2: Shape – how “weird” the distribution is

If prices were perfectly Gaussian, you could stop at standard deviation. They’re not.

Shape looks at how far each series’ return distribution deviates from a bell curve. We use three signals.

Skewness (left/right tilt)

Skewness measures asymmetry. For the index we take absolute skew, |skew|, because any strong one-sided tilt changes the risk profile.

Excess kurtosis (fat tails)

Kurtosis measures how heavy the tails are relative to a normal distribution. We clamp at 0 so we don’t reward “too Gaussian” series:

kurtosis_plus = max(kurtosis, 0)

Jarque–Bera statistic (overall non-normality)

Jarque–Bera combines skewness and kurtosis into a single “how non-normal is this?” score. Higher values mean further from a bell curve, in either skew or tails.

After median normalisation we blend:

$$\text{Shape} = 0.25\,|\text{skew}| + 0.25\,\text{kurtosis\_plus} + 0.5\,\text{JB}$$

Shape doesn’t care how big the moves are – it cares how non-Gaussian the motion is.

Pillar 3: Temporal – does volatility cluster?

Systems don’t just “have a volatility level”; they have regimes and bursts.

Temporal asks: do big moves arrive in clusters, or are they spread out?

Autocorrelation of absolute returns (lag 1)

Take the series of absolute returns |r_t| and look at correlation with the previous bar |r_(t-1)|. Positive correlation means “big follows big, small follows small” – classic volatility clustering. Negative values are rare; we clamp them to 0.

Hurst exponent distance from 0.5

The Hurst exponent H summarises long-range dependence:

  • H ≈ 0.5: random, like white noise.
  • H > 0.5: persistent – trends and long bursts.
  • H < 0.5: mean-reverting.

We only care how far it deviates from random:

$$\text{hurst\_dev} = |H - 0.5|$$

After median normalisation, we blend:

$$\text{Temporal} = 0.6\,\text{autocorr\_abs\_lag1} + 0.4\,\text{hurst\_dev}$$

Series where “nothing happens, then everything happens” score higher on Temporal.

Pillar 4: Correlation – does it move with the pack?

Volatility is more worrying when many systems move together.

Correlation asks: how much does this series move with a reference basket of other series?

We use two flavours:

  • Pearson correlation – standard linear correlation of returns with the basket.
  • Spearman rank correlation – the same idea, but on rank-ordered returns, which is more robust to outliers.

After median normalisation:

$$\text{Correlation} = 0.7\,\text{pearson} + 0.3\,\text{spearman}$$

Series that only produce big moves when everything else does get a higher Correlation pillar, even if their own Scale isn’t huge.

Pillar 5: Composite – one number per series

Finally, we blend the four pillars into a single index:

$$\text{Composite} = 0.5\,\text{Scale} + 0.2\,\text{Shape} + 0.2\,\text{Temporal} + 0.1\,\text{Correlation}$$

Scale does most of the work. Shape and Temporal refine the risk story. Correlation is a penalty for “systemic” volatility.

You can optionally rescale this as a “VXI” style index:

$$\text{VXI} = 100 \times \text{Composite}$$

So a series with Composite = 1.65 shows up as VXI = 165.

A tiny worked example

Let’s do a tiny calculation on a single series, over 6 minutes of values.

Minute Price
0 100
1 101
2 98
3 99
4 102
5 101

Compute log returns:

$$r_t = \ln\left(\frac{P_t}{P_{t-1}}\right)$$

That gives approximately:

From → To Price move Log return r_t
100 → 101 +1% +0.0100
101 → 98 -3% -0.0302
98 → 99 +1% +0.0102
99 → 102 +3% +0.0299
102 → 101 -1% -0.0099

Now some Scale metrics from this tiny window:

  • Mean return r_bar ≈ 0.0020
  • Standard deviation sigma ≈ 0.0228
  • Mean absolute deviation MAD ≈ 0.0176

Realised volatility per hour:

  • Total time T_hours = 5 / 60.
  • Sum of squared returns sum(r_t^2) ≈ 0.00126.
  • realised_vol_per_hour = sqrt(sum(r_t^2) / T_hours) ≈ 0.159.

So in this tiny window, the series is showing roughly 16% hourly volatility. On real data you’d use many more points; this is just to make the mechanics tangible.

From here you would:

  1. Compute Shape (skew, kurtosis, Jarque–Bera) on the same returns.
  2. Compute Temporal (autocorr of |r_t|, Hurst).
  3. Compute Correlation with the basket.
  4. Median-normalise each pillar across all series in your comparison set.
  5. Blend them into the Composite per the formulas above.

The end result is a table where 1.0 is roughly “median series”, values above 1 mean more volatile than the pack, and values below 1 are calmer.


← Back to home