//! Compare oracle filter/EMA variants against the historical OHLC set. //! //! This is a diagnostic harness, not production code. It mirrors the production //! state machine closely enough to compare candidate changes in one pass over //! the indexed chain while recording the recent bad-lock heights. //! //! Run: //! cargo run -p brk_oracle --example experiment --release use std::{cmp::Ordering, env, path::PathBuf}; use brk_indexer::Indexer; use brk_oracle::{ bin_to_cents, cents_to_bin, seed_bin as oracle_seed_bin, Config, PaymentFilter, BINS_PER_DECADE, NUM_BINS, START_HEIGHT_FAST, START_HEIGHT_SLOW, }; use brk_types::{OutputType, Sats, TxIndex, TxOutIndex}; use vecdb::{AnyVec, ReadableVec, VecIndex}; const GENESIS_DAY: u32 = 14252; const BINS_5PCT: f64 = 4.24; const BINS_10PCT: f64 = 8.28; const BINS_20PCT: f64 = 15.84; const STENCIL_OFFSETS: [i32; 19] = [ -400, -340, -305, -260, -200, -165, -140, -120, -105, -60, 0, 35, 60, 95, 140, 200, 260, 340, 400, ]; const N_ARMS: usize = STENCIL_OFFSETS.len(); const TARGET_HEIGHTS: &[usize] = &[952_286, 952_287, 952_288, 952_289, 952_290]; fn bins_to_pct(bins: f64) -> f64 { (10.0_f64.powf(bins / BINS_PER_DECADE as f64) - 1.0) * 100.0 } fn timestamp_to_year(ts: u32) -> u16 { let years_since_1970 = ts as f64 / 31_557_600.0; (1970.0 + years_since_1970) as u16 } #[derive(Clone)] struct ShapeAnchor { weight: f64, profile: [f64; N_ARMS], } impl ShapeAnchor { fn new(weight: f64) -> Self { Self { weight, profile: [1.0 / N_ARMS as f64; N_ARMS], } } fn score(&self, state: &OracleState, center: i64) -> f64 { if self.weight == 0.0 { return 0.0; } self.weight * normalized_arms_at(state, center) .map(|arms| { 1.0 - (0..N_ARMS) .map(|i| (arms[i] - self.profile[i]).abs()) .sum::() }) .unwrap_or(0.0) } fn update(&mut self, state: &OracleState, pick: i64) { const BETA: f64 = 0.004; if self.weight == 0.0 { return; } if let Some(arms) = normalized_arms_at(state, pick) { for (p, arm) in self.profile.iter_mut().zip(arms) { *p = (1.0 - BETA) * *p + BETA * arm; } } } } #[derive(Clone)] struct OracleState { config: Config, ring: Vec>, nonzero: Vec>, weights: Vec, cursor: usize, filled: usize, ref_bin: f64, warmup: bool, shape: ShapeAnchor, } impl OracleState { fn new(ref_bin: f64, config: Config) -> Self { let weights = weights(config.window_size, config.alpha); Self { ring: vec![vec![0.0; NUM_BINS]; config.window_size], nonzero: vec![Vec::new(); config.window_size], weights, cursor: 0, filled: 0, ref_bin, warmup: false, shape: ShapeAnchor::new(config.shape_weight), config, } } fn reconfigure(&mut self, config: Config) { let kept = self.recent(config.window_size); let mut next = Self::new(self.ref_bin, config); next.warmup = true; for hist in kept { next.push_existing(hist); } next.warmup = false; *self = next; } fn recent(&self, n: usize) -> Vec> { (0..self.filled.min(n)) .rev() .map(|age| self.ring[self.index_at_age(age)].clone()) .collect() } fn index_at_age(&self, age: usize) -> usize { (self.cursor + self.ring.len() - 1 - age) % self.ring.len() } fn start_block(&mut self) { for bin in self.nonzero[self.cursor].drain(..) { self.ring[self.cursor][bin] = 0.0; } } fn add(&mut self, bin: usize, weight: f64) { let slot = &mut self.ring[self.cursor]; if slot[bin] == 0.0 { self.nonzero[self.cursor].push(bin); } slot[bin] += weight; } fn push_existing(&mut self, hist: Vec) { self.start_block(); for (bin, value) in hist .into_iter() .enumerate() .filter(|(_, value)| *value != 0.0) { self.add(bin, value); } self.finish_block(); } fn finish_block(&mut self) { self.cursor = (self.cursor + 1) % self.ring.len(); self.filled = (self.filled + 1).min(self.ring.len()); if self.warmup { return; } self.ref_bin = find_best_bin(self); let mut shape = self.shape.clone(); shape.update(self, self.ref_bin.round() as i64); self.shape = shape; } fn value_at(&self, bin: i64) -> f64 { if bin < 0 || bin as usize >= NUM_BINS { return 0.0; } let bin = bin as usize; (0..self.filled) .map(|age| self.weights[age] * self.ring[self.index_at_age(age)][bin]) .sum() } } fn weights(window_size: usize, alpha: f64) -> Vec { let decay = 1.0 - alpha; (0..window_size) .map(|i| alpha * decay.powi(i as i32)) .collect() } fn normalized_arms_at(state: &OracleState, center: i64) -> Option<[f64; N_ARMS]> { let mut arms = STENCIL_OFFSETS.map(|offset| state.value_at(center + offset as i64)); let sum: f64 = arms.iter().sum(); if sum <= 0.0 { return None; } for arm in &mut arms { *arm /= sum; } Some(arms) } fn find_best_bin(state: &OracleState) -> f64 { let center = state.ref_bin.round() as usize; let search_start = center.saturating_sub(state.config.search_below); let search_end = (center + state.config.search_above + 1).min(NUM_BINS); if search_start >= search_end { return state.ref_bin; } let mut arm_peaks = [0.0f64; N_ARMS]; for (i, &offset) in STENCIL_OFFSETS.iter().enumerate() { for bin in search_start..search_end { arm_peaks[i] = arm_peaks[i].max(state.value_at(bin as i64 + offset as i64)); } } let score = |bin: usize| -> f64 { let mut total = 0.0; for (i, &offset) in STENCIL_OFFSETS.iter().enumerate() { if arm_peaks[i] > 0.0 { total += state.value_at(bin as i64 + offset as i64) / arm_peaks[i]; } } total + state.shape.score(state, bin as i64) }; let mut best_bin = search_start; let mut best_score = score(search_start); for bin in (search_start + 1)..search_end { let candidate = score(bin); if candidate > best_score { best_score = candidate; best_bin = bin; } } let score_center = best_score; let score_left = if best_bin > search_start { score(best_bin - 1) } else { score_center }; let score_right = if best_bin + 1 < search_end { score(best_bin + 1) } else { score_center }; let denom = score_left - 2.0 * score_center + score_right; let sub_bin = if denom.abs() > 1e-10 { (0.5 * (score_left - score_right) / denom).clamp(-0.5, 0.5) } else { 0.0 }; best_bin as f64 + sub_bin } #[derive(Clone)] struct VariantCfg { name: String, fast_alpha: f64, fast_window: usize, max_outputs: Option, max_outputs_until: usize, max_outputs_after: Option, } struct Variant { cfg: VariantCfg, state: OracleState, overall: YearStats, years: Vec, bias: f64, target_prices: Vec<(usize, f64)>, } fn cap_label(cap: Option) -> String { cap.map(|cap| cap.to_string()) .unwrap_or_else(|| "none".to_string()) } fn target_price(target_prices: &[(usize, f64)], height: usize) -> Option { target_prices .iter() .find(|(h, _)| *h == height) .map(|(_, price)| *price) } fn fixes_bad_lock(target_prices: &[(usize, f64)]) -> bool { target_price(target_prices, 952_287).is_some_and(|price| price > 62_000.0) && target_price(target_prices, 952_288).is_some_and(|price| price > 62_000.0) } impl Variant { fn new(cfg: VariantCfg, seed_bin: f64) -> Self { Self { cfg, state: OracleState::new(seed_bin, Config::slow()), overall: YearStats::new(0), years: Vec::new(), bias: 0.0, target_prices: Vec::new(), } } fn fast_config(&self) -> Config { Config { alpha: self.cfg.fast_alpha, window_size: self.cfg.fast_window, ..Config::default() } } fn maybe_reconfigure(&mut self, height: usize) { if height == START_HEIGHT_FAST { self.state.reconfigure(self.fast_config()); } } fn should_drop_tx(&self, height: usize, output_count: usize) -> bool { if height < self.cfg.max_outputs_until { self.cfg.max_outputs.is_some_and(|max| output_count > max) } else { self.cfg .max_outputs_after .is_some_and(|max| output_count > max) } } fn add_tx(&mut self, bins: &[u16], height: usize, output_count: usize) { if bins.is_empty() || self.should_drop_tx(height, output_count) { return; } for &bin in bins { self.state.add(bin as usize, 1.0); } } fn finish_block(&mut self, height: usize) { self.state.finish_block(); if TARGET_HEIGHTS.contains(&height) { self.target_prices .push((height, bin_to_cents(self.state.ref_bin) as f64 / 100.0)); } } fn update_stats( &mut self, height: usize, height_bands: &[(f64, f64)], height_ohlc: &[[f64; 4]], height_years: &[u16], ) { if height >= height_bands.len() { return; } let (high_bin, low_bin) = height_bands[height]; if high_bin <= 0.0 || low_bin <= 0.0 { return; } let err = if self.state.ref_bin < high_bin { self.state.ref_bin - high_bin } else if self.state.ref_bin > low_bin { self.state.ref_bin - low_bin } else { 0.0 }; let exchange_high = height_ohlc[height][1]; let exchange_low = height_ohlc[height][2]; self.overall.update(err, exchange_high, exchange_low); self.bias += err; let year = height_years[height]; if self.years.last().is_none_or(|stats| stats.year != year) { self.years.push(YearStats::new(year)); } self.years .last_mut() .unwrap() .update(err, exchange_high, exchange_low); } } struct YearStats { year: u16, total_sq_err: f64, max_err: f64, total_blocks: u64, gt_5pct: u64, gt_10pct: u64, gt_20pct: u64, errors: Vec, } impl YearStats { fn new(year: u16) -> Self { Self { year, total_sq_err: 0.0, max_err: 0.0, total_blocks: 0, gt_5pct: 0, gt_10pct: 0, gt_20pct: 0, errors: Vec::new(), } } fn update(&mut self, err: f64, _exchange_high: f64, _exchange_low: f64) { let abs_err = err.abs(); self.total_sq_err += err * err; self.total_blocks += 1; self.errors.push(bins_to_pct(abs_err)); self.max_err = self.max_err.max(abs_err); if abs_err > BINS_5PCT { self.gt_5pct += 1; } if abs_err > BINS_10PCT { self.gt_10pct += 1; } if abs_err > BINS_20PCT { self.gt_20pct += 1; } } fn rmse_pct(&self) -> f64 { if self.total_blocks == 0 { return 0.0; } bins_to_pct((self.total_sq_err / self.total_blocks as f64).sqrt()) } fn max_pct(&self) -> f64 { bins_to_pct(self.max_err) } fn percentile(&self, p: f64) -> f64 { if self.errors.is_empty() { return 0.0; } let mut errors = self.errors.clone(); errors.sort_by(|a, b| a.partial_cmp(b).unwrap_or(Ordering::Equal)); let idx = ((p / 100.0) * (errors.len() - 1) as f64).round() as usize; errors[idx.min(errors.len() - 1)] } } fn main() { let data_dir = std::env::var("BRK_DIR") .map(PathBuf::from) .unwrap_or_else(|_| PathBuf::from(std::env::var("HOME").unwrap()).join(".brk")); let end_override = std::env::var("ORACLE_END") .ok() .and_then(|s| s.parse::().ok()); let stats_start = std::env::var("ORACLE_STATS_START") .ok() .and_then(|s| s.parse::().ok()) .unwrap_or(START_HEIGHT_SLOW) .max(START_HEIGHT_SLOW); let indexer = Indexer::forced_import(&data_dir).expect("Failed to load indexer"); let total_heights = indexer.vecs.blocks.timestamp.len(); let end = end_override.unwrap_or(total_heights).min(total_heights); let manifest_dir = env!("CARGO_MANIFEST_DIR"); let height_ohlc: Vec<[f64; 4]> = serde_json::from_str( &std::fs::read_to_string(format!("{manifest_dir}/examples/height_price_ohlc.json")) .expect("read height_price_ohlc.json"), ) .expect("parse height OHLC"); let height_bands: Vec<(f64, f64)> = height_ohlc .iter() .map(|ohlc| { let high = ohlc[1]; let low = ohlc[2]; if high > 0.0 && low > 0.0 { (cents_to_bin(high * 100.0), cents_to_bin(low * 100.0)) } else { (0.0, 0.0) } }) .collect(); let timestamps: Vec = indexer.vecs.blocks.timestamp.collect(); let height_years: Vec = timestamps .iter() .map(|ts| timestamp_to_year(**ts)) .collect(); let _height_day1s: Vec = timestamps .iter() .map(|ts| (**ts / 86_400).saturating_sub(GENESIS_DAY) as usize) .collect(); let seed_bin = oracle_seed_bin(); let current_alpha = 2.0 / 7.0; let current_window = 12; let mut cfgs = Vec::::new(); let mut add_cfg = |name: String, max_outputs: Option, max_outputs_until: usize, max_outputs_after: Option| { cfgs.push(VariantCfg { name, fast_alpha: current_alpha, fast_window: current_window, max_outputs, max_outputs_until, max_outputs_after, }); }; for post in [200, 250] { add_cfg( format!("pre100_post{post}"), Some(100), PaymentFilter::MODERN_TX_OUTPUT_FANOUT_CAP_START_HEIGHT, Some(post), ); } cfgs.dedup_by(|a, b| a.name == b.name); if let Ok(only) = env::var("BRK_ORACLE_EXPERIMENT_ONLY") { let names = only .split(',') .map(str::trim) .filter(|name| !name.is_empty()) .collect::>(); cfgs.retain(|cfg| names.iter().any(|name| *name == cfg.name)); } let mut variants: Vec = cfgs .into_iter() .map(|cfg| Variant::new(cfg, seed_bin)) .collect(); let total_txs = indexer.vecs.transactions.txid.len(); let total_outputs = indexer.vecs.outputs.value.len(); let first_tx_index: Vec = indexer.vecs.transactions.first_tx_index.collect(); let out_first: Vec = indexer.vecs.outputs.first_txout_index.collect(); let mut txout_cursor = indexer.vecs.transactions.first_txout_index.cursor(); let mut tx_starts: Vec = Vec::new(); let mut values: Vec = Vec::new(); let mut output_types: Vec = Vec::new(); let mut bins: Vec = Vec::new(); eprintln!( "running {} variants over heights {START_HEIGHT_SLOW}..{end}; stats from {stats_start}", variants.len() ); for h in START_HEIGHT_SLOW..end { if h % 25_000 == 0 { eprintln!("height {h}"); } for variant in &mut variants { variant.maybe_reconfigure(h); variant.state.start_block(); } let ft = first_tx_index[h]; let next_ft = first_tx_index .get(h + 1) .copied() .unwrap_or(TxIndex::from(total_txs)); let block_first_tx = ft.to_usize() + 1; let tx_count = next_ft.to_usize() - block_first_tx; let out_end = out_first .get(h + 1) .copied() .unwrap_or(TxOutIndex::from(total_outputs)) .to_usize(); txout_cursor.advance(block_first_tx - txout_cursor.position()); tx_starts.clear(); for _ in 0..tx_count { tx_starts.push(txout_cursor.next().unwrap().to_usize()); } let out_start = tx_starts.first().copied().unwrap_or(out_end); indexer .vecs .outputs .value .collect_range_into_at(out_start, out_end, &mut values); indexer.vecs.outputs.output_type.collect_range_into_at( out_start, out_end, &mut output_types, ); for tx in 0..tx_count { let lo = tx_starts[tx] - out_start; let hi = tx_starts .get(tx + 1) .map(|s| s - out_start) .unwrap_or(out_end - out_start); if output_types[lo..hi].contains(&OutputType::OpReturn) { continue; } bins.clear(); for i in lo..hi { if let Some(bin) = PaymentFilter::eligible_bin(values[i], output_types[i]) { bins.push(bin); } } for variant in &mut variants { variant.add_tx(&bins, h, hi - lo); } } for variant in &mut variants { variant.finish_block(h); if h >= stats_start { variant.update_stats(h, &height_bands, &height_ohlc, &height_years); } } } variants.sort_by(|a, b| { fixes_bad_lock(&b.target_prices) .cmp(&fixes_bad_lock(&a.target_prices)) .then_with(|| { a.overall .rmse_pct() .partial_cmp(&b.overall.rmse_pct()) .unwrap_or(Ordering::Equal) }) .then_with(|| a.overall.gt_5pct.cmp(&b.overall.gt_5pct)) }); println!( "variant\tpre_cap\tpost_cap\tfixed\tmedian\tp95\tp99\tp999\trmse\tmax\tbias_bins\tgt5\tgt10\tgt20\tp952287\tp952288\ttarget_prices\trmse_by_year\tgt5_by_year" ); for variant in &variants { let overall = &variant.overall; let bias = if overall.total_blocks > 0 { variant.bias / overall.total_blocks as f64 } else { 0.0 }; let rmse_by_year = (2015..=2026) .map(|year| { let rmse = variant .years .iter() .find(|stats| stats.year == year) .map(YearStats::rmse_pct) .unwrap_or(0.0); format!("{year}:{rmse:.3}") }) .collect::>() .join(","); let gt5_by_year = (2015..=2026) .map(|year| { let gt5 = variant .years .iter() .find(|stats| stats.year == year) .map(|stats| stats.gt_5pct) .unwrap_or(0); format!("{year}:{gt5}") }) .collect::>() .join(","); println!( "{}\t{}\t{}\t{}\t{:.3}\t{:.3}\t{:.3}\t{:.3}\t{:.3}\t{:.3}\t{:.3}\t{}\t{}\t{}\t{:.2}\t{:.2}\t{}\t{}\t{}", variant.cfg.name, cap_label(variant.cfg.max_outputs), cap_label(variant.cfg.max_outputs_after), fixes_bad_lock(&variant.target_prices), overall.percentile(50.0), overall.percentile(95.0), overall.percentile(99.0), overall.percentile(99.9), overall.rmse_pct(), overall.max_pct(), bias, overall.gt_5pct, overall.gt_10pct, overall.gt_20pct, target_price(&variant.target_prices, 952_287).unwrap_or(0.0), target_price(&variant.target_prices, 952_288).unwrap_or(0.0), variant .target_prices .iter() .map(|(height, price)| format!("{height}:{price:.2}")) .collect::>() .join(","), rmse_by_year, gt5_by_year ); } }