global: big snapshot

This commit is contained in:
nym21
2026-03-13 12:47:01 +01:00
parent c83955eea7
commit 2b31c7f6b7
158 changed files with 4961 additions and 6939 deletions
@@ -1,67 +0,0 @@
//! Amount type with height-level data only (no period-derived views).
//!
//! Stores sats and cents per index, plus lazy btc and usd transforms.
//! Use when period views are unnecessary (e.g., rolling windows provide windowed data).
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{Bitcoin, Cents, Dollars, Height, Sats, Version};
use vecdb::{
Database, EagerVec, Exit, ImportableVec, LazyVecFrom1, PcoVec, ReadableCloneableVec, Rw,
StorageMode, VecIndex,
};
use crate::{
internal::{CentsUnsignedToDollars, SatsToBitcoin, SatsToCents},
prices,
};
const VERSION: Version = Version::TWO; // Match AmountPerBlock versioning
#[derive(Traversable)]
pub struct Amount<I: VecIndex, M: StorageMode = Rw> {
pub sats: M::Stored<EagerVec<PcoVec<I, Sats>>>,
pub btc: LazyVecFrom1<I, Bitcoin, I, Sats>,
pub cents: M::Stored<EagerVec<PcoVec<I, Cents>>>,
pub usd: LazyVecFrom1<I, Dollars, I, Cents>,
}
impl Amount<Height> {
pub(crate) fn forced_import(db: &Database, name: &str, version: Version) -> Result<Self> {
let v = version + VERSION;
let sats: EagerVec<PcoVec<Height, Sats>> =
EagerVec::forced_import(db, &format!("{name}_sats"), v)?;
let btc = LazyVecFrom1::transformed::<SatsToBitcoin>(name, v, sats.read_only_boxed_clone());
let cents: EagerVec<PcoVec<Height, Cents>> =
EagerVec::forced_import(db, &format!("{name}_cents"), v)?;
let usd = LazyVecFrom1::transformed::<CentsUnsignedToDollars>(
&format!("{name}_usd"),
v,
cents.read_only_boxed_clone(),
);
Ok(Self {
sats,
btc,
cents,
usd,
})
}
/// Eagerly compute cents height values: sats[h] * price_cents[h] / 1e8.
pub(crate) fn compute_cents(
&mut self,
prices: &prices::Vecs,
max_from: Height,
exit: &Exit,
) -> Result<()> {
self.cents.compute_binary::<Sats, Cents, SatsToCents>(
max_from,
&self.sats,
&prices.spot.cents.height,
exit,
)?;
Ok(())
}
}
@@ -1,5 +1,3 @@
mod base;
mod lazy;
pub use base::*;
pub use lazy::*;
@@ -2,12 +2,10 @@ mod distribution_stats;
mod per_resolution;
mod window_24h;
mod windows;
mod windows_except_1m;
mod windows_from_1w;
pub use distribution_stats::*;
pub use per_resolution::*;
pub use window_24h::*;
pub use windows::*;
pub use windows_except_1m::*;
pub use windows_from_1w::*;
@@ -1,4 +1,11 @@
use brk_traversable::Traversable;
use brk_types::Height;
use vecdb::CachedVec;
/// Cached window starts for lazy rolling computations.
/// Clone-cheap (all fields are Arc-backed). Shared across all metrics.
#[derive(Clone)]
pub struct CachedWindowStarts(pub Windows<CachedVec<Height, Height>>);
#[derive(Clone, Traversable)]
pub struct Windows<A> {
@@ -33,4 +40,32 @@ impl<A> Windows<A> {
pub fn as_mut_array_from_1w(&mut self) -> [&mut A; 3] {
[&mut self._1w, &mut self._1m, &mut self._1y]
}
pub fn map_with_suffix<B>(&self, mut f: impl FnMut(&str, &A) -> B) -> Windows<B> {
Windows {
_24h: f(Self::SUFFIXES[0], &self._24h),
_1w: f(Self::SUFFIXES[1], &self._1w),
_1m: f(Self::SUFFIXES[2], &self._1m),
_1y: f(Self::SUFFIXES[3], &self._1y),
}
}
}
impl<A, B> Windows<(A, B)> {
pub fn unzip(self) -> (Windows<A>, Windows<B>) {
(
Windows {
_24h: self._24h.0,
_1w: self._1w.0,
_1m: self._1m.0,
_1y: self._1y.0,
},
Windows {
_24h: self._24h.1,
_1w: self._1w.1,
_1m: self._1m.1,
_1y: self._1y.1,
},
)
}
}
@@ -1,30 +0,0 @@
use brk_traversable::Traversable;
#[derive(Clone, Traversable)]
pub struct WindowsExcept1m<A> {
pub _24h: A,
pub _1w: A,
pub _1y: A,
}
impl<A> WindowsExcept1m<A> {
pub const SUFFIXES: [&'static str; 3] = ["24h", "1w", "1y"];
pub fn try_from_fn<E>(
mut f: impl FnMut(&str) -> std::result::Result<A, E>,
) -> std::result::Result<Self, E> {
Ok(Self {
_24h: f(Self::SUFFIXES[0])?,
_1w: f(Self::SUFFIXES[1])?,
_1y: f(Self::SUFFIXES[2])?,
})
}
pub fn as_array(&self) -> [&A; 3] {
[&self._24h, &self._1w, &self._1y]
}
pub fn as_mut_array(&mut self) -> [&mut A; 3] {
[&mut self._24h, &mut self._1w, &mut self._1y]
}
}
@@ -1,13 +1,12 @@
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{Bitcoin, Cents, Dollars, Height, Sats, Version};
use vecdb::{AnyVec, Database, Exit, ReadableCloneableVec, ReadableVec, Rw, StorageMode};
use vecdb::{AnyVec, Database, Exit, ReadableCloneableVec, Rw, StorageMode};
use crate::{
indexes,
internal::{
CentsUnsignedToDollars, ComputedPerBlock, LazyPerBlock, SatsToBitcoin, SatsToCents,
Windows,
},
prices,
};
@@ -74,33 +73,5 @@ impl AmountPerBlock {
Ok(())
}
pub(crate) fn compute_rolling_sum(
&mut self,
max_from: Height,
window_starts: &impl ReadableVec<Height, Height>,
sats_source: &impl ReadableVec<Height, Sats>,
cents_source: &impl ReadableVec<Height, Cents>,
exit: &Exit,
) -> Result<()> {
self.sats
.height
.compute_rolling_sum(max_from, window_starts, sats_source, exit)?;
self.cents
.height
.compute_rolling_sum(max_from, window_starts, cents_source, exit)?;
Ok(())
}
}
impl Windows<AmountPerBlock> {
pub(crate) fn forced_import(
db: &Database,
name: &str,
version: Version,
indexes: &indexes::Vecs,
) -> Result<Self> {
Windows::try_from_fn(|suffix| {
AmountPerBlock::forced_import(db, &format!("{name}_{suffix}"), version, indexes)
})
}
}
@@ -5,61 +5,76 @@ use vecdb::{Database, EagerVec, Exit, PcoVec, Rw, StorageMode};
use crate::{
indexes,
internal::{AmountPerBlock, RollingSumAmountPerBlock, SatsToCents, WindowStarts},
internal::{AmountPerBlock, CachedWindowStarts, LazyRollingSumsAmountFromHeight, SatsToCents},
prices,
};
#[derive(Traversable)]
pub struct AmountPerBlockCumulativeSum<M: StorageMode = Rw> {
pub base: AmountPerBlock<M>,
pub struct AmountPerBlockCumulativeWithSums<M: StorageMode = Rw> {
pub raw: AmountPerBlock<M>,
pub cumulative: AmountPerBlock<M>,
pub sum: RollingSumAmountPerBlock<M>,
pub sum: LazyRollingSumsAmountFromHeight,
}
const VERSION: Version = Version::TWO;
impl AmountPerBlockCumulativeSum {
impl AmountPerBlockCumulativeWithSums {
pub(crate) fn forced_import(
db: &Database,
name: &str,
version: Version,
indexes: &indexes::Vecs,
cached_starts: &CachedWindowStarts,
) -> Result<Self> {
let v = version + VERSION;
let raw = AmountPerBlock::forced_import(db, name, v, indexes)?;
let cumulative =
AmountPerBlock::forced_import(db, &format!("{name}_cumulative"), v, indexes)?;
let sum = LazyRollingSumsAmountFromHeight::new(
&format!("{name}_sum"),
v,
&cumulative.sats.height,
&cumulative.cents.height,
cached_starts,
indexes,
);
Ok(Self {
base: AmountPerBlock::forced_import(db, name, v, indexes)?,
cumulative: AmountPerBlock::forced_import(
db,
&format!("{name}_cumulative"),
v,
indexes,
)?,
sum: RollingSumAmountPerBlock::forced_import(db, name, v, indexes)?,
raw,
cumulative,
sum,
})
}
pub(crate) fn compute(
&mut self,
max_from: Height,
windows: &WindowStarts<'_>,
prices: &prices::Vecs,
exit: &Exit,
compute_sats: impl FnOnce(&mut EagerVec<PcoVec<Height, Sats>>) -> Result<()>,
) -> Result<()> {
compute_sats(&mut self.base.sats.height)?;
compute_sats(&mut self.raw.sats.height)?;
self.compute_rest(max_from, prices, exit)
}
pub(crate) fn compute_rest(
&mut self,
max_from: Height,
prices: &prices::Vecs,
exit: &Exit,
) -> Result<()> {
self.cumulative
.sats
.height
.compute_cumulative(max_from, &self.base.sats.height, exit)?;
.compute_cumulative(max_from, &self.raw.sats.height, exit)?;
self.base
self.raw
.cents
.height
.compute_binary::<Sats, Cents, SatsToCents>(
max_from,
&self.base.sats.height,
&self.raw.sats.height,
&prices.spot.cents.height,
exit,
)?;
@@ -67,15 +82,7 @@ impl AmountPerBlockCumulativeSum {
self.cumulative
.cents
.height
.compute_cumulative(max_from, &self.base.cents.height, exit)?;
self.sum.compute_rolling_sum(
max_from,
windows,
&self.base.sats.height,
&self.base.cents.height,
exit,
)?;
.compute_cumulative(max_from, &self.raw.cents.height, exit)?;
Ok(())
}
@@ -5,7 +5,10 @@ use vecdb::{Database, EagerVec, Exit, PcoVec, Rw, StorageMode};
use crate::{
indexes,
internal::{AmountPerBlock, RollingFullAmountPerBlock, SatsToCents, WindowStarts},
internal::{
AmountPerBlock, CachedWindowStarts, LazyRollingSumsAmountFromHeight,
RollingDistributionAmountPerBlock, SatsToCents, WindowStarts,
},
prices,
};
@@ -13,8 +16,9 @@ use crate::{
pub struct AmountPerBlockFull<M: StorageMode = Rw> {
pub base: AmountPerBlock<M>,
pub cumulative: AmountPerBlock<M>,
pub sum: LazyRollingSumsAmountFromHeight,
#[traversable(flatten)]
pub rolling: RollingFullAmountPerBlock<M>,
pub rolling: RollingDistributionAmountPerBlock<M>,
}
const VERSION: Version = Version::TWO;
@@ -25,18 +29,33 @@ impl AmountPerBlockFull {
name: &str,
version: Version,
indexes: &indexes::Vecs,
cached_starts: &CachedWindowStarts,
) -> Result<Self> {
let v = version + VERSION;
let base = AmountPerBlock::forced_import(db, name, v, indexes)?;
let cumulative = AmountPerBlock::forced_import(
db,
&format!("{name}_cumulative"),
v,
indexes,
)?;
let sum = LazyRollingSumsAmountFromHeight::new(
&format!("{name}_sum"),
v,
&cumulative.sats.height,
&cumulative.cents.height,
cached_starts,
indexes,
);
let rolling =
RollingDistributionAmountPerBlock::forced_import(db, name, v, indexes)?;
Ok(Self {
base: AmountPerBlock::forced_import(db, name, v, indexes)?,
cumulative: AmountPerBlock::forced_import(
db,
&format!("{name}_cumulative"),
v,
indexes,
)?,
rolling: RollingFullAmountPerBlock::forced_import(db, name, v, indexes)?,
base,
cumulative,
sum,
rolling,
})
}
@@ -0,0 +1,124 @@
use brk_traversable::Traversable;
use brk_types::{Bitcoin, Cents, Dollars, Height, Sats, Version};
use derive_more::{Deref, DerefMut};
use vecdb::{DeltaSub, LazyDeltaVec, LazyVecFrom1, ReadableCloneableVec};
use crate::{
indexes,
internal::{
CachedWindowStarts, CentsUnsignedToDollars, DerivedResolutions, LazyPerBlock,
LazyRollingSumFromHeight, Resolutions, SatsToBitcoin, Windows,
},
};
/// Single window slot: lazy rolling sum for Amount (sats + btc + cents + usd).
#[derive(Clone, Traversable)]
pub struct LazyRollingSumAmountFromHeight {
pub sats: LazyRollingSumFromHeight<Sats>,
pub btc: LazyPerBlock<Bitcoin, Sats>,
pub cents: LazyRollingSumFromHeight<Cents>,
pub usd: LazyPerBlock<Dollars, Cents>,
}
/// Lazy rolling sums for all 4 windows, for Amount (sats + btc + cents + usd).
#[derive(Clone, Deref, DerefMut, Traversable)]
#[traversable(transparent)]
pub struct LazyRollingSumsAmountFromHeight(pub Windows<LazyRollingSumAmountFromHeight>);
impl LazyRollingSumsAmountFromHeight {
pub fn new(
name: &str,
version: Version,
cumulative_sats: &(impl ReadableCloneableVec<Height, Sats> + 'static),
cumulative_cents: &(impl ReadableCloneableVec<Height, Cents> + 'static),
cached_starts: &CachedWindowStarts,
indexes: &indexes::Vecs,
) -> Self {
let cum_sats = cumulative_sats.read_only_boxed_clone();
let cum_cents = cumulative_cents.read_only_boxed_clone();
let make_slot = |suffix: &str, cached_start: &vecdb::CachedVec<Height, Height>| {
let full_name = format!("{name}_{suffix}");
let cached = cached_start.clone();
let starts_version = cached.version();
// Sats lazy rolling sum
let sats_sum = LazyDeltaVec::<Height, Sats, Sats, DeltaSub>::new(
&format!("{full_name}_sats"),
version,
cum_sats.clone(),
starts_version,
{
let cached = cached.clone();
move || cached.get()
},
);
let sats_resolutions = Resolutions::forced_import(
&format!("{full_name}_sats"),
sats_sum.read_only_boxed_clone(),
version,
indexes,
);
let sats = LazyRollingSumFromHeight {
height: sats_sum,
resolutions: Box::new(sats_resolutions),
};
// Btc lazy from sats
let btc = LazyPerBlock {
height: LazyVecFrom1::transformed::<SatsToBitcoin>(
&full_name,
version,
sats.height.read_only_boxed_clone(),
),
resolutions: Box::new(DerivedResolutions::from_derived_computed::<SatsToBitcoin>(
&full_name,
version,
&sats.resolutions,
)),
};
// Cents rolling sum
let cents_sum = LazyDeltaVec::<Height, Cents, Cents, DeltaSub>::new(
&format!("{full_name}_cents"),
version,
cum_cents.clone(),
starts_version,
move || cached.get(),
);
let cents_resolutions = Resolutions::forced_import(
&format!("{full_name}_cents"),
cents_sum.read_only_boxed_clone(),
version,
indexes,
);
let cents = LazyRollingSumFromHeight {
height: cents_sum,
resolutions: Box::new(cents_resolutions),
};
// Usd lazy from cents
let usd = LazyPerBlock {
height: LazyVecFrom1::transformed::<CentsUnsignedToDollars>(
&format!("{full_name}_usd"),
version,
cents.height.read_only_boxed_clone(),
),
resolutions: Box::new(DerivedResolutions::from_derived_computed::<
CentsUnsignedToDollars,
>(
&format!("{full_name}_usd"), version, &cents.resolutions
)),
};
LazyRollingSumAmountFromHeight {
sats,
btc,
cents,
usd,
}
};
Self(cached_starts.0.map_with_suffix(make_slot))
}
}
@@ -4,11 +4,8 @@ mod cumulative_sum;
mod full;
mod lazy;
mod lazy_derived_resolutions;
mod rolling;
mod rolling_full;
mod rolling_sum;
mod windows;
mod with_sum_24h;
mod lazy_rolling_sum;
mod rolling_distribution;
pub use base::*;
pub use cumulative::*;
@@ -16,8 +13,5 @@ pub use cumulative_sum::*;
pub use full::*;
pub use lazy::*;
pub use lazy_derived_resolutions::*;
pub use rolling::*;
pub use rolling_full::*;
pub use rolling_sum::*;
pub use windows::*;
pub use with_sum_24h::*;
pub use lazy_rolling_sum::*;
pub use rolling_distribution::*;
@@ -1,61 +0,0 @@
//! Value type for Height + Rolling pattern.
//!
//! Combines Value (sats/btc/usd per height, no period views) with
//! AmountPerBlockWindows (rolling sums across 4 windows).
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{Height, Sats, Version};
use derive_more::{Deref, DerefMut};
use vecdb::{Database, EagerVec, Exit, PcoVec, Rw, StorageMode};
use crate::{
indexes,
internal::{Amount, AmountPerBlockWindows, WindowStarts},
prices,
};
#[derive(Deref, DerefMut, Traversable)]
pub struct AmountPerBlockRolling<M: StorageMode = Rw> {
#[deref]
#[deref_mut]
#[traversable(flatten)]
pub amount: Amount<Height, M>,
#[traversable(flatten)]
pub rolling: AmountPerBlockWindows<M>,
}
impl AmountPerBlockRolling {
pub(crate) fn forced_import(
db: &Database,
name: &str,
version: Version,
indexes: &indexes::Vecs,
) -> Result<Self> {
Ok(Self {
amount: Amount::forced_import(db, name, version)?,
rolling: AmountPerBlockWindows::forced_import(db, name, version, indexes)?,
})
}
/// Compute sats height via closure, then cents from price, then rolling windows.
pub(crate) fn compute(
&mut self,
max_from: Height,
windows: &WindowStarts<'_>,
prices: &prices::Vecs,
exit: &Exit,
compute_sats: impl FnOnce(&mut EagerVec<PcoVec<Height, Sats>>) -> Result<()>,
) -> Result<()> {
compute_sats(&mut self.amount.sats)?;
self.amount.compute_cents(prices, max_from, exit)?;
self.rolling.compute_rolling_sum(
max_from,
windows,
&self.amount.sats,
&self.amount.cents,
exit,
)?;
Ok(())
}
}
@@ -7,21 +7,21 @@ use vecdb::{Database, Exit, ReadableVec, Rw, StorageMode};
use crate::{
indexes,
internal::{
AmountPerBlock, DistributionStats, WindowStarts, Windows, compute_rolling_distribution_from_starts,
AmountPerBlock, DistributionStats, WindowStarts, Windows,
compute_rolling_distribution_from_starts,
},
};
/// One window slot: sum + 8 distribution stats, each a AmountPerBlock.
/// One window slot: 8 distribution stats, each a AmountPerBlock.
///
/// Tree: `sum.sats.height`, `average.sats.height`, etc.
/// Tree: `average.sats.height`, `min.sats.height`, etc.
#[derive(Traversable)]
pub struct RollingFullSlot<M: StorageMode = Rw> {
pub sum: AmountPerBlock<M>,
pub struct RollingDistributionSlot<M: StorageMode = Rw> {
#[traversable(flatten)]
pub distribution: DistributionStats<AmountPerBlock<M>>,
}
impl RollingFullSlot {
impl RollingDistributionSlot {
pub(crate) fn forced_import(
db: &Database,
name: &str,
@@ -29,7 +29,6 @@ impl RollingFullSlot {
indexes: &indexes::Vecs,
) -> Result<Self> {
Ok(Self {
sum: AmountPerBlock::forced_import(db, &format!("{name}_sum"), version, indexes)?,
distribution: DistributionStats::try_from_fn(|suffix| {
AmountPerBlock::forced_import(db, &format!("{name}_{suffix}"), version, indexes)
})?,
@@ -44,15 +43,6 @@ impl RollingFullSlot {
cents_source: &impl ReadableVec<Height, Cents>,
exit: &Exit,
) -> Result<()> {
self.sum
.sats
.height
.compute_rolling_sum(max_from, starts, sats_source, exit)?;
self.sum
.cents
.height
.compute_rolling_sum(max_from, starts, cents_source, exit)?;
let d = &mut self.distribution;
macro_rules! compute_unit {
@@ -80,14 +70,16 @@ impl RollingFullSlot {
}
}
/// Rolling sum + distribution across 4 windows, window-first.
/// Rolling distribution across 4 windows, window-first.
///
/// Tree: `_24h.sum.sats.height`, `_24h.average.sats.height`, etc.
/// Tree: `_24h.average.sats.height`, `_24h.min.sats.height`, etc.
#[derive(Deref, DerefMut, Traversable)]
#[traversable(transparent)]
pub struct RollingFullAmountPerBlock<M: StorageMode = Rw>(pub Windows<RollingFullSlot<M>>);
pub struct RollingDistributionAmountPerBlock<M: StorageMode = Rw>(
pub Windows<RollingDistributionSlot<M>>,
);
impl RollingFullAmountPerBlock {
impl RollingDistributionAmountPerBlock {
pub(crate) fn forced_import(
db: &Database,
name: &str,
@@ -95,7 +87,12 @@ impl RollingFullAmountPerBlock {
indexes: &indexes::Vecs,
) -> Result<Self> {
Ok(Self(Windows::try_from_fn(|suffix| {
RollingFullSlot::forced_import(db, &format!("{name}_{suffix}"), version, indexes)
RollingDistributionSlot::forced_import(
db,
&format!("{name}_{suffix}"),
version,
indexes,
)
})?))
}
@@ -1,86 +0,0 @@
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{Cents, Height, Sats, Version};
use derive_more::{Deref, DerefMut};
use vecdb::{Database, Exit, ReadableVec, Rw, StorageMode};
use crate::{
indexes,
internal::{AmountPerBlock, RollingWindow24h, WindowStarts, Windows},
};
/// Single 24h rolling sum as amount (sats + btc + cents + usd).
///
/// Tree: `_24h.sats.height`, `_24h.btc.height`, etc.
#[derive(Deref, DerefMut, Traversable)]
#[traversable(transparent)]
pub struct RollingWindow24hAmountPerBlock<M: StorageMode = Rw>(
pub RollingWindow24h<AmountPerBlock<M>>,
);
impl RollingWindow24hAmountPerBlock {
pub(crate) fn forced_import(
db: &Database,
name: &str,
version: Version,
indexes: &indexes::Vecs,
) -> Result<Self> {
Ok(Self(RollingWindow24h {
_24h: AmountPerBlock::forced_import(db, &format!("{name}_24h"), version, indexes)?,
}))
}
pub(crate) fn compute_rolling_sum(
&mut self,
max_from: Height,
height_24h_ago: &impl ReadableVec<Height, Height>,
sats_source: &impl ReadableVec<Height, Sats>,
cents_source: &impl ReadableVec<Height, Cents>,
exit: &Exit,
) -> Result<()> {
self._24h
.compute_rolling_sum(max_from, height_24h_ago, sats_source, cents_source, exit)
}
}
/// Rolling sum only, window-first then unit.
///
/// Tree: `_24h.sats.height`, `_24h.btc.height`, etc.
#[derive(Deref, DerefMut, Traversable)]
#[traversable(transparent)]
pub struct RollingSumAmountPerBlock<M: StorageMode = Rw>(pub Windows<AmountPerBlock<M>>);
impl RollingSumAmountPerBlock {
pub(crate) fn forced_import(
db: &Database,
name: &str,
version: Version,
indexes: &indexes::Vecs,
) -> Result<Self> {
Ok(Self(Windows::<AmountPerBlock>::forced_import(
db,
&format!("{name}_sum"),
version,
indexes,
)?))
}
pub(crate) fn compute_rolling_sum(
&mut self,
max_from: Height,
windows: &WindowStarts<'_>,
sats_source: &impl ReadableVec<Height, Sats>,
cents_source: &impl ReadableVec<Height, Cents>,
exit: &Exit,
) -> Result<()> {
for (w, starts) in self.0.as_mut_array().into_iter().zip(windows.as_array()) {
w.sats
.height
.compute_rolling_sum(max_from, *starts, sats_source, exit)?;
w.cents
.height
.compute_rolling_sum(max_from, *starts, cents_source, exit)?;
}
Ok(())
}
}
@@ -1,52 +0,0 @@
//! AmountPerBlockWindows - window-first ordering.
//!
//! Access pattern: `coinbase_sum._24h.sats.height`
//! Each window (24h, 7d, 30d, 1y) contains sats (stored) + btc (lazy) + usd (stored).
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{Height, Version};
use derive_more::{Deref, DerefMut};
use vecdb::{Database, Exit, ReadableVec, Rw, StorageMode};
use brk_types::{Cents, Sats};
use crate::{
indexes,
internal::{AmountPerBlock, WindowStarts, Windows},
};
/// Value rolling windows — window-first, currency-last.
///
/// Each window contains `AmountPerBlock` (sats + btc lazy + usd).
#[derive(Deref, DerefMut, Traversable)]
#[traversable(transparent)]
pub struct AmountPerBlockWindows<M: StorageMode = Rw>(pub Windows<AmountPerBlock<M>>);
impl AmountPerBlockWindows {
pub(crate) fn forced_import(
db: &Database,
name: &str,
version: Version,
indexes: &indexes::Vecs,
) -> Result<Self> {
Ok(Self(Windows::try_from_fn(|suffix| {
AmountPerBlock::forced_import(db, &format!("{name}_{suffix}"), version, indexes)
})?))
}
pub(crate) fn compute_rolling_sum(
&mut self,
max_from: Height,
windows: &WindowStarts<'_>,
sats_source: &impl ReadableVec<Height, Sats>,
cents_source: &impl ReadableVec<Height, Cents>,
exit: &Exit,
) -> Result<()> {
for (w, starts) in self.0.as_mut_array().into_iter().zip(windows.as_array()) {
w.compute_rolling_sum(max_from, *starts, sats_source, cents_source, exit)?;
}
Ok(())
}
}
@@ -1,13 +0,0 @@
//! AmountPerBlockWithSum24h - AmountPerBlock raw + RollingWindow24hAmountPerBlock sum.
use brk_traversable::Traversable;
use vecdb::{Rw, StorageMode};
use crate::internal::{AmountPerBlock, RollingWindow24hAmountPerBlock};
/// Amount per-block value (sats + cents) with 24h rolling sum (also amount).
#[derive(Traversable)]
pub struct AmountPerBlockWithSum24h<M: StorageMode = Rw> {
pub raw: AmountPerBlock<M>,
pub sum: RollingWindow24hAmountPerBlock<M>,
}
@@ -3,8 +3,6 @@
//! For metrics aggregated per-block from finer-grained sources (e.g., per-tx data),
//! where we want full per-block stats plus rolling window stats.
use std::ops::SubAssign;
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{Height, Version};
@@ -13,7 +11,7 @@ use vecdb::{Database, Exit, Rw, StorageMode};
use crate::{
indexes,
internal::{Full, NumericValue, RollingFull, WindowStarts},
internal::{CachedWindowStarts, Full, NumericValue, RollingFull, WindowStarts},
};
#[derive(Traversable)]
@@ -35,17 +33,22 @@ where
name: &str,
version: Version,
indexes: &indexes::Vecs,
cached_starts: &CachedWindowStarts,
) -> Result<Self> {
let height = Full::forced_import(db, name, version)?;
let rolling = RollingFull::forced_import(db, name, version, indexes)?;
let full = Full::forced_import(db, name, version)?;
let rolling = RollingFull::forced_import(
db,
name,
version,
indexes,
&full.cumulative,
cached_starts,
)?;
Ok(Self {
full: height,
rolling,
})
Ok(Self { full, rolling })
}
/// Compute Full stats via closure, then rolling windows from the per-block sum.
/// Compute Full stats via closure, then rolling distribution from the per-block sum.
pub(crate) fn compute(
&mut self,
max_from: Height,
@@ -54,7 +57,7 @@ where
compute_full: impl FnOnce(&mut Full<Height, T>) -> Result<()>,
) -> Result<()>
where
T: From<f64> + Default + SubAssign + Copy + Ord,
T: From<f64> + Default + Copy + Ord,
f64: From<T>,
{
compute_full(&mut self.full)?;
@@ -1,13 +1,13 @@
//! ComputedPerBlockCumulative - raw ComputedPerBlock + cumulative ComputedPerBlock.
//!
//! Like ComputedPerBlockCumulativeSum but without RollingWindows.
//! Like ComputedPerBlockCumulativeWithSums but without RollingWindows.
//! Used for distribution metrics where rolling is optional per cohort.
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{Height, Version};
use schemars::JsonSchema;
use vecdb::{Database, EagerVec, Exit, PcoVec, Rw, StorageMode};
use vecdb::{Database, Exit, Rw, StorageMode};
use crate::{
indexes,
@@ -40,20 +40,6 @@ where
Ok(Self { raw, cumulative })
}
/// Compute raw data via closure, then cumulative only (no rolling).
pub(crate) fn compute(
&mut self,
max_from: Height,
exit: &Exit,
compute_raw: impl FnOnce(&mut EagerVec<PcoVec<Height, T>>) -> Result<()>,
) -> Result<()>
where
T: Default,
{
compute_raw(&mut self.raw.height)?;
self.compute_rest(max_from, exit)
}
/// Compute cumulative from already-filled raw vec.
pub(crate) fn compute_rest(&mut self, max_from: Height, exit: &Exit) -> Result<()>
where
@@ -1,9 +1,13 @@
//! ComputedPerBlockCumulativeSum - raw ComputedPerBlock + cumulative ComputedPerBlock + RollingWindows (sum).
//! ComputedPerBlockCumulativeWithSums - raw ComputedPerBlock + cumulative ComputedPerBlock + lazy rolling sums.
//!
//! Like ComputedPerBlockFull but with rolling sum only (no distribution).
//! Used for count metrics where distribution stats aren't meaningful.
use std::ops::SubAssign;
//! Rolling sums are derived lazily from the cumulative vec via LazyDeltaVec.
//! No rolling sum vecs are stored on disk.
//!
//! Type parameters:
//! - `T`: per-block value type (e.g., `StoredU32` for tx counts)
//! - `M`: storage mode (`Rw` or `Ro`)
//! - `C`: cumulative type, defaults to `T`. Use a wider type (e.g., `StoredU64`)
//! when the prefix sum of `T` values could overflow `T`.
use brk_error::Result;
use brk_traversable::Traversable;
@@ -13,33 +17,42 @@ use vecdb::{Database, EagerVec, Exit, PcoVec, Rw, StorageMode};
use crate::{
indexes,
internal::{ComputedPerBlock, NumericValue, RollingWindows, WindowStarts},
internal::{CachedWindowStarts, ComputedPerBlock, LazyRollingSumsFromHeight, NumericValue},
};
#[derive(Traversable)]
pub struct ComputedPerBlockCumulativeSum<T, M: StorageMode = Rw>
pub struct ComputedPerBlockCumulativeWithSums<T, C, M: StorageMode = Rw>
where
T: NumericValue + JsonSchema,
C: NumericValue + JsonSchema,
{
pub raw: ComputedPerBlock<T, M>,
pub cumulative: ComputedPerBlock<T, M>,
pub sum: RollingWindows<T, M>,
pub cumulative: ComputedPerBlock<C, M>,
pub sum: LazyRollingSumsFromHeight<C>,
}
impl<T> ComputedPerBlockCumulativeSum<T>
impl<T, C> ComputedPerBlockCumulativeWithSums<T, C>
where
T: NumericValue + JsonSchema,
T: NumericValue + JsonSchema + Into<C>,
C: NumericValue + JsonSchema,
{
pub(crate) fn forced_import(
db: &Database,
name: &str,
version: Version,
indexes: &indexes::Vecs,
cached_starts: &CachedWindowStarts,
) -> Result<Self> {
let raw = ComputedPerBlock::forced_import(db, name, version, indexes)?;
let cumulative =
ComputedPerBlock::forced_import(db, &format!("{name}_cumulative"), version, indexes)?;
let sum = RollingWindows::forced_import(db, name, version, indexes)?;
let sum = LazyRollingSumsFromHeight::new(
&format!("{name}_sum"),
version,
&cumulative.height,
cached_starts,
indexes,
);
Ok(Self {
raw,
@@ -48,36 +61,28 @@ where
})
}
/// Compute raw data via closure, then cumulative + rolling sum.
/// Compute raw data via closure, then cumulative. Rolling sums are lazy.
pub(crate) fn compute(
&mut self,
max_from: Height,
windows: &WindowStarts<'_>,
exit: &Exit,
compute_raw: impl FnOnce(&mut EagerVec<PcoVec<Height, T>>) -> Result<()>,
) -> Result<()>
where
T: Default + SubAssign,
C: Default,
{
compute_raw(&mut self.raw.height)?;
self.compute_rest(max_from, windows, exit)
self.compute_rest(max_from, exit)
}
/// Compute cumulative + rolling sum from already-populated raw data.
pub(crate) fn compute_rest(
&mut self,
max_from: Height,
windows: &WindowStarts<'_>,
exit: &Exit,
) -> Result<()>
/// Compute cumulative from already-populated raw data. Rolling sums are lazy.
pub(crate) fn compute_rest(&mut self, max_from: Height, exit: &Exit) -> Result<()>
where
T: Default + SubAssign,
C: Default,
{
self.cumulative
.height
.compute_cumulative(max_from, &self.raw.height, exit)?;
self.sum
.compute_rolling_sum(max_from, windows, &self.raw.height, exit)?;
Ok(())
}
}
@@ -1,292 +0,0 @@
//! RollingDelta - raw change + growth rate (%) across time windows.
//!
//! Three tiers:
//! - `RollingDelta1m` — 1m window only (2 stored vecs: change + rate). Default for all cohorts.
//! - `RollingDeltaExcept1m` — 24h + 1w + 1y windows (6 stored vecs). Extended tier only.
//! - `RollingDelta` — all 4 windows (8 stored vecs). Used for standalone global metrics.
//!
//! For a monotonic source (e.g., cumulative address count):
//! - `change._24h` = count_now - count_24h_ago
//! - `rate._24h` = (count_now - count_24h_ago) / count_24h_ago in BPS
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{BasisPoints32, BasisPointsSigned32, Height, Version};
use schemars::JsonSchema;
use vecdb::{AnyVec, Database, EagerVec, Exit, PcoVec, ReadableVec, Rw, StorageMode, VecIndex};
use crate::{
indexes,
internal::{
ComputedPerBlock, NumericValue, PercentPerBlock, PercentRollingWindows,
RollingWindows, WindowStarts, WindowsExcept1m,
},
};
/// Pre-collect source data from the earliest needed offset.
/// Returns (source_data, offset) for use in compute_delta_window.
fn collect_source<S: NumericValue>(
source: &impl ReadableVec<Height, S>,
skip: usize,
earliest_starts: &impl ReadableVec<Height, Height>,
) -> (Vec<S>, usize) {
let source_len = source.len();
let offset = if skip > 0 && skip < earliest_starts.len() {
earliest_starts.collect_one_at(skip).unwrap().to_usize()
} else {
0
};
(source.collect_range_at(offset, source_len), offset)
}
/// Shared computation: change = current - ago, rate = change / ago.
pub(super) fn compute_delta_window<S, C, B>(
change_h: &mut EagerVec<PcoVec<Height, C>>,
rate_bps_h: &mut EagerVec<PcoVec<Height, B>>,
max_from: Height,
starts: &impl ReadableVec<Height, Height>,
source: &impl ReadableVec<Height, S>,
exit: &Exit,
) -> Result<()>
where
S: NumericValue,
C: NumericValue,
B: NumericValue,
{
let skip = change_h.len();
let (mut source_data, mut offset) = collect_source(source, skip, starts);
change_h.compute_transform(
max_from,
starts,
|(h, ago_h, ..)| {
if h.to_usize() < offset || ago_h.to_usize() < offset {
// Version reset cleared the vec — re-collect from scratch
source_data = source.collect();
offset = 0;
}
let current: f64 = source_data[h.to_usize() - offset].into();
let ago: f64 = source_data[ago_h.to_usize() - offset].into();
(h, C::from(current - ago))
},
exit,
)?;
rate_bps_h.compute_transform(
max_from,
&*change_h,
|(h, change, ..)| {
if h.to_usize() < offset {
// Version reset cleared the vec — re-collect from scratch
source_data = source.collect();
offset = 0;
}
let current_f: f64 = source_data[h.to_usize() - offset].into();
let change_f: f64 = change.into();
let ago = current_f - change_f;
let rate = if ago == 0.0 { 0.0 } else { change_f / ago };
(h, B::from(rate))
},
exit,
)?;
Ok(())
}
#[derive(Traversable)]
pub struct RollingDelta<S, C = S, M: StorageMode = Rw>
where
S: NumericValue + JsonSchema,
C: NumericValue + JsonSchema,
{
pub change: RollingWindows<C, M>,
pub rate: PercentRollingWindows<BasisPoints32, M>,
_phantom: std::marker::PhantomData<S>,
}
impl<S, C> RollingDelta<S, C>
where
S: NumericValue + JsonSchema,
C: NumericValue + JsonSchema,
{
pub(crate) fn forced_import(
db: &Database,
name: &str,
version: Version,
indexes: &indexes::Vecs,
) -> Result<Self> {
Ok(Self {
change: RollingWindows::forced_import(
db,
&format!("{name}_change"),
version,
indexes,
)?,
rate: PercentRollingWindows::forced_import(
db,
&format!("{name}_rate"),
version,
indexes,
)?,
_phantom: std::marker::PhantomData,
})
}
pub(crate) fn compute(
&mut self,
max_from: Height,
windows: &WindowStarts<'_>,
source: &impl ReadableVec<Height, S>,
exit: &Exit,
) -> Result<()> {
for ((change_w, rate_w), starts) in self
.change
.0
.as_mut_array()
.into_iter()
.zip(self.rate.0.as_mut_array())
.zip(windows.as_array())
{
compute_delta_window(
&mut change_w.height,
&mut rate_w.bps.height,
max_from,
*starts,
source,
exit,
)?;
}
Ok(())
}
}
/// 1m-only delta: change + growth rate for the 1-month window.
/// Default tier for all cohorts (2 stored vecs).
#[derive(Traversable)]
pub struct RollingDelta1m<S, C = S, M: StorageMode = Rw>
where
S: NumericValue + JsonSchema,
C: NumericValue + JsonSchema,
{
#[traversable(wrap = "change", rename = "1m")]
pub change_1m: ComputedPerBlock<C, M>,
#[traversable(wrap = "rate", rename = "1m")]
pub rate_1m: PercentPerBlock<BasisPointsSigned32, M>,
_phantom: std::marker::PhantomData<S>,
}
impl<S, C> RollingDelta1m<S, C>
where
S: NumericValue + JsonSchema,
C: NumericValue + JsonSchema,
{
pub(crate) fn forced_import(
db: &Database,
name: &str,
version: Version,
indexes: &indexes::Vecs,
) -> Result<Self> {
Ok(Self {
change_1m: ComputedPerBlock::forced_import(
db,
&format!("{name}_change_1m"),
version,
indexes,
)?,
rate_1m: PercentPerBlock::forced_import(
db,
&format!("{name}_rate_1m"),
version,
indexes,
)?,
_phantom: std::marker::PhantomData,
})
}
pub(crate) fn compute(
&mut self,
max_from: Height,
height_1m_ago: &impl ReadableVec<Height, Height>,
source: &impl ReadableVec<Height, S>,
exit: &Exit,
) -> Result<()> {
compute_delta_window(
&mut self.change_1m.height,
&mut self.rate_1m.bps.height,
max_from,
height_1m_ago,
source,
exit,
)
}
}
/// Extended delta: 24h + 1w + 1y windows (6 stored vecs).
/// Only for All/LTH/STH cohorts (Extended tier).
#[derive(Traversable)]
pub struct RollingDeltaExcept1m<S, C = S, M: StorageMode = Rw>
where
S: NumericValue + JsonSchema,
C: NumericValue + JsonSchema,
{
pub change: WindowsExcept1m<ComputedPerBlock<C, M>>,
pub rate: WindowsExcept1m<PercentPerBlock<BasisPointsSigned32, M>>,
_phantom: std::marker::PhantomData<S>,
}
impl<S, C> RollingDeltaExcept1m<S, C>
where
S: NumericValue + JsonSchema,
C: NumericValue + JsonSchema,
{
pub(crate) fn forced_import(
db: &Database,
name: &str,
version: Version,
indexes: &indexes::Vecs,
) -> Result<Self> {
Ok(Self {
change: WindowsExcept1m::try_from_fn(|suffix| {
ComputedPerBlock::forced_import(
db,
&format!("{name}_change_{suffix}"),
version,
indexes,
)
})?,
rate: WindowsExcept1m::try_from_fn(|suffix| {
PercentPerBlock::forced_import(
db,
&format!("{name}_rate_{suffix}"),
version,
indexes,
)
})?,
_phantom: std::marker::PhantomData,
})
}
pub(crate) fn compute(
&mut self,
max_from: Height,
windows: &WindowStarts<'_>,
source: &impl ReadableVec<Height, S>,
exit: &Exit,
) -> Result<()> {
let changes = self.change.as_mut_array();
let rates = self.rate.as_mut_array();
let starts = [windows._24h, windows._1w, windows._1y];
for ((change_w, rate_w), starts) in changes.into_iter().zip(rates).zip(starts) {
compute_delta_window(
&mut change_w.height,
&mut rate_w.bps.height,
max_from,
starts,
source,
exit,
)?;
}
Ok(())
}
}
@@ -1,214 +0,0 @@
//! Fiat delta variants — same as RollingDelta* but change is FiatPerBlock<C>
//! (stored cents + lazy USD) instead of ComputedPerBlock<C> (stored cents only).
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{BasisPointsSigned32, Height, Version};
use schemars::JsonSchema;
use vecdb::{Database, Exit, ReadableVec, Rw, StorageMode};
use crate::{
indexes,
internal::{
CentsType, FiatPerBlock, NumericValue, PercentPerBlock, PercentRollingWindows,
Windows, WindowStarts, WindowsExcept1m,
},
};
use super::delta::compute_delta_window;
/// Fiat 1m-only delta: fiat change (cents + usd) + rate for the 1-month window.
#[derive(Traversable)]
pub struct FiatRollingDelta1m<S, C, M: StorageMode = Rw>
where
S: NumericValue + JsonSchema,
C: CentsType,
{
#[traversable(wrap = "change", rename = "1m")]
pub change_1m: FiatPerBlock<C, M>,
#[traversable(wrap = "rate", rename = "1m")]
pub rate_1m: PercentPerBlock<BasisPointsSigned32, M>,
_phantom: std::marker::PhantomData<S>,
}
impl<S, C> FiatRollingDelta1m<S, C>
where
S: NumericValue + JsonSchema,
C: CentsType,
{
pub(crate) fn forced_import(
db: &Database,
name: &str,
version: Version,
indexes: &indexes::Vecs,
) -> Result<Self> {
Ok(Self {
change_1m: FiatPerBlock::forced_import(
db,
&format!("{name}_change_1m"),
version,
indexes,
)?,
rate_1m: PercentPerBlock::forced_import(
db,
&format!("{name}_rate_1m"),
version,
indexes,
)?,
_phantom: std::marker::PhantomData,
})
}
pub(crate) fn compute(
&mut self,
max_from: Height,
height_1m_ago: &impl ReadableVec<Height, Height>,
source: &impl ReadableVec<Height, S>,
exit: &Exit,
) -> Result<()> {
compute_delta_window(
&mut self.change_1m.cents.height,
&mut self.rate_1m.bps.height,
max_from,
height_1m_ago,
source,
exit,
)
}
}
/// Fiat extended delta: 24h + 1w + 1y windows, fiat change (cents + usd) + rate.
#[derive(Traversable)]
pub struct FiatRollingDeltaExcept1m<S, C, M: StorageMode = Rw>
where
S: NumericValue + JsonSchema,
C: CentsType,
{
pub change: WindowsExcept1m<FiatPerBlock<C, M>>,
pub rate: WindowsExcept1m<PercentPerBlock<BasisPointsSigned32, M>>,
_phantom: std::marker::PhantomData<S>,
}
impl<S, C> FiatRollingDeltaExcept1m<S, C>
where
S: NumericValue + JsonSchema,
C: CentsType,
{
pub(crate) fn forced_import(
db: &Database,
name: &str,
version: Version,
indexes: &indexes::Vecs,
) -> Result<Self> {
Ok(Self {
change: WindowsExcept1m::try_from_fn(|suffix| {
FiatPerBlock::forced_import(
db,
&format!("{name}_change_{suffix}"),
version,
indexes,
)
})?,
rate: WindowsExcept1m::try_from_fn(|suffix| {
PercentPerBlock::forced_import(
db,
&format!("{name}_rate_{suffix}"),
version,
indexes,
)
})?,
_phantom: std::marker::PhantomData,
})
}
pub(crate) fn compute(
&mut self,
max_from: Height,
windows: &WindowStarts<'_>,
source: &impl ReadableVec<Height, S>,
exit: &Exit,
) -> Result<()> {
let changes = self.change.as_mut_array();
let rates = self.rate.as_mut_array();
let starts = [windows._24h, windows._1w, windows._1y];
for ((change_w, rate_w), starts) in changes.into_iter().zip(rates).zip(starts) {
compute_delta_window(
&mut change_w.cents.height,
&mut rate_w.bps.height,
max_from,
starts,
source,
exit,
)?;
}
Ok(())
}
}
/// Fiat rolling delta: all 4 windows, fiat change (cents + usd) + rate.
#[derive(Traversable)]
pub struct FiatRollingDelta<S, C, M: StorageMode = Rw>
where
S: NumericValue + JsonSchema,
C: CentsType,
{
pub change: Windows<FiatPerBlock<C, M>>,
pub rate: PercentRollingWindows<BasisPointsSigned32, M>,
_phantom: std::marker::PhantomData<S>,
}
impl<S, C> FiatRollingDelta<S, C>
where
S: NumericValue + JsonSchema,
C: CentsType,
{
pub(crate) fn forced_import(
db: &Database,
name: &str,
version: Version,
indexes: &indexes::Vecs,
) -> Result<Self> {
Ok(Self {
change: Windows::try_from_fn(|suffix| {
FiatPerBlock::forced_import(
db,
&format!("{name}_change_{suffix}"),
version,
indexes,
)
})?,
rate: PercentRollingWindows::forced_import(
db,
&format!("{name}_rate"),
version,
indexes,
)?,
_phantom: std::marker::PhantomData,
})
}
pub(crate) fn compute(
&mut self,
max_from: Height,
windows: &WindowStarts<'_>,
source: &impl ReadableVec<Height, S>,
exit: &Exit,
) -> Result<()> {
let changes = self.change.as_mut_array();
let rates = self.rate.0.as_mut_array();
let starts = windows.as_array();
for ((change_w, rate_w), starts) in changes.into_iter().zip(rates).zip(starts) {
compute_delta_window(
&mut change_w.cents.height,
&mut rate_w.bps.height,
max_from,
*starts,
source,
exit,
)?;
}
Ok(())
}
}
@@ -2,8 +2,6 @@
//!
//! For metrics with stored per-block data, cumulative sums, and rolling windows.
use std::ops::SubAssign;
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{Height, Version};
@@ -12,7 +10,7 @@ use vecdb::{Database, EagerVec, Exit, PcoVec, Rw, StorageMode};
use crate::{
indexes,
internal::{ComputedPerBlock, NumericValue, RollingFull, WindowStarts},
internal::{CachedWindowStarts, ComputedPerBlock, NumericValue, RollingFull, WindowStarts},
};
#[derive(Traversable)]
@@ -35,11 +33,19 @@ where
name: &str,
version: Version,
indexes: &indexes::Vecs,
cached_starts: &CachedWindowStarts,
) -> Result<Self> {
let raw = ComputedPerBlock::forced_import(db, name, version, indexes)?;
let cumulative =
ComputedPerBlock::forced_import(db, &format!("{name}_cumulative"), version, indexes)?;
let rolling = RollingFull::forced_import(db, name, version, indexes)?;
let rolling = RollingFull::forced_import(
db,
name,
version,
indexes,
&cumulative.height,
cached_starts,
)?;
Ok(Self {
raw,
@@ -48,7 +54,7 @@ where
})
}
/// Compute raw data via closure, then cumulative + rolling.
/// Compute raw data via closure, then cumulative + rolling distribution.
pub(crate) fn compute(
&mut self,
max_from: Height,
@@ -57,7 +63,7 @@ where
compute_raw: impl FnOnce(&mut EagerVec<PcoVec<Height, T>>) -> Result<()>,
) -> Result<()>
where
T: From<f64> + Default + SubAssign + Copy + Ord,
T: From<f64> + Default + Copy + Ord,
f64: From<T>,
{
compute_raw(&mut self.raw.height)?;
@@ -3,25 +3,19 @@ mod base;
mod constant;
mod cumulative;
mod cumulative_sum;
mod delta;
mod resolutions;
mod resolutions_full;
mod fiat_delta;
mod full;
mod rolling_average;
mod sum;
mod with_sum_24h;
mod with_deltas;
pub use aggregated::*;
pub use base::*;
pub use constant::*;
pub use cumulative::*;
pub use cumulative_sum::*;
pub use delta::*;
pub use resolutions::*;
pub use resolutions_full::*;
pub use fiat_delta::*;
pub use full::*;
pub use rolling_average::*;
pub use sum::*;
pub use with_sum_24h::*;
pub use with_deltas::*;
@@ -3,8 +3,6 @@
//! For metrics derived from indexer sources (no stored height vec).
//! Cumulative gets its own ComputedPerBlock so it has LazyAggVec index views too.
use std::ops::SubAssign;
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{Height, Version};
@@ -13,7 +11,7 @@ use vecdb::{Database, Exit, ReadableVec, Rw, StorageMode};
use crate::{
indexes,
internal::{ComputedPerBlock, NumericValue, RollingFull, WindowStarts},
internal::{CachedWindowStarts, ComputedPerBlock, NumericValue, RollingFull, WindowStarts},
};
#[derive(Traversable)]
@@ -35,10 +33,18 @@ where
name: &str,
version: Version,
indexes: &indexes::Vecs,
cached_starts: &CachedWindowStarts,
) -> Result<Self> {
let cumulative =
ComputedPerBlock::forced_import(db, &format!("{name}_cumulative"), version, indexes)?;
let rolling = RollingFull::forced_import(db, name, version, indexes)?;
let rolling = RollingFull::forced_import(
db,
name,
version,
indexes,
&cumulative.height,
cached_starts,
)?;
Ok(Self {
cumulative,
@@ -54,7 +60,7 @@ where
exit: &Exit,
) -> Result<()>
where
T: From<f64> + Default + SubAssign + Copy + Ord,
T: From<f64> + Default + Copy + Ord,
f64: From<T>,
{
self.cumulative
@@ -1,8 +1,7 @@
//! ComputedPerBlock with rolling average (no distribution stats).
//!
//! Stored height data + 4-window rolling averages (24h, 1w, 1m, 1y).
//! Use instead of ComputedPerBlockDistribution when only the average
//! is analytically useful (e.g., block interval, activity counts).
//! Stored height data + f64 cumulative + lazy 4-window rolling averages.
//! Rolling averages are computed on-the-fly from the cumulative via DeltaAvg.
use brk_error::Result;
@@ -13,7 +12,7 @@ use vecdb::{Database, EagerVec, Exit, ImportableVec, PcoVec, Rw, StorageMode};
use crate::indexes;
use crate::internal::{NumericValue, RollingWindows, WindowStarts};
use crate::internal::{CachedWindowStarts, LazyRollingAvgsFromHeight, NumericValue};
#[derive(Traversable)]
pub struct ComputedPerBlockRollingAverage<T, M: StorageMode = Rw>
@@ -21,8 +20,10 @@ where
T: NumericValue + JsonSchema,
{
pub height: M::Stored<EagerVec<PcoVec<Height, T>>>,
#[traversable(hidden)]
pub cumulative: M::Stored<EagerVec<PcoVec<Height, f64>>>,
#[traversable(flatten)]
pub average: RollingWindows<T, M>,
pub average: LazyRollingAvgsFromHeight<T>,
}
impl<T> ComputedPerBlockRollingAverage<T>
@@ -34,45 +35,41 @@ where
name: &str,
version: Version,
indexes: &indexes::Vecs,
cached_starts: &CachedWindowStarts,
) -> Result<Self> {
let height: EagerVec<PcoVec<Height, T>> = EagerVec::forced_import(db, name, version)?;
let average =
RollingWindows::forced_import(db, &format!("{name}_average"), version + Version::ONE, indexes)?;
let cumulative: EagerVec<PcoVec<Height, f64>> =
EagerVec::forced_import(db, &format!("{name}_cumulative"), version)?;
let average = LazyRollingAvgsFromHeight::new(
&format!("{name}_average"),
version + Version::ONE,
&cumulative,
cached_starts,
indexes,
);
Ok(Self { height, average })
Ok(Self {
height,
cumulative,
average,
})
}
/// Compute height data via closure, then rolling averages.
/// Compute height data via closure, then cumulative. Rolling averages are lazy.
pub(crate) fn compute(
&mut self,
max_from: Height,
windows: &WindowStarts<'_>,
exit: &Exit,
compute_height: impl FnOnce(&mut EagerVec<PcoVec<Height, T>>) -> Result<()>,
) -> Result<()>
where
T: Default,
f64: From<T>,
{
) -> Result<()> {
compute_height(&mut self.height)?;
self.compute_rest(max_from, windows, exit)
self.compute_rest(max_from, exit)
}
/// Compute rolling averages from already-populated height data.
pub(crate) fn compute_rest(
&mut self,
max_from: Height,
windows: &WindowStarts<'_>,
exit: &Exit,
) -> Result<()>
where
T: Default,
f64: From<T>,
{
for (w, starts) in self.average.0.as_mut_array().into_iter().zip(windows.as_array()) {
w.height
.compute_rolling_average(max_from, *starts, &self.height, exit)?;
}
/// Compute cumulative from already-populated height data. Rolling averages are lazy.
pub(crate) fn compute_rest(&mut self, max_from: Height, exit: &Exit) -> Result<()> {
self.cumulative
.compute_cumulative(max_from, &self.height, exit)?;
Ok(())
}
}
@@ -1,59 +0,0 @@
//! ComputedPerBlockSum - raw ComputedPerBlock + RollingWindows (sum only).
//!
//! Like ComputedPerBlockCumulativeSum but without the cumulative vec.
use std::ops::SubAssign;
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{Height, Version};
use schemars::JsonSchema;
use vecdb::{Database, EagerVec, Exit, PcoVec, Rw, StorageMode};
use crate::{
indexes,
internal::{ComputedPerBlock, NumericValue, RollingWindows, WindowStarts},
};
#[derive(Traversable)]
pub struct ComputedPerBlockSum<T, M: StorageMode = Rw>
where
T: NumericValue + JsonSchema,
{
pub raw: ComputedPerBlock<T, M>,
pub sum: RollingWindows<T, M>,
}
impl<T> ComputedPerBlockSum<T>
where
T: NumericValue + JsonSchema,
{
pub(crate) fn forced_import(
db: &Database,
name: &str,
version: Version,
indexes: &indexes::Vecs,
) -> Result<Self> {
let raw = ComputedPerBlock::forced_import(db, name, version, indexes)?;
let sum = RollingWindows::forced_import(db, &format!("{name}_sum"), version, indexes)?;
Ok(Self { raw, sum })
}
/// Compute raw data via closure, then rolling sum.
pub(crate) fn compute(
&mut self,
max_from: Height,
windows: &WindowStarts<'_>,
exit: &Exit,
compute_raw: impl FnOnce(&mut EagerVec<PcoVec<Height, T>>) -> Result<()>,
) -> Result<()>
where
T: Default + SubAssign,
{
compute_raw(&mut self.raw.height)?;
self.sum
.compute_rolling_sum(max_from, windows, &self.raw.height, exit)?;
Ok(())
}
}
@@ -0,0 +1,55 @@
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::Version;
use derive_more::{Deref, DerefMut};
use schemars::JsonSchema;
use vecdb::{Rw, StorageMode};
use crate::{
indexes,
internal::{
BpsType, CachedWindowStarts, ComputedPerBlock, LazyRollingDeltasFromHeight, NumericValue,
},
};
#[derive(Deref, DerefMut, Traversable)]
pub struct ComputedPerBlockWithDeltas<S, C, B, M: StorageMode = Rw>
where
S: NumericValue + JsonSchema + Into<f64>,
C: NumericValue + JsonSchema + From<f64>,
B: BpsType + From<f64>,
{
#[deref]
#[deref_mut]
#[traversable(flatten)]
pub inner: ComputedPerBlock<S, M>,
pub delta: LazyRollingDeltasFromHeight<S, C, B>,
}
impl<S, C, B> ComputedPerBlockWithDeltas<S, C, B>
where
S: NumericValue + JsonSchema + Into<f64>,
C: NumericValue + JsonSchema + From<f64>,
B: BpsType + From<f64>,
{
pub(crate) fn forced_import(
db: &vecdb::Database,
name: &str,
version: Version,
delta_version_offset: Version,
indexes: &indexes::Vecs,
cached_starts: &CachedWindowStarts,
) -> Result<Self> {
let inner = ComputedPerBlock::forced_import(db, name, version, indexes)?;
let delta = LazyRollingDeltasFromHeight::new(
&format!("{name}_delta"),
version + delta_version_offset,
&inner.height,
cached_starts,
indexes,
);
Ok(Self { inner, delta })
}
}
@@ -1,19 +0,0 @@
//! PerBlockWithSum24h - ComputedPerBlock + RollingWindow24hPerBlock rolling sum.
//!
//! Generic building block for metrics that store a per-block value
//! plus its 24h rolling sum. Used across activity and realized metrics.
use brk_traversable::Traversable;
use schemars::JsonSchema;
use vecdb::{Rw, StorageMode};
use crate::internal::{ComputedPerBlock, ComputedVecValue, RollingWindow24hPerBlock};
#[derive(Traversable)]
pub struct PerBlockWithSum24h<T, M: StorageMode = Rw>
where
T: ComputedVecValue + PartialOrd + JsonSchema,
{
pub raw: ComputedPerBlock<T, M>,
pub sum: RollingWindow24hPerBlock<T, M>,
}
@@ -0,0 +1,53 @@
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{Height, Version};
use vecdb::{Database, Exit, Rw, StorageMode};
use crate::{
indexes,
internal::{CachedWindowStarts, CentsType, FiatPerBlock, LazyRollingSumsFiatFromHeight},
};
#[derive(Traversable)]
pub struct FiatPerBlockCumulativeWithSums<C: CentsType, M: StorageMode = Rw> {
pub raw: FiatPerBlock<C, M>,
pub cumulative: FiatPerBlock<C, M>,
pub sum: LazyRollingSumsFiatFromHeight<C>,
}
impl<C: CentsType> FiatPerBlockCumulativeWithSums<C> {
pub(crate) fn forced_import(
db: &Database,
name: &str,
version: Version,
indexes: &indexes::Vecs,
cached_starts: &CachedWindowStarts,
) -> Result<Self> {
let raw = FiatPerBlock::forced_import(db, name, version, indexes)?;
let cumulative = FiatPerBlock::forced_import(
db,
&format!("{name}_cumulative"),
version,
indexes,
)?;
let sum = LazyRollingSumsFiatFromHeight::new(
&format!("{name}_sum"),
version,
&cumulative.cents.height,
cached_starts,
indexes,
);
Ok(Self { raw, cumulative, sum })
}
pub(crate) fn compute_rest(&mut self, max_from: Height, exit: &Exit) -> Result<()>
where
C: Default,
{
self.cumulative
.cents
.height
.compute_cumulative(max_from, &self.raw.cents.height, exit)?;
Ok(())
}
}
@@ -0,0 +1,55 @@
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::Version;
use derive_more::{Deref, DerefMut};
use vecdb::{Database, Rw, StorageMode};
use crate::{
indexes,
internal::{BpsType, CachedWindowStarts, LazyRollingDeltasFiatFromHeight},
};
use super::{CentsType, FiatPerBlockCumulativeWithSums};
#[derive(Deref, DerefMut, Traversable)]
pub struct FiatPerBlockCumulativeWithSumsAndDeltas<C, CS, B, M: StorageMode = Rw>
where
C: CentsType + Into<f64>,
CS: CentsType + From<f64>,
B: BpsType + From<f64>,
{
#[deref]
#[deref_mut]
#[traversable(flatten)]
pub inner: FiatPerBlockCumulativeWithSums<C, M>,
pub delta: LazyRollingDeltasFiatFromHeight<C, CS, B>,
}
impl<C, CS, B> FiatPerBlockCumulativeWithSumsAndDeltas<C, CS, B>
where
C: CentsType + Into<f64>,
CS: CentsType + From<f64>,
B: BpsType + From<f64>,
{
pub(crate) fn forced_import(
db: &Database,
name: &str,
version: Version,
delta_version_offset: Version,
indexes: &indexes::Vecs,
cached_starts: &CachedWindowStarts,
) -> Result<Self> {
let inner =
FiatPerBlockCumulativeWithSums::forced_import(db, name, version, indexes, cached_starts)?;
let delta = LazyRollingDeltasFiatFromHeight::new(
&format!("{name}_delta"),
version + delta_version_offset,
&inner.cumulative.cents.height,
cached_starts,
indexes,
);
Ok(Self { inner, delta })
}
}
@@ -0,0 +1,77 @@
use brk_traversable::Traversable;
use brk_types::{Dollars, Height, Version};
use derive_more::{Deref, DerefMut};
use vecdb::{DeltaSub, LazyDeltaVec, LazyVecFrom1, ReadableCloneableVec};
use crate::{
indexes,
internal::{
CachedWindowStarts, CentsType, DerivedResolutions, LazyPerBlock, LazyRollingSumFromHeight,
Resolutions, Windows,
},
};
#[derive(Clone, Traversable)]
pub struct LazyRollingSumFiatFromHeight<C: CentsType> {
pub cents: LazyRollingSumFromHeight<C>,
pub usd: LazyPerBlock<Dollars, C>,
}
#[derive(Clone, Deref, DerefMut, Traversable)]
#[traversable(transparent)]
pub struct LazyRollingSumsFiatFromHeight<C: CentsType>(
pub Windows<LazyRollingSumFiatFromHeight<C>>,
);
impl<C: CentsType> LazyRollingSumsFiatFromHeight<C> {
pub fn new(
name: &str,
version: Version,
cumulative_cents: &(impl ReadableCloneableVec<Height, C> + 'static),
cached_starts: &CachedWindowStarts,
indexes: &indexes::Vecs,
) -> Self {
let cum_cents = cumulative_cents.read_only_boxed_clone();
let make_slot = |suffix: &str, cached_start: &vecdb::CachedVec<Height, Height>| {
let full_name = format!("{name}_{suffix}");
let cached = cached_start.clone();
let starts_version = cached.version();
let cents_sum = LazyDeltaVec::<Height, C, C, DeltaSub>::new(
&format!("{full_name}_cents"),
version,
cum_cents.clone(),
starts_version,
move || cached.get(),
);
let cents_resolutions = Resolutions::forced_import(
&format!("{full_name}_cents"),
cents_sum.read_only_boxed_clone(),
version,
indexes,
);
let cents = LazyRollingSumFromHeight {
height: cents_sum,
resolutions: Box::new(cents_resolutions),
};
let usd = LazyPerBlock {
height: LazyVecFrom1::transformed::<C::ToDollars>(
&full_name,
version,
cents.height.read_only_boxed_clone(),
),
resolutions: Box::new(DerivedResolutions::from_derived_computed::<C::ToDollars>(
&full_name,
version,
&cents.resolutions,
)),
};
LazyRollingSumFiatFromHeight { cents, usd }
};
Self(cached_starts.0.map_with_suffix(make_slot))
}
}
@@ -1,7 +1,12 @@
mod base;
mod cumulative_sum;
mod cumulative_sum_with_deltas;
mod lazy;
mod with_sum_24h;
mod lazy_rolling_sum;
mod with_deltas;
pub use base::*;
pub use cumulative_sum::*;
pub use cumulative_sum_with_deltas::*;
pub use lazy::*;
pub use with_sum_24h::*;
pub use lazy_rolling_sum::*;
pub use with_deltas::*;
@@ -0,0 +1,55 @@
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::Version;
use derive_more::{Deref, DerefMut};
use schemars::JsonSchema;
use vecdb::{Database, Rw, StorageMode};
use crate::{
indexes,
internal::{BpsType, CachedWindowStarts, LazyRollingDeltasFiatFromHeight},
};
use super::{CentsType, FiatPerBlock};
#[derive(Deref, DerefMut, Traversable)]
pub struct FiatPerBlockWithDeltas<C, CS, B, M: StorageMode = Rw>
where
C: CentsType + Into<f64>,
CS: CentsType + From<f64>,
B: BpsType + From<f64>,
{
#[deref]
#[deref_mut]
#[traversable(flatten)]
pub inner: FiatPerBlock<C, M>,
pub delta: LazyRollingDeltasFiatFromHeight<C, CS, B>,
}
impl<C, CS, B> FiatPerBlockWithDeltas<C, CS, B>
where
C: CentsType + JsonSchema + Into<f64>,
CS: CentsType + From<f64>,
B: BpsType + From<f64>,
{
pub(crate) fn forced_import(
db: &Database,
name: &str,
version: Version,
delta_version_offset: Version,
indexes: &indexes::Vecs,
cached_starts: &CachedWindowStarts,
) -> Result<Self> {
let inner = FiatPerBlock::forced_import(db, name, version, indexes)?;
let delta = LazyRollingDeltasFiatFromHeight::new(
&format!("{name}_delta"),
version + delta_version_offset,
&inner.cents.height,
cached_starts,
indexes,
);
Ok(Self { inner, delta })
}
}
@@ -1,58 +0,0 @@
//! FiatPerBlockWithSum24h - FiatPerBlock raw + RollingWindow24hFiatPerBlock sum.
use std::ops::SubAssign;
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{Height, Version};
use derive_more::{Deref, DerefMut};
use vecdb::{Database, Exit, ReadableVec, Rw, StorageMode};
use crate::{
indexes,
internal::{CentsType, FiatPerBlock, RollingWindow24h},
};
/// Single 24h rolling window backed by FiatPerBlock (cents + lazy usd).
#[derive(Deref, DerefMut, Traversable)]
#[traversable(transparent)]
pub struct RollingWindow24hFiatPerBlock<C: CentsType, M: StorageMode = Rw>(
pub RollingWindow24h<FiatPerBlock<C, M>>,
);
impl<C: CentsType> RollingWindow24hFiatPerBlock<C> {
pub(crate) fn forced_import(
db: &Database,
name: &str,
version: Version,
indexes: &indexes::Vecs,
) -> Result<Self> {
Ok(Self(RollingWindow24h {
_24h: FiatPerBlock::forced_import(db, &format!("{name}_24h"), version, indexes)?,
}))
}
pub(crate) fn compute_rolling_sum(
&mut self,
max_from: Height,
height_24h_ago: &impl ReadableVec<Height, Height>,
source: &impl ReadableVec<Height, C>,
exit: &Exit,
) -> Result<()>
where
C: Default + SubAssign,
{
self._24h
.cents
.height
.compute_rolling_sum(max_from, height_24h_ago, source, exit)?;
Ok(())
}
}
/// Fiat per-block value (cents + usd) with 24h rolling sum (also fiat).
#[derive(Traversable)]
pub struct FiatPerBlockWithSum24h<C: CentsType, M: StorageMode = Rw> {
pub raw: FiatPerBlock<C, M>,
pub sum: RollingWindow24hFiatPerBlock<C, M>,
}
@@ -5,7 +5,7 @@ use vecdb::{Database, EagerVec, Exit, PcoVec, ReadableCloneableVec, Rw, StorageM
use crate::{
indexes,
internal::{BpsType, WindowStarts},
internal::{BpsType, CachedWindowStarts},
};
use crate::internal::{ComputedPerBlockRollingAverage, LazyPerBlock};
@@ -24,12 +24,14 @@ impl<B: BpsType> PercentPerBlockRollingAverage<B> {
name: &str,
version: Version,
indexes: &indexes::Vecs,
cached_starts: &CachedWindowStarts,
) -> Result<Self> {
let bps = ComputedPerBlockRollingAverage::forced_import(
db,
&format!("{name}_bps"),
version,
indexes,
cached_starts,
)?;
let ratio = LazyPerBlock::from_height_source::<B::ToRatio>(
@@ -56,14 +58,9 @@ impl<B: BpsType> PercentPerBlockRollingAverage<B> {
pub(crate) fn compute(
&mut self,
max_from: Height,
windows: &WindowStarts<'_>,
exit: &Exit,
compute_height: impl FnOnce(&mut EagerVec<PcoVec<Height, B>>) -> Result<()>,
) -> Result<()>
where
B: Default,
f64: From<B>,
{
self.bps.compute(max_from, windows, exit, compute_height)
) -> Result<()> {
self.bps.compute(max_from, exit, compute_height)
}
}
@@ -0,0 +1,18 @@
use brk_traversable::Traversable;
use brk_types::Height;
use schemars::JsonSchema;
use vecdb::{DeltaAvg, LazyDeltaVec};
use crate::internal::{NumericValue, Resolutions};
/// A single lazy rolling-average slot from height: the lazy delta vec + its resolution views.
#[derive(Clone, Traversable)]
#[traversable(merge)]
pub struct LazyRollingAvgFromHeight<T>
where
T: NumericValue + JsonSchema,
{
pub height: LazyDeltaVec<Height, f64, T, DeltaAvg>,
#[traversable(flatten)]
pub resolutions: Box<Resolutions<T>>,
}
@@ -0,0 +1,61 @@
use brk_traversable::Traversable;
use brk_types::{Height, Version};
use derive_more::{Deref, DerefMut};
use schemars::JsonSchema;
use vecdb::{DeltaAvg, LazyDeltaVec, ReadableCloneableVec};
use crate::{
indexes,
internal::{CachedWindowStarts, NumericValue, Resolutions, Windows},
};
use super::LazyRollingAvgFromHeight;
/// Lazy rolling averages for all 4 window durations (24h, 1w, 1m, 1y),
/// derived from an f64 cumulative vec + cached window starts.
///
/// Nothing is stored on disk — all values are computed on-the-fly via
/// `LazyDeltaVec<Height, f64, T, DeltaAvg>`: `(cum[h] - cum[start-1]) / (h - start + 1)`.
#[derive(Clone, Deref, DerefMut, Traversable)]
#[traversable(transparent)]
pub struct LazyRollingAvgsFromHeight<T>(pub Windows<LazyRollingAvgFromHeight<T>>)
where
T: NumericValue + JsonSchema;
impl<T> LazyRollingAvgsFromHeight<T>
where
T: NumericValue + JsonSchema,
{
pub fn new(
name: &str,
version: Version,
cumulative: &(impl ReadableCloneableVec<Height, f64> + 'static),
cached_starts: &CachedWindowStarts,
indexes: &indexes::Vecs,
) -> Self {
let cum_source = cumulative.read_only_boxed_clone();
Self(cached_starts.0.map_with_suffix(|suffix, cached_start| {
let full_name = format!("{name}_{suffix}");
let cached = cached_start.clone();
let starts_version = cached.version();
let avg = LazyDeltaVec::<Height, f64, T, DeltaAvg>::new(
&full_name,
version,
cum_source.clone(),
starts_version,
move || cached.get(),
);
let resolutions = Resolutions::forced_import(
&full_name,
avg.read_only_boxed_clone(),
version,
indexes,
);
LazyRollingAvgFromHeight {
height: avg,
resolutions: Box::new(resolutions),
}
}))
}
}
@@ -0,0 +1,313 @@
use brk_traversable::Traversable;
use brk_types::{Dollars, Height, StoredF32, Version};
use schemars::JsonSchema;
use vecdb::{DeltaChange, DeltaRate, LazyDeltaVec, LazyVecFrom1, ReadableCloneableVec, VecValue};
use crate::{
indexes,
internal::{
BpsType, CachedWindowStarts, CentsType, DerivedResolutions, LazyPerBlock, NumericValue,
Resolutions, Windows,
},
};
/// Generic single-slot lazy delta: a `LazyDeltaVec` + resolution views.
///
/// Used as building block for both change and rate deltas.
/// - Change: `LazyDeltaFromHeight<S, C, DeltaChange>`
/// - Rate BPS: `LazyDeltaFromHeight<S, B, DeltaRate>`
#[derive(Clone, Traversable)]
#[traversable(merge)]
pub struct LazyDeltaFromHeight<S, T, Op: 'static>
where
S: VecValue,
T: NumericValue + JsonSchema,
{
pub height: LazyDeltaVec<Height, S, T, Op>,
#[traversable(flatten)]
pub resolutions: Box<Resolutions<T>>,
}
/// Single-slot lazy delta percent: BPS delta + lazy ratio + lazy percent views.
///
/// Mirrors `PercentPerBlock<B>` but with lazy delta for the BPS source.
#[derive(Clone, Traversable)]
pub struct LazyDeltaPercentFromHeight<S, B>
where
S: VecValue,
B: BpsType,
{
pub bps: LazyDeltaFromHeight<S, B, DeltaRate>,
pub ratio: LazyPerBlock<StoredF32, B>,
pub percent: LazyPerBlock<StoredF32, B>,
}
/// Lazy rolling deltas for all 4 window durations (24h, 1w, 1m, 1y).
///
/// Tree shape: `change._24h/...`, `rate._24h/...` — matches old `RollingDelta`.
///
/// Replaces `RollingDelta`, `RollingDelta1m`, and `RollingDeltaExcept1m` — since
/// there is no storage cost, all 4 windows are always available.
#[derive(Clone, Traversable)]
pub struct LazyRollingDeltasFromHeight<S, C, B>
where
S: VecValue,
C: NumericValue + JsonSchema,
B: BpsType,
{
pub change: Windows<LazyDeltaFromHeight<S, C, DeltaChange>>,
pub rate: Windows<LazyDeltaPercentFromHeight<S, B>>,
}
impl<S, C, B> LazyRollingDeltasFromHeight<S, C, B>
where
S: VecValue + Into<f64>,
C: NumericValue + JsonSchema + From<f64>,
B: BpsType + From<f64>,
{
pub fn new(
name: &str,
version: Version,
source: &(impl ReadableCloneableVec<Height, S> + 'static),
cached_starts: &CachedWindowStarts,
indexes: &indexes::Vecs,
) -> Self {
let src = source.read_only_boxed_clone();
let make_slot = |suffix: &str, cached_start: &vecdb::CachedVec<Height, Height>| {
let full_name = format!("{name}_{suffix}");
let cached = cached_start.clone();
let starts_version = cached.version();
// Change: source[h] - source[ago] as C (via f64)
let change_vec = LazyDeltaVec::<Height, S, C, DeltaChange>::new(
&format!("{full_name}_change"),
version,
src.clone(),
starts_version,
{
let cached = cached.clone();
move || cached.get()
},
);
let change_resolutions = Resolutions::forced_import(
&format!("{full_name}_change"),
change_vec.read_only_boxed_clone(),
version,
indexes,
);
let change = LazyDeltaFromHeight {
height: change_vec,
resolutions: Box::new(change_resolutions),
};
// Rate BPS: (source[h] - source[ago]) / source[ago] as B (via f64)
let rate_vec = LazyDeltaVec::<Height, S, B, DeltaRate>::new(
&format!("{full_name}_rate_bps"),
version,
src.clone(),
starts_version,
move || cached.get(),
);
let rate_resolutions = Resolutions::forced_import(
&format!("{full_name}_rate_bps"),
rate_vec.read_only_boxed_clone(),
version,
indexes,
);
let bps = LazyDeltaFromHeight {
height: rate_vec,
resolutions: Box::new(rate_resolutions),
};
// Ratio: bps / 10000
let ratio = LazyPerBlock {
height: LazyVecFrom1::transformed::<B::ToRatio>(
&format!("{full_name}_rate_ratio"),
version,
bps.height.read_only_boxed_clone(),
),
resolutions: Box::new(DerivedResolutions::from_derived_computed::<B::ToRatio>(
&format!("{full_name}_rate_ratio"),
version,
&bps.resolutions,
)),
};
// Percent: bps / 100
let percent = LazyPerBlock {
height: LazyVecFrom1::transformed::<B::ToPercent>(
&format!("{full_name}_rate"),
version,
bps.height.read_only_boxed_clone(),
),
resolutions: Box::new(DerivedResolutions::from_derived_computed::<B::ToPercent>(
&format!("{full_name}_rate"),
version,
&bps.resolutions,
)),
};
let rate = LazyDeltaPercentFromHeight {
bps,
ratio,
percent,
};
(change, rate)
};
let (change, rate) = cached_starts.0.map_with_suffix(make_slot).unzip();
Self { change, rate }
}
}
// ---------------------------------------------------------------------------
// Fiat delta types (cents change + lazy USD + rate)
// ---------------------------------------------------------------------------
/// Single-slot fiat delta change: cents delta + lazy USD.
#[derive(Clone, Traversable)]
pub struct LazyDeltaFiatFromHeight<S, C>
where
S: VecValue,
C: CentsType,
{
pub cents: LazyDeltaFromHeight<S, C, DeltaChange>,
pub usd: LazyPerBlock<Dollars, C>,
}
/// Lazy fiat rolling deltas for all 4 windows.
///
/// Tree shape: `change._24h.{cents,usd}/...`, `rate._24h/...` — matches old `FiatRollingDelta`.
///
/// Replaces `FiatRollingDelta`, `FiatRollingDelta1m`, and `FiatRollingDeltaExcept1m`.
#[derive(Clone, Traversable)]
pub struct LazyRollingDeltasFiatFromHeight<S, C, B>
where
S: VecValue,
C: CentsType,
B: BpsType,
{
pub change: Windows<LazyDeltaFiatFromHeight<S, C>>,
pub rate: Windows<LazyDeltaPercentFromHeight<S, B>>,
}
impl<S, C, B> LazyRollingDeltasFiatFromHeight<S, C, B>
where
S: VecValue + Into<f64>,
C: CentsType + From<f64>,
B: BpsType + From<f64>,
{
pub fn new(
name: &str,
version: Version,
source: &(impl ReadableCloneableVec<Height, S> + 'static),
cached_starts: &CachedWindowStarts,
indexes: &indexes::Vecs,
) -> Self {
let src = source.read_only_boxed_clone();
let make_slot = |suffix: &str, cached_start: &vecdb::CachedVec<Height, Height>| {
let full_name = format!("{name}_{suffix}");
let cached = cached_start.clone();
let starts_version = cached.version();
// Change cents: source[h] - source[ago] as C (via f64)
let change_vec = LazyDeltaVec::<Height, S, C, DeltaChange>::new(
&format!("{full_name}_change"),
version,
src.clone(),
starts_version,
{
let cached = cached.clone();
move || cached.get()
},
);
let change_resolutions = Resolutions::forced_import(
&format!("{full_name}_change"),
change_vec.read_only_boxed_clone(),
version,
indexes,
);
let cents = LazyDeltaFromHeight {
height: change_vec,
resolutions: Box::new(change_resolutions),
};
// Change USD: lazy from cents delta
let usd = LazyPerBlock {
height: LazyVecFrom1::transformed::<C::ToDollars>(
&format!("{full_name}_change_usd"),
version,
cents.height.read_only_boxed_clone(),
),
resolutions: Box::new(DerivedResolutions::from_derived_computed::<C::ToDollars>(
&format!("{full_name}_change_usd"),
version,
&cents.resolutions,
)),
};
let change = LazyDeltaFiatFromHeight { cents, usd };
// Rate BPS: (source[h] - source[ago]) / source[ago] as B (via f64)
let rate_vec = LazyDeltaVec::<Height, S, B, DeltaRate>::new(
&format!("{full_name}_rate_bps"),
version,
src.clone(),
starts_version,
move || cached.get(),
);
let rate_resolutions = Resolutions::forced_import(
&format!("{full_name}_rate_bps"),
rate_vec.read_only_boxed_clone(),
version,
indexes,
);
let bps = LazyDeltaFromHeight {
height: rate_vec,
resolutions: Box::new(rate_resolutions),
};
let ratio = LazyPerBlock {
height: LazyVecFrom1::transformed::<B::ToRatio>(
&format!("{full_name}_rate_ratio"),
version,
bps.height.read_only_boxed_clone(),
),
resolutions: Box::new(DerivedResolutions::from_derived_computed::<B::ToRatio>(
&format!("{full_name}_rate_ratio"),
version,
&bps.resolutions,
)),
};
let percent = LazyPerBlock {
height: LazyVecFrom1::transformed::<B::ToPercent>(
&format!("{full_name}_rate"),
version,
bps.height.read_only_boxed_clone(),
),
resolutions: Box::new(DerivedResolutions::from_derived_computed::<B::ToPercent>(
&format!("{full_name}_rate"),
version,
&bps.resolutions,
)),
};
let rate = LazyDeltaPercentFromHeight {
bps,
ratio,
percent,
};
(change, rate)
};
let (change, rate) = cached_starts.0.map_with_suffix(make_slot).unzip();
Self { change, rate }
}
}
@@ -1,28 +1,27 @@
//! RollingFull - Sum + Distribution per rolling window.
//!
//! 36 stored height vecs per metric (4 sum + 32 distribution), each with 17 index views.
use std::ops::SubAssign;
//! RollingFull - Lazy rolling sums + stored rolling distribution per window.
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{Height, Version};
use schemars::JsonSchema;
use vecdb::{Database, Exit, ReadableVec, Rw, StorageMode};
use vecdb::{Database, Exit, ReadableCloneableVec, ReadableVec, Rw, StorageMode};
use crate::{
indexes,
internal::{ComputedVecValue, NumericValue, RollingDistribution, RollingWindows, WindowStarts},
internal::{
CachedWindowStarts, NumericValue, LazyRollingSumsFromHeight, RollingDistribution,
WindowStarts,
},
};
/// Sum (4 windows) + Distribution (8 stats × 4 windows) = 36 stored height vecs.
/// Lazy rolling sums + stored rolling distribution (8 stats × 4 windows).
#[derive(Traversable)]
pub struct RollingFull<T, M: StorageMode = Rw>
where
T: ComputedVecValue + PartialOrd + JsonSchema,
T: NumericValue + JsonSchema,
{
pub sum: RollingWindows<T, M>,
pub sum: LazyRollingSumsFromHeight<T>,
#[traversable(flatten)]
pub distribution: RollingDistribution<T, M>,
}
@@ -36,14 +35,22 @@ where
name: &str,
version: Version,
indexes: &indexes::Vecs,
cumulative: &(impl ReadableCloneableVec<Height, T> + 'static),
cached_starts: &CachedWindowStarts,
) -> Result<Self> {
Ok(Self {
sum: RollingWindows::forced_import(db, &format!("{name}_sum"), version, indexes)?,
distribution: RollingDistribution::forced_import(db, name, version, indexes)?,
})
let sum = LazyRollingSumsFromHeight::new(
&format!("{name}_sum"),
version,
cumulative,
cached_starts,
indexes,
);
let distribution = RollingDistribution::forced_import(db, name, version, indexes)?;
Ok(Self { sum, distribution })
}
/// Compute rolling sum + all 8 distribution stats across all 4 windows.
/// Compute rolling distribution stats across all 4 windows.
pub(crate) fn compute(
&mut self,
max_from: Height,
@@ -52,13 +59,10 @@ where
exit: &Exit,
) -> Result<()>
where
T: From<f64> + Default + SubAssign + Copy + Ord,
T: From<f64> + Default + Copy + Ord,
f64: From<T>,
{
self.sum
.compute_rolling_sum(max_from, windows, source, exit)?;
self.distribution
.compute_distribution(max_from, windows, source, exit)?;
Ok(())
.compute_distribution(max_from, windows, source, exit)
}
}
@@ -1,7 +1,17 @@
mod avg;
mod avgs;
mod delta;
mod distribution;
mod full;
mod sum;
mod sums;
mod windows;
pub use avg::*;
pub use avgs::*;
pub use delta::*;
pub use distribution::*;
pub use full::*;
pub use sum::*;
pub use sums::*;
pub use windows::*;
@@ -0,0 +1,18 @@
use brk_traversable::Traversable;
use brk_types::Height;
use schemars::JsonSchema;
use vecdb::{DeltaSub, LazyDeltaVec};
use crate::internal::{NumericValue, Resolutions};
/// A single lazy rolling-sum slot from height: the lazy delta vec + its resolution views.
#[derive(Clone, Traversable)]
#[traversable(merge)]
pub struct LazyRollingSumFromHeight<T>
where
T: NumericValue + JsonSchema,
{
pub height: LazyDeltaVec<Height, T, T, DeltaSub>,
#[traversable(flatten)]
pub resolutions: Box<Resolutions<T>>,
}
@@ -0,0 +1,64 @@
use brk_traversable::Traversable;
use brk_types::{Height, Version};
use derive_more::{Deref, DerefMut};
use schemars::JsonSchema;
use vecdb::{DeltaSub, LazyDeltaVec, ReadableCloneableVec};
use crate::{
indexes,
internal::{CachedWindowStarts, NumericValue, Resolutions, Windows},
};
use super::LazyRollingSumFromHeight;
/// Lazy rolling sums for all 4 window durations (24h, 1w, 1m, 1y),
/// derived from a cumulative vec + cached window starts.
///
/// Nothing is stored on disk — all values are computed on-the-fly via
/// `LazyDeltaVec<Height, T, T, DeltaSub>`: `cum[h] - cum[window_start[h]]`.
///
/// Implements `Traversable` to expose `_24h`, `_1w`, `_1m`, `_1y` with
/// the same tree structure as the old `RollingWindows<T>`.
#[derive(Clone, Deref, DerefMut, Traversable)]
#[traversable(transparent)]
pub struct LazyRollingSumsFromHeight<T>(pub Windows<LazyRollingSumFromHeight<T>>)
where
T: NumericValue + JsonSchema;
impl<T> LazyRollingSumsFromHeight<T>
where
T: NumericValue + JsonSchema,
{
pub fn new(
name: &str,
version: Version,
cumulative: &(impl ReadableCloneableVec<Height, T> + 'static),
cached_starts: &CachedWindowStarts,
indexes: &indexes::Vecs,
) -> Self {
let cum_source = cumulative.read_only_boxed_clone();
Self(cached_starts.0.map_with_suffix(|suffix, cached_start| {
let full_name = format!("{name}_{suffix}");
let cached = cached_start.clone();
let starts_version = cached.version();
let sum = LazyDeltaVec::<Height, T, T, DeltaSub>::new(
&full_name,
version,
cum_source.clone(),
starts_version,
move || cached.get(),
);
let resolutions = Resolutions::forced_import(
&full_name,
sum.read_only_boxed_clone(),
version,
indexes,
);
LazyRollingSumFromHeight {
height: sum,
resolutions: Box::new(resolutions),
}
}))
}
}
@@ -3,15 +3,13 @@
//! Each of the 4 windows (24h, 1w, 1m, 1y) contains a height-level stored vec
//! plus all 17 LazyAggVec index views.
use std::ops::SubAssign;
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{Height, Version};
use derive_more::{Deref, DerefMut};
use schemars::JsonSchema;
use vecdb::{Database, EagerVec, Exit, PcoVec, ReadableVec, Rw, StorageMode};
use vecdb::{Database, EagerVec, PcoVec, Rw, StorageMode};
use crate::{
indexes,
@@ -42,23 +40,6 @@ where
ComputedPerBlock::forced_import(db, &format!("{name}_{suffix}"), version, indexes)
})?))
}
pub(crate) fn compute_rolling_sum(
&mut self,
max_from: Height,
windows: &WindowStarts<'_>,
source: &impl ReadableVec<Height, T>,
exit: &Exit,
) -> Result<()>
where
T: Default + SubAssign,
{
for (w, starts) in self.0.as_mut_array().into_iter().zip(windows.as_array()) {
w.height
.compute_rolling_sum(max_from, *starts, source, exit)?;
}
Ok(())
}
}
/// Single 24h rolling window backed by ComputedPerBlock (1 stored vec).
@@ -89,22 +70,6 @@ where
)?,
}))
}
pub(crate) fn compute_rolling_sum(
&mut self,
max_from: Height,
height_24h_ago: &impl ReadableVec<Height, Height>,
source: &impl ReadableVec<Height, T>,
exit: &Exit,
) -> Result<()>
where
T: Default + SubAssign,
{
self._24h
.height
.compute_rolling_sum(max_from, height_24h_ago, source, exit)?;
Ok(())
}
}
/// Extended rolling windows: 1w + 1m + 1y (3 stored vecs).
@@ -128,22 +93,4 @@ where
ComputedPerBlock::forced_import(db, &format!("{name}_{suffix}"), version, indexes)
})?))
}
pub(crate) fn compute_rolling_sum(
&mut self,
max_from: Height,
windows: &WindowStarts<'_>,
source: &impl ReadableVec<Height, T>,
exit: &Exit,
) -> Result<()>
where
T: Default + SubAssign,
{
let starts = [windows._1w, windows._1m, windows._1y];
for (w, starts) in self.0.as_mut_array().into_iter().zip(starts) {
w.height
.compute_rolling_sum(max_from, starts, source, exit)?;
}
Ok(())
}
}
+3 -3
View File
@@ -3,7 +3,7 @@ use std::ops::{Add, AddAssign, Div};
use brk_types::{BasisPoints16, BasisPoints32, BasisPointsSigned16, BasisPointsSigned32, StoredF32};
use schemars::JsonSchema;
use serde::Serialize;
use vecdb::{Formattable, PcoVecValue, UnaryTransform};
use vecdb::{CheckedSub, Formattable, PcoVecValue, UnaryTransform};
use crate::internal::{
Bp16ToFloat, Bp16ToPercent, Bp32ToFloat, Bp32ToPercent, Bps16ToFloat, Bps16ToPercent,
@@ -34,9 +34,9 @@ impl<T> ComputedVecValue for T where
{
}
pub trait NumericValue: ComputedVecValue + From<f64> + Into<f64> {}
pub trait NumericValue: ComputedVecValue + CheckedSub + Default + From<f64> + Into<f64> {}
impl<T> NumericValue for T where T: ComputedVecValue + From<f64> + Into<f64> {}
impl<T> NumericValue for T where T: ComputedVecValue + CheckedSub + Default + From<f64> + Into<f64> {}
/// Trait that associates a basis-point type with its transforms to ratio and percent.
pub trait BpsType: NumericValue + JsonSchema {
@@ -25,7 +25,6 @@ pub use derived::{
pub use ratio::{
RatioCentsBp32, RatioCentsSignedCentsBps32,
RatioCentsSignedDollarsBps32, RatioDiffCentsBps32, RatioDiffDollarsBps32, RatioDiffF32Bps32,
RatioDollarsBp16, RatioDollarsBp32, RatioDollarsBps32, RatioSatsBp16, RatioU32Bp16,
RatioU64Bp16,
RatioDollarsBp16, RatioDollarsBp32, RatioDollarsBps32, RatioSatsBp16, RatioU64Bp16,
};
pub use specialized::{BlockCountTarget, OhlcCentsToDollars, OhlcCentsToSats};
@@ -1,6 +1,6 @@
use brk_types::{
BasisPoints16, BasisPoints32, BasisPointsSigned32, Cents, CentsSigned, Dollars, Sats, StoredF32,
StoredU32, StoredU64,
StoredU64,
};
use vecdb::BinaryTransform;
@@ -43,19 +43,6 @@ impl BinaryTransform<Cents, Cents, BasisPoints32> for RatioCentsBp32 {
}
}
pub struct RatioU32Bp16;
impl BinaryTransform<StoredU32, StoredU32, BasisPoints16> for RatioU32Bp16 {
#[inline(always)]
fn apply(numerator: StoredU32, denominator: StoredU32) -> BasisPoints16 {
if *denominator > 0 {
BasisPoints16::from(*numerator as f64 / *denominator as f64)
} else {
BasisPoints16::ZERO
}
}
}
pub struct RatioDollarsBp16;
impl BinaryTransform<Dollars, Dollars, BasisPoints16> for RatioDollarsBp16 {