mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-07-13 12:08:13 -07:00
global: snapshot
This commit is contained in:
@@ -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 })
|
||||
}
|
||||
}
|
||||
@@ -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(())
|
||||
}
|
||||
}
|
||||
@@ -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(())
|
||||
}
|
||||
}
|
||||
@@ -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)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
use brk_traversable::Traversable;
|
||||
use brk_types::{
|
||||
Day1, Day3, DifficultyEpoch, HalvingEpoch, Height, Hour1, Hour12, Hour4, Minute1, Minute10,
|
||||
Minute30, Minute5, Month1, Month3, Month6, Version, Week1, Year1, Year10,
|
||||
};
|
||||
use schemars::JsonSchema;
|
||||
use serde::Serialize;
|
||||
use vecdb::{Formattable, ReadableCloneableVec, LazyVecFrom1, UnaryTransform, VecValue};
|
||||
|
||||
use crate::indexes;
|
||||
|
||||
/// Lazy constant vecs for all index levels.
|
||||
/// Uses const generic transforms to return the same value for every index.
|
||||
#[derive(Clone, Traversable)]
|
||||
#[traversable(merge)]
|
||||
pub struct ConstantVecs<T>
|
||||
where
|
||||
T: VecValue + Formattable + Serialize + JsonSchema,
|
||||
{
|
||||
pub height: LazyVecFrom1<Height, T, Height, Height>,
|
||||
pub minute1: LazyVecFrom1<Minute1, T, Minute1, Minute1>,
|
||||
pub minute5: LazyVecFrom1<Minute5, T, Minute5, Minute5>,
|
||||
pub minute10: LazyVecFrom1<Minute10, T, Minute10, Minute10>,
|
||||
pub minute30: LazyVecFrom1<Minute30, T, Minute30, Minute30>,
|
||||
pub hour1: LazyVecFrom1<Hour1, T, Hour1, Hour1>,
|
||||
pub hour4: LazyVecFrom1<Hour4, T, Hour4, Hour4>,
|
||||
pub hour12: LazyVecFrom1<Hour12, T, Hour12, Hour12>,
|
||||
pub day1: LazyVecFrom1<Day1, T, Day1, Day1>,
|
||||
pub day3: LazyVecFrom1<Day3, T, Day3, Day3>,
|
||||
pub week1: LazyVecFrom1<Week1, T, Week1, Week1>,
|
||||
pub month1: LazyVecFrom1<Month1, T, Month1, Month1>,
|
||||
pub month3: LazyVecFrom1<Month3, T, Month3, Month3>,
|
||||
pub month6: LazyVecFrom1<Month6, T, Month6, Month6>,
|
||||
pub year1: LazyVecFrom1<Year1, T, Year1, Year1>,
|
||||
pub year10: LazyVecFrom1<Year10, T, Year10, Year10>,
|
||||
pub halvingepoch: LazyVecFrom1<HalvingEpoch, T, HalvingEpoch, HalvingEpoch>,
|
||||
pub difficultyepoch: LazyVecFrom1<DifficultyEpoch, T, DifficultyEpoch, DifficultyEpoch>,
|
||||
}
|
||||
|
||||
impl<T: VecValue + Formattable + Serialize + JsonSchema> ConstantVecs<T> {
|
||||
/// Create constant vecs using a transform that ignores input and returns a constant.
|
||||
pub(crate) fn new<F>(name: &str, version: Version, indexes: &indexes::Vecs) -> Self
|
||||
where
|
||||
F: UnaryTransform<Height, T>
|
||||
+ UnaryTransform<Minute1, T>
|
||||
+ UnaryTransform<Minute5, T>
|
||||
+ UnaryTransform<Minute10, T>
|
||||
+ UnaryTransform<Minute30, T>
|
||||
+ UnaryTransform<Hour1, T>
|
||||
+ UnaryTransform<Hour4, T>
|
||||
+ UnaryTransform<Hour12, T>
|
||||
+ UnaryTransform<Day1, T>
|
||||
+ UnaryTransform<Day3, T>
|
||||
+ UnaryTransform<Week1, T>
|
||||
+ UnaryTransform<Month1, T>
|
||||
+ UnaryTransform<Month3, T>
|
||||
+ UnaryTransform<Month6, T>
|
||||
+ UnaryTransform<Year1, T>
|
||||
+ UnaryTransform<Year10, T>
|
||||
+ UnaryTransform<HalvingEpoch, T>
|
||||
+ UnaryTransform<DifficultyEpoch, T>,
|
||||
{
|
||||
macro_rules! period {
|
||||
($idx:ident, $I:ty) => {
|
||||
LazyVecFrom1::transformed::<F>(
|
||||
name,
|
||||
version,
|
||||
indexes.$idx.identity.read_only_boxed_clone(),
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
Self {
|
||||
height: LazyVecFrom1::transformed::<F>(
|
||||
name,
|
||||
version,
|
||||
indexes.height.identity.read_only_boxed_clone(),
|
||||
),
|
||||
minute1: period!(minute1, Minute1),
|
||||
minute5: period!(minute5, Minute5),
|
||||
minute10: period!(minute10, Minute10),
|
||||
minute30: period!(minute30, Minute30),
|
||||
hour1: period!(hour1, Hour1),
|
||||
hour4: period!(hour4, Hour4),
|
||||
hour12: period!(hour12, Hour12),
|
||||
day1: period!(day1, Day1),
|
||||
day3: period!(day3, Day3),
|
||||
week1: period!(week1, Week1),
|
||||
month1: period!(month1, Month1),
|
||||
month3: period!(month3, Month3),
|
||||
month6: period!(month6, Month6),
|
||||
year1: period!(year1, Year1),
|
||||
year10: period!(year10, Year10),
|
||||
halvingepoch: period!(halvingepoch, HalvingEpoch),
|
||||
difficultyepoch: period!(difficultyepoch, DifficultyEpoch),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
//! ComputedFromHeightCumulative - stored height + LazyAggVec + cumulative (from height).
|
||||
//!
|
||||
//! Like ComputedFromHeightCumulativeSum but without RollingWindows.
|
||||
//! Used for distribution metrics where rolling is optional per cohort.
|
||||
//! Cumulative gets its own ComputedFromHeightLast so it has LazyAggVec index views.
|
||||
|
||||
use brk_error::Result;
|
||||
use brk_traversable::Traversable;
|
||||
use brk_types::{Height, Version};
|
||||
use schemars::JsonSchema;
|
||||
use vecdb::{Database, EagerVec, Exit, ImportableVec, PcoVec, Rw, StorageMode};
|
||||
|
||||
use crate::{
|
||||
indexes,
|
||||
internal::{ComputedFromHeightLast, NumericValue},
|
||||
};
|
||||
|
||||
#[derive(Traversable)]
|
||||
pub struct ComputedFromHeightCumulative<T, M: StorageMode = Rw>
|
||||
where
|
||||
T: NumericValue + JsonSchema,
|
||||
{
|
||||
pub height: M::Stored<EagerVec<PcoVec<Height, T>>>,
|
||||
pub cumulative: ComputedFromHeightLast<T, M>,
|
||||
}
|
||||
|
||||
const VERSION: Version = Version::ZERO;
|
||||
|
||||
impl<T> ComputedFromHeightCumulative<T>
|
||||
where
|
||||
T: NumericValue + JsonSchema,
|
||||
{
|
||||
pub(crate) fn forced_import(
|
||||
db: &Database,
|
||||
name: &str,
|
||||
version: Version,
|
||||
indexes: &indexes::Vecs,
|
||||
) -> Result<Self> {
|
||||
let v = version + VERSION;
|
||||
|
||||
let height: EagerVec<PcoVec<Height, T>> = EagerVec::forced_import(db, name, v)?;
|
||||
let cumulative =
|
||||
ComputedFromHeightLast::forced_import(db, &format!("{name}_cumulative"), v, indexes)?;
|
||||
|
||||
Ok(Self { height, cumulative })
|
||||
}
|
||||
|
||||
/// Compute height data via closure, then cumulative only (no rolling).
|
||||
pub(crate) fn compute(
|
||||
&mut self,
|
||||
max_from: Height,
|
||||
exit: &Exit,
|
||||
compute_height: impl FnOnce(&mut EagerVec<PcoVec<Height, T>>) -> Result<()>,
|
||||
) -> Result<()>
|
||||
where
|
||||
T: Default,
|
||||
{
|
||||
compute_height(&mut self.height)?;
|
||||
self.compute_rest(max_from, exit)
|
||||
}
|
||||
|
||||
/// Compute cumulative from already-filled height vec.
|
||||
pub(crate) fn compute_rest(&mut self, max_from: Height, exit: &Exit) -> Result<()>
|
||||
where
|
||||
T: Default,
|
||||
{
|
||||
self.cumulative
|
||||
.height
|
||||
.compute_cumulative(max_from, &self.height, exit)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
//! ComputedFromHeightCumulativeFull - stored height + LazyAggVec + cumulative (from height) + RollingFull.
|
||||
//!
|
||||
//! For metrics with stored per-block data, cumulative sums, and rolling windows.
|
||||
//! Cumulative gets its own ComputedFromHeightLast so it has LazyAggVec index views too.
|
||||
|
||||
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, ImportableVec, PcoVec, Rw, StorageMode};
|
||||
|
||||
use crate::{
|
||||
indexes,
|
||||
internal::{ComputedFromHeightLast, NumericValue, RollingFull, WindowStarts},
|
||||
};
|
||||
|
||||
#[derive(Traversable)]
|
||||
pub struct ComputedFromHeightCumulativeFull<T, M: StorageMode = Rw>
|
||||
where
|
||||
T: NumericValue + JsonSchema,
|
||||
{
|
||||
pub height: M::Stored<EagerVec<PcoVec<Height, T>>>,
|
||||
pub cumulative: ComputedFromHeightLast<T, M>,
|
||||
#[traversable(flatten)]
|
||||
pub rolling: RollingFull<T, M>,
|
||||
}
|
||||
|
||||
const VERSION: Version = Version::ZERO;
|
||||
|
||||
impl<T> ComputedFromHeightCumulativeFull<T>
|
||||
where
|
||||
T: NumericValue + JsonSchema,
|
||||
{
|
||||
pub(crate) fn forced_import(
|
||||
db: &Database,
|
||||
name: &str,
|
||||
version: Version,
|
||||
indexes: &indexes::Vecs,
|
||||
) -> Result<Self> {
|
||||
let v = version + VERSION;
|
||||
|
||||
let height: EagerVec<PcoVec<Height, T>> = EagerVec::forced_import(db, name, v)?;
|
||||
let cumulative =
|
||||
ComputedFromHeightLast::forced_import(db, &format!("{name}_cumulative"), v, indexes)?;
|
||||
let rolling = RollingFull::forced_import(db, name, v, indexes)?;
|
||||
|
||||
Ok(Self {
|
||||
height,
|
||||
cumulative,
|
||||
rolling,
|
||||
})
|
||||
}
|
||||
|
||||
/// Compute height data via closure, then cumulative + rolling.
|
||||
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: From<f64> + Default + SubAssign + Copy + Ord,
|
||||
f64: From<T>,
|
||||
{
|
||||
compute_height(&mut self.height)?;
|
||||
self.cumulative
|
||||
.height
|
||||
.compute_cumulative(max_from, &self.height, exit)?;
|
||||
self.rolling
|
||||
.compute(max_from, windows, &self.height, exit)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
//! ComputedFromHeightCumulativeSum - stored height + LazyAggVec + cumulative (from height) + RollingWindows (sum).
|
||||
//!
|
||||
//! Like ComputedFromHeightCumulativeFull but with rolling sum only (no distribution).
|
||||
//! Used for count metrics where distribution stats aren't meaningful.
|
||||
//! Cumulative gets its own ComputedFromHeightLast so it has LazyAggVec index views too.
|
||||
|
||||
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, ImportableVec, PcoVec, Rw, StorageMode};
|
||||
|
||||
use crate::{
|
||||
indexes,
|
||||
internal::{ComputedFromHeightLast, NumericValue, RollingWindows, WindowStarts},
|
||||
};
|
||||
|
||||
#[derive(Traversable)]
|
||||
pub struct ComputedFromHeightCumulativeSum<T, M: StorageMode = Rw>
|
||||
where
|
||||
T: NumericValue + JsonSchema,
|
||||
{
|
||||
pub height: M::Stored<EagerVec<PcoVec<Height, T>>>,
|
||||
pub cumulative: ComputedFromHeightLast<T, M>,
|
||||
pub sum: RollingWindows<T, M>,
|
||||
}
|
||||
|
||||
const VERSION: Version = Version::ZERO;
|
||||
|
||||
impl<T> ComputedFromHeightCumulativeSum<T>
|
||||
where
|
||||
T: NumericValue + JsonSchema,
|
||||
{
|
||||
pub(crate) fn forced_import(
|
||||
db: &Database,
|
||||
name: &str,
|
||||
version: Version,
|
||||
indexes: &indexes::Vecs,
|
||||
) -> Result<Self> {
|
||||
let v = version + VERSION;
|
||||
|
||||
let height: EagerVec<PcoVec<Height, T>> = EagerVec::forced_import(db, name, v)?;
|
||||
let cumulative =
|
||||
ComputedFromHeightLast::forced_import(db, &format!("{name}_cumulative"), v, indexes)?;
|
||||
let rolling = RollingWindows::forced_import(db, name, v, indexes)?;
|
||||
|
||||
Ok(Self {
|
||||
height,
|
||||
cumulative,
|
||||
sum: rolling,
|
||||
})
|
||||
}
|
||||
|
||||
/// Compute height data via closure, then cumulative + rolling sum.
|
||||
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 + SubAssign,
|
||||
{
|
||||
compute_height(&mut self.height)?;
|
||||
self.cumulative
|
||||
.height
|
||||
.compute_cumulative(max_from, &self.height, exit)?;
|
||||
self.sum
|
||||
.compute_rolling_sum(max_from, windows, &self.height, exit)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
//! ComputedFromHeight using Distribution aggregation (no sum/cumulative).
|
||||
//!
|
||||
//! Stored height data + LazyAggVec index views + rolling distribution windows.
|
||||
//! Use for block-based metrics where sum/cumulative would be misleading
|
||||
//! (e.g., activity counts that can't be deduplicated across blocks).
|
||||
|
||||
use brk_error::Result;
|
||||
|
||||
use brk_traversable::Traversable;
|
||||
use brk_types::{Height, Version};
|
||||
use schemars::JsonSchema;
|
||||
use vecdb::{Database, EagerVec, Exit, ImportableVec, PcoVec, Rw, StorageMode};
|
||||
|
||||
use crate::indexes;
|
||||
|
||||
use crate::internal::{ComputedVecValue, NumericValue, RollingDistribution, WindowStarts};
|
||||
|
||||
#[derive(Traversable)]
|
||||
pub struct ComputedFromHeightDistribution<T, M: StorageMode = Rw>
|
||||
where
|
||||
T: ComputedVecValue + PartialOrd + JsonSchema,
|
||||
{
|
||||
pub height: M::Stored<EagerVec<PcoVec<Height, T>>>,
|
||||
#[traversable(flatten)]
|
||||
pub rolling: RollingDistribution<T, M>,
|
||||
}
|
||||
|
||||
const VERSION: Version = Version::ZERO;
|
||||
|
||||
impl<T> ComputedFromHeightDistribution<T>
|
||||
where
|
||||
T: NumericValue + JsonSchema,
|
||||
{
|
||||
pub(crate) fn forced_import(
|
||||
db: &Database,
|
||||
name: &str,
|
||||
version: Version,
|
||||
indexes: &indexes::Vecs,
|
||||
) -> Result<Self> {
|
||||
let v = version + VERSION;
|
||||
|
||||
let height: EagerVec<PcoVec<Height, T>> = EagerVec::forced_import(db, name, v)?;
|
||||
let rolling = RollingDistribution::forced_import(db, name, v, indexes)?;
|
||||
|
||||
Ok(Self { height, rolling })
|
||||
}
|
||||
|
||||
/// Compute height data via closure, then rolling distribution.
|
||||
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: Copy + Ord + From<f64> + Default,
|
||||
f64: From<T>,
|
||||
{
|
||||
compute_height(&mut self.height)?;
|
||||
self.rolling
|
||||
.compute_distribution(max_from, windows, &self.height, exit)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
//! ComputedFromHeightFull - Full (distribution + sum + cumulative) + RollingFull.
|
||||
//!
|
||||
//! 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};
|
||||
use schemars::JsonSchema;
|
||||
use vecdb::{Database, Exit, Rw, StorageMode};
|
||||
|
||||
use crate::{
|
||||
indexes,
|
||||
internal::{Full, NumericValue, RollingFull, WindowStarts},
|
||||
};
|
||||
|
||||
#[derive(Traversable)]
|
||||
pub struct ComputedFromHeightFull<T, M: StorageMode = Rw>
|
||||
where
|
||||
T: NumericValue + JsonSchema,
|
||||
{
|
||||
#[traversable(flatten)]
|
||||
pub full: Full<Height, T, M>,
|
||||
pub rolling: RollingFull<T, M>,
|
||||
}
|
||||
|
||||
const VERSION: Version = Version::ZERO;
|
||||
|
||||
impl<T> ComputedFromHeightFull<T>
|
||||
where
|
||||
T: NumericValue + JsonSchema,
|
||||
{
|
||||
pub(crate) fn forced_import(
|
||||
db: &Database,
|
||||
name: &str,
|
||||
version: Version,
|
||||
indexes: &indexes::Vecs,
|
||||
) -> Result<Self> {
|
||||
let v = version + VERSION;
|
||||
|
||||
let height = Full::forced_import(db, name, v)?;
|
||||
let rolling = RollingFull::forced_import(db, name, v, indexes)?;
|
||||
|
||||
Ok(Self {
|
||||
full: height,
|
||||
rolling,
|
||||
})
|
||||
}
|
||||
|
||||
/// Compute Full stats via closure, then rolling windows from the per-block sum.
|
||||
pub(crate) fn compute(
|
||||
&mut self,
|
||||
max_from: Height,
|
||||
windows: &WindowStarts<'_>,
|
||||
exit: &Exit,
|
||||
compute_full: impl FnOnce(&mut Full<Height, T>) -> Result<()>,
|
||||
) -> Result<()>
|
||||
where
|
||||
T: From<f64> + Default + SubAssign + Copy + Ord,
|
||||
f64: From<T>,
|
||||
{
|
||||
compute_full(&mut self.full)?;
|
||||
self.rolling.compute(
|
||||
max_from,
|
||||
windows,
|
||||
self.full.sum_cumulative.sum.inner(),
|
||||
exit,
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
//! ComputedFromHeight using only Last aggregation.
|
||||
|
||||
use brk_error::Result;
|
||||
|
||||
use brk_traversable::Traversable;
|
||||
use brk_types::{Height, Version};
|
||||
use derive_more::{Deref, DerefMut};
|
||||
use schemars::JsonSchema;
|
||||
use vecdb::{
|
||||
BinaryTransform, Database, EagerVec, Exit, ImportableVec, PcoVec, ReadableCloneableVec,
|
||||
ReadableVec, Rw, StorageMode, VecValue,
|
||||
};
|
||||
|
||||
use crate::indexes;
|
||||
|
||||
use crate::internal::{ComputedHeightDerivedLast, ComputedVecValue, NumericValue};
|
||||
|
||||
#[derive(Deref, DerefMut, Traversable)]
|
||||
#[traversable(merge)]
|
||||
pub struct ComputedFromHeightLast<T, M: StorageMode = Rw>
|
||||
where
|
||||
T: ComputedVecValue + PartialOrd + JsonSchema,
|
||||
{
|
||||
pub height: M::Stored<EagerVec<PcoVec<Height, T>>>,
|
||||
#[deref]
|
||||
#[deref_mut]
|
||||
#[traversable(flatten)]
|
||||
pub rest: Box<ComputedHeightDerivedLast<T>>,
|
||||
}
|
||||
|
||||
const VERSION: Version = Version::ZERO;
|
||||
|
||||
impl<T> ComputedFromHeightLast<T>
|
||||
where
|
||||
T: NumericValue + JsonSchema,
|
||||
{
|
||||
pub(crate) fn forced_import(
|
||||
db: &Database,
|
||||
name: &str,
|
||||
version: Version,
|
||||
indexes: &indexes::Vecs,
|
||||
) -> Result<Self> {
|
||||
let v = version + VERSION;
|
||||
|
||||
let height: EagerVec<PcoVec<Height, T>> = EagerVec::forced_import(db, name, v)?;
|
||||
|
||||
let rest = ComputedHeightDerivedLast::forced_import(
|
||||
name,
|
||||
height.read_only_boxed_clone(),
|
||||
v,
|
||||
indexes,
|
||||
);
|
||||
|
||||
Ok(Self {
|
||||
height,
|
||||
rest: Box::new(rest),
|
||||
})
|
||||
}
|
||||
|
||||
/// Eagerly compute this vec as a binary transform of two sources.
|
||||
pub(crate) fn compute_binary<S1T, S2T, F>(
|
||||
&mut self,
|
||||
max_from: Height,
|
||||
source1: &impl ReadableVec<Height, S1T>,
|
||||
source2: &impl ReadableVec<Height, S2T>,
|
||||
exit: &Exit,
|
||||
) -> Result<()>
|
||||
where
|
||||
S1T: VecValue,
|
||||
S2T: VecValue,
|
||||
F: BinaryTransform<S1T, S2T, T>,
|
||||
{
|
||||
self.height.compute_binary::<S1T, S2T, F>(
|
||||
max_from,
|
||||
source1,
|
||||
source2,
|
||||
exit,
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
//! LazyComputedFromHeightCumulativeFull - block full with lazy height transform + cumulative + rolling.
|
||||
|
||||
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, Exit, LazyVecFrom1, ReadableCloneableVec, Rw, StorageMode, UnaryTransform};
|
||||
|
||||
use crate::{
|
||||
indexes,
|
||||
internal::{ComputedHeightDerivedCumulativeFull, ComputedVecValue, NumericValue, WindowStarts},
|
||||
};
|
||||
|
||||
const VERSION: Version = Version::ZERO;
|
||||
|
||||
/// Block full aggregation with lazy height transform + cumulative + rolling windows.
|
||||
#[derive(Deref, DerefMut, Traversable)]
|
||||
pub struct LazyComputedFromHeightFull<T, S = T, M: StorageMode = Rw>
|
||||
where
|
||||
T: NumericValue + JsonSchema,
|
||||
S: ComputedVecValue,
|
||||
{
|
||||
#[traversable(rename = "base")]
|
||||
pub height: LazyVecFrom1<Height, T, Height, S>,
|
||||
#[deref]
|
||||
#[deref_mut]
|
||||
pub rest: Box<ComputedHeightDerivedCumulativeFull<T, M>>,
|
||||
}
|
||||
|
||||
impl<T, S> LazyComputedFromHeightFull<T, S>
|
||||
where
|
||||
T: NumericValue + JsonSchema,
|
||||
S: ComputedVecValue + JsonSchema,
|
||||
{
|
||||
pub(crate) fn forced_import<F: UnaryTransform<S, T>>(
|
||||
db: &Database,
|
||||
name: &str,
|
||||
version: Version,
|
||||
source: &impl ReadableCloneableVec<Height, S>,
|
||||
indexes: &indexes::Vecs,
|
||||
) -> Result<Self> {
|
||||
let v = version + VERSION;
|
||||
|
||||
let height = LazyVecFrom1::transformed::<F>(name, v, source.read_only_boxed_clone());
|
||||
|
||||
let rest = ComputedHeightDerivedCumulativeFull::forced_import(db, name, v, indexes)?;
|
||||
|
||||
Ok(Self {
|
||||
height,
|
||||
rest: Box::new(rest),
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn compute(
|
||||
&mut self,
|
||||
max_from: Height,
|
||||
windows: &WindowStarts<'_>,
|
||||
exit: &Exit,
|
||||
) -> Result<()>
|
||||
where
|
||||
T: From<f64> + Default + SubAssign + Copy + Ord,
|
||||
f64: From<T>,
|
||||
{
|
||||
self.rest.compute(max_from, windows, &self.height, exit)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
use brk_error::Result;
|
||||
use brk_traversable::Traversable;
|
||||
use brk_types::{Dollars, Height, Sats, Version};
|
||||
use vecdb::{Database, Exit, Rw, StorageMode};
|
||||
|
||||
use crate::{
|
||||
indexes,
|
||||
internal::{ByUnit, SatsToDollars},
|
||||
prices,
|
||||
};
|
||||
|
||||
#[derive(Traversable)]
|
||||
pub struct LazyComputedValueFromHeightCumulative<M: StorageMode = Rw> {
|
||||
pub base: ByUnit<M>,
|
||||
pub cumulative: ByUnit<M>,
|
||||
}
|
||||
|
||||
const VERSION: Version = Version::ONE;
|
||||
|
||||
impl LazyComputedValueFromHeightCumulative {
|
||||
pub(crate) fn forced_import(
|
||||
db: &Database,
|
||||
name: &str,
|
||||
version: Version,
|
||||
indexes: &indexes::Vecs,
|
||||
) -> Result<Self> {
|
||||
let v = version + VERSION;
|
||||
|
||||
Ok(Self {
|
||||
base: ByUnit::forced_import(db, name, v, indexes)?,
|
||||
cumulative: ByUnit::forced_import(db, &format!("{name}_cumulative"), v, indexes)?,
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn compute(
|
||||
&mut self,
|
||||
prices: &prices::Vecs,
|
||||
max_from: Height,
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
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.height,
|
||||
exit,
|
||||
)?;
|
||||
|
||||
self.cumulative
|
||||
.usd
|
||||
.height
|
||||
.compute_cumulative(max_from, &self.base.usd.height, exit)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
//! Lazy unary transform from height with Last aggregation.
|
||||
|
||||
use brk_traversable::Traversable;
|
||||
use brk_types::{Height, Version};
|
||||
use derive_more::{Deref, DerefMut};
|
||||
use schemars::JsonSchema;
|
||||
use vecdb::{LazyVecFrom1, ReadableBoxedVec, ReadableCloneableVec, UnaryTransform};
|
||||
|
||||
use crate::{
|
||||
indexes,
|
||||
internal::{ComputedFromHeightLast, ComputedVecValue, LazyHeightDerivedLast, NumericValue},
|
||||
};
|
||||
#[derive(Clone, Deref, DerefMut, Traversable)]
|
||||
#[traversable(merge)]
|
||||
pub struct LazyFromHeightLast<T, S1T = T>
|
||||
where
|
||||
T: ComputedVecValue + PartialOrd + JsonSchema,
|
||||
S1T: ComputedVecValue,
|
||||
{
|
||||
pub height: LazyVecFrom1<Height, T, Height, S1T>,
|
||||
#[deref]
|
||||
#[deref_mut]
|
||||
#[traversable(flatten)]
|
||||
pub rest: Box<LazyHeightDerivedLast<T, S1T>>,
|
||||
}
|
||||
|
||||
const VERSION: Version = Version::ZERO;
|
||||
|
||||
impl<T, S1T> LazyFromHeightLast<T, S1T>
|
||||
where
|
||||
T: ComputedVecValue + JsonSchema + 'static,
|
||||
S1T: ComputedVecValue + JsonSchema,
|
||||
{
|
||||
pub(crate) fn from_computed<F: UnaryTransform<S1T, T>>(
|
||||
name: &str,
|
||||
version: Version,
|
||||
height_source: ReadableBoxedVec<Height, S1T>,
|
||||
source: &ComputedFromHeightLast<S1T>,
|
||||
) -> Self
|
||||
where
|
||||
S1T: NumericValue,
|
||||
{
|
||||
let v = version + VERSION;
|
||||
Self {
|
||||
height: LazyVecFrom1::transformed::<F>(name, v, height_source),
|
||||
rest: Box::new(LazyHeightDerivedLast::from_computed::<F>(name, v, source)),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn from_height_source<F: UnaryTransform<S1T, T>>(
|
||||
name: &str,
|
||||
version: Version,
|
||||
height_source: ReadableBoxedVec<Height, S1T>,
|
||||
indexes: &indexes::Vecs,
|
||||
) -> Self
|
||||
where
|
||||
S1T: NumericValue,
|
||||
{
|
||||
let v = version + VERSION;
|
||||
Self {
|
||||
height: LazyVecFrom1::transformed::<F>(name, v, height_source.clone()),
|
||||
rest: Box::new(LazyHeightDerivedLast::from_height_source::<F>(
|
||||
name,
|
||||
v,
|
||||
height_source,
|
||||
indexes,
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create by unary-transforming a LazyFromHeightLast source (chaining lazy vecs).
|
||||
pub(crate) fn from_lazy<F, S2T>(
|
||||
name: &str,
|
||||
version: Version,
|
||||
source: &LazyFromHeightLast<S1T, S2T>,
|
||||
) -> Self
|
||||
where
|
||||
F: UnaryTransform<S1T, T>,
|
||||
S2T: ComputedVecValue + JsonSchema,
|
||||
{
|
||||
let v = version + VERSION;
|
||||
Self {
|
||||
height: LazyVecFrom1::transformed::<F>(name, v, source.height.read_only_boxed_clone()),
|
||||
rest: Box::new(LazyHeightDerivedLast::from_lazy::<F, S2T>(
|
||||
name,
|
||||
v,
|
||||
&source.rest,
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
//! Lazy value wrapper for ValueFromHeightLast - all transforms are lazy.
|
||||
|
||||
use brk_traversable::Traversable;
|
||||
use brk_types::{Bitcoin, Dollars, Sats, Version};
|
||||
use derive_more::{Deref, DerefMut};
|
||||
use vecdb::UnaryTransform;
|
||||
|
||||
use crate::internal::{LazyValueFromHeight, LazyValueHeightDerivedLast, ValueFromHeightLast};
|
||||
|
||||
const VERSION: Version = Version::ZERO;
|
||||
|
||||
/// Lazy value wrapper with height + all derived last transforms from ValueFromHeightLast.
|
||||
#[derive(Clone, Deref, DerefMut, Traversable)]
|
||||
#[traversable(merge)]
|
||||
pub struct LazyValueFromHeightLast {
|
||||
#[traversable(flatten)]
|
||||
pub height: LazyValueFromHeight,
|
||||
#[deref]
|
||||
#[deref_mut]
|
||||
#[traversable(flatten)]
|
||||
pub rest: Box<LazyValueHeightDerivedLast>,
|
||||
}
|
||||
|
||||
impl LazyValueFromHeightLast {
|
||||
pub(crate) fn from_block_source<SatsTransform, BitcoinTransform, DollarsTransform>(
|
||||
name: &str,
|
||||
source: &ValueFromHeightLast,
|
||||
version: Version,
|
||||
) -> Self
|
||||
where
|
||||
SatsTransform: UnaryTransform<Sats, Sats>,
|
||||
BitcoinTransform: UnaryTransform<Sats, Bitcoin>,
|
||||
DollarsTransform: UnaryTransform<Dollars, Dollars>,
|
||||
{
|
||||
let v = version + VERSION;
|
||||
|
||||
let height =
|
||||
LazyValueFromHeight::from_block_source::<SatsTransform, BitcoinTransform, DollarsTransform>(name, source, v);
|
||||
|
||||
let rest =
|
||||
LazyValueHeightDerivedLast::from_block_source::<SatsTransform, BitcoinTransform, DollarsTransform>(
|
||||
name, source, v,
|
||||
);
|
||||
|
||||
Self { height, rest: Box::new(rest) }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
mod by_unit;
|
||||
mod constant;
|
||||
mod cumulative;
|
||||
mod cumulative_full;
|
||||
mod cumulative_sum;
|
||||
mod distribution;
|
||||
mod full;
|
||||
mod last;
|
||||
mod lazy_computed_full;
|
||||
mod lazy_computed_value_cumulative;
|
||||
mod lazy_last;
|
||||
mod lazy_value_last;
|
||||
mod percentiles;
|
||||
mod price;
|
||||
mod ratio;
|
||||
mod stddev;
|
||||
mod stored_value_last;
|
||||
mod value_change;
|
||||
mod value_ema;
|
||||
mod value_full;
|
||||
mod value_last;
|
||||
mod value_last_rolling;
|
||||
mod value_sum_cumulative;
|
||||
|
||||
pub use by_unit::*;
|
||||
pub use constant::*;
|
||||
pub use cumulative::*;
|
||||
pub use cumulative_full::*;
|
||||
pub use cumulative_sum::*;
|
||||
pub use distribution::*;
|
||||
pub use full::*;
|
||||
pub use last::*;
|
||||
pub use lazy_computed_full::*;
|
||||
pub use lazy_computed_value_cumulative::*;
|
||||
pub use lazy_last::*;
|
||||
pub use lazy_value_last::*;
|
||||
pub use percentiles::*;
|
||||
pub use price::*;
|
||||
pub use ratio::*;
|
||||
pub use stddev::*;
|
||||
pub use stored_value_last::*;
|
||||
pub use value_change::*;
|
||||
pub use value_ema::*;
|
||||
pub use value_full::*;
|
||||
pub use value_last::*;
|
||||
pub use value_last_rolling::*;
|
||||
pub use value_sum_cumulative::*;
|
||||
@@ -0,0 +1,150 @@
|
||||
use brk_error::Result;
|
||||
use brk_traversable::{Traversable, TreeNode};
|
||||
use brk_types::{Dollars, Height, StoredF32, Version};
|
||||
use vecdb::{AnyExportableVec, Database, ReadOnlyClone, Ro, Rw, StorageMode, WritableVec};
|
||||
|
||||
use crate::indexes;
|
||||
use crate::internal::{ComputedFromHeightLast, Price};
|
||||
|
||||
pub const PERCENTILES: [u8; 19] = [
|
||||
5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90, 95,
|
||||
];
|
||||
pub const PERCENTILES_LEN: usize = PERCENTILES.len();
|
||||
|
||||
/// Compute spot percentile rank by interpolating within percentile bands.
|
||||
/// Returns a value between 0 and 100 indicating where spot sits in the distribution.
|
||||
pub(crate) fn compute_spot_percentile_rank(
|
||||
percentile_prices: &[Dollars; PERCENTILES_LEN],
|
||||
spot: Dollars,
|
||||
) -> StoredF32 {
|
||||
if spot.is_nan() || percentile_prices[0].is_nan() {
|
||||
return StoredF32::NAN;
|
||||
}
|
||||
|
||||
let spot_f64 = f64::from(spot);
|
||||
|
||||
// Below lowest percentile (p5) - extrapolate towards 0
|
||||
let p5 = f64::from(percentile_prices[0]);
|
||||
if spot_f64 <= p5 {
|
||||
if p5 == 0.0 {
|
||||
return StoredF32::from(0.0);
|
||||
}
|
||||
// Linear extrapolation: rank = 5 * (spot / p5)
|
||||
return StoredF32::from((5.0 * spot_f64 / p5).max(0.0));
|
||||
}
|
||||
|
||||
// Above highest percentile (p95) - extrapolate towards 100
|
||||
let p95 = f64::from(percentile_prices[PERCENTILES_LEN - 1]);
|
||||
let p90 = f64::from(percentile_prices[PERCENTILES_LEN - 2]);
|
||||
if spot_f64 >= p95 {
|
||||
if p95 == p90 {
|
||||
return StoredF32::from(100.0);
|
||||
}
|
||||
// Linear extrapolation using p90-p95 slope
|
||||
let slope = 5.0 / (p95 - p90);
|
||||
return StoredF32::from((95.0 + (spot_f64 - p95) * slope).min(100.0));
|
||||
}
|
||||
|
||||
// Find the band containing spot and interpolate
|
||||
for i in 0..PERCENTILES_LEN - 1 {
|
||||
let lower = f64::from(percentile_prices[i]);
|
||||
let upper = f64::from(percentile_prices[i + 1]);
|
||||
|
||||
if spot_f64 >= lower && spot_f64 <= upper {
|
||||
let lower_pct = f64::from(PERCENTILES[i]);
|
||||
let upper_pct = f64::from(PERCENTILES[i + 1]);
|
||||
|
||||
if upper == lower {
|
||||
return StoredF32::from(lower_pct);
|
||||
}
|
||||
|
||||
// Linear interpolation
|
||||
let ratio = (spot_f64 - lower) / (upper - lower);
|
||||
return StoredF32::from(lower_pct + ratio * (upper_pct - lower_pct));
|
||||
}
|
||||
}
|
||||
|
||||
StoredF32::NAN
|
||||
}
|
||||
|
||||
pub struct PercentilesVecs<M: StorageMode = Rw> {
|
||||
pub vecs: [Price<ComputedFromHeightLast<Dollars, M>>; PERCENTILES_LEN],
|
||||
}
|
||||
|
||||
const VERSION: Version = Version::ONE;
|
||||
|
||||
impl PercentilesVecs {
|
||||
pub(crate) fn forced_import(
|
||||
db: &Database,
|
||||
prefix: &str,
|
||||
version: Version,
|
||||
indexes: &indexes::Vecs,
|
||||
) -> Result<Self> {
|
||||
let vecs = PERCENTILES
|
||||
.into_iter()
|
||||
.map(|p| {
|
||||
let metric_name = format!("{prefix}_pct{p:02}");
|
||||
Price::forced_import(db, &metric_name, version + VERSION, indexes)
|
||||
})
|
||||
.collect::<Result<Vec<_>>>()?
|
||||
.try_into()
|
||||
.ok()
|
||||
.expect("PERCENTILES length mismatch");
|
||||
|
||||
Ok(Self { vecs })
|
||||
}
|
||||
|
||||
/// Push percentile prices at this height.
|
||||
pub(crate) fn truncate_push(
|
||||
&mut self,
|
||||
height: Height,
|
||||
percentile_prices: &[Dollars; PERCENTILES_LEN],
|
||||
) -> Result<()> {
|
||||
for (i, v) in self.vecs.iter_mut().enumerate() {
|
||||
v.usd.height.truncate_push(height, percentile_prices[i])?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Validate computed versions or reset if mismatched.
|
||||
pub(crate) fn validate_computed_version_or_reset(&mut self, version: Version) -> Result<()> {
|
||||
for vec in self.vecs.iter_mut() {
|
||||
vec.usd.height.validate_computed_version_or_reset(version)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl ReadOnlyClone for PercentilesVecs {
|
||||
type ReadOnly = PercentilesVecs<Ro>;
|
||||
|
||||
fn read_only_clone(&self) -> Self::ReadOnly {
|
||||
PercentilesVecs {
|
||||
vecs: self
|
||||
.vecs
|
||||
.each_ref()
|
||||
.map(|v| v.read_only_clone()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<M: StorageMode> Traversable for PercentilesVecs<M>
|
||||
where
|
||||
Price<ComputedFromHeightLast<Dollars, M>>: Traversable,
|
||||
{
|
||||
fn to_tree_node(&self) -> TreeNode {
|
||||
TreeNode::Branch(
|
||||
PERCENTILES
|
||||
.iter()
|
||||
.zip(self.vecs.iter())
|
||||
.map(|(p, v)| (format!("pct{p:02}"), v.to_tree_node()))
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
|
||||
fn iter_any_exportable(&self) -> impl Iterator<Item = &dyn AnyExportableVec> {
|
||||
self.vecs
|
||||
.iter()
|
||||
.flat_map(|p| p.iter_any_exportable())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
//! Generic price wrapper with both USD and sats representations.
|
||||
//!
|
||||
//! All prices use this single struct with different USD types.
|
||||
//! Sats is always lazily derived from USD via DollarsToSatsFract.
|
||||
|
||||
use brk_error::Result;
|
||||
use brk_traversable::Traversable;
|
||||
use brk_types::{Dollars, SatsFract, Version};
|
||||
use schemars::JsonSchema;
|
||||
use vecdb::{Database, ReadableCloneableVec, UnaryTransform};
|
||||
|
||||
use super::{ComputedFromHeightLast, LazyFromHeightLast};
|
||||
use crate::{
|
||||
indexes,
|
||||
internal::{ComputedVecValue, DollarsToSatsFract, NumericValue},
|
||||
};
|
||||
|
||||
/// Generic price metric with both USD and sats representations.
|
||||
#[derive(Clone, Traversable)]
|
||||
pub struct Price<U> {
|
||||
pub usd: U,
|
||||
pub sats: LazyFromHeightLast<SatsFract, Dollars>,
|
||||
}
|
||||
|
||||
impl Price<ComputedFromHeightLast<Dollars>> {
|
||||
pub(crate) fn forced_import(
|
||||
db: &Database,
|
||||
name: &str,
|
||||
version: Version,
|
||||
indexes: &indexes::Vecs,
|
||||
) -> Result<Self> {
|
||||
let usd = ComputedFromHeightLast::forced_import(db, name, version, indexes)?;
|
||||
let sats = LazyFromHeightLast::from_computed::<DollarsToSatsFract>(
|
||||
&format!("{name}_sats"),
|
||||
version,
|
||||
usd.height.read_only_boxed_clone(),
|
||||
&usd,
|
||||
);
|
||||
Ok(Self { usd, sats })
|
||||
}
|
||||
}
|
||||
|
||||
impl<ST> Price<LazyFromHeightLast<Dollars, ST>>
|
||||
where
|
||||
ST: ComputedVecValue + NumericValue + JsonSchema + 'static,
|
||||
{
|
||||
pub(crate) fn from_computed<F: UnaryTransform<ST, Dollars>>(
|
||||
name: &str,
|
||||
version: Version,
|
||||
source: &ComputedFromHeightLast<ST>,
|
||||
) -> Self {
|
||||
let usd = LazyFromHeightLast::from_computed::<F>(
|
||||
name,
|
||||
version,
|
||||
source.height.read_only_boxed_clone(),
|
||||
source,
|
||||
);
|
||||
let sats = LazyFromHeightLast::from_lazy::<DollarsToSatsFract, ST>(
|
||||
&format!("{name}_sats"),
|
||||
version,
|
||||
&usd,
|
||||
);
|
||||
Self { usd, sats }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
use brk_error::Result;
|
||||
use brk_traversable::Traversable;
|
||||
use brk_types::{Dollars, Height, Version};
|
||||
use derive_more::{Deref, DerefMut};
|
||||
use vecdb::{Database, Exit, ReadableVec, Rw, StorageMode};
|
||||
|
||||
use crate::{ComputeIndexes, blocks, indexes, prices};
|
||||
|
||||
use super::{ComputedFromHeightRatio, ComputedFromHeightRatioExtension};
|
||||
|
||||
#[derive(Deref, DerefMut, Traversable)]
|
||||
pub struct ComputedFromHeightRatioExtended<M: StorageMode = Rw> {
|
||||
#[deref]
|
||||
#[deref_mut]
|
||||
#[traversable(flatten)]
|
||||
pub base: ComputedFromHeightRatio<M>,
|
||||
#[traversable(flatten)]
|
||||
pub extended: ComputedFromHeightRatioExtension<M>,
|
||||
}
|
||||
|
||||
impl ComputedFromHeightRatioExtended {
|
||||
pub(crate) fn forced_import(
|
||||
db: &Database,
|
||||
name: &str,
|
||||
version: Version,
|
||||
indexes: &indexes::Vecs,
|
||||
) -> Result<Self> {
|
||||
Ok(Self {
|
||||
base: ComputedFromHeightRatio::forced_import(db, name, version, indexes)?,
|
||||
extended: ComputedFromHeightRatioExtension::forced_import(db, name, version, indexes)?,
|
||||
})
|
||||
}
|
||||
|
||||
/// Compute ratio and all extended metrics from an externally-provided metric price.
|
||||
pub(crate) fn compute_rest(
|
||||
&mut self,
|
||||
blocks: &blocks::Vecs,
|
||||
prices: &prices::Vecs,
|
||||
starting_indexes: &ComputeIndexes,
|
||||
exit: &Exit,
|
||||
metric_price: &impl ReadableVec<Height, Dollars>,
|
||||
) -> Result<()> {
|
||||
let close_price = &prices.price.usd.height;
|
||||
self.base
|
||||
.compute_ratio(starting_indexes, close_price, metric_price, exit)?;
|
||||
self.extended
|
||||
.compute_rest(blocks, starting_indexes, exit, &self.base.ratio.height)?;
|
||||
self.extended
|
||||
.compute_usd_bands(starting_indexes, metric_price, exit)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,277 @@
|
||||
use brk_error::Result;
|
||||
use brk_traversable::Traversable;
|
||||
use brk_types::{Dollars, Height, StoredF32, Version};
|
||||
use vecdb::{AnyStoredVec, AnyVec, Database, EagerVec, Exit, PcoVec, ReadableVec, Rw, StorageMode, VecIndex, WritableVec};
|
||||
|
||||
use crate::{
|
||||
ComputeIndexes, blocks, indexes,
|
||||
internal::{ComputedFromHeightStdDevExtended, Price},
|
||||
};
|
||||
use brk_types::get_percentile;
|
||||
|
||||
use super::super::ComputedFromHeightLast;
|
||||
|
||||
#[derive(Traversable)]
|
||||
pub struct ComputedFromHeightRatioExtension<M: StorageMode = Rw> {
|
||||
pub ratio_1w_sma: ComputedFromHeightLast<StoredF32, M>,
|
||||
pub ratio_1m_sma: ComputedFromHeightLast<StoredF32, M>,
|
||||
pub ratio_pct99: ComputedFromHeightLast<StoredF32, M>,
|
||||
pub ratio_pct98: ComputedFromHeightLast<StoredF32, M>,
|
||||
pub ratio_pct95: ComputedFromHeightLast<StoredF32, M>,
|
||||
pub ratio_pct5: ComputedFromHeightLast<StoredF32, M>,
|
||||
pub ratio_pct2: ComputedFromHeightLast<StoredF32, M>,
|
||||
pub ratio_pct1: ComputedFromHeightLast<StoredF32, M>,
|
||||
pub ratio_pct99_usd: Price<ComputedFromHeightLast<Dollars, M>>,
|
||||
pub ratio_pct98_usd: Price<ComputedFromHeightLast<Dollars, M>>,
|
||||
pub ratio_pct95_usd: Price<ComputedFromHeightLast<Dollars, M>>,
|
||||
pub ratio_pct5_usd: Price<ComputedFromHeightLast<Dollars, M>>,
|
||||
pub ratio_pct2_usd: Price<ComputedFromHeightLast<Dollars, M>>,
|
||||
pub ratio_pct1_usd: Price<ComputedFromHeightLast<Dollars, M>>,
|
||||
|
||||
pub ratio_sd: ComputedFromHeightStdDevExtended<M>,
|
||||
pub ratio_4y_sd: ComputedFromHeightStdDevExtended<M>,
|
||||
pub ratio_2y_sd: ComputedFromHeightStdDevExtended<M>,
|
||||
pub ratio_1y_sd: ComputedFromHeightStdDevExtended<M>,
|
||||
}
|
||||
|
||||
const VERSION: Version = Version::TWO;
|
||||
|
||||
impl ComputedFromHeightRatioExtension {
|
||||
pub(crate) fn forced_import(
|
||||
db: &Database,
|
||||
name: &str,
|
||||
version: Version,
|
||||
indexes: &indexes::Vecs,
|
||||
) -> Result<Self> {
|
||||
let v = version + VERSION;
|
||||
|
||||
macro_rules! import {
|
||||
($suffix:expr) => {
|
||||
ComputedFromHeightLast::forced_import(
|
||||
db,
|
||||
&format!("{name}_{}", $suffix),
|
||||
v,
|
||||
indexes,
|
||||
)?
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! import_sd {
|
||||
($suffix:expr, $days:expr) => {
|
||||
ComputedFromHeightStdDevExtended::forced_import(
|
||||
db,
|
||||
&format!("{name}_{}", $suffix),
|
||||
$days,
|
||||
v,
|
||||
indexes,
|
||||
)?
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! import_usd {
|
||||
($suffix:expr) => {
|
||||
Price::forced_import(db, &format!("{name}_{}", $suffix), v, indexes)?
|
||||
};
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
ratio_1w_sma: import!("ratio_1w_sma"),
|
||||
ratio_1m_sma: import!("ratio_1m_sma"),
|
||||
ratio_sd: import_sd!("ratio", usize::MAX),
|
||||
ratio_1y_sd: import_sd!("ratio_1y", 365),
|
||||
ratio_2y_sd: import_sd!("ratio_2y", 2 * 365),
|
||||
ratio_4y_sd: import_sd!("ratio_4y", 4 * 365),
|
||||
ratio_pct99: import!("ratio_pct99"),
|
||||
ratio_pct98: import!("ratio_pct98"),
|
||||
ratio_pct95: import!("ratio_pct95"),
|
||||
ratio_pct5: import!("ratio_pct5"),
|
||||
ratio_pct2: import!("ratio_pct2"),
|
||||
ratio_pct1: import!("ratio_pct1"),
|
||||
ratio_pct99_usd: import_usd!("ratio_pct99_usd"),
|
||||
ratio_pct98_usd: import_usd!("ratio_pct98_usd"),
|
||||
ratio_pct95_usd: import_usd!("ratio_pct95_usd"),
|
||||
ratio_pct5_usd: import_usd!("ratio_pct5_usd"),
|
||||
ratio_pct2_usd: import_usd!("ratio_pct2_usd"),
|
||||
ratio_pct1_usd: import_usd!("ratio_pct1_usd"),
|
||||
})
|
||||
}
|
||||
|
||||
/// Compute extended ratio metrics from an externally-provided ratio source.
|
||||
pub(crate) fn compute_rest(
|
||||
&mut self,
|
||||
blocks: &blocks::Vecs,
|
||||
starting_indexes: &ComputeIndexes,
|
||||
exit: &Exit,
|
||||
ratio_source: &impl ReadableVec<Height, StoredF32>,
|
||||
) -> Result<()> {
|
||||
// SMA using lookback vecs
|
||||
self.ratio_1w_sma.height.compute_rolling_average(
|
||||
starting_indexes.height,
|
||||
&blocks.count.height_1w_ago,
|
||||
ratio_source,
|
||||
exit,
|
||||
)?;
|
||||
|
||||
self.ratio_1m_sma.height.compute_rolling_average(
|
||||
starting_indexes.height,
|
||||
&blocks.count.height_1m_ago,
|
||||
ratio_source,
|
||||
exit,
|
||||
)?;
|
||||
|
||||
// Percentiles: insert into sorted array on day boundaries
|
||||
let ratio_version = ratio_source.version();
|
||||
self.mut_ratio_vecs()
|
||||
.try_for_each(|v| -> Result<()> {
|
||||
v.validate_computed_version_or_reset(ratio_version)?;
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
let starting_height = self
|
||||
.mut_ratio_vecs()
|
||||
.map(|v| Height::from(v.len()))
|
||||
.min()
|
||||
.unwrap()
|
||||
.min(starting_indexes.height);
|
||||
|
||||
let start = starting_height.to_usize();
|
||||
let day_start = &blocks.count.height_24h_ago;
|
||||
|
||||
// Collect sorted history up to starting point (one per day boundary)
|
||||
let mut sorted = {
|
||||
let ratio_data = ratio_source.collect_range_at(0, start);
|
||||
let day_start_hist = day_start.collect_range_at(0, start);
|
||||
let mut sorted: Vec<StoredF32> = Vec::new();
|
||||
let mut last_day_start = Height::from(0_usize);
|
||||
for (h, ratio) in ratio_data.into_iter().enumerate() {
|
||||
let cur_day_start = day_start_hist[h];
|
||||
if h == 0 || cur_day_start != last_day_start {
|
||||
sorted.push(ratio);
|
||||
last_day_start = cur_day_start;
|
||||
}
|
||||
}
|
||||
sorted.sort_unstable();
|
||||
sorted
|
||||
};
|
||||
|
||||
let pct1_vec = &mut self.ratio_pct1.height;
|
||||
let pct2_vec = &mut self.ratio_pct2.height;
|
||||
let pct5_vec = &mut self.ratio_pct5.height;
|
||||
let pct95_vec = &mut self.ratio_pct95.height;
|
||||
let pct98_vec = &mut self.ratio_pct98.height;
|
||||
let pct99_vec = &mut self.ratio_pct99.height;
|
||||
|
||||
let ratio_len = ratio_source.len();
|
||||
let ratio_data = ratio_source.collect_range_at(start, ratio_len);
|
||||
let mut last_day_start = if start > 0 {
|
||||
day_start
|
||||
.collect_one_at(start - 1)
|
||||
.unwrap_or(Height::from(0_usize))
|
||||
} else {
|
||||
Height::from(0_usize)
|
||||
};
|
||||
|
||||
let day_start_data = day_start.collect_range_at(start, ratio_len);
|
||||
|
||||
for (offset, ratio) in ratio_data.into_iter().enumerate() {
|
||||
let index = start + offset;
|
||||
|
||||
let cur_day_start = day_start_data[offset];
|
||||
if index == 0 || cur_day_start != last_day_start {
|
||||
let pos = sorted.binary_search(&ratio).unwrap_or_else(|p| p);
|
||||
sorted.insert(pos, ratio);
|
||||
last_day_start = cur_day_start;
|
||||
}
|
||||
|
||||
if sorted.is_empty() {
|
||||
pct1_vec.truncate_push_at(index, StoredF32::NAN)?;
|
||||
pct2_vec.truncate_push_at(index, StoredF32::NAN)?;
|
||||
pct5_vec.truncate_push_at(index, StoredF32::NAN)?;
|
||||
pct95_vec.truncate_push_at(index, StoredF32::NAN)?;
|
||||
pct98_vec.truncate_push_at(index, StoredF32::NAN)?;
|
||||
pct99_vec.truncate_push_at(index, StoredF32::NAN)?;
|
||||
} else {
|
||||
pct1_vec.truncate_push_at(index, get_percentile(&sorted, 0.01))?;
|
||||
pct2_vec.truncate_push_at(index, get_percentile(&sorted, 0.02))?;
|
||||
pct5_vec.truncate_push_at(index, get_percentile(&sorted, 0.05))?;
|
||||
pct95_vec.truncate_push_at(index, get_percentile(&sorted, 0.95))?;
|
||||
pct98_vec.truncate_push_at(index, get_percentile(&sorted, 0.98))?;
|
||||
pct99_vec.truncate_push_at(index, get_percentile(&sorted, 0.99))?;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
let _lock = exit.lock();
|
||||
self.mut_ratio_vecs()
|
||||
.try_for_each(|v| v.flush())?;
|
||||
}
|
||||
|
||||
// Compute stddev at height level
|
||||
self.ratio_sd
|
||||
.compute_all(blocks, starting_indexes, exit, ratio_source)?;
|
||||
self.ratio_4y_sd
|
||||
.compute_all(blocks, starting_indexes, exit, ratio_source)?;
|
||||
self.ratio_2y_sd
|
||||
.compute_all(blocks, starting_indexes, exit, ratio_source)?;
|
||||
self.ratio_1y_sd
|
||||
.compute_all(blocks, starting_indexes, exit, ratio_source)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Compute USD ratio bands: usd_band = metric_price * ratio_percentile
|
||||
pub(crate) fn compute_usd_bands(
|
||||
&mut self,
|
||||
starting_indexes: &ComputeIndexes,
|
||||
metric_price: &impl ReadableVec<Height, Dollars>,
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
use crate::internal::PriceTimesRatio;
|
||||
|
||||
macro_rules! compute_band {
|
||||
($usd_field:ident, $band_source:expr) => {
|
||||
self.$usd_field
|
||||
.usd
|
||||
.compute_binary::<Dollars, StoredF32, PriceTimesRatio>(
|
||||
starting_indexes.height,
|
||||
metric_price,
|
||||
$band_source,
|
||||
exit,
|
||||
)?;
|
||||
};
|
||||
}
|
||||
|
||||
compute_band!(ratio_pct99_usd, &self.ratio_pct99.height);
|
||||
compute_band!(ratio_pct98_usd, &self.ratio_pct98.height);
|
||||
compute_band!(ratio_pct95_usd, &self.ratio_pct95.height);
|
||||
compute_band!(ratio_pct5_usd, &self.ratio_pct5.height);
|
||||
compute_band!(ratio_pct2_usd, &self.ratio_pct2.height);
|
||||
compute_band!(ratio_pct1_usd, &self.ratio_pct1.height);
|
||||
|
||||
// Stddev USD bands
|
||||
self.ratio_sd
|
||||
.compute_usd_bands(starting_indexes, metric_price, exit)?;
|
||||
self.ratio_4y_sd
|
||||
.compute_usd_bands(starting_indexes, metric_price, exit)?;
|
||||
self.ratio_2y_sd
|
||||
.compute_usd_bands(starting_indexes, metric_price, exit)?;
|
||||
self.ratio_1y_sd
|
||||
.compute_usd_bands(starting_indexes, metric_price, exit)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn mut_ratio_vecs(
|
||||
&mut self,
|
||||
) -> impl Iterator<Item = &mut EagerVec<PcoVec<Height, StoredF32>>> {
|
||||
[
|
||||
&mut self.ratio_pct1.height,
|
||||
&mut self.ratio_pct2.height,
|
||||
&mut self.ratio_pct5.height,
|
||||
&mut self.ratio_pct95.height,
|
||||
&mut self.ratio_pct98.height,
|
||||
&mut self.ratio_pct99.height,
|
||||
]
|
||||
.into_iter()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
mod extended;
|
||||
mod extension;
|
||||
mod price_extended;
|
||||
|
||||
pub use extended::*;
|
||||
pub use extension::*;
|
||||
pub use price_extended::*;
|
||||
|
||||
use brk_error::Result;
|
||||
use brk_traversable::Traversable;
|
||||
use brk_types::{Dollars, Height, StoredF32, Version};
|
||||
use vecdb::{Database, Exit, ReadableVec, Rw, StorageMode};
|
||||
|
||||
use crate::{ComputeIndexes, indexes};
|
||||
|
||||
use super::ComputedFromHeightLast;
|
||||
|
||||
#[derive(Traversable)]
|
||||
pub struct ComputedFromHeightRatio<M: StorageMode = Rw> {
|
||||
pub ratio: ComputedFromHeightLast<StoredF32, M>,
|
||||
}
|
||||
|
||||
const VERSION: Version = Version::TWO;
|
||||
|
||||
impl ComputedFromHeightRatio {
|
||||
pub(crate) fn forced_import(
|
||||
db: &Database,
|
||||
name: &str,
|
||||
version: Version,
|
||||
indexes: &indexes::Vecs,
|
||||
) -> Result<Self> {
|
||||
let v = version + VERSION;
|
||||
|
||||
Ok(Self {
|
||||
ratio: ComputedFromHeightLast::forced_import(db, &format!("{name}_ratio"), v, indexes)?,
|
||||
})
|
||||
}
|
||||
|
||||
/// Compute ratio = close_price / metric_price at height level
|
||||
pub(crate) fn compute_ratio(
|
||||
&mut self,
|
||||
starting_indexes: &ComputeIndexes,
|
||||
close_price: &impl ReadableVec<Height, Dollars>,
|
||||
metric_price: &impl ReadableVec<Height, Dollars>,
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
self.ratio.height.compute_transform2(
|
||||
starting_indexes.height,
|
||||
close_price,
|
||||
metric_price,
|
||||
|(i, close, price, ..)| {
|
||||
if price == Dollars::ZERO {
|
||||
(i, StoredF32::from(1.0))
|
||||
} else {
|
||||
(i, StoredF32::from(close / price))
|
||||
}
|
||||
},
|
||||
exit,
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
use brk_error::Result;
|
||||
use brk_traversable::Traversable;
|
||||
use brk_types::{Dollars, Height, Version};
|
||||
use derive_more::{Deref, DerefMut};
|
||||
use vecdb::{Database, EagerVec, Exit, PcoVec, Rw, StorageMode};
|
||||
|
||||
use crate::internal::{ComputedFromHeightLast, Price};
|
||||
use crate::{ComputeIndexes, blocks, indexes, prices};
|
||||
|
||||
use super::ComputedFromHeightRatioExtended;
|
||||
|
||||
#[derive(Deref, DerefMut, Traversable)]
|
||||
pub struct ComputedFromHeightPriceWithRatioExtended<M: StorageMode = Rw> {
|
||||
#[deref]
|
||||
#[deref_mut]
|
||||
#[traversable(flatten)]
|
||||
pub inner: ComputedFromHeightRatioExtended<M>,
|
||||
pub price: Price<ComputedFromHeightLast<Dollars, M>>,
|
||||
}
|
||||
|
||||
impl ComputedFromHeightPriceWithRatioExtended {
|
||||
pub(crate) fn forced_import(
|
||||
db: &Database,
|
||||
name: &str,
|
||||
version: Version,
|
||||
indexes: &indexes::Vecs,
|
||||
) -> Result<Self> {
|
||||
let v = version + Version::TWO;
|
||||
Ok(Self {
|
||||
inner: ComputedFromHeightRatioExtended::forced_import(db, name, version, indexes)?,
|
||||
price: Price::forced_import(db, name, v, indexes)?,
|
||||
})
|
||||
}
|
||||
|
||||
/// Compute price via closure, then compute ratio + extended metrics.
|
||||
pub(crate) fn compute_all<F>(
|
||||
&mut self,
|
||||
blocks: &blocks::Vecs,
|
||||
prices: &prices::Vecs,
|
||||
starting_indexes: &ComputeIndexes,
|
||||
exit: &Exit,
|
||||
mut compute_price: F,
|
||||
) -> Result<()>
|
||||
where
|
||||
F: FnMut(&mut EagerVec<PcoVec<Height, Dollars>>) -> Result<()>,
|
||||
{
|
||||
compute_price(&mut self.price.usd.height)?;
|
||||
self.inner.compute_rest(
|
||||
blocks,
|
||||
prices,
|
||||
starting_indexes,
|
||||
exit,
|
||||
&self.price.usd.height,
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,278 @@
|
||||
use brk_error::Result;
|
||||
use brk_traversable::Traversable;
|
||||
use brk_types::{Dollars, Height, StoredF32, Version};
|
||||
use vecdb::{
|
||||
AnyStoredVec, AnyVec, Database, EagerVec, Exit, PcoVec, ReadableVec, Rw, StorageMode, VecIndex,
|
||||
WritableVec,
|
||||
};
|
||||
|
||||
use crate::{ComputeIndexes, blocks, indexes};
|
||||
|
||||
use crate::internal::{ComputedFromHeightLast, Price};
|
||||
|
||||
use super::ComputedFromHeightStdDev;
|
||||
|
||||
#[derive(Traversable)]
|
||||
pub struct ComputedFromHeightStdDevExtended<M: StorageMode = Rw> {
|
||||
#[traversable(flatten)]
|
||||
pub base: ComputedFromHeightStdDev<M>,
|
||||
|
||||
pub zscore: ComputedFromHeightLast<StoredF32, M>,
|
||||
|
||||
pub p0_5sd: ComputedFromHeightLast<StoredF32, M>,
|
||||
pub p1sd: ComputedFromHeightLast<StoredF32, M>,
|
||||
pub p1_5sd: ComputedFromHeightLast<StoredF32, M>,
|
||||
pub p2sd: ComputedFromHeightLast<StoredF32, M>,
|
||||
pub p2_5sd: ComputedFromHeightLast<StoredF32, M>,
|
||||
pub p3sd: ComputedFromHeightLast<StoredF32, M>,
|
||||
pub m0_5sd: ComputedFromHeightLast<StoredF32, M>,
|
||||
pub m1sd: ComputedFromHeightLast<StoredF32, M>,
|
||||
pub m1_5sd: ComputedFromHeightLast<StoredF32, M>,
|
||||
pub m2sd: ComputedFromHeightLast<StoredF32, M>,
|
||||
pub m2_5sd: ComputedFromHeightLast<StoredF32, M>,
|
||||
pub m3sd: ComputedFromHeightLast<StoredF32, M>,
|
||||
|
||||
pub _0sd_usd: Price<ComputedFromHeightLast<Dollars, M>>,
|
||||
pub p0_5sd_usd: Price<ComputedFromHeightLast<Dollars, M>>,
|
||||
pub p1sd_usd: Price<ComputedFromHeightLast<Dollars, M>>,
|
||||
pub p1_5sd_usd: Price<ComputedFromHeightLast<Dollars, M>>,
|
||||
pub p2sd_usd: Price<ComputedFromHeightLast<Dollars, M>>,
|
||||
pub p2_5sd_usd: Price<ComputedFromHeightLast<Dollars, M>>,
|
||||
pub p3sd_usd: Price<ComputedFromHeightLast<Dollars, M>>,
|
||||
pub m0_5sd_usd: Price<ComputedFromHeightLast<Dollars, M>>,
|
||||
pub m1sd_usd: Price<ComputedFromHeightLast<Dollars, M>>,
|
||||
pub m1_5sd_usd: Price<ComputedFromHeightLast<Dollars, M>>,
|
||||
pub m2sd_usd: Price<ComputedFromHeightLast<Dollars, M>>,
|
||||
pub m2_5sd_usd: Price<ComputedFromHeightLast<Dollars, M>>,
|
||||
pub m3sd_usd: Price<ComputedFromHeightLast<Dollars, M>>,
|
||||
}
|
||||
|
||||
impl ComputedFromHeightStdDevExtended {
|
||||
pub(crate) fn forced_import(
|
||||
db: &Database,
|
||||
name: &str,
|
||||
days: usize,
|
||||
parent_version: Version,
|
||||
indexes: &indexes::Vecs,
|
||||
) -> Result<Self> {
|
||||
let version = parent_version + Version::TWO;
|
||||
|
||||
macro_rules! import {
|
||||
($suffix:expr) => {
|
||||
ComputedFromHeightLast::forced_import(
|
||||
db,
|
||||
&format!("{name}_{}", $suffix),
|
||||
version,
|
||||
indexes,
|
||||
)?
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! import_usd {
|
||||
($suffix:expr) => {
|
||||
Price::forced_import(db, &format!("{name}_{}", $suffix), version, indexes)?
|
||||
};
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
base: ComputedFromHeightStdDev::forced_import(db, name, days, parent_version, indexes)?,
|
||||
zscore: import!("zscore"),
|
||||
p0_5sd: import!("p0_5sd"),
|
||||
p1sd: import!("p1sd"),
|
||||
p1_5sd: import!("p1_5sd"),
|
||||
p2sd: import!("p2sd"),
|
||||
p2_5sd: import!("p2_5sd"),
|
||||
p3sd: import!("p3sd"),
|
||||
m0_5sd: import!("m0_5sd"),
|
||||
m1sd: import!("m1sd"),
|
||||
m1_5sd: import!("m1_5sd"),
|
||||
m2sd: import!("m2sd"),
|
||||
m2_5sd: import!("m2_5sd"),
|
||||
m3sd: import!("m3sd"),
|
||||
_0sd_usd: import_usd!("0sd_usd"),
|
||||
p0_5sd_usd: import_usd!("p0_5sd_usd"),
|
||||
p1sd_usd: import_usd!("p1sd_usd"),
|
||||
p1_5sd_usd: import_usd!("p1_5sd_usd"),
|
||||
p2sd_usd: import_usd!("p2sd_usd"),
|
||||
p2_5sd_usd: import_usd!("p2_5sd_usd"),
|
||||
p3sd_usd: import_usd!("p3sd_usd"),
|
||||
m0_5sd_usd: import_usd!("m0_5sd_usd"),
|
||||
m1sd_usd: import_usd!("m1sd_usd"),
|
||||
m1_5sd_usd: import_usd!("m1_5sd_usd"),
|
||||
m2sd_usd: import_usd!("m2sd_usd"),
|
||||
m2_5sd_usd: import_usd!("m2_5sd_usd"),
|
||||
m3sd_usd: import_usd!("m3sd_usd"),
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn compute_all(
|
||||
&mut self,
|
||||
blocks: &blocks::Vecs,
|
||||
starting_indexes: &ComputeIndexes,
|
||||
exit: &Exit,
|
||||
source: &impl ReadableVec<Height, StoredF32>,
|
||||
) -> Result<()> {
|
||||
self.base
|
||||
.compute_all(blocks, starting_indexes, exit, source)?;
|
||||
|
||||
let sma_opt: Option<&EagerVec<PcoVec<Height, StoredF32>>> = None;
|
||||
self.compute_bands(starting_indexes, exit, sma_opt, source)
|
||||
}
|
||||
|
||||
pub(crate) fn compute_bands(
|
||||
&mut self,
|
||||
starting_indexes: &ComputeIndexes,
|
||||
exit: &Exit,
|
||||
sma_opt: Option<&impl ReadableVec<Height, StoredF32>>,
|
||||
source: &impl ReadableVec<Height, StoredF32>,
|
||||
) -> Result<()> {
|
||||
let source_version = source.version();
|
||||
|
||||
self.mut_band_height_vecs()
|
||||
.try_for_each(|v| -> Result<()> {
|
||||
v.validate_computed_version_or_reset(source_version)?;
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
let starting_height = self
|
||||
.mut_band_height_vecs()
|
||||
.map(|v| Height::from(v.len()))
|
||||
.min()
|
||||
.unwrap()
|
||||
.min(starting_indexes.height);
|
||||
|
||||
let start = starting_height.to_usize();
|
||||
|
||||
let source_len = source.len();
|
||||
let source_data = source.collect_range_at(start, source_len);
|
||||
|
||||
let sma_len = sma_opt
|
||||
.map(|s| s.len())
|
||||
.unwrap_or(self.base.sma.height.len());
|
||||
let sma_data: Vec<StoredF32> = if let Some(sma) = sma_opt {
|
||||
sma.collect_range_at(start, sma_len)
|
||||
} else {
|
||||
self.base.sma.height.collect_range_at(start, sma_len)
|
||||
};
|
||||
let sd_data = self
|
||||
.base
|
||||
.sd
|
||||
.height
|
||||
.collect_range_at(start, self.base.sd.height.len());
|
||||
|
||||
for (offset, _ratio) in source_data.into_iter().enumerate() {
|
||||
let index = start + offset;
|
||||
let average = sma_data[offset];
|
||||
let sd = sd_data[offset];
|
||||
|
||||
self.p0_5sd
|
||||
.height
|
||||
.truncate_push_at(index, average + StoredF32::from(0.5 * *sd))?;
|
||||
self.p1sd.height.truncate_push_at(index, average + sd)?;
|
||||
self.p1_5sd
|
||||
.height
|
||||
.truncate_push_at(index, average + StoredF32::from(1.5 * *sd))?;
|
||||
self.p2sd.height.truncate_push_at(index, average + 2 * sd)?;
|
||||
self.p2_5sd
|
||||
.height
|
||||
.truncate_push_at(index, average + StoredF32::from(2.5 * *sd))?;
|
||||
self.p3sd.height.truncate_push_at(index, average + 3 * sd)?;
|
||||
self.m0_5sd
|
||||
.height
|
||||
.truncate_push_at(index, average - StoredF32::from(0.5 * *sd))?;
|
||||
self.m1sd.height.truncate_push_at(index, average - sd)?;
|
||||
self.m1_5sd
|
||||
.height
|
||||
.truncate_push_at(index, average - StoredF32::from(1.5 * *sd))?;
|
||||
self.m2sd.height.truncate_push_at(index, average - 2 * sd)?;
|
||||
self.m2_5sd
|
||||
.height
|
||||
.truncate_push_at(index, average - StoredF32::from(2.5 * *sd))?;
|
||||
self.m3sd.height.truncate_push_at(index, average - 3 * sd)?;
|
||||
}
|
||||
|
||||
{
|
||||
let _lock = exit.lock();
|
||||
self.mut_band_height_vecs().try_for_each(|v| v.flush())?;
|
||||
}
|
||||
|
||||
if let Some(sma) = sma_opt {
|
||||
self.zscore.height.compute_zscore(
|
||||
starting_indexes.height,
|
||||
source,
|
||||
sma,
|
||||
&self.base.sd.height,
|
||||
exit,
|
||||
)?;
|
||||
} else {
|
||||
self.zscore.height.compute_zscore(
|
||||
starting_indexes.height,
|
||||
source,
|
||||
&self.base.sma.height,
|
||||
&self.base.sd.height,
|
||||
exit,
|
||||
)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Compute USD price bands: usd_band = metric_price * band_ratio
|
||||
pub(crate) fn compute_usd_bands(
|
||||
&mut self,
|
||||
starting_indexes: &ComputeIndexes,
|
||||
metric_price: &impl ReadableVec<Height, Dollars>,
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
use crate::internal::PriceTimesRatio;
|
||||
|
||||
macro_rules! compute_band {
|
||||
($usd_field:ident, $band_source:expr) => {
|
||||
self.$usd_field
|
||||
.usd
|
||||
.compute_binary::<Dollars, StoredF32, PriceTimesRatio>(
|
||||
starting_indexes.height,
|
||||
metric_price,
|
||||
$band_source,
|
||||
exit,
|
||||
)?;
|
||||
};
|
||||
}
|
||||
|
||||
compute_band!(_0sd_usd, &self.base.sma.height);
|
||||
compute_band!(p0_5sd_usd, &self.p0_5sd.height);
|
||||
compute_band!(p1sd_usd, &self.p1sd.height);
|
||||
compute_band!(p1_5sd_usd, &self.p1_5sd.height);
|
||||
compute_band!(p2sd_usd, &self.p2sd.height);
|
||||
compute_band!(p2_5sd_usd, &self.p2_5sd.height);
|
||||
compute_band!(p3sd_usd, &self.p3sd.height);
|
||||
compute_band!(m0_5sd_usd, &self.m0_5sd.height);
|
||||
compute_band!(m1sd_usd, &self.m1sd.height);
|
||||
compute_band!(m1_5sd_usd, &self.m1_5sd.height);
|
||||
compute_band!(m2sd_usd, &self.m2sd.height);
|
||||
compute_band!(m2_5sd_usd, &self.m2_5sd.height);
|
||||
compute_band!(m3sd_usd, &self.m3sd.height);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn mut_band_height_vecs(
|
||||
&mut self,
|
||||
) -> impl Iterator<Item = &mut EagerVec<PcoVec<Height, StoredF32>>> {
|
||||
[
|
||||
&mut self.p0_5sd.height,
|
||||
&mut self.p1sd.height,
|
||||
&mut self.p1_5sd.height,
|
||||
&mut self.p2sd.height,
|
||||
&mut self.p2_5sd.height,
|
||||
&mut self.p3sd.height,
|
||||
&mut self.m0_5sd.height,
|
||||
&mut self.m1sd.height,
|
||||
&mut self.m1_5sd.height,
|
||||
&mut self.m2sd.height,
|
||||
&mut self.m2_5sd.height,
|
||||
&mut self.m3sd.height,
|
||||
]
|
||||
.into_iter()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
mod extended;
|
||||
|
||||
pub use extended::*;
|
||||
|
||||
use brk_error::Result;
|
||||
use brk_traversable::Traversable;
|
||||
use brk_types::{Height, StoredF32, Version};
|
||||
use vecdb::{AnyStoredVec, AnyVec, Database, Exit, ReadableVec, Rw, StorageMode, VecIndex, WritableVec};
|
||||
|
||||
use crate::{ComputeIndexes, blocks, indexes};
|
||||
|
||||
use crate::internal::ComputedFromHeightLast;
|
||||
|
||||
#[derive(Traversable)]
|
||||
pub struct ComputedFromHeightStdDev<M: StorageMode = Rw> {
|
||||
days: usize,
|
||||
pub sma: ComputedFromHeightLast<StoredF32, M>,
|
||||
pub sd: ComputedFromHeightLast<StoredF32, M>,
|
||||
}
|
||||
|
||||
impl ComputedFromHeightStdDev {
|
||||
pub(crate) fn forced_import(
|
||||
db: &Database,
|
||||
name: &str,
|
||||
days: usize,
|
||||
parent_version: Version,
|
||||
indexes: &indexes::Vecs,
|
||||
) -> Result<Self> {
|
||||
let version = parent_version + Version::TWO;
|
||||
|
||||
let sma = ComputedFromHeightLast::forced_import(
|
||||
db,
|
||||
&format!("{name}_sma"),
|
||||
version,
|
||||
indexes,
|
||||
)?;
|
||||
let sd = ComputedFromHeightLast::forced_import(
|
||||
db,
|
||||
&format!("{name}_sd"),
|
||||
version,
|
||||
indexes,
|
||||
)?;
|
||||
|
||||
Ok(Self { days, sma, sd })
|
||||
}
|
||||
|
||||
pub(crate) fn compute_all(
|
||||
&mut self,
|
||||
blocks: &blocks::Vecs,
|
||||
starting_indexes: &ComputeIndexes,
|
||||
exit: &Exit,
|
||||
source: &impl ReadableVec<Height, StoredF32>,
|
||||
) -> Result<()> {
|
||||
// 1. Compute SMA using the appropriate lookback vec (or full-history SMA)
|
||||
if self.days != usize::MAX {
|
||||
let window_starts = blocks.count.start_vec(self.days);
|
||||
self.sma.height.compute_rolling_average(
|
||||
starting_indexes.height,
|
||||
window_starts,
|
||||
source,
|
||||
exit,
|
||||
)?;
|
||||
} else {
|
||||
// Full history SMA (days == usize::MAX)
|
||||
self.sma.height.compute_sma_(
|
||||
starting_indexes.height,
|
||||
source,
|
||||
self.days,
|
||||
exit,
|
||||
None,
|
||||
)?;
|
||||
}
|
||||
|
||||
// Split borrows: sd is mutated, sma is read
|
||||
compute_sd(
|
||||
&mut self.sd,
|
||||
blocks,
|
||||
starting_indexes,
|
||||
exit,
|
||||
&self.sma.height,
|
||||
source,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn compute_sd(
|
||||
sd: &mut ComputedFromHeightLast<StoredF32>,
|
||||
blocks: &blocks::Vecs,
|
||||
starting_indexes: &ComputeIndexes,
|
||||
exit: &Exit,
|
||||
sma: &impl ReadableVec<Height, StoredF32>,
|
||||
source: &impl ReadableVec<Height, StoredF32>,
|
||||
) -> Result<()> {
|
||||
let source_version = source.version();
|
||||
|
||||
sd.height
|
||||
.validate_computed_version_or_reset(source_version)?;
|
||||
|
||||
let starting_height = Height::from(sd.height.len()).min(starting_indexes.height);
|
||||
|
||||
let day_start = &blocks.count.height_24h_ago;
|
||||
let start = starting_height.to_usize();
|
||||
|
||||
let mut n: usize = 0;
|
||||
let mut welford_sum: f64 = 0.0;
|
||||
let mut welford_sum_sq: f64 = 0.0;
|
||||
if start > 0 {
|
||||
let day_start_hist = day_start.collect_range_at(0, start);
|
||||
let source_hist = source.collect_range_at(0, start);
|
||||
let mut last_ds = Height::from(0_usize);
|
||||
for h in 0..start {
|
||||
let cur_ds = day_start_hist[h];
|
||||
if h == 0 || cur_ds != last_ds {
|
||||
let val = *source_hist[h] as f64;
|
||||
n += 1;
|
||||
welford_sum += val;
|
||||
welford_sum_sq += val * val;
|
||||
last_ds = cur_ds;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let source_len = source.len();
|
||||
let source_data = source.collect_range_at(start, source_len);
|
||||
let sma_data = sma.collect_range_at(start, sma.len());
|
||||
let mut last_day_start = if start > 0 {
|
||||
day_start
|
||||
.collect_one_at(start - 1)
|
||||
.unwrap_or(Height::from(0_usize))
|
||||
} else {
|
||||
Height::from(0_usize)
|
||||
};
|
||||
|
||||
let day_start_data = day_start.collect_range_at(start, source_len);
|
||||
|
||||
for (offset, ratio) in source_data.into_iter().enumerate() {
|
||||
let index = start + offset;
|
||||
let cur_day_start = day_start_data[offset];
|
||||
if index == 0 || cur_day_start != last_day_start {
|
||||
let val = *ratio as f64;
|
||||
n += 1;
|
||||
welford_sum += val;
|
||||
welford_sum_sq += val * val;
|
||||
last_day_start = cur_day_start;
|
||||
}
|
||||
|
||||
let average = sma_data[offset];
|
||||
let avg_f64 = *average as f64;
|
||||
|
||||
let sd_val = if n > 0 {
|
||||
let nf = n as f64;
|
||||
let variance =
|
||||
welford_sum_sq / nf - 2.0 * avg_f64 * welford_sum / nf + avg_f64 * avg_f64;
|
||||
StoredF32::from(variance.max(0.0).sqrt() as f32)
|
||||
} else {
|
||||
StoredF32::from(0.0_f32)
|
||||
};
|
||||
|
||||
sd.height.truncate_push_at(index, sd_val)?;
|
||||
}
|
||||
|
||||
{
|
||||
let _lock = exit.lock();
|
||||
sd.height.flush()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
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,
|
||||
};
|
||||
|
||||
const VERSION: Version = Version::ZERO;
|
||||
|
||||
#[derive(Deref, DerefMut, Traversable)]
|
||||
#[traversable(transparent)]
|
||||
pub struct StoredValueFromHeightLast<M: StorageMode = Rw> {
|
||||
#[deref]
|
||||
#[deref_mut]
|
||||
pub base: ByUnit<M>,
|
||||
}
|
||||
|
||||
impl StoredValueFromHeightLast {
|
||||
pub(crate) fn forced_import(
|
||||
db: &Database,
|
||||
name: &str,
|
||||
version: Version,
|
||||
indexes: &indexes::Vecs,
|
||||
) -> Result<Self> {
|
||||
let v = version + VERSION;
|
||||
Ok(Self {
|
||||
base: ByUnit::forced_import(db, name, v, indexes)?,
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn compute_rolling_sum(
|
||||
&mut self,
|
||||
max_from: Height,
|
||||
window_starts: &impl ReadableVec<Height, Height>,
|
||||
sats_source: &impl ReadableVec<Height, Sats>,
|
||||
usd_source: &impl ReadableVec<Height, Dollars>,
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
self.base
|
||||
.sats
|
||||
.height
|
||||
.compute_rolling_sum(max_from, window_starts, sats_source, exit)?;
|
||||
self.base
|
||||
.usd
|
||||
.height
|
||||
.compute_rolling_sum(max_from, window_starts, usd_source, exit)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
//! Change values from Height - stores signed sats and dollars (changes can be negative).
|
||||
|
||||
use brk_error::Result;
|
||||
use brk_traversable::Traversable;
|
||||
use brk_types::{Bitcoin, Dollars, Height, Sats, SatsSigned, Version};
|
||||
use vecdb::{Database, Exit, ReadableCloneableVec, ReadableVec, Rw, StorageMode};
|
||||
|
||||
use crate::{
|
||||
indexes,
|
||||
internal::{ComputedFromHeightLast, LazyFromHeightLast, SatsSignedToBitcoin},
|
||||
};
|
||||
|
||||
const VERSION: Version = Version::ZERO;
|
||||
|
||||
/// Change values indexed by height - sats (stored), btc (lazy), usd (stored).
|
||||
#[derive(Traversable)]
|
||||
pub struct ValueChangeFromHeight<M: StorageMode = Rw> {
|
||||
pub sats: ComputedFromHeightLast<SatsSigned, M>,
|
||||
pub btc: LazyFromHeightLast<Bitcoin, SatsSigned>,
|
||||
pub usd: ComputedFromHeightLast<Dollars, M>,
|
||||
}
|
||||
|
||||
impl ValueChangeFromHeight {
|
||||
pub(crate) fn forced_import(
|
||||
db: &Database,
|
||||
name: &str,
|
||||
version: Version,
|
||||
indexes: &indexes::Vecs,
|
||||
) -> Result<Self> {
|
||||
let v = version + VERSION;
|
||||
|
||||
let sats = ComputedFromHeightLast::forced_import(db, name, v, indexes)?;
|
||||
|
||||
let btc = LazyFromHeightLast::from_computed::<SatsSignedToBitcoin>(
|
||||
&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 })
|
||||
}
|
||||
|
||||
/// Compute rolling change for both sats and dollars in one call.
|
||||
pub(crate) fn compute_rolling(
|
||||
&mut self,
|
||||
starting_height: Height,
|
||||
window_starts: &impl ReadableVec<Height, Height>,
|
||||
sats_source: &impl ReadableVec<Height, Sats>,
|
||||
dollars_source: &(impl ReadableVec<Height, Dollars> + Sync),
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
self.sats
|
||||
.height
|
||||
.compute_rolling_change(starting_height, window_starts, sats_source, exit)?;
|
||||
self.usd
|
||||
.height
|
||||
.compute_rolling_change(starting_height, window_starts, dollars_source, exit)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
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,
|
||||
};
|
||||
|
||||
const VERSION: Version = Version::ZERO;
|
||||
|
||||
#[derive(Deref, DerefMut, Traversable)]
|
||||
#[traversable(transparent)]
|
||||
pub struct ValueEmaFromHeight<M: StorageMode = Rw> {
|
||||
#[deref]
|
||||
#[deref_mut]
|
||||
pub base: ByUnit<M>,
|
||||
}
|
||||
|
||||
impl ValueEmaFromHeight {
|
||||
pub(crate) fn forced_import(
|
||||
db: &Database,
|
||||
name: &str,
|
||||
version: Version,
|
||||
indexes: &indexes::Vecs,
|
||||
) -> Result<Self> {
|
||||
let v = version + VERSION;
|
||||
Ok(Self {
|
||||
base: ByUnit::forced_import(db, name, v, indexes)?,
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn compute_rolling_average(
|
||||
&mut self,
|
||||
starting_height: Height,
|
||||
window_starts: &impl ReadableVec<Height, Height>,
|
||||
sats_source: &impl ReadableVec<Height, Sats>,
|
||||
dollars_source: &(impl ReadableVec<Height, Dollars> + Sync),
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
self.base
|
||||
.sats
|
||||
.height
|
||||
.compute_rolling_average(starting_height, window_starts, sats_source, exit)?;
|
||||
self.base
|
||||
.usd
|
||||
.height
|
||||
.compute_rolling_average(starting_height, window_starts, dollars_source, exit)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
use brk_error::Result;
|
||||
use brk_traversable::Traversable;
|
||||
use brk_types::{Dollars, Height, Sats, Version};
|
||||
use vecdb::{Database, EagerVec, Exit, PcoVec, Rw, StorageMode};
|
||||
|
||||
use crate::{
|
||||
indexes,
|
||||
internal::{ByUnit, RollingFullByUnit, SatsToDollars, WindowStarts},
|
||||
prices,
|
||||
};
|
||||
|
||||
#[derive(Traversable)]
|
||||
pub struct ValueFromHeightFull<M: StorageMode = Rw> {
|
||||
pub base: ByUnit<M>,
|
||||
pub cumulative: ByUnit<M>,
|
||||
#[traversable(flatten)]
|
||||
pub rolling: RollingFullByUnit<M>,
|
||||
}
|
||||
|
||||
const VERSION: Version = Version::TWO;
|
||||
|
||||
impl ValueFromHeightFull {
|
||||
pub(crate) fn forced_import(
|
||||
db: &Database,
|
||||
name: &str,
|
||||
version: Version,
|
||||
indexes: &indexes::Vecs,
|
||||
) -> Result<Self> {
|
||||
let v = version + VERSION;
|
||||
|
||||
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(
|
||||
&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)?;
|
||||
|
||||
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.base.sats.height,
|
||||
&prices.price.usd.height,
|
||||
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(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
use brk_error::Result;
|
||||
use brk_traversable::Traversable;
|
||||
use brk_types::{Dollars, Height, Sats, Version};
|
||||
use derive_more::{Deref, DerefMut};
|
||||
use vecdb::{Database, Exit, Rw, StorageMode};
|
||||
|
||||
use crate::{
|
||||
indexes, prices,
|
||||
internal::{ByUnit, SatsToDollars},
|
||||
};
|
||||
|
||||
#[derive(Deref, DerefMut, Traversable)]
|
||||
#[traversable(transparent)]
|
||||
pub struct ValueFromHeightLast<M: StorageMode = Rw> {
|
||||
#[deref]
|
||||
#[deref_mut]
|
||||
pub base: ByUnit<M>,
|
||||
}
|
||||
|
||||
const VERSION: Version = Version::TWO;
|
||||
|
||||
impl ValueFromHeightLast {
|
||||
pub(crate) fn forced_import(
|
||||
db: &Database,
|
||||
name: &str,
|
||||
version: Version,
|
||||
indexes: &indexes::Vecs,
|
||||
) -> Result<Self> {
|
||||
let v = version + VERSION;
|
||||
Ok(Self {
|
||||
base: ByUnit::forced_import(db, name, v, indexes)?,
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn compute(
|
||||
&mut self,
|
||||
prices: &prices::Vecs,
|
||||
max_from: Height,
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
self.base.usd.compute_binary::<Sats, Dollars, SatsToDollars>(
|
||||
max_from,
|
||||
&self.base.sats.height,
|
||||
&prices.price.usd.height,
|
||||
exit,
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
//! Value type for Height + Rolling pattern.
|
||||
//!
|
||||
//! Combines ValueFromHeight (sats/btc/usd per height, no period views) with
|
||||
//! StoredValueRollingWindows (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::{StoredValueRollingWindows, ValueFromHeight, WindowStarts},
|
||||
prices,
|
||||
};
|
||||
|
||||
#[derive(Deref, DerefMut, Traversable)]
|
||||
pub struct ValueFromHeightLastRolling<M: StorageMode = Rw> {
|
||||
#[deref]
|
||||
#[deref_mut]
|
||||
#[traversable(flatten)]
|
||||
pub value: ValueFromHeight<M>,
|
||||
#[traversable(flatten)]
|
||||
pub rolling: StoredValueRollingWindows<M>,
|
||||
}
|
||||
|
||||
const VERSION: Version = Version::ZERO;
|
||||
|
||||
impl ValueFromHeightLastRolling {
|
||||
pub(crate) fn forced_import(
|
||||
db: &Database,
|
||||
name: &str,
|
||||
version: Version,
|
||||
indexes: &indexes::Vecs,
|
||||
) -> Result<Self> {
|
||||
let v = version + VERSION;
|
||||
Ok(Self {
|
||||
value: ValueFromHeight::forced_import(db, name, v)?,
|
||||
rolling: StoredValueRollingWindows::forced_import(db, name, v, indexes)?,
|
||||
})
|
||||
}
|
||||
|
||||
/// Compute sats height via closure, then USD 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.value.sats)?;
|
||||
self.value.compute_usd(prices, max_from, exit)?;
|
||||
self.rolling.compute_rolling_sum(
|
||||
max_from,
|
||||
windows,
|
||||
&self.value.sats,
|
||||
&self.value.usd,
|
||||
exit,
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
use brk_error::Result;
|
||||
use brk_traversable::Traversable;
|
||||
use brk_types::{Dollars, Height, Sats, Version};
|
||||
use vecdb::{Database, EagerVec, Exit, PcoVec, Rw, StorageMode};
|
||||
|
||||
use crate::{
|
||||
indexes,
|
||||
internal::{ByUnit, RollingSumByUnit, SatsToDollars, WindowStarts},
|
||||
prices,
|
||||
};
|
||||
|
||||
#[derive(Traversable)]
|
||||
pub struct ValueFromHeightSumCumulative<M: StorageMode = Rw> {
|
||||
pub base: ByUnit<M>,
|
||||
pub cumulative: ByUnit<M>,
|
||||
pub sum: RollingSumByUnit<M>,
|
||||
}
|
||||
|
||||
const VERSION: Version = Version::TWO;
|
||||
|
||||
impl ValueFromHeightSumCumulative {
|
||||
pub(crate) fn forced_import(
|
||||
db: &Database,
|
||||
name: &str,
|
||||
version: Version,
|
||||
indexes: &indexes::Vecs,
|
||||
) -> Result<Self> {
|
||||
let v = version + VERSION;
|
||||
|
||||
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(
|
||||
&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)?;
|
||||
|
||||
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.base.sats.height,
|
||||
&prices.price.usd.height,
|
||||
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(())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user