mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-06-08 14:11:56 -07:00
heatmaps: part 14
This commit is contained in:
@@ -127,39 +127,45 @@ impl Vecs {
|
||||
// Slow cold-start EMA up to START_HEIGHT_FAST, then switch to the fast
|
||||
// mature-market EMA. Steady-state runs start past START_HEIGHT_FAST and skip
|
||||
// the slow segment entirely.
|
||||
let mut ref_bins = Vec::with_capacity(num_new);
|
||||
if committed < START_HEIGHT_FAST {
|
||||
let slow_end = START_HEIGHT_FAST.min(total_heights);
|
||||
ref_bins.extend(Self::feed_blocks(
|
||||
&mut oracle,
|
||||
indexer,
|
||||
committed..slow_end,
|
||||
None,
|
||||
));
|
||||
if slow_end == START_HEIGHT_FAST {
|
||||
oracle.reconfigure(Config::default());
|
||||
{
|
||||
let mut processed = 0usize;
|
||||
let mut push_ref_bin = |ref_bin| {
|
||||
self.spot
|
||||
.cents
|
||||
.height
|
||||
.inner
|
||||
.push(Cents::new(bin_to_cents(ref_bin)));
|
||||
|
||||
processed += 1;
|
||||
let progress = (processed * 100 / num_new) as u8;
|
||||
if processed > 1 && progress > (((processed - 1) * 100 / num_new) as u8) {
|
||||
info!("Oracle price computation: {}%", progress);
|
||||
}
|
||||
};
|
||||
|
||||
if committed < START_HEIGHT_FAST {
|
||||
let slow_end = START_HEIGHT_FAST.min(total_heights);
|
||||
Self::feed_blocks_with(
|
||||
&mut oracle,
|
||||
indexer,
|
||||
committed..slow_end,
|
||||
None,
|
||||
|_, _, ref_bin| push_ref_bin(ref_bin),
|
||||
);
|
||||
if slow_end == START_HEIGHT_FAST {
|
||||
oracle.reconfigure(Config::default());
|
||||
}
|
||||
}
|
||||
}
|
||||
let fast_start = committed.max(START_HEIGHT_FAST);
|
||||
if fast_start < total_heights {
|
||||
ref_bins.extend(Self::feed_blocks(
|
||||
&mut oracle,
|
||||
indexer,
|
||||
fast_start..total_heights,
|
||||
None,
|
||||
));
|
||||
}
|
||||
|
||||
for (i, ref_bin) in ref_bins.into_iter().enumerate() {
|
||||
self.spot
|
||||
.cents
|
||||
.height
|
||||
.inner
|
||||
.push(Cents::new(bin_to_cents(ref_bin)));
|
||||
|
||||
let progress = ((i + 1) * 100 / num_new) as u8;
|
||||
if i > 0 && progress > ((i * 100 / num_new) as u8) {
|
||||
info!("Oracle price computation: {}%", progress);
|
||||
let fast_start = committed.max(START_HEIGHT_FAST);
|
||||
if fast_start < total_heights {
|
||||
Self::feed_blocks_with(
|
||||
&mut oracle,
|
||||
indexer,
|
||||
fast_start..total_heights,
|
||||
None,
|
||||
|_, _, ref_bin| push_ref_bin(ref_bin),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,15 +4,13 @@ use brk_computer::prices::Vecs as PricesVecs;
|
||||
use brk_error::{Error, Result};
|
||||
use brk_indexer::Lengths;
|
||||
use brk_oracle::{
|
||||
Config, HistogramEma, HistogramEmaCompact, HistogramRaw, Oracle, cents_to_bin, sats_to_bin,
|
||||
cents_to_bin, sats_to_bin, Config, HistogramEma, HistogramEmaCompact, HistogramRaw, Oracle,
|
||||
};
|
||||
use brk_types::{Day1, Dollars, Sats, TxOutIndex};
|
||||
use brk_types::{Day1, Dollars, TxOutIndex};
|
||||
use vecdb::{AnyVec, ReadableVec, VecIndex};
|
||||
|
||||
use crate::Query;
|
||||
|
||||
const RAW_HISTOGRAM_VALUE_CHUNK: usize = 1_000_000;
|
||||
|
||||
impl Query {
|
||||
pub fn live_price(&self) -> Result<Dollars> {
|
||||
Ok(self.live_oracle()?.price_dollars())
|
||||
@@ -233,70 +231,38 @@ impl Query {
|
||||
/// range instead of one block at a time.
|
||||
fn output_histogram_for_blocks(&self, range: Range<usize>, safe: &Lengths) -> HistogramRaw {
|
||||
let indexer = self.indexer();
|
||||
let safe_height = safe.height.to_usize();
|
||||
let total_outputs = safe.txout_index.to_usize();
|
||||
let collect_end = (range.end + 1).min(safe.height.to_usize());
|
||||
|
||||
let out_firsts: Vec<TxOutIndex> = indexer
|
||||
let out_start = indexer
|
||||
.vecs
|
||||
.outputs
|
||||
.first_txout_index
|
||||
.collect_range_at(range.start, collect_end);
|
||||
let out_start = out_firsts[0].to_usize();
|
||||
let out_end = out_firsts
|
||||
.get(range.end - range.start)
|
||||
.copied()
|
||||
.unwrap_or(TxOutIndex::from(total_outputs))
|
||||
.collect_one_at(range.start)
|
||||
.unwrap()
|
||||
.to_usize();
|
||||
|
||||
let mut hist = HistogramRaw::zeros();
|
||||
let mut values: Vec<Sats> = Vec::new();
|
||||
let mut start = out_start;
|
||||
while start < out_end {
|
||||
let end = (start + RAW_HISTOGRAM_VALUE_CHUNK).min(out_end);
|
||||
values.clear();
|
||||
let out_end = if range.end < safe_height {
|
||||
indexer
|
||||
.vecs
|
||||
.outputs
|
||||
.value
|
||||
.collect_range_into_at(start, end, &mut values);
|
||||
add_sats_to_raw_histogram(&mut hist, &values);
|
||||
start = end;
|
||||
.first_txout_index
|
||||
.collect_one_at(range.end)
|
||||
.unwrap()
|
||||
} else {
|
||||
TxOutIndex::from(total_outputs)
|
||||
}
|
||||
.to_usize();
|
||||
|
||||
let mut hist = HistogramRaw::zeros();
|
||||
indexer
|
||||
.vecs
|
||||
.outputs
|
||||
.value
|
||||
.for_each_range_at(out_start, out_end, |sats| {
|
||||
if let Some(bin) = sats_to_bin(sats) {
|
||||
hist.increment(bin);
|
||||
}
|
||||
});
|
||||
hist
|
||||
}
|
||||
}
|
||||
|
||||
fn add_sats_to_raw_histogram(hist: &mut HistogramRaw, values: &[Sats]) {
|
||||
for &sats in values {
|
||||
if let Some(bin) = sats_to_bin(sats) {
|
||||
hist.increment(bin);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn raw_histogram_accumulation_is_additive() {
|
||||
let values = [
|
||||
Sats::ZERO,
|
||||
Sats::new(1),
|
||||
Sats::new(10),
|
||||
Sats::new(100_000_000),
|
||||
Sats::new(1_000_000_000_000),
|
||||
Sats::new(5_000_000_000),
|
||||
];
|
||||
|
||||
let mut one_shot = HistogramRaw::zeros();
|
||||
add_sats_to_raw_histogram(&mut one_shot, &values);
|
||||
|
||||
let mut chunked = HistogramRaw::zeros();
|
||||
for chunk in values.chunks(2) {
|
||||
add_sats_to_raw_histogram(&mut chunked, chunk);
|
||||
}
|
||||
|
||||
assert!(one_shot.iter().eq(chunked.iter()));
|
||||
}
|
||||
}
|
||||
|
||||
+16
-15
@@ -37,8 +37,7 @@ export function createAverageGrid({
|
||||
const sums = new Float64Array(cols * rows);
|
||||
const counts = new Uint32Array(cols * rows);
|
||||
const maxByCol = new Float64Array(cols);
|
||||
const cumulativeMaxByCol = new Float64Array(cols);
|
||||
let cumulativeMaxDirty = true;
|
||||
let maxValue = 0;
|
||||
const ySpan = yMax - yMin;
|
||||
|
||||
/** @param {number} dateIndex */
|
||||
@@ -85,7 +84,10 @@ export function createAverageGrid({
|
||||
if (counts[index]) max = Math.max(max, sums[index] / counts[index]);
|
||||
}
|
||||
maxByCol[col] = max;
|
||||
cumulativeMaxDirty = true;
|
||||
maxValue = 0;
|
||||
for (let c = 0; c < cols; c++) {
|
||||
maxValue = Math.max(maxValue, maxByCol[c]);
|
||||
}
|
||||
}
|
||||
|
||||
/** @type {HeatmapGrid} */
|
||||
@@ -99,7 +101,13 @@ export function createAverageGrid({
|
||||
let dirty = false;
|
||||
if (points.kind === "implicit") {
|
||||
for (let i = 0; i < points.values.length; i++) {
|
||||
if (addValue(col, points.yStart + i * points.yStep, points.values[i])) {
|
||||
if (
|
||||
addValue(
|
||||
col,
|
||||
points.yStart + i * points.yStep,
|
||||
points.values[i],
|
||||
)
|
||||
) {
|
||||
dirty = true;
|
||||
}
|
||||
}
|
||||
@@ -110,8 +118,9 @@ export function createAverageGrid({
|
||||
}
|
||||
}
|
||||
if (!dirty) return undefined;
|
||||
const previousMax = maxValue;
|
||||
updateColumnMax(col);
|
||||
return col;
|
||||
return { col, maxChanged: maxValue !== previousMax };
|
||||
},
|
||||
getValue(col, row) {
|
||||
if (col < 0 || col >= cols || row < 0 || row >= rows) {
|
||||
@@ -120,16 +129,8 @@ export function createAverageGrid({
|
||||
const index = row * cols + col;
|
||||
return counts[index] ? sums[index] / counts[index] : Number.NaN;
|
||||
},
|
||||
getMaxValue(col = cols - 1) {
|
||||
if (cumulativeMaxDirty) {
|
||||
let max = 0;
|
||||
for (let c = 0; c < cols; c++) {
|
||||
max = Math.max(max, maxByCol[c]);
|
||||
cumulativeMaxByCol[c] = max;
|
||||
}
|
||||
cumulativeMaxDirty = false;
|
||||
}
|
||||
return cumulativeMaxByCol[clamp(col, 0, cols - 1)] ?? 0;
|
||||
getMaxValue() {
|
||||
return maxValue;
|
||||
},
|
||||
getDateIndexRange(col) {
|
||||
if (col < 0 || col >= cols || dates.length === 0) {
|
||||
|
||||
@@ -248,8 +248,13 @@ function rebuildGrid() {
|
||||
*/
|
||||
function addDateToGrid(dateIndex, points) {
|
||||
if (!currentGrid) return;
|
||||
const dirtyCol = currentGrid.add(dateIndex, points);
|
||||
if (dirtyCol !== undefined) schedulePaint(dirtyCol);
|
||||
const result = currentGrid.add(dateIndex, points);
|
||||
if (!result) return;
|
||||
if (result.maxChanged) {
|
||||
paint();
|
||||
} else {
|
||||
schedulePaint(result.col);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -38,7 +38,7 @@ export function intensityColor({ light, dark }) {
|
||||
export function logIntensityColor({ light, dark }) {
|
||||
return (value, context) => {
|
||||
if (!Number.isFinite(value) || value <= 0) return 0x00000000;
|
||||
const max = context.grid.getMaxValue(context.col);
|
||||
const max = context.grid.getMaxValue();
|
||||
if (max <= 0) return 0x00000000;
|
||||
const lut = context.dark ? dark : light;
|
||||
const t = Math.log2(value + 1) / Math.log2(max + 1);
|
||||
|
||||
@@ -44,7 +44,7 @@ function createOracleHeatmapOption(mode, name) {
|
||||
kind: "heatmap",
|
||||
name,
|
||||
title:
|
||||
mode === "outputs" ? "Output Value Histogram" : "Payment Output Histogram",
|
||||
mode === "outputs" ? "Output Value Histogram" : "Payment Value Histogram",
|
||||
points: {
|
||||
fetch: (date, signal, onPoints) =>
|
||||
fetchOraclePoints(mode, date, signal, onPoints),
|
||||
|
||||
@@ -19,13 +19,17 @@
|
||||
* @property {number} start
|
||||
* @property {number} end
|
||||
*
|
||||
* @typedef {Object} HeatmapGridAddResult
|
||||
* @property {number} col
|
||||
* @property {boolean} maxChanged
|
||||
*
|
||||
* @typedef {Object} HeatmapGrid
|
||||
* @property {readonly string[]} dates
|
||||
* @property {number} cols
|
||||
* @property {number} rows
|
||||
* @property {(dateIndex: number, points: HeatmapPoints) => number | undefined} add
|
||||
* @property {(dateIndex: number, points: HeatmapPoints) => HeatmapGridAddResult | undefined} add
|
||||
* @property {(col: number, row: number) => number} getValue
|
||||
* @property {(col?: number) => number} getMaxValue
|
||||
* @property {() => number} getMaxValue
|
||||
* @property {(col: number) => HeatmapRange} getDateIndexRange
|
||||
* @property {(row: number) => HeatmapRange} getYRange
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user