Detectors
This guide helps you choose and configure the right detector for your metrics.
Overview
Section titled “Overview”detectkit provides several detector types for anomaly detection:
| Detector | Best For | Robustness | Seasonality | Speed |
|---|---|---|---|---|
| MAD | General-purpose, seasonal data | High | Yes | Fast |
| Z-Score | Normal distributions, clean data | Low | Yes | Very Fast |
| IQR | Skewed distributions, outliers | High | Yes | Fast |
| Manual Bounds | Known thresholds, SLAs | N/A | No | Fastest |
| Autoreg | Fast-moving, non-seasonal metrics; dynamics breaks | Via stabilization | No (v1) | Fast |
Don’t want to choose by hand?
dtk autotunecan pick the detector type, hyperparameters and seasonality grouping for you from the metric’s data (and labeled incidents, if you have them), then write a ready-to-run, annotated config. See Auto-tuning a Detector.
Decision Tree
Section titled “Decision Tree”1. Do you know the acceptable bounds?
Section titled “1. Do you know the acceptable bounds?”YES → Use Manual Bounds
Examples:
- CPU usage should be ≤ 90%
- Response time SLA < 1000ms
- Error rate should be 0
NO → Continue to question 2
2. Does your metric have seasonal patterns?
Section titled “2. Does your metric have seasonal patterns?”YES → Use MAD with Seasonality
Examples:
- Website traffic (hourly/daily patterns)
- Sales (day-of-week patterns)
- Gaming metrics (event-based patterns)
NO → Continue to question 3
3. Do anomalies look like broken dynamics rather than an unusual level?
Section titled “3. Do anomalies look like broken dynamics rather than an unusual level?”Some metrics move fast and have no stable “normal level”, so a value can sit well inside the usual range yet still be wrong given the last few points — a sudden jump, a reversal, a step a level detector would wave through.
Examples:
- Queue depth / backlog
- Request rate, in-flight count
- Any fast-moving, non-seasonal series where shape matters more than level
YES → Use Autoreg. It predicts each point from a short
autoregressive fit on recent history and flags large residuals, catching shape
breaks the level-based detectors miss. Pair it with a level detector
(mad/zscore/iqr) via min_detectors for both level and shape coverage.
NO → Continue to question 4
4. Is your data normally distributed?
Section titled “4. Is your data normally distributed?”Test: Create a histogram. Does it look like a bell curve?
YES → Use Z-Score
NO → Continue to question 5
5. Does your data have outliers or heavy tails?
Section titled “5. Does your data have outliers or heavy tails?”UNSURE → Use MAD (safe default)
Detector Details
Section titled “Detector Details”MAD Detector (Basic)
Section titled “MAD Detector (Basic)”Use when:
- General-purpose anomaly detection
- Data with outliers
- Skewed or non-normal distributions
- Good default choice
Advantages:
- Robust to outliers
- No distribution assumptions
- Fast computation
- Excellent seasonality support
Configuration:
detectors: - type: mad params: threshold: 3.0 # In sigma-equivalents (MAD scaled by 1.4826) window_size: 100 # Historical window size min_samples: 30 # Warm-up periodThreshold is in σ-equivalents: MAD is multiplied by the normal-consistency
constant 1.4826, so threshold: 3.0 corresponds to 3-sigma on Gaussian noise
(~0.27% false positives), exactly like Z-Score.
Tuning threshold:
threshold: 2.0- More sensitive (more anomalies)threshold: 3.0- Balanced (recommended)threshold: 5.0- Less sensitive (fewer anomalies)
MAD Detector (with Seasonality)
Section titled “MAD Detector (with Seasonality)”Use when:
- Metric has time-based patterns
- Different behavior at different times (hour/day/week)
- Need adaptive confidence intervals
Examples:
- Website traffic (higher during business hours)
- API calls (spikes during events)
- Gaming metrics (tournament schedules)
Configuration:
seasonality_columns:is a top-level metric key (a sibling ofdetectors:), not a detector param. It extracts the features;seasonality_componentsinside a detector’sparams:then groups on them.
# Extract seasonality features from timestamps (built-in names:# hour, day_of_week, day_of_month, month, is_weekend, is_holiday)seasonality_columns: - hour - day_of_week
detectors: - type: mad params: threshold: 3.0 window_size: 2016 # 2 weeks of hourly data min_samples: 500
# Apply seasonality grouping seasonality_components: - "hour" # Different intervals per hour # OR combine multiple: # - ["hour", "day_of_week"] # Different per hour+day comboSeasonality components:
- Single:
["hour"]- One group per hour (24 groups) - Multiple separate:
["hour", "day_of_week"]- Two separate adjustments - Combined:
[["hour", "day_of_week"]]- One group per hour+day combo (168 groups)
Component names must match the metric’s seasonality feature names: the
built-in seasonality_columns names shown above, or custom column names
(e.g. hour_of_day) — the latter only when your query returns them and
they are declared in query_columns.seasonality.
Window size recommendations:
- Hourly data: 672-2016 (1-3 weeks)
- 10-minute data: 4320-8640 (30-60 days)
- Daily data: 60-90 (2-3 months)
Rule: window_size should contain multiple full cycles of your seasonality.
Z-Score Detector
Section titled “Z-Score Detector”Use when:
- Data is normally distributed (bell curve)
- No significant outliers in historical data
- Need high sensitivity on clean data
Advantages:
- Very fast computation
- High sensitivity on normal data
- Well-understood (3-sigma rule)
Disadvantages:
- Sensitive to outliers (can produce false positives)
- Assumes normal distribution
Configuration:
detectors: - type: zscore params: threshold: 3.0 # 3 standard deviations window_size: 100 min_samples: 30Threshold interpretation:
threshold: 1.0→ 68.3% confidence (very sensitive)threshold: 2.0→ 95.4% confidence (sensitive)threshold: 3.0→ 99.7% confidence (balanced)threshold: 4.0→ 99.99% confidence (conservative)
When to avoid:
- Skewed distributions (use MAD or IQR)
- Data with outliers (use MAD or IQR)
IQR Detector
Section titled “IQR Detector”Use when:
- Data is heavily skewed
- Percentile-based metrics (P95, P99)
- Need quartile-based detection
- Want box plot visualization
Advantages:
- Robust to outliers
- Works with any distribution
- Natural for percentile metrics
- Creates asymmetric bounds (good for skewed data)
Disadvantages:
- Less sensitive than MAD
- Slightly slower than Z-Score
Configuration:
detectors: - type: iqr params: threshold: 1.5 # Tukey's fences (standard) window_size: 100 min_samples: 30Threshold values:
threshold: 1.0- More sensitivethreshold: 1.5- Standard outliers (Tukey’s fences)threshold: 3.0- Extreme outliers only
Comparison with MAD:
- IQR uses Q1/Q3 (25%/75% percentiles)
- MAD uses median (50% percentile)
- Both are robust, MAD slightly more sensitive
Manual Bounds Detector
Section titled “Manual Bounds Detector”Use when:
- You know acceptable thresholds
- SLA/compliance monitoring
- Physical/logical constraints
- Binary “too high/low” alerts
Advantages:
- Instant detection (no warm-up)
- Simple and transparent
- Predictable behavior
- Fastest detector
Disadvantages:
- Requires domain knowledge
- No adaptation to data patterns
- Can’t handle seasonality
Configuration:
# Upper bound onlydetectors: - type: manual_bounds params: upper_bound: 90.0 # Alert when value > 90
# Lower bound onlydetectors: - type: manual_bounds params: lower_bound: 0.8 # Alert when value < 0.8
# Both bounds (range check)detectors: - type: manual_bounds params: lower_bound: 0.0 upper_bound: 100.0Use cases:
- SLA monitoring (response time < 1000ms)
- Resource limits (memory < 8GB)
- Error rates (errors should be 0)
- Percentages (0-100% range)
Full Manual Bounds Reference →
Autoreg Detector
Section titled “Autoreg Detector”MAD/Z-Score/IQR ask “is this value far from the recent center?”; Autoreg asks “is this value far from what the last few points would have predicted?” — it fits a short autoregressive model on recent history instead of a level statistic, so it can catch a value that sits well inside the metric’s normal range but breaks its usual short-range dynamics (a sudden jump, a reversal).
Use when:
- Fast-moving, non-seasonal metrics (queue depth, request rate, in-flight count)
- You want to catch “unusual dynamics”, not just “unusual level”
- Pairing with a level detector (
mad/zscore/iqr) viamin_detectorsfor full coverage
Disadvantages:
- No seasonality support in v1 (rejected at construction)
- No smoothing, recency weighting or detrending
dtk autotune sweeps threshold, lags, stabilization and window size for
Autoreg (its own axis set — no weighting/detrend/seasonality), and it’s
tunable in the dtk tune cockpit too.
Configuration:
detectors: - type: autoreg params: lags: 3 # AR order (predictors = last 3 values) window_size: 300 # trailing history used to refit at each point threshold: 3.5 # residual-sigma band half-widthMultiple Detectors
Section titled “Multiple Detectors”You can configure multiple detectors per metric. Use cases:
Hard Limit + Statistical Detection
Section titled “Hard Limit + Statistical Detection”detectors: # Hard limit: never exceed 95% - type: manual_bounds params: upper_bound: 95.0
# Soft limit: detect unusual patterns - type: mad params: threshold: 3.0 window_size: 1440Conservative + Aggressive Detection
Section titled “Conservative + Aggressive Detection”detectors: # Conservative: fewer false positives - type: mad params: threshold: 5.0 window_size: 2880
# Aggressive: catch subtle anomalies - type: zscore params: threshold: 2.5 window_size: 1440Alert Filtering
Section titled “Alert Filtering”Control when alerts trigger with multiple detectors:
detectors: - type: mad params: threshold: 3.0 - type: zscore params: threshold: 3.0
alerting: enabled: true min_detectors: 2 # Both must agree to trigger alert direction: "same" # Both must agree on ONE direction (up or down)With direction: "same", at least min_detectors detectors must agree on a
single direction at the latest point — one detector firing “up” and another
firing “down” is disagreement, not consensus. Other policies: "up" / "down"
(only that direction counts) and "any" (every anomaly counts regardless of
direction). See the Alerting Guide for the full contract.
Common Patterns
Section titled “Common Patterns”Pattern 1: High-Traffic Website
Section titled “Pattern 1: High-Traffic Website”name: website_visitorsinterval: 10min
seasonality_columns: - hour - day_of_week
detectors: - type: mad params: threshold: 3.0 window_size: 4320 # 30 days min_samples: 1000 seasonality_components: - ["hour", "day_of_week"]Why: Traffic varies by hour and day of week. Seasonality ensures different thresholds for peak vs off-peak times.
Pattern 2: System Metrics (CPU/Memory)
Section titled “Pattern 2: System Metrics (CPU/Memory)”name: cpu_usageinterval: 30s
detectors: # Hard limit - type: manual_bounds params: upper_bound: 90.0
# Statistical - type: zscore params: threshold: 3.0 window_size: 2880 # 1 dayWhy: System metrics are often normally distributed. Combine hard limit with statistical detection.
Pattern 3: Error Rates
Section titled “Pattern 3: Error Rates”name: api_errorsinterval: 1min
detectors: # Zero tolerance - type: manual_bounds params: upper_bound: 0
# Allow small spikes but catch sustained increases - type: mad params: threshold: 3.0 window_size: 1440Why: Errors should be rare. Manual bounds catches any error, MAD catches unusual patterns.
Pattern 4: Business Metrics (Revenue, Conversions)
Section titled “Pattern 4: Business Metrics (Revenue, Conversions)”name: daily_revenueinterval: 1day
detectors: - type: mad params: threshold: 3.0 window_size: 90 # 3 months min_samples: 30Why: Business metrics often have trends and outliers. MAD is robust to both.
Pattern 5: Latency Percentiles
Section titled “Pattern 5: Latency Percentiles”name: api_p99_latencyinterval: 1min
detectors: # SLA limit - type: manual_bounds params: upper_bound: 1000 # 1 second max
# Detect degradation - type: iqr params: threshold: 1.5 window_size: 1440Why: Percentile metrics are skewed. IQR handles skewness better than Z-Score.
Tuning Tips
Section titled “Tuning Tips”After retuning a live metric: a detector’s identity is a hash of its parameters, so detections written under the old parameters stay in
_dtk_detectionsas orphaned rows once you change a param (or remove the detector). Rundtk clean --select <metric>to prune them (preview first, then--execute). To recompute detections for the new parameters over history instead, usedtk run --select <metric> --steps detect --full-refresh.
Window Size
Section titled “Window Size”Too small (< 50 points):
- Con: Unstable confidence intervals
- Con: Sensitive to recent outliers
- Pro: Responsive to changes
Too large (> window with 10+ cycles):
- Con: Slow to adapt to changes
- Pro: Very stable intervals
Recommended:
- Non-seasonal: 100-500 points
- Seasonal: 2-4 complete cycles
Threshold
Section titled “Threshold”Start with defaults:
- MAD: 3.0
- Z-Score: 3.0
- IQR: 1.5
Tune based on results:
- Too many false positives → Increase threshold
- Missing real anomalies → Decrease threshold
Min Samples
Section titled “Min Samples”Too small (< 30):
- Con: Unreliable statistics
- Pro: Faster detection startup
Too large (> 50% of window_size):
- Con: Long warm-up period
- Pro: Very reliable statistics
Recommended: 10-30% of window_size
Per-detector floors (a value below the floor raises ValueError):
- Z-Score:
min_samples>= 2 - IQR:
min_samples>= 4,min_samples_per_group>= 4 (quartiles need 4 points)
Performance Comparison
Section titled “Performance Comparison”Approximate speeds (including I/O):
| Detector | Points/Second | Notes |
|---|---|---|
| Manual Bounds | ~3,000 | Fastest (simple comparison) |
| Z-Score | ~1,800 | Fast (mean/std) |
| MAD (no seasonality) | ~1,500 | Fast (median/MAD) |
| MAD (with seasonality) | ~1,450 | Minimal seasonality penalty |
| IQR | ~1,400 | Percentile calculation |
These rates describe incremental runs — the normal path, where each run scores only the handful of new points and stays cheap. Detection runs a per-point loop, so a large historical backfill costs roughly O(points × window_size) and can be slow (recomputing every point against a long window). Pick a detector for accuracy; size backfills with the per-point loop in mind, not the steady-state rate.
Troubleshooting
Section titled “Troubleshooting”All points marked as “insufficient_data”
Section titled “All points marked as “insufficient_data””Cause: Not enough historical data before min_samples threshold.
Solution:
- Lower
min_samplesparameter - Increase
loading_start_timeto load more history - Wait for more data to accumulate
Too many false positives
Section titled “Too many false positives”Causes:
- Threshold too low
- No seasonality on seasonal data
- Wrong detector for data distribution
Solutions:
- Increase
thresholdparameter - Add
seasonality_components(works with MAD, Z-Score and IQR) - For trending metrics: add
window_weights: exponentialand/ordetrend: linear(see Handling Metrics with Trends) - Try different detector (e.g., MAD instead of Z-Score)
- Increase
consecutive_anomaliesin alerting config
Missing real anomalies
Section titled “Missing real anomalies”Causes:
- Threshold too high
- Window too large (includes outliers)
- Wrong detector
Solutions:
- Decrease
thresholdparameter - Decrease
window_size - Try more sensitive detector (Z-Score instead of MAD)
Confidence intervals don’t vary with seasonality
Section titled “Confidence intervals don’t vary with seasonality”Cause: Seasonality not configured correctly.
Checklist:
-
Seasonality features exist — either built-in
seasonality_columns(allowed names:hour,day_of_week,day_of_month,month,is_weekend,is_holiday) or custom columns returned by the query and declared inquery_columns.seasonality -
seasonality_componentsuses exactly those feature names -
The window is large enough to fill a group. A group’s statistics only engage once the trailing window holds at least
min_samples_per_grouppoints sharing the current point’s seasonal key — and same-key points recur only once per cardinality of the key. So the window must span roughly:window_size ≳ min_samples_per_group × (number of distinct keys)For hourly data grouped by
hour(24 keys) with the MAD defaultmin_samples_per_group = 10, that meanswindow_size ≳ 240. With the defaultwindow_size = 100only ~4 same-hour points land in the window (< 10), so every point falls back to the global band and the seasonality has no effect — which looks exactly like “the interval doesn’t vary with seasonality”. A conjunctive group like[["hour", "day_of_week"]]has up to 24 × 7 = 168 keys and needswindow_size ≳ 1680. The detector logs a one-time warning when the window is too small to ever fill a group; raisewindow_size, lowermin_samples_per_group, or use a coarser grouping.
Example (built-in extraction):
# Extract features from timestampsseasonality_columns: - hour # Feature is named "hour" — must match below
# Use in detectordetectors: - type: mad params: seasonality_components: - "hour" # Must match aboveCustom feature names (e.g. hour_of_day) only work when your query returns
such a column and it is declared in query_columns.seasonality — they are
not valid in the built-in seasonality_columns list.
Advanced Detector Features
Section titled “Advanced Detector Features”MAD, Z-Score and IQR share one windowed implementation, so every parameter
below behaves identically across the three. Manual Bounds supports only
input_type (it has no window, so smoothing, weighting and detrending do not
apply). Autoreg — a separate, non-windowed dynamics model — supports
input_type and stabilization (defaulting to clamp there) but not
smoothing, recency weighting, detrending or seasonality; see its own
reference page for the full parameter set.
The full reference for these shared parameters lives on one page — Shared Detector Parameters — which documents each with defaults, examples and tuning recipes:
- Input preprocessing —
values/changes/absolute_changes/log_changes - Value smoothing — EMA / SMA
- Window weighting —
window_weights/half_liferecency weighting - Detrending — robust in-window trend removal
- Stabilization —
stabilization: clampkeeps a sustained incident from poisoning its own baseline - Handling metrics with trends — the recommended recipe + its measured effect
- Detector identity & recomputation
- Debugging preprocessed detections — reading
detection_metadata