Outlier Detection in Price Feeds: Median Bands, MAD, Z-scores and Jump Guards Compared
Outlier detection in price feeds compared: mean vs median, percentage bands, MAD and robust Z-scores, weighted medians, jump guards and minimum sources.

- Price feeds built from many quotes must reject fat-finger errors, stale reposts and manipulation without lagging real moves.
- Median-based methods (percentage band, MAD) are robust; mean-and-standard-deviation methods are easily fooled by the outliers they try to catch.
- A jump guard and a minimum-source rule handle the cases cross-sectional filters cannot.
When a feed aggregates dozens of independent quotes, a few will always be wrong: a missing zero, a Rial price in a Toman list, an hour-old repost, a deliberate fake. Outlier detection in price feeds decides which quotes to trust. This article compares the main techniques and explains the combination EZRate uses.
Part of the Iranian Rial exchange rate API guide. Pipeline overview: OTC market data aggregation.
Why the mean fails
from statistics import mean, median
quotes = [105_100, 105_200, 105_150, 105_250, 10_520] # one quote in the wrong unit
print(mean(quotes)) # 86,244 — useless
print(median(quotes)) # 105,150 — unaffectedThe mean has a breakdown point of 0%: one bad value can move it arbitrarily. The median has a breakdown point of 50%: it stays correct as long as most quotes are honest.
Method 1: percentage band around the median
def band(quotes, pct=0.01):
m = median(quotes)
return [q for q in quotes if abs(q / m - 1) <= pct]Simple, interpretable and well suited to liquid assets whose honest quotes cluster tightly. The band must suit the asset: too tight rejects real dispersion, too wide lets errors through.
Method 2: MAD and robust Z-scores
import numpy as np
def mad_filter(quotes, k=3.5):
x = np.asarray(quotes, float)
m = np.median(x)
mad = np.median(np.abs(x - m)) or 1e-9
z = 0.6745 * (x - m) / mad # robust z-score
return x[np.abs(z) <= k].tolist()The band adapts to the current dispersion: wider when the market is choppy, tighter when it is calm. A common threshold is |z| ≤ 3.5. MAD collapses to zero when most quotes are identical, so set a floor.
Method 3: classic Z-score (and why to avoid it here)
The classic z = (x − mean) / std uses the very statistics that outliers distort. With few quotes, one large error inflates the standard deviation enough to hide itself. Use it only on already-cleaned data.
| Method | Robust? | Adapts to volatility? | Notes |
|---|---|---|---|
| Mean ± k·std | No | Yes | Masks outliers in small samples |
| Median ± fixed % | Yes | No | Simple; tune per asset |
| Median ± k·MAD | Yes | Yes | Needs a floor when MAD ≈ 0 |
| Trimmed mean | Partly | No | Drop top/bottom x%; wastes good data |

Weighted median
def weighted_median(values, weights):
pairs = sorted(zip(values, weights))
half, acc = sum(weights) / 2, 0
for v, w in pairs:
acc += w
if acc >= half:
return vWeights can reflect source reliability (historical agreement with consensus). Cap individual weights so no single source decides the result.
Time-series guards
- Jump guard: if the new value differs from the last published one by more than a threshold, publish only after it persists for a few cycles or across enough sources. This stops flash spikes but does delay genuine jumps slightly — a deliberate trade-off.
- Minimum sources: below N valid sources, do not compute a new value; keep the old one and flag it (
low_sources). - Time window: only quotes from the last few minutes count; deduplicate edited or reposted messages.
EZRate combines a ±1% median band, a weighted median, a jump guard and a five-source minimum; details on the methodology page. Persian readers: median and outliers in currency pricing.
US Dollar270,680Toman+0.40%
Whatever the method, log rejected quotes with their reason; reviewing them regularly is the fastest way to tune bands and spot broken sources.
Related live rates: US dollar rate, 18K gold price, Emami coin price.

Comments (0)
Comments are shown after review. Sign-in is needed to send.


