UPDATED · 23 JUN 2026 · EDIT ON GITHUB
GUIDES · DETECT

Backtesting a detection over 30 days.

Before a rule ever reaches an analyst, replay it across real history on the fabric. A backtest shows exactly how often the rule would have fired and on what — the fastest way to kill a false positive before it kills a SOC’s trust.

14 min read Intermediate By T. Volkov

Run a backtest

Backtesting replays historical events against a detection plan without re-ingesting anything and without firing a single real action. Point it at a window and let it score.

SHELLbacktest
$ td detect backtest ./detections/svc-account-from-corp-ip.yml --since 30d -> replaying 1.84B events from the fabric... -> 14 matches across 30 days -> 3 in allowlisted windows . 11 candidate true positives ok report at /detections/_preview/backtest/bt_9Hk2

Read the results

The report is not just a count. It clusters matches so you can tell a real pattern from a noisy one, and flags the matches that overlap known-good activity as false-positive candidates to suppress.

SignalRead it as
Match rateRoughly how many cases per day this rule will open. Over ~5/day for a HIGH, retune.
FP candidatesMatches overlapping allowlisted windows or service accounts — add an except.
ClustersMany matches on one entity is usually one incident, not many — good. Spread across unrelated entities on a narrow rule is suspicious.

Gate CI on the number

Make the backtest a merge gate. A pull request that would introduce false positives fails before it can ship.

YAML.github/workflows/detections.yml
- run: td detect backtest ./detections --since 30d --max-fp 0
Backtests are free. Because compute is decoupled from storage, a replay reads the past without re-ingesting it and without side effects — run one on every change, not just the scary ones.

Where to go next

← PREV Tuning behavioral models NEXT → Cross-stream window joins