spread = 0→ asset trades at intrinsicspread > 0→ market below intrinsic (under-priced, common stress signal)spread < 0→ market above intrinsic (over-priced, less common)
EWMA smoothing
Raw spread ticks are noisy. We smooth with an exponentially-weighted moving average:α = 0.3 default. That is:
- New tick contributes 30% to the published spread
- Prior smoothed value carries 70%
α (say 0.6) reacts faster but is noisier. A lower α (0.1) is laggy and
misses real depegs. 0.3 is calibrated against 24h p99 noise per asset class — tight
enough to catch a real depeg inside ~15s, loose enough that a single bad Jupiter
quote doesn’t trip the FSM.
Per-asset thresholds
A single fixed threshold across all 63 assets tracked live (50 monitoring-only) would produce false positives on noisy LSTs (INF: 24h p99 ~1.12%) and false negatives on tight stables (USDC: 24h p99 ~10 bps). We calibrate per-asset.
These are representative bands; live per-asset values vary (e.g. jitoSOL
0.45/1.10/2.50%, xSOL 15/30/60%). Every asset’s exact threshold is in
assets.toml. To
inspect:
The 5-state FSM
UNKNOWN. The engine publishes
UNKNOWN (honest-dark) at cold-start and when an anchor is detectably broken — a >10%
premium on a NAV-priced asset, via the NAV-sanity gate (ADR-0025) — but never as a routine
stale-data verdict.
Each transition has a dwell timer: the smoothed spread must hold the crossed
threshold for the specified time before the transition fires.
Asymmetric hysteresis
Entry and exit thresholds are deliberately different, like a thermostat:
Why exit dwell > entry dwell? Entering a stressed state is something subscribers
want fast — 30s is fine. Exiting back to
PEGGED is something they want to trust —
a single tick at 29 bps after a 200 bps spike is not the same as a sustained recovery.
The longer exit dwell (120s vs the 30s entry dwell) is a small cost — an extra minute and
a half of “alert” state — and a big honesty win.
See hysteresis-fsm for the deeper read.
What the FSM guarantees
- A transition fires once per real state change. A signal that oscillates inside a threshold band stays in the stressed state until it genuinely recovers.
- Every transition is timestamped server-side with the spread that triggered it.
BLACK_SWANis reachable from both the spread path (a discount beyond2× criticalby default, overridable per asset viablack_swan/black_swan_bps) and the CR path (hyUSD, CR < 100%). It re-labels only the most extreme moves — a USDC 4%+ break, a UST-styleCRITICAL → BLACK_SWANcascade — and auto-exits via the standard decay-down hysteresis like every other band (ADR-0025); it is not a no-reset terminal state, and there is no public ack endpoint.
What the FSM cannot fix
- A stale input. If Sanctum stops publishing, the smoothed spread will lag — the FSM
has no way to detect that. We mitigate via a staleness gate (30s) at the source
layer: on a quote older than 30s the engine skips the recompute and holds the last
published state rather than emitting a fresh verdict on degraded input. High-risk
illiquid / single-source assets are flagged
monitoring_only(beta): their alerts are live but labeled provisional, and a detectably frozen anchor publishes UNKNOWN via the NAV-sanity gate rather than a false reading. - A wrong threshold. We calibrated against 24h p99 over the soak period. If an asset’s
noise profile changes (new venue, new MEV regime), the threshold may need
re-calibration.
CALIBRATION-v1.mddocuments the procedure.
Next
Honesty about limits
What we don’t model. Where the signal can lie. When to mistrust an alert.