global: snapshot

This commit is contained in:
nym21
2026-02-27 18:48:37 +01:00
parent 6845ad409b
commit d5ec291579
62 changed files with 1960 additions and 1449 deletions

View File

@@ -9,11 +9,11 @@ pub struct DistributionStats<A, B = A, C = A, D = A, E = A, F = A, G = A, H = A>
pub average: A,
pub min: B,
pub max: C,
pub p10: D,
pub p25: E,
pub pct10: D,
pub pct25: E,
pub median: F,
pub p75: G,
pub p90: H,
pub pct75: G,
pub pct90: H,
}
impl<A> DistributionStats<A> {
@@ -22,11 +22,11 @@ impl<A> DistributionStats<A> {
f(&mut self.average)?;
f(&mut self.min)?;
f(&mut self.max)?;
f(&mut self.p10)?;
f(&mut self.p25)?;
f(&mut self.pct10)?;
f(&mut self.pct25)?;
f(&mut self.median)?;
f(&mut self.p75)?;
f(&mut self.p90)?;
f(&mut self.pct75)?;
f(&mut self.pct90)?;
Ok(())
}
@@ -35,11 +35,11 @@ impl<A> DistributionStats<A> {
f(&self.average)
.min(f(&self.min))
.min(f(&self.max))
.min(f(&self.p10))
.min(f(&self.p25))
.min(f(&self.pct10))
.min(f(&self.pct25))
.min(f(&self.median))
.min(f(&self.p75))
.min(f(&self.p90))
.min(f(&self.pct75))
.min(f(&self.pct90))
}
}

View File

@@ -0,0 +1,46 @@
mod rolling_full;
mod rolling_sum;
mod windows;
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{Bitcoin, Dollars, Sats, Version};
use vecdb::{Database, ReadableCloneableVec, Rw, StorageMode};
use crate::{
indexes,
internal::{ComputedFromHeightLast, LazyFromHeightLast, SatsToBitcoin},
};
pub use rolling_full::*;
pub use rolling_sum::*;
#[derive(Traversable)]
pub struct ByUnit<M: StorageMode = Rw> {
pub sats: ComputedFromHeightLast<Sats, M>,
pub btc: LazyFromHeightLast<Bitcoin, Sats>,
pub usd: ComputedFromHeightLast<Dollars, M>,
}
impl ByUnit {
pub(crate) fn forced_import(
db: &Database,
name: &str,
version: Version,
indexes: &indexes::Vecs,
) -> Result<Self> {
let sats = ComputedFromHeightLast::forced_import(db, name, version, indexes)?;
let btc = LazyFromHeightLast::from_computed::<SatsToBitcoin>(
&format!("{name}_btc"),
version,
sats.height.read_only_boxed_clone(),
&sats,
);
let usd =
ComputedFromHeightLast::forced_import(db, &format!("{name}_usd"), version, indexes)?;
Ok(Self { sats, btc, usd })
}
}

View File

@@ -0,0 +1,116 @@
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{Dollars, Height, Sats, Version};
use derive_more::{Deref, DerefMut};
use vecdb::{Database, Exit, ReadableVec, Rw, StorageMode};
use crate::{
indexes,
internal::{ByUnit, DistributionStats, WindowStarts, Windows},
traits::compute_rolling_distribution_from_starts,
};
/// One window slot: sum + 8 distribution stats, each a ByUnit.
///
/// Tree: `sum.sats.height`, `average.sats.height`, etc.
#[derive(Traversable)]
pub struct RollingFullSlot<M: StorageMode = Rw> {
pub sum: ByUnit<M>,
#[traversable(flatten)]
pub distribution: DistributionStats<ByUnit<M>>,
}
impl RollingFullSlot {
pub(crate) fn forced_import(
db: &Database,
name: &str,
version: Version,
indexes: &indexes::Vecs,
) -> Result<Self> {
Ok(Self {
sum: ByUnit::forced_import(db, &format!("{name}_sum"), version, indexes)?,
distribution: DistributionStats {
average: ByUnit::forced_import(db, &format!("{name}_average"), version, indexes)?,
min: ByUnit::forced_import(db, &format!("{name}_min"), version, indexes)?,
max: ByUnit::forced_import(db, &format!("{name}_max"), version, indexes)?,
pct10: ByUnit::forced_import(db, &format!("{name}_p10"), version, indexes)?,
pct25: ByUnit::forced_import(db, &format!("{name}_p25"), version, indexes)?,
median: ByUnit::forced_import(db, &format!("{name}_median"), version, indexes)?,
pct75: ByUnit::forced_import(db, &format!("{name}_p75"), version, indexes)?,
pct90: ByUnit::forced_import(db, &format!("{name}_p90"), version, indexes)?,
},
})
}
pub(crate) fn compute(
&mut self,
max_from: Height,
starts: &impl ReadableVec<Height, Height>,
sats_source: &impl ReadableVec<Height, Sats>,
usd_source: &impl ReadableVec<Height, Dollars>,
exit: &Exit,
) -> Result<()> {
self.sum.sats.height.compute_rolling_sum(max_from, starts, sats_source, exit)?;
self.sum.usd.height.compute_rolling_sum(max_from, starts, usd_source, exit)?;
let d = &mut self.distribution;
compute_rolling_distribution_from_starts(
max_from, starts, sats_source,
&mut d.average.sats.height, &mut d.min.sats.height,
&mut d.max.sats.height, &mut d.pct10.sats.height,
&mut d.pct25.sats.height, &mut d.median.sats.height,
&mut d.pct75.sats.height, &mut d.pct90.sats.height, exit,
)?;
compute_rolling_distribution_from_starts(
max_from, starts, usd_source,
&mut d.average.usd.height, &mut d.min.usd.height,
&mut d.max.usd.height, &mut d.pct10.usd.height,
&mut d.pct25.usd.height, &mut d.median.usd.height,
&mut d.pct75.usd.height, &mut d.pct90.usd.height, exit,
)?;
Ok(())
}
}
/// Rolling sum + distribution across 4 windows, window-first.
///
/// Tree: `_24h.sum.sats.height`, `_24h.average.sats.height`, etc.
#[derive(Deref, DerefMut, Traversable)]
#[traversable(transparent)]
pub struct RollingFullByUnit<M: StorageMode = Rw>(pub Windows<RollingFullSlot<M>>);
const VERSION: Version = Version::ZERO;
impl RollingFullByUnit {
pub(crate) fn forced_import(
db: &Database,
name: &str,
version: Version,
indexes: &indexes::Vecs,
) -> Result<Self> {
let v = version + VERSION;
Ok(Self(Windows {
_24h: RollingFullSlot::forced_import(db, &format!("{name}_24h"), v, indexes)?,
_7d: RollingFullSlot::forced_import(db, &format!("{name}_7d"), v, indexes)?,
_30d: RollingFullSlot::forced_import(db, &format!("{name}_30d"), v, indexes)?,
_1y: RollingFullSlot::forced_import(db, &format!("{name}_1y"), v, indexes)?,
}))
}
pub(crate) fn compute(
&mut self,
max_from: Height,
windows: &WindowStarts<'_>,
sats_source: &impl ReadableVec<Height, Sats>,
usd_source: &impl ReadableVec<Height, Dollars>,
exit: &Exit,
) -> Result<()> {
for (slot, starts) in self.0.as_mut_array().into_iter().zip(windows.as_array()) {
slot.compute(max_from, starts, sats_source, usd_source, exit)?;
}
Ok(())
}
}

View File

@@ -0,0 +1,46 @@
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{Dollars, Height, Sats, Version};
use derive_more::{Deref, DerefMut};
use vecdb::{Database, Exit, ReadableVec, Rw, StorageMode};
use crate::{
indexes,
internal::{ByUnit, WindowStarts, Windows},
};
/// Rolling sum only, window-first then unit.
///
/// Tree: `_24h.sats.height`, `_24h.btc.height`, etc.
#[derive(Deref, DerefMut, Traversable)]
#[traversable(transparent)]
pub struct RollingSumByUnit<M: StorageMode = Rw>(pub Windows<ByUnit<M>>);
const VERSION: Version = Version::ZERO;
impl RollingSumByUnit {
pub(crate) fn forced_import(
db: &Database,
name: &str,
version: Version,
indexes: &indexes::Vecs,
) -> Result<Self> {
let v = version + VERSION;
Ok(Self(Windows::<ByUnit>::forced_import(db, &format!("{name}_sum"), v, indexes)?))
}
pub(crate) fn compute_rolling_sum(
&mut self,
max_from: Height,
windows: &WindowStarts<'_>,
sats_source: &impl ReadableVec<Height, Sats>,
usd_source: &impl ReadableVec<Height, Dollars>,
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.usd.height.compute_rolling_sum(max_from, starts, usd_source, exit)?;
}
Ok(())
}
}

View File

@@ -0,0 +1,24 @@
use brk_error::Result;
use brk_types::Version;
use vecdb::Database;
use crate::{
indexes,
internal::{ByUnit, Windows},
};
impl Windows<ByUnit> {
pub(crate) fn forced_import(
db: &Database,
name: &str,
version: Version,
indexes: &indexes::Vecs,
) -> Result<Self> {
Ok(Self {
_24h: ByUnit::forced_import(db, &format!("{name}_24h"), version, indexes)?,
_7d: ByUnit::forced_import(db, &format!("{name}_7d"), version, indexes)?,
_30d: ByUnit::forced_import(db, &format!("{name}_30d"), version, indexes)?,
_1y: ByUnit::forced_import(db, &format!("{name}_1y"), version, indexes)?,
})
}
}

View File

@@ -23,6 +23,7 @@ where
{
pub height: M::Stored<EagerVec<PcoVec<Height, T>>>,
pub cumulative: ComputedFromHeightLast<T, M>,
#[traversable(flatten)]
pub rolling: RollingFull<T, M>,
}

View File

@@ -1,3 +1,4 @@
mod by_unit;
mod constant;
mod cumulative;
mod cumulative_rolling_full;
@@ -21,6 +22,7 @@ mod value_lazy_computed_cumulative;
mod value_lazy_last;
mod value_sum_cumulative;
pub use by_unit::*;
pub use constant::*;
pub use cumulative::*;
pub use cumulative_rolling_full::*;

View File

@@ -1,26 +1,22 @@
//! Stored value type for Last pattern from Height.
//!
//! Both sats and USD are stored eagerly at the height level.
//! Used for rolling-window sums where USD = sum(usd_per_block),
//! NOT sats * current_price.
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{Bitcoin, Dollars, Height, Sats, Version};
use vecdb::{Database, Exit, ReadableCloneableVec, ReadableVec, Rw, StorageMode};
use brk_types::{Dollars, Height, Sats, Version};
use derive_more::{Deref, DerefMut};
use vecdb::{Database, Exit, ReadableVec, Rw, StorageMode};
use crate::{
indexes,
internal::{ComputedFromHeightLast, LazyFromHeightLast, SatsToBitcoin},
internal::ByUnit,
};
const VERSION: Version = Version::ZERO;
#[derive(Traversable)]
#[derive(Deref, DerefMut, Traversable)]
#[traversable(transparent)]
pub struct StoredValueFromHeightLast<M: StorageMode = Rw> {
pub sats: ComputedFromHeightLast<Sats, M>,
pub btc: LazyFromHeightLast<Bitcoin, Sats>,
pub usd: ComputedFromHeightLast<Dollars, M>,
#[deref]
#[deref_mut]
pub base: ByUnit<M>,
}
impl StoredValueFromHeightLast {
@@ -31,19 +27,9 @@ impl StoredValueFromHeightLast {
indexes: &indexes::Vecs,
) -> Result<Self> {
let v = version + VERSION;
let sats = ComputedFromHeightLast::forced_import(db, name, v, indexes)?;
let btc = LazyFromHeightLast::from_computed::<SatsToBitcoin>(
&format!("{name}_btc"),
v,
sats.height.read_only_boxed_clone(),
&sats,
);
let usd = ComputedFromHeightLast::forced_import(db, &format!("{name}_usd"), v, indexes)?;
Ok(Self { sats, btc, usd })
Ok(Self {
base: ByUnit::forced_import(db, name, v, indexes)?,
})
}
pub(crate) fn compute_rolling_sum(
@@ -54,10 +40,12 @@ impl StoredValueFromHeightLast {
usd_source: &impl ReadableVec<Height, Dollars>,
exit: &Exit,
) -> Result<()> {
self.sats
self.base
.sats
.height
.compute_rolling_sum(max_from, window_starts, sats_source, exit)?;
self.usd
self.base
.usd
.height
.compute_rolling_sum(max_from, window_starts, usd_source, exit)?;
Ok(())

View File

@@ -1,23 +1,22 @@
//! Rolling average values from Height - stores sats and dollars, btc is lazy.
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{Bitcoin, Dollars, Height, Sats, Version};
use vecdb::{Database, Exit, ReadableCloneableVec, ReadableVec, Rw, StorageMode};
use brk_types::{Dollars, Height, Sats, Version};
use derive_more::{Deref, DerefMut};
use vecdb::{Database, Exit, ReadableVec, Rw, StorageMode};
use crate::{
indexes,
internal::{ComputedFromHeightLast, LazyFromHeightLast, SatsToBitcoin},
internal::ByUnit,
};
const VERSION: Version = Version::ZERO;
/// Rolling average values indexed by height - sats (stored), btc (lazy), usd (stored).
#[derive(Traversable)]
#[derive(Deref, DerefMut, Traversable)]
#[traversable(transparent)]
pub struct ValueEmaFromHeight<M: StorageMode = Rw> {
pub sats: ComputedFromHeightLast<Sats, M>,
pub btc: LazyFromHeightLast<Bitcoin, Sats>,
pub usd: ComputedFromHeightLast<Dollars, M>,
#[deref]
#[deref_mut]
pub base: ByUnit<M>,
}
impl ValueEmaFromHeight {
@@ -28,27 +27,11 @@ impl ValueEmaFromHeight {
indexes: &indexes::Vecs,
) -> Result<Self> {
let v = version + VERSION;
let sats = ComputedFromHeightLast::forced_import(db, name, v, indexes)?;
let btc = LazyFromHeightLast::from_computed::<SatsToBitcoin>(
&format!("{name}_btc"),
v,
sats.height.read_only_boxed_clone(),
&sats,
);
let usd = ComputedFromHeightLast::forced_import(
db,
&format!("{name}_usd"),
v,
indexes,
)?;
Ok(Self { sats, btc, usd })
Ok(Self {
base: ByUnit::forced_import(db, name, v, indexes)?,
})
}
/// Compute rolling average for both sats and dollars in one call.
pub(crate) fn compute_rolling_average(
&mut self,
starting_height: Height,
@@ -57,10 +40,12 @@ impl ValueEmaFromHeight {
dollars_source: &(impl ReadableVec<Height, Dollars> + Sync),
exit: &Exit,
) -> Result<()> {
self.sats
self.base
.sats
.height
.compute_rolling_average(starting_height, window_starts, sats_source, exit)?;
self.usd
self.base
.usd
.height
.compute_rolling_average(starting_height, window_starts, dollars_source, exit)?;
Ok(())

View File

@@ -1,30 +1,23 @@
//! Value type for Full pattern from Height.
//!
//! Height-level USD stats are stored (eagerly computed from sats × price).
//! Uses CumFull: stored base + cumulative + rolling windows.
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{Bitcoin, Dollars, Height, Sats, Version};
use vecdb::{Database, EagerVec, Exit, PcoVec, ReadableCloneableVec, Rw, StorageMode};
use brk_types::{Dollars, Height, Sats, Version};
use vecdb::{Database, EagerVec, Exit, PcoVec, Rw, StorageMode};
use crate::{
indexes,
internal::{
ComputedFromHeightCumulativeFull, LazyFromHeightLast, SatsToBitcoin, SatsToDollars,
WindowStarts,
},
internal::{ByUnit, RollingFullByUnit, SatsToDollars, WindowStarts},
prices,
};
#[derive(Traversable)]
pub struct ValueFromHeightFull<M: StorageMode = Rw> {
pub sats: ComputedFromHeightCumulativeFull<Sats, M>,
pub btc: LazyFromHeightLast<Bitcoin, Sats>,
pub usd: ComputedFromHeightCumulativeFull<Dollars, M>,
pub base: ByUnit<M>,
pub cumulative: ByUnit<M>,
#[traversable(flatten)]
pub rolling: RollingFullByUnit<M>,
}
const VERSION: Version = Version::TWO; // Bumped for stored height dollars
const VERSION: Version = Version::TWO;
impl ValueFromHeightFull {
pub(crate) fn forced_import(
@@ -35,19 +28,11 @@ impl ValueFromHeightFull {
) -> Result<Self> {
let v = version + VERSION;
let sats = ComputedFromHeightCumulativeFull::forced_import(db, name, v, indexes)?;
let btc = LazyFromHeightLast::from_height_source::<SatsToBitcoin>(
&format!("{name}_btc"),
v,
sats.height.read_only_boxed_clone(),
indexes,
);
let usd =
ComputedFromHeightCumulativeFull::forced_import(db, &format!("{name}_usd"), v, indexes)?;
Ok(Self { sats, btc, usd })
Ok(Self {
base: ByUnit::forced_import(db, name, v, indexes)?,
cumulative: ByUnit::forced_import(db, &format!("{name}_cumulative"), v, indexes)?,
rolling: RollingFullByUnit::forced_import(db, name, v, indexes)?,
})
}
pub(crate) fn compute(
@@ -58,15 +43,36 @@ impl ValueFromHeightFull {
exit: &Exit,
compute_sats: impl FnOnce(&mut EagerVec<PcoVec<Height, Sats>>) -> Result<()>,
) -> Result<()> {
self.sats.compute(max_from, windows, exit, compute_sats)?;
compute_sats(&mut self.base.sats.height)?;
self.usd.compute(max_from, windows, exit, |vec| {
Ok(vec.compute_binary::<Sats, Dollars, SatsToDollars>(
self.cumulative
.sats
.height
.compute_cumulative(max_from, &self.base.sats.height, exit)?;
self.base
.usd
.height
.compute_binary::<Sats, Dollars, SatsToDollars>(
max_from,
&self.sats.height,
&self.base.sats.height,
&prices.price.usd,
exit,
)?)
})
)?;
self.cumulative
.usd
.height
.compute_cumulative(max_from, &self.base.usd.height, exit)?;
self.rolling.compute(
max_from,
windows,
&self.base.sats.height,
&self.base.usd.height,
exit,
)?;
Ok(())
}
}

View File

@@ -1,26 +1,23 @@
//! Value type for Last pattern from Height.
//!
//! Height-level USD value is stored (eagerly computed from sats × price).
//! Day1 last is stored since it requires finding the last value within each date.
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{Bitcoin, Dollars, Height, Sats, Version};
use vecdb::{Database, Exit, ReadableCloneableVec, Rw, StorageMode};
use brk_types::{Dollars, Height, Sats, Version};
use derive_more::{Deref, DerefMut};
use vecdb::{Database, Exit, Rw, StorageMode};
use crate::{
indexes, prices,
internal::{ComputedFromHeightLast, LazyFromHeightLast, SatsToBitcoin, SatsToDollars},
internal::{ByUnit, SatsToDollars},
};
#[derive(Traversable)]
#[derive(Deref, DerefMut, Traversable)]
#[traversable(transparent)]
pub struct ValueFromHeightLast<M: StorageMode = Rw> {
pub sats: ComputedFromHeightLast<Sats, M>,
pub btc: LazyFromHeightLast<Bitcoin, Sats>,
pub usd: ComputedFromHeightLast<Dollars, M>,
#[deref]
#[deref_mut]
pub base: ByUnit<M>,
}
const VERSION: Version = Version::TWO; // Bumped for stored height dollars
const VERSION: Version = Version::TWO;
impl ValueFromHeightLast {
pub(crate) fn forced_import(
@@ -30,35 +27,20 @@ impl ValueFromHeightLast {
indexes: &indexes::Vecs,
) -> Result<Self> {
let v = version + VERSION;
let sats = ComputedFromHeightLast::forced_import(db, name, v, indexes)?;
let btc = LazyFromHeightLast::from_computed::<SatsToBitcoin>(
&format!("{name}_btc"),
v,
sats.height.read_only_boxed_clone(),
&sats,
);
let usd = ComputedFromHeightLast::forced_import(db, &format!("{name}_usd"), v, indexes)?;
Ok(Self {
sats,
btc,
usd,
base: ByUnit::forced_import(db, name, v, indexes)?,
})
}
/// Eagerly compute USD height values: sats[h] * price[h].
pub(crate) fn compute(
&mut self,
prices: &prices::Vecs,
max_from: Height,
exit: &Exit,
) -> Result<()> {
self.usd.compute_binary::<Sats, Dollars, SatsToDollars>(
self.base.usd.compute_binary::<Sats, Dollars, SatsToDollars>(
max_from,
&self.sats.height,
&self.base.sats.height,
&prices.price.usd,
exit,
)?;

View File

@@ -21,6 +21,7 @@ pub struct ValueFromHeightLastRolling<M: StorageMode = Rw> {
#[deref_mut]
#[traversable(flatten)]
pub value: ValueFromHeight<M>,
#[traversable(flatten)]
pub rolling: StoredValueRollingWindows<M>,
}

View File

@@ -1,32 +1,21 @@
//! Value type with stored sats height + cumulative, stored usd, lazy btc.
//!
//! - Sats: stored height + cumulative (ComputedFromHeightCumulative)
//! - BTC: lazy transform from sats (LazyFromHeightLast)
//! - USD: stored (eagerly computed from price × sats)
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{Bitcoin, Dollars, Height, Sats, Version};
use vecdb::{Database, Exit, ReadableCloneableVec, Rw, StorageMode};
use brk_types::{Dollars, Height, Sats, Version};
use vecdb::{Database, Exit, Rw, StorageMode};
use crate::{
indexes,
internal::{
ComputedFromHeightCumulative, ComputedFromHeightLast, LazyFromHeightLast, SatsToBitcoin,
SatsToDollars,
},
internal::{ByUnit, SatsToDollars},
prices,
};
/// Value wrapper with stored sats height + cumulative, lazy btc + stored usd.
#[derive(Traversable)]
pub struct LazyComputedValueFromHeightCumulative<M: StorageMode = Rw> {
pub sats: ComputedFromHeightCumulative<Sats, M>,
pub btc: LazyFromHeightLast<Bitcoin, Sats>,
pub usd: ComputedFromHeightLast<Dollars, M>,
pub base: ByUnit<M>,
pub cumulative: ByUnit<M>,
}
const VERSION: Version = Version::ONE; // Bumped for stored height dollars
const VERSION: Version = Version::ONE;
impl LazyComputedValueFromHeightCumulative {
pub(crate) fn forced_import(
@@ -37,35 +26,37 @@ impl LazyComputedValueFromHeightCumulative {
) -> Result<Self> {
let v = version + VERSION;
let sats = ComputedFromHeightCumulative::forced_import(db, name, v, indexes)?;
let btc = LazyFromHeightLast::from_height_source::<SatsToBitcoin>(
&format!("{name}_btc"),
v,
sats.height.read_only_boxed_clone(),
indexes,
);
let usd = ComputedFromHeightLast::forced_import(db, &format!("{name}_usd"), v, indexes)?;
Ok(Self { sats, btc, usd })
Ok(Self {
base: ByUnit::forced_import(db, name, v, indexes)?,
cumulative: ByUnit::forced_import(db, &format!("{name}_cumulative"), v, indexes)?,
})
}
/// Compute cumulative + USD from already-filled sats height vec.
pub(crate) fn compute(
&mut self,
prices: &prices::Vecs,
max_from: Height,
exit: &Exit,
) -> Result<()> {
self.sats.compute_rest(max_from, exit)?;
self.cumulative
.sats
.height
.compute_cumulative(max_from, &self.base.sats.height, exit)?;
self.base
.usd
.compute_binary::<Sats, Dollars, SatsToDollars>(
max_from,
&self.base.sats.height,
&prices.price.usd,
exit,
)?;
self.cumulative
.usd
.height
.compute_cumulative(max_from, &self.base.usd.height, exit)?;
self.usd.compute_binary::<Sats, Dollars, SatsToDollars>(
max_from,
&self.sats.height,
&prices.price.usd,
exit,
)?;
Ok(())
}
}

View File

@@ -1,29 +1,22 @@
//! Value type for SumCumulative pattern from Height.
//!
//! Height-level USD sum is stored (eagerly computed from sats × price).
//! Uses CumSum: stored base + cumulative + rolling sum windows.
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{Bitcoin, Dollars, Height, Sats, Version};
use vecdb::{Database, EagerVec, Exit, PcoVec, ReadableCloneableVec, Rw, StorageMode};
use brk_types::{Dollars, Height, Sats, Version};
use vecdb::{Database, EagerVec, Exit, PcoVec, Rw, StorageMode};
use crate::{
indexes, prices,
internal::{
ComputedFromHeightCumulativeSum, LazyFromHeightLast, SatsToBitcoin, SatsToDollars,
WindowStarts,
},
indexes,
internal::{ByUnit, RollingSumByUnit, SatsToDollars, WindowStarts},
prices,
};
#[derive(Traversable)]
pub struct ValueFromHeightSumCumulative<M: StorageMode = Rw> {
pub sats: ComputedFromHeightCumulativeSum<Sats, M>,
pub btc: LazyFromHeightLast<Bitcoin, Sats>,
pub usd: ComputedFromHeightCumulativeSum<Dollars, M>,
pub base: ByUnit<M>,
pub cumulative: ByUnit<M>,
pub sum: RollingSumByUnit<M>,
}
const VERSION: Version = Version::TWO; // Bumped for stored height dollars
const VERSION: Version = Version::TWO;
impl ValueFromHeightSumCumulative {
pub(crate) fn forced_import(
@@ -34,19 +27,11 @@ impl ValueFromHeightSumCumulative {
) -> Result<Self> {
let v = version + VERSION;
let sats = ComputedFromHeightCumulativeSum::forced_import(db, name, v, indexes)?;
let btc = LazyFromHeightLast::from_height_source::<SatsToBitcoin>(
&format!("{name}_btc"),
v,
sats.height.read_only_boxed_clone(),
indexes,
);
let usd =
ComputedFromHeightCumulativeSum::forced_import(db, &format!("{name}_usd"), v, indexes)?;
Ok(Self { sats, btc, usd })
Ok(Self {
base: ByUnit::forced_import(db, name, v, indexes)?,
cumulative: ByUnit::forced_import(db, &format!("{name}_cumulative"), v, indexes)?,
sum: RollingSumByUnit::forced_import(db, name, v, indexes)?,
})
}
pub(crate) fn compute(
@@ -57,15 +42,36 @@ impl ValueFromHeightSumCumulative {
exit: &Exit,
compute_sats: impl FnOnce(&mut EagerVec<PcoVec<Height, Sats>>) -> Result<()>,
) -> Result<()> {
self.sats.compute(max_from, windows, exit, compute_sats)?;
compute_sats(&mut self.base.sats.height)?;
self.usd.compute(max_from, windows, exit, |vec| {
Ok(vec.compute_binary::<Sats, Dollars, SatsToDollars>(
self.cumulative
.sats
.height
.compute_cumulative(max_from, &self.base.sats.height, exit)?;
self.base
.usd
.height
.compute_binary::<Sats, Dollars, SatsToDollars>(
max_from,
&self.sats.height,
&self.base.sats.height,
&prices.price.usd,
exit,
)?)
})
)?;
self.cumulative
.usd
.height
.compute_cumulative(max_from, &self.base.usd.height, exit)?;
self.sum.compute_rolling_sum(
max_from,
windows,
&self.base.sats.height,
&self.base.usd.height,
exit,
)?;
Ok(())
}
}

View File

@@ -2,12 +2,14 @@
use brk_traversable::Traversable;
use brk_types::{
Day1, Day3, DifficultyEpoch, HalvingEpoch, Height, Hour1, Hour4, Hour12, Minute1, Minute5,
Minute10, Minute30, Month1, Month3, Month6, Version, Week1, Year1, Year10,
Day1, Day3, DifficultyEpoch, FromCoarserIndex, HalvingEpoch, Height, Hour1, Hour4, Hour12,
Minute1, Minute5, Minute10, Minute30, Month1, Month3, Month6, Version, Week1, Year1, Year10,
};
use derive_more::{Deref, DerefMut};
use schemars::JsonSchema;
use vecdb::{LazyAggVec, ReadOnlyClone, ReadableBoxedVec, ReadableCloneableVec};
use vecdb::{
Cursor, LazyAggVec, ReadOnlyClone, ReadableBoxedVec, ReadableCloneableVec, VecIndex, VecValue,
};
use crate::{
indexes, indexes_from,
@@ -77,13 +79,45 @@ where
};
}
fn for_each_range_from_coarser<
I: VecIndex,
O: VecValue,
S1I: VecIndex + FromCoarserIndex<I>,
S2T: VecValue,
>(
from: usize,
to: usize,
source: &ReadableBoxedVec<S1I, O>,
mapping: &ReadableBoxedVec<I, S2T>,
f: &mut dyn FnMut(O),
) {
let mapping_len = mapping.len();
let source_len = source.len();
let mut cursor = Cursor::from_dyn(&**source);
for i in from..to {
if i >= mapping_len {
break;
}
let target = S1I::max_from(I::from(i), source_len);
if cursor.position() <= target {
cursor.advance(target - cursor.position());
if let Some(v) = cursor.next() {
f(v);
}
} else if let Some(v) = source.collect_one_at(target) {
f(v);
}
}
}
macro_rules! epoch {
($idx:ident) => {
LazyAggVec::from_source(
LazyAggVec::new(
name,
v,
height_source.clone(),
indexes.$idx.identity.read_only_boxed_clone(),
for_each_range_from_coarser,
)
};
}

View File

@@ -1,29 +1,25 @@
use brk_error::Result;
use brk_traversable::Traversable;
use schemars::JsonSchema;
use vecdb::{
Database, Exit, ReadableVec, Ro, Rw, StorageMode, VecIndex, VecValue, Version,
Database, Exit, ReadableVec, Ro, Rw, VecIndex, VecValue, Version,
};
use crate::internal::{
AverageVec, ComputedVecValue, MaxVec, MedianVec, MinVec, Pct10Vec, Pct25Vec, Pct75Vec,
Pct90Vec,
AverageVec, ComputedVecValue, DistributionStats, MaxVec, MedianVec, MinVec, Pct10Vec,
Pct25Vec, Pct75Vec, Pct90Vec,
};
/// Distribution stats (average + min + max + percentiles) — flat 8-field struct.
#[derive(Traversable)]
pub struct Distribution<I: VecIndex, T: ComputedVecValue + JsonSchema, M: StorageMode = Rw> {
pub average: AverageVec<I, T, M>,
#[traversable(flatten)]
pub min: MinVec<I, T, M>,
#[traversable(flatten)]
pub max: MaxVec<I, T, M>,
pub pct10: Pct10Vec<I, T, M>,
pub pct25: Pct25Vec<I, T, M>,
pub median: MedianVec<I, T, M>,
pub pct75: Pct75Vec<I, T, M>,
pub pct90: Pct90Vec<I, T, M>,
}
/// Distribution stats (average + min + max + percentiles) — concrete vec type alias.
pub type Distribution<I, T, M = Rw> = DistributionStats<
AverageVec<I, T, M>,
MinVec<I, T, M>,
MaxVec<I, T, M>,
Pct10Vec<I, T, M>,
Pct25Vec<I, T, M>,
MedianVec<I, T, M>,
Pct75Vec<I, T, M>,
Pct90Vec<I, T, M>,
>;
impl<I: VecIndex, T: ComputedVecValue + JsonSchema> Distribution<I, T> {
pub(crate) fn forced_import(db: &Database, name: &str, version: Version) -> Result<Self> {
@@ -111,7 +107,7 @@ impl<I: VecIndex, T: ComputedVecValue + JsonSchema> Distribution<I, T> {
}
pub fn read_only_clone(&self) -> Distribution<I, T, Ro> {
Distribution {
DistributionStats {
average: self.average.read_only_clone(),
min: self.min.read_only_clone(),
max: self.max.read_only_clone(),

View File

@@ -41,11 +41,11 @@ where
average: RollingWindows::forced_import(db, &format!("{name}_average"), v, indexes)?,
min: RollingWindows::forced_import(db, &format!("{name}_min"), v, indexes)?,
max: RollingWindows::forced_import(db, &format!("{name}_max"), v, indexes)?,
p10: RollingWindows::forced_import(db, &format!("{name}_p10"), v, indexes)?,
p25: RollingWindows::forced_import(db, &format!("{name}_p25"), v, indexes)?,
pct10: RollingWindows::forced_import(db, &format!("{name}_p10"), v, indexes)?,
pct25: RollingWindows::forced_import(db, &format!("{name}_p25"), v, indexes)?,
median: RollingWindows::forced_import(db, &format!("{name}_median"), v, indexes)?,
p75: RollingWindows::forced_import(db, &format!("{name}_p75"), v, indexes)?,
p90: RollingWindows::forced_import(db, &format!("{name}_p90"), v, indexes)?,
pct75: RollingWindows::forced_import(db, &format!("{name}_p75"), v, indexes)?,
pct90: RollingWindows::forced_import(db, &format!("{name}_p90"), v, indexes)?,
}))
}
@@ -69,30 +69,30 @@ where
compute_rolling_distribution_from_starts(
max_from, windows._24h, source,
&mut self.0.average._24h.height, &mut self.0.min._24h.height,
&mut self.0.max._24h.height, &mut self.0.p10._24h.height,
&mut self.0.p25._24h.height, &mut self.0.median._24h.height,
&mut self.0.p75._24h.height, &mut self.0.p90._24h.height, exit,
&mut self.0.max._24h.height, &mut self.0.pct10._24h.height,
&mut self.0.pct25._24h.height, &mut self.0.median._24h.height,
&mut self.0.pct75._24h.height, &mut self.0.pct90._24h.height, exit,
)?;
compute_rolling_distribution_from_starts(
max_from, windows._7d, source,
&mut self.0.average._7d.height, &mut self.0.min._7d.height,
&mut self.0.max._7d.height, &mut self.0.p10._7d.height,
&mut self.0.p25._7d.height, &mut self.0.median._7d.height,
&mut self.0.p75._7d.height, &mut self.0.p90._7d.height, exit,
&mut self.0.max._7d.height, &mut self.0.pct10._7d.height,
&mut self.0.pct25._7d.height, &mut self.0.median._7d.height,
&mut self.0.pct75._7d.height, &mut self.0.pct90._7d.height, exit,
)?;
compute_rolling_distribution_from_starts(
max_from, windows._30d, source,
&mut self.0.average._30d.height, &mut self.0.min._30d.height,
&mut self.0.max._30d.height, &mut self.0.p10._30d.height,
&mut self.0.p25._30d.height, &mut self.0.median._30d.height,
&mut self.0.p75._30d.height, &mut self.0.p90._30d.height, exit,
&mut self.0.max._30d.height, &mut self.0.pct10._30d.height,
&mut self.0.pct25._30d.height, &mut self.0.median._30d.height,
&mut self.0.pct75._30d.height, &mut self.0.pct90._30d.height, exit,
)?;
compute_rolling_distribution_from_starts(
max_from, windows._1y, source,
&mut self.0.average._1y.height, &mut self.0.min._1y.height,
&mut self.0.max._1y.height, &mut self.0.p10._1y.height,
&mut self.0.p25._1y.height, &mut self.0.median._1y.height,
&mut self.0.p75._1y.height, &mut self.0.p90._1y.height, exit,
&mut self.0.max._1y.height, &mut self.0.pct10._1y.height,
&mut self.0.pct25._1y.height, &mut self.0.median._1y.height,
&mut self.0.pct75._1y.height, &mut self.0.pct90._1y.height, exit,
)?;
Ok(())

View File

@@ -1,15 +1,14 @@
mod block_count_target;
mod cents_to_dollars;
mod cents_to_sats;
mod ohlc_cents_to_dollars;
mod ohlc_cents_to_sats;
mod dollar_halve;
mod dollar_identity;
mod dollar_plus;
mod dollar_times_tenths;
mod dollars_to_sats_fract;
mod f32_identity;
mod ohlc_cents_to_dollars;
mod ohlc_cents_to_sats;
mod percentage_diff_close_dollars;
mod percentage_dollars_f32;
mod percentage_dollars_f32_neg;
@@ -18,7 +17,7 @@ mod percentage_u32_f32;
mod price_times_ratio;
mod ratio32;
mod ratio64;
mod ratio_u64_f32;
mod return_f32_tenths;
mod return_i8;
mod return_u16;
@@ -52,13 +51,12 @@ pub use percentage_dollars_f32_neg::*;
pub use percentage_sats_f64::*;
pub use percentage_u32_f32::*;
pub use price_times_ratio::*;
pub use ratio32::*;
pub use ratio64::*;
pub use ratio_u64_f32::*;
pub use return_f32_tenths::*;
pub use return_i8::*;
pub use return_u16::*;
pub use sat_halve::*;
pub use sat_halve_to_bitcoin::*;
pub use sat_identity::*;

View File

@@ -0,0 +1,17 @@
use brk_types::{StoredF32, StoredU64};
use vecdb::BinaryTransform;
/// (StoredU64, StoredU64) -> StoredF32 ratio (a/b)
/// Used for adoption ratio calculations (script_count / total_outputs)
pub struct RatioU64F32;
impl BinaryTransform<StoredU64, StoredU64, StoredF32> for RatioU64F32 {
#[inline(always)]
fn apply(numerator: StoredU64, denominator: StoredU64) -> StoredF32 {
if *denominator > 0 {
StoredF32::from(*numerator as f64 / *denominator as f64)
} else {
StoredF32::from(0.0)
}
}
}