oracle: doc fixes

This commit is contained in:
nym21
2026-05-23 12:18:34 +02:00
parent 9c74881c5d
commit 0aaffc6c43
4 changed files with 137 additions and 31 deletions
+110 -13
View File
@@ -52,6 +52,13 @@ struct GuardCfg {
raw_margin: f64, // octave neighbor raw mass must be >= raw_margin * current
q_margin: usize, // neighbor must have >= q_margin MORE lit arms than current
q_min: usize, // neighbor must have at least this many lit arms (looks full)
// Lever 2: global re-acquire. Instead of only checking the +-60 octave
// neighbors, scan a wide band beyond the local search window for the
// strongest true-price peak (most lit arms, raw mass as tiebreak) and snap
// to it when it clearly beats the locally-trapped pick. Escapes any
// local-max trap, not just the octave alias.
global: bool,
global_radius: i64, // bins scanned on each side of the local pick
}
impl GuardCfg {
@@ -71,6 +78,11 @@ impl GuardCfg {
raw_margin: g("GUARD_RAW", 1.0),
q_margin: g("GUARD_QMARGIN", 4.0) as usize,
q_min: g("GUARD_QMIN", 14.0) as usize,
global: std::env::var("GLOBAL_REACQUIRE")
.ok()
.map(|v| v != "0")
.unwrap_or(false),
global_radius: g("GLOBAL_RADIUS", 600.0) as i64,
}
}
}
@@ -182,21 +194,54 @@ fn guarded_best_bin(
let qb = arm_count(ema, b, guard.tau);
let raw_b = ema_stencil_sum(ema, b);
let mut target = b;
let mut best: Option<(usize, f64)> = None;
for &delta in &[-OCTAVE_BINS, OCTAVE_BINS] {
let n = b + delta;
if n < 0 || n as usize >= brk_oracle::NUM_BINS {
continue;
}
let qn = arm_count(ema, n, guard.tau);
let raw_n = ema_stencil_sum(ema, n);
if qn >= qb + guard.q_margin && qn >= guard.q_min && raw_n >= guard.raw_margin * raw_b {
let better = best.is_none_or(|(sq, sr)| qn > sq || (qn == sq && raw_n > sr));
if guard.global {
// Scan beyond the local window for the strongest peak by lit-arm
// count (raw mass as tiebreak), considering only bins carrying at
// least the local pick's raw mass. Snap to it when it lights up
// q_margin more arms and looks full (>= q_min), regardless of how
// many bins away it sits.
let lo = (b - guard.global_radius).max(0);
let hi = (b + guard.global_radius).min(brk_oracle::NUM_BINS as i64 - 1);
let mut best: Option<(i64, usize, f64)> = None;
for n in lo..=hi {
if n >= search_start as i64 && n < search_end as i64 {
continue; // window interior is owned by the local search
}
let raw_n = ema_stencil_sum(ema, n);
if raw_n < guard.raw_margin * raw_b {
continue;
}
let qn = arm_count(ema, n, guard.tau);
let better = best.is_none_or(|(_, sq, sr)| qn > sq || (qn == sq && raw_n > sr));
if better {
best = Some((qn, raw_n));
best = Some((n, qn, raw_n));
}
}
if let Some((n, qn, _)) = best {
if qn >= qb + guard.q_margin && qn >= guard.q_min {
target = n;
}
}
} else {
let mut best: Option<(usize, f64)> = None;
for &delta in &[-OCTAVE_BINS, OCTAVE_BINS] {
let n = b + delta;
if n < 0 || n as usize >= brk_oracle::NUM_BINS {
continue;
}
let qn = arm_count(ema, n, guard.tau);
let raw_n = ema_stencil_sum(ema, n);
if qn >= qb + guard.q_margin
&& qn >= guard.q_min
&& raw_n >= guard.raw_margin * raw_b
{
let better = best.is_none_or(|(sq, sr)| qn > sq || (qn == sq && raw_n > sr));
if better {
best = Some((qn, raw_n));
target = n;
}
}
}
}
if target != b {
return target as f64;
@@ -480,7 +525,7 @@ fn main() {
max_outputs,
);
eprintln!(
" cfg: window_size={} alpha={:.5} (~{:.0}-block span) search -{}/+{} guard={} (tau={} raw={} qm={} qmin={})",
" cfg: window_size={} alpha={:.5} (~{:.0}-block span) search -{}/+{} guard={} (tau={} raw={} qm={} qmin={}) global={} radius={}",
config.window_size,
config.alpha,
2.0 / config.alpha - 1.0,
@@ -491,6 +536,8 @@ fn main() {
guard.raw_margin,
guard.q_margin,
guard.q_min,
guard.global,
guard.global_radius,
);
let (sb, sa) = (config.search_below, config.search_above);
let window_size = config.window_size;
@@ -504,6 +551,31 @@ fn main() {
let mut ema = HistogramEma::zeros();
let mut ref_bin = cents_to_bin(start_price * 100.0);
// Lever 4: a parallel "sharp" detection EMA (fast span, short window) folded
// from the same per-block hists. The slow EMA above still sets the price; this
// is diagnostic only, used to check whether the true-price stencil holes (the
// arm-count contrast that the smeared slow EMA flattens during a crash) survive
// when the histogram is not smoothed.
let sharp_span: f64 = std::env::var("SHARP_SPAN")
.ok()
.and_then(|s| s.parse().ok())
.unwrap_or(3.0);
let sharp_window: usize = std::env::var("SHARP_WINDOW")
.ok()
.and_then(|s| s.parse().ok())
.unwrap_or(6);
let sharp_alpha = 2.0 / (sharp_span + 1.0);
let sharp_weights: Vec<f64> = (0..sharp_window)
.map(|i| sharp_alpha * (1.0 - sharp_alpha).powi(i as i32))
.collect();
let mut sharp_ring: Vec<Vec<f64>> = vec![vec![0.0; NUM_BINS]; sharp_window];
let mut sharp_cursor = 0usize;
let mut sharp_filled = 0usize;
let mut sharp_ema = HistogramEma::zeros();
eprintln!(
" sharp: span={sharp_span:.0} window={sharp_window} alpha={sharp_alpha:.5}"
);
let total_txs = indexer.vecs.transactions.txid.len();
let total_outputs = indexer.vecs.outputs.value.len();
@@ -600,6 +672,26 @@ fn main() {
ema[b] += w * block[b];
}
}
// Sharp detection EMA (diagnostic only - does not drive the price).
{
let slot = &mut sharp_ring[sharp_cursor];
for b in 0..NUM_BINS {
slot[b] = hist[b] as f64 * scale;
}
}
sharp_cursor = (sharp_cursor + 1) % sharp_window;
if sharp_filled < sharp_window {
sharp_filled += 1;
}
sharp_ema.fill(0.0);
for age in 0..sharp_filled {
let idx = (sharp_cursor + sharp_window - 1 - age) % sharp_window;
let w = sharp_weights[age];
let block = &sharp_ring[idx];
for b in 0..NUM_BINS {
sharp_ema[b] += w * block[b];
}
}
ref_bin = guarded_best_bin(&ema, ref_bin, sb, sa, &guard);
let oracle_price = bin_to_cents(ref_bin) as f64 / 100.0;
@@ -632,9 +724,14 @@ fn main() {
let qh = arm_count(&ema, true_bin + 60, guard.tau);
let qd = arm_count(&ema, true_bin - 60, guard.tau);
let pat = arm_pattern(&ema, true_bin, guard.tau);
// Same arm-count contrast measured on the sharp detection EMA.
let qst = arm_count(&sharp_ema, true_bin, guard.tau);
let qsh = arm_count(&sharp_ema, true_bin + 60, guard.tau);
let qsd = arm_count(&sharp_ema, true_bin - 60, guard.tau);
let spat = arm_pattern(&sharp_ema, true_bin, guard.tau);
let ts_secs: u32 = *timestamps[h];
eprintln!(
"{h}\t{ts_secs}\t{oracle_price:.0}\t{ex_close:.0}\t{band_err:+.2}\t{eligible}\tT={s_true:.1}\tH={s_half:.1}\tD={s_dbl:.1}\tQt={qt}\tQh={qh}\tQd={qd}\t{pat}"
"{h}\t{ts_secs}\t{oracle_price:.0}\t{ex_close:.0}\t{band_err:+.2}\t{eligible}\tT={s_true:.1}\tH={s_half:.1}\tD={s_dbl:.1}\tQt={qt}\tQh={qh}\tQd={qd}\t{pat}\t|sharp Qt={qst} Qh={qsh} Qd={qsd}\t{spat}"
);
}
+4 -3
View File
@@ -342,9 +342,10 @@ impl Oracle {
let idx = (self.cursor + self.config.window_size - 1 - age) % self.config.window_size;
let weight = self.weights[age];
let h = &self.histograms[idx];
(0..NUM_BINS).for_each(|bin| {
self.ema[bin] += weight * h[bin] as f64;
});
self.ema
.iter_mut()
.zip(h.iter())
.for_each(|(e, &c)| *e += weight * c as f64);
}
}
}
+14
View File
@@ -177,6 +177,20 @@ All errors return structured JSON with a consistent format:
),
..Default::default()
},
Tag {
name: "Oracle".to_string(),
description: Some(
"On-chain BTC/USD price derived purely from round-dollar payment patterns in \
transaction outputs, with no external price feed. Payment activity is binned on a \
log scale, and a smoothed EMA over recent blocks locates the price.\n\n\
Histograms come in two flavors, each available at the live tip (mempool-blended) \
or at any confirmed height: `raw` (per-block counts) and `ema` (the smoothed \
window). The live price is also at `/api/mempool/price`. Confirmed per-height \
price history is at `/api/vecs/height-to-price`."
.to_string(),
),
..Default::default()
},
Tag {
name: "URPD".to_string(),
description: Some(
+9 -15
View File
@@ -31,10 +31,9 @@ impl OracleRoutes for ApiRouter<AppState> {
.oracle_tag()
.summary("Live BTC/USD price")
.description(
"Current BTC/USD price in dollars, derived purely from on-chain \
round-dollar output patterns over the last 12 blocks plus the \
forming mempool block. Same value as `/api/mempool/price`. \
Confirmed per-height history is available at `/api/vecs/height-to-price`.",
"Current BTC/USD price in dollars. Same value as \
`/api/mempool/price`. Confirmed per-height history is available at \
`/api/vecs/height-to-price`.",
)
.json_response::<Dollars>()
.not_modified()
@@ -58,9 +57,8 @@ impl OracleRoutes for ApiRouter<AppState> {
.summary("Live EMA histogram")
.description(
"Smoothed round-dollar payment histogram at the live tip: the \
committed 12-block EMA with the forming mempool block blended in \
as a final slot. A flat array of 2400 log-scale bins, quantized \
to `u16` for the wire. This is the heatmap column you render.",
committed EMA with the forming mempool block blended in. \
A flat array of log-scale bins.",
)
.json_response::<HistogramEmaCompact>()
.not_modified()
@@ -88,11 +86,8 @@ impl OracleRoutes for ApiRouter<AppState> {
.oracle_tag()
.summary("EMA histogram at height")
.description(
"Smoothed round-dollar payment histogram for a confirmed height, \
deterministically reconstructed by replaying the 12-block window \
ending at that height. Immutable once buried, so repeated requests \
return byte-identical results. A flat array of 2400 log-scale bins, \
quantized to `u16`.",
"Smoothed round-dollar payment histogram for a confirmed height. \
A flat array of log-scale bins.",
)
.json_response::<HistogramEmaCompact>()
.not_modified()
@@ -118,8 +113,7 @@ impl OracleRoutes for ApiRouter<AppState> {
.summary("Live raw histogram")
.description(
"Un-smoothed per-block round-dollar counts for the forming mempool \
block: the spiky primitive the EMA smooths over. A flat array of \
2400 log-scale bins (`u32` counts), all zero when no mempool is \
block. A flat array of log-scale bins, all zero when no mempool is \
configured.",
)
.json_response::<HistogramRaw>()
@@ -149,7 +143,7 @@ impl OracleRoutes for ApiRouter<AppState> {
.summary("Raw histogram at height")
.description(
"Un-smoothed round-dollar counts for a single confirmed block. A \
flat array of 2400 log-scale bins (`u32` counts).",
flat array of log-scale bins.",
)
.json_response::<HistogramRaw>()
.not_modified()