Skip to main content
The spread is the only number Pegana publishes that you really need to care about.
spread = 1 − market / intrinsic
  • spread = 0 → asset trades at intrinsic
  • spread > 0 → market below intrinsic (under-priced, common stress signal)
  • spread < 0 → market above intrinsic (over-priced, less common)
A 100 bps spread means market is 1.00% below intrinsic.

EWMA smoothing

Raw spread ticks are noisy. We smooth with an exponentially-weighted moving average:
smoothed[t] = α × raw[t] + (1 − α) × smoothed[t − 1]
with α = 0.3 default. That is:
  • New tick contributes 30% to the published spread
  • Prior smoothed value carries 70%
A higher α (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 22 active assets 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.
Asset classDrift entryDepeg entryCritical entry
Fiat stable0.15%0.50%2.00%
SOL-pegged LST0.30%2.00%5.00%
Yield-bearing1.00% – 2.50%2.50% – 4.00%5.00%
CDP (Hylo hyUSD)CR < 130%CR < 115%CR < 105%
Synthetic (xSOL)leverage-bandwiderterminal
Every asset’s exact threshold is in assets.toml. To inspect:
curl https://api.pegana.xyz/v1/assets/INF/extra

The 5-state FSM

   ┌───────────┐
   │  PEGGED   │←──┐
   └────┬──────┘   │
        │ 30s @    │ 60s @
        │ ≥ drift  │ < drift-exit
        ▼          │
   ┌───────────┐   │
   │  DRIFT    │───┘
   └────┬──────┘
        │ 30s @
        │ ≥ depeg

   ┌───────────┐
   │  DEPEG    │
   └────┬──────┘
        │ 30s @
        │ ≥ critical

   ┌───────────┐
   │  CRITICAL │
   └───────────┘

  orthogonal: UNKNOWN (stale / low-confidence input)
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:
TransitionThresholdDwell
PEGGED → DRIFT≥ drift_entry30s
DRIFT → PEGGED≤ drift_exit (≈ 67% of drift_entry)60s
DRIFT → DEPEG≥ depeg_entry30s
DEPEG → DRIFT≤ depeg_exit60s
DEPEG → CRITICAL≥ critical_entry30s
CRITICAL → DEPEG≤ critical_exit60s
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. Doubling the exit dwell is a small cost (one extra minute 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_SWAN (spread beyond historical p99.9) never auto-exits — it requires a manual /v1/me/alerts/:id/ack from the operator.

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: any quote older than 30s flips the asset to UNKNOWN.
  • 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.md documents the procedure.

Next

Honesty about limits

What we don’t model. Where the signal can lie. When to mistrust an alert.