Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 23 additions & 17 deletions benchmarks/stablecoin-peg.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -57,7 +57,8 @@ abstract: |
methodology:
- "Sources per stable, USDC: Binance USDC/USDT (USDT-anchored, secondary), Kraken USDCUSD, Bitstamp usdcusd. USDT: Kraken USDTUSD, Bitstamp usdtusd. FDUSD: Binance FDUSDUSDT (USDT-anchored, secondary). USDe: Binance USDEUSDT (USDT-anchored, secondary). DAI: Curve 3pool `get_dy` forward (USDC → DAI) and reverse (DAI → USDC)."
- "Cadence: 5 seconds for CEX REST tickers, 12 seconds for Curve `get_dy` on-chain (matches Ethereum block time). Per-venue samples are grouped into 60-second buckets for the liquidity-weighted median aggregation."
- "Aggregation: per-minute liquidity-weighted median across USD-quoted venues (`Quote = QuoteUSD`). Weight is 24 h USD volume estimate. The aggregated price drives `peg_deviation_bps` and `peg_deviation_bps_histogram`; the percentile metric is computed on this series, not on raw per-venue samples."
- "Aggregation: per-minute liquidity-weighted median across USD-quoted venues (`Quote = QuoteUSD`). Weight is 24 h USD volume estimate. The aggregated price drives `peg_deviation_bps` (per-minute median, retained for backward compatibility) and `peg_deviation_bps_histogram`."
- "Primary headline: `peg_deviation_worst_bps` — the per-minute MAX |price − $1.00| across every venue sample in the 60-second bucket, in basis points. The leaderboard ranks stables on `quantile_over_time(0.99, peg_deviation_worst_bps[24h])`. Switching from per-minute median to per-minute max surfaces the sub-minute depeg wicks (e.g. a 5-second print to $0.92 on one venue) that the median would smooth into invisibility — matching the OHLC convention used by every TradFi reference rate. The companion gauges `peg_minute_min_bps`, `peg_minute_open_bps` and `peg_minute_close_bps` expose the full bar shape so consumers can reconstruct the within-minute distribution."
- "Cross-venue gap (OCB-only number): `max(price across USD-quoted venues), min(price)`, per minute, in basis points. Surfaces the windows where Coinbase quotes $1.0003 while Kraken quotes $0.9978. Stored as `peg_cross_venue_gap_bps`."
- "USDT-anchored secondary metric: Binance USDC/USDT, FDUSD/USDT and USDE/USDT are exposed on `peg_deviation_usdt_anchored_bps{venue}` separately so the USD-anchored primary leaderboard is not contaminated by USDT's own peg deviation."
- "Time outside band: total seconds in the trailing 24 h during which the per-minute aggregated price fell outside [0.995, 1.005] (±50 bps). Split into `peg_time_below_peg_24h_seconds` (< 0.995) and `peg_time_above_peg_24h_seconds` (> 1.005) because Circle redemption only clears above-peg, so the direction tells you which failure mode is active."
Expand DownExpand Up@@ -94,7 +95,12 @@ prometheus:
# Real metrics emitted by the stablecoin-peg harness:
# peg_raw_price{stable, venue, quote} gauge
# peg_aggregated_price_usd{stable} gauge
# peg_deviation_bps{stable} gauge
# peg_deviation_bps{stable} gauge (legacy median)
# peg_deviation_worst_bps{stable} gauge (PRIMARY, per-minute MAX)
# peg_minute_max_bps{stable} gauge
# peg_minute_min_bps{stable} gauge
# peg_minute_open_bps{stable} gauge
# peg_minute_close_bps{stable} gauge
# peg_deviation_bps_histogram{stable} histogram
# peg_cross_venue_gap_bps{stable} gauge
# peg_cross_venue_gap_bps_histogram{stable} histogram
Expand All@@ -118,39 +124,39 @@ providers:
tag: Circle, USD-redeemed primary, audited on Kraken + Bitstamp
formula: "Median |aggregated_price − $1.00| in bps, where aggregated_price is the per-minute liquidity-weighted median of USDCUSD ticks on Kraken and Bitstamp."
queries:
p50: quantile_over_time(0.50, peg_deviation_bps{stable="usdc"}[24h])
p90: quantile_over_time(0.90, peg_deviation_bps{stable="usdc"}[24h])
p99: quantile_over_time(0.99, peg_deviation_bps{stable="usdc"}[24h])
mean: avg_over_time(peg_deviation_bps{stable="usdc"}[24h])
p50: quantile_over_time(0.50, peg_deviation_worst_bps{stable="usdc"}[24h])
p90: quantile_over_time(0.90, peg_deviation_worst_bps{stable="usdc"}[24h])
p99: quantile_over_time(0.99, peg_deviation_worst_bps{stable="usdc"}[24h])
mean: avg_over_time(peg_deviation_worst_bps{stable="usdc"}[24h])
success: avg_over_time(peg_source_health{stable="usdc"}[24h])
sample_size: sum(increase(peg_source_call_total{stable="usdc"}[24h]))
series: peg_deviation_bps{stable="usdc"}
series: peg_deviation_worst_bps{stable="usdc"}

- slug: usdt
name: USDT
tag: Tether, dominant CEX pair currency, audited on Kraken + Bitstamp
formula: "Median |aggregated_price − $1.00| in bps, where aggregated_price is the per-minute liquidity-weighted median of USDTUSD ticks on Kraken and Bitstamp."
queries:
p50: quantile_over_time(0.50, peg_deviation_bps{stable="usdt"}[24h])
p90: quantile_over_time(0.90, peg_deviation_bps{stable="usdt"}[24h])
p99: quantile_over_time(0.99, peg_deviation_bps{stable="usdt"}[24h])
mean: avg_over_time(peg_deviation_bps{stable="usdt"}[24h])
p50: quantile_over_time(0.50, peg_deviation_worst_bps{stable="usdt"}[24h])
p90: quantile_over_time(0.90, peg_deviation_worst_bps{stable="usdt"}[24h])
p99: quantile_over_time(0.99, peg_deviation_worst_bps{stable="usdt"}[24h])
mean: avg_over_time(peg_deviation_worst_bps{stable="usdt"}[24h])
success: avg_over_time(peg_source_health{stable="usdt"}[24h])
sample_size: sum(increase(peg_source_call_total{stable="usdt"}[24h]))
series: peg_deviation_bps{stable="usdt"}
series: peg_deviation_worst_bps{stable="usdt"}

- slug: dai
name: DAI
tag: MakerDAO, audited via Curve 3pool get_dy (on-chain only)
formula: "Median |dai_price − $1.00| in bps, derived from Curve 3pool get_dy forward (USDC → DAI) and reverse (DAI → USDC) every 12s over 24h."
queries:
p50: quantile_over_time(0.50, peg_deviation_bps{stable="dai"}[24h])
p90: quantile_over_time(0.90, peg_deviation_bps{stable="dai"}[24h])
p99: quantile_over_time(0.99, peg_deviation_bps{stable="dai"}[24h])
mean: avg_over_time(peg_deviation_bps{stable="dai"}[24h])
p50: quantile_over_time(0.50, peg_deviation_worst_bps{stable="dai"}[24h])
p90: quantile_over_time(0.90, peg_deviation_worst_bps{stable="dai"}[24h])
p99: quantile_over_time(0.99, peg_deviation_worst_bps{stable="dai"}[24h])
mean: avg_over_time(peg_deviation_worst_bps{stable="dai"}[24h])
success: avg_over_time(peg_source_health{stable="dai"}[24h])
sample_size: sum(increase(peg_source_call_total{stable="dai"}[24h]))
series: peg_deviation_bps{stable="dai"}
series: peg_deviation_worst_bps{stable="dai"}

# FDUSD and USDe are USDT-anchored stables with no deep USD-quoted
# venue, so they have no signal on the primary `peg_deviation_bps`
Expand Down
42 changes: 42 additions & 0 deletions harnesses/stablecoin-peg/cmd/script/aggregator.go
Original file line numberDiff line numberDiff line change
Expand Up@@ -184,6 +184,48 @@ func (a *Aggregator) closeMinuteLocked(closedMinute time.Time) {
pegDeviationBps.WithLabelValues(stable).Set(devBps)
pegDeviationHist.WithLabelValues(stable).Observe(devBps)

// OHLC pass: compute open/high/low/close on the per-minute
// |price-1| series across every venue sample in the bucket,
// in chronological order. The median we computed above is
// what the bench reports as the "typical" value within the
// minute; the high (worst) is what a depeg detector or a
// stress-aware integrator needs to see. A 5-second print to
// $0.92 is invisible in the median, undeniable in the high.
var (
firstTime, lastTime time.Time
firstDev, lastDev float64
maxDev float64
minDev = math.MaxFloat64
haveAny bool
)
for _, samples := range byVenue {
for _, s := range samples {
dev := math.Abs(s.price-1.0) * 10000
if !haveAny || s.receivedAt.Before(firstTime) {
firstTime = s.receivedAt
firstDev = dev
}
if !haveAny || s.receivedAt.After(lastTime) {
lastTime = s.receivedAt
lastDev = dev
}
if dev > maxDev {
maxDev = dev
}
if dev < minDev {
minDev = dev
}
haveAny = true
}
}
if haveAny {
pegDeviationWorstBps.WithLabelValues(stable).Set(maxDev)
pegMinuteMaxBps.WithLabelValues(stable).Set(maxDev)
pegMinuteMinBps.WithLabelValues(stable).Set(minDev)
pegMinuteOpenBps.WithLabelValues(stable).Set(firstDev)
pegMinuteCloseBps.WithLabelValues(stable).Set(lastDev)
}

// Cross-venue gap = max - min in bps. Only meaningful with
// ≥2 venues in the same minute.
if len(venuePrices) >= 2 {
Expand Down
51 changes: 50 additions & 1 deletion harnesses/stablecoin-peg/cmd/script/metrics.go
Original file line numberDiff line numberDiff line change
Expand Up@@ -42,7 +42,56 @@ var (
pegDeviationBps = promauto.NewGaugeVec(
prometheus.GaugeOpts{
Name: "peg_deviation_bps",
Help: "Current absolute deviation from $1.00 in basis points, computed from the per-minute aggregated price. Drives p99 over 24h via quantile_over_time.",
Help: "Per-minute median absolute deviation from $1.00 in basis points (legacy headline, retained for backward compatibility). The new primary leaderboard ranks on peg_deviation_worst_bps which captures the per-minute MAX, not the median.",
},
[]string{"stable"},
)

// THE new primary headline. Per-minute MAX deviation across
// every venue's samples within the bucket — surfaces the worst
// case the bench observed during the minute. A 5-second depeg
// to $0.92 is invisible in the median (which averages it with
// 11 healthy samples in the same minute) but pops out in this
// gauge. Matches what TradFi reference rates publish as the
// "high" of an OHLC bar. Methodology recommendation from
// Coinpaprika data team.
pegDeviationWorstBps = promauto.NewGaugeVec(
prometheus.GaugeOpts{
Name: "peg_deviation_worst_bps",
Help: "Per-minute MAX |price - $1.00| across every venue sample within the bucket, in basis points. Captures sub-minute depeg wicks that the median smooths away. Primary leaderboard metric.",
},
[]string{"stable"},
)

// Companion OHLC gauges for the same per-minute bucket. open
// and close are deviation in bps of the first / last sample;
// max and min are the bucket's extremes. Together they expose
// the bar shape that a single median value collapses.
pegMinuteMaxBps = promauto.NewGaugeVec(
prometheus.GaugeOpts{
Name: "peg_minute_max_bps",
Help: "MAX |price - $1.00| in bps observed within the most-recently-closed 60s bucket across all venues. Identical to peg_deviation_worst_bps; exposed under both names for legibility.",
},
[]string{"stable"},
)
pegMinuteMinBps = promauto.NewGaugeVec(
prometheus.GaugeOpts{
Name: "peg_minute_min_bps",
Help: "MIN |price - $1.00| in bps observed within the most-recently-closed 60s bucket across all venues. Floor of the bar.",
},
[]string{"stable"},
)
pegMinuteOpenBps = promauto.NewGaugeVec(
prometheus.GaugeOpts{
Name: "peg_minute_open_bps",
Help: "|first_sample_price - $1.00| in bps for the most-recently-closed 60s bucket. Open of the bar.",
},
[]string{"stable"},
)
pegMinuteCloseBps = promauto.NewGaugeVec(
prometheus.GaugeOpts{
Name: "peg_minute_close_bps",
Help: "|last_sample_price - $1.00| in bps for the most-recently-closed 60s bucket. Close of the bar.",
},
[]string{"stable"},
)
Expand Down
Loading