> ## Documentation Index
> Fetch the complete documentation index at: https://pegana.xyz/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# The spread

> Formula, EWMA smoothing, per-asset thresholds, and the 5-state FSM with asymmetric hysteresis.

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 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.

| Asset class                   | Drift entry   | Depeg entry   | Critical entry |
| ----------------------------- | ------------- | ------------- | -------------- |
| Fiat stable                   | 0.15%         | 0.50%         | 2.00%          |
| SOL-pegged LST                | 0.30%         | 2.00%         | 5.00%          |
| Yield-bearing                 | 1.00% – 2.50% | 2.50% – 4.00% | 5.00%          |
| Delta-neutral (USDe / JupUSD) | 0.25%         | 1.00%         | 3.00%          |
| FX-pegged (EURC)              | 0.20%         | 0.75%         | 2.50%          |
| CDP (Hylo hyUSD)              | CR \< 130%    | CR \< 115%    | CR \< 105%     |
| Synthetic (xSOL)              | leverage-band | wider         | terminal       |

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:

```bash theme={"theme":"github-dark"}
curl https://api.pegana.xyz/v1/assets/INF
```

## The 5-state FSM

```
   ┌───────────┐
   │  PEGGED   │←──┐
   └────┬──────┘   │
        │ 30s @    │ 120s @
        │ ≥ drift  │ < drift-exit
        ▼          │
   ┌───────────┐   │
   │  DRIFT    │───┘
   └────┬──────┘
        │ 30s @
        │ ≥ depeg
        ▼
   ┌───────────┐
   │  DEPEG    │
   └────┬──────┘
        │ 30s @
        │ ≥ critical
        ▼
   ┌───────────┐
   │  CRITICAL │
   └────┬──────┘
        │ 30s @
        │ ≥ 2×critical
        ▼
   ┌────────────┐
   │ BLACK_SWAN │
   └────────────┘
```

On routine stale / low-confidence / decoder-drift input the engine **skips the recompute
and holds the last published state** — it does not enter `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:

| Transition         | Threshold                                                   | Dwell    |
| ------------------ | ----------------------------------------------------------- | -------- |
| `PEGGED → DRIFT`   | `≥ drift_entry`                                             | 30s      |
| `DRIFT → PEGGED`   | `≤ drift_exit` (75% of `drift_entry`, `DEADBAND_PCT = 25%`) | **120s** |
| `DRIFT → DEPEG`    | `≥ depeg_entry`                                             | 30s      |
| `DEPEG → DRIFT`    | `≤ depeg_exit`                                              | 120s     |
| `DEPEG → CRITICAL` | `≥ critical_entry`                                          | 30s      |
| `CRITICAL → DEPEG` | `≤ critical_exit`                                           | 120s     |

**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](/docs/concepts/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` is reachable from **both** the spread path (a discount beyond `2× critical`
  by default, overridable per asset via `black_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-style `CRITICAL → BLACK_SWAN` cascade — 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.md` documents the procedure.

## Next

<Card title="Honesty about limits" icon="circle-info" href="/docs/methodology/honesty">
  What we don't model. Where the signal can lie. When to mistrust an alert.
</Card>
