global: snapshot

This commit is contained in:
nym21
2026-03-07 13:00:10 +01:00
parent bf07570848
commit 1011825949
87 changed files with 1304 additions and 1201 deletions

View File

@@ -1,7 +1,7 @@
use brk_cohort::ByAddressType;
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{Height, Indexes, StoredF64, StoredU64, Version};
use brk_types::{Height, Indexes, StoredU64, Version};
use derive_more::{Deref, DerefMut};
use rayon::prelude::*;
use vecdb::{
@@ -9,15 +9,12 @@ use vecdb::{
WritableVec,
};
use crate::{blocks, indexes, internal::ComputedFromHeight};
use crate::{indexes, internal::ComputedFromHeight};
/// Address count with 1m change metric for a single type.
#[derive(Traversable)]
pub struct AddrCountVecs<M: StorageMode = Rw> {
#[traversable(flatten)]
pub count: ComputedFromHeight<StoredU64, M>,
pub change_1m: ComputedFromHeight<StoredF64, M>,
}
#[derive(Deref, DerefMut, Traversable)]
pub struct AddrCountVecs<M: StorageMode = Rw>(
#[traversable(flatten)] pub ComputedFromHeight<StoredU64, M>,
);
impl AddrCountVecs {
pub(crate) fn forced_import(
@@ -26,31 +23,9 @@ impl AddrCountVecs {
version: Version,
indexes: &indexes::Vecs,
) -> Result<Self> {
Ok(Self {
count: ComputedFromHeight::forced_import(db, name, version, indexes)?,
change_1m: ComputedFromHeight::forced_import(
db,
&format!("{name}_change_1m"),
version,
indexes,
)?,
})
}
pub(crate) fn compute_rest(
&mut self,
blocks: &blocks::Vecs,
starting_indexes: &Indexes,
exit: &Exit,
) -> Result<()> {
self.change_1m.height.compute_rolling_change(
starting_indexes.height,
&blocks.count.height_1m_ago,
&self.count.height,
exit,
)?;
Ok(())
Ok(Self(ComputedFromHeight::forced_import(
db, name, version, indexes,
)?))
}
}
@@ -72,56 +47,48 @@ impl From<(&AddressTypeToAddrCountVecs, Height)> for AddressTypeToAddressCount {
Self(ByAddressType {
p2pk65: groups
.p2pk65
.count
.height
.collect_one(prev_height)
.unwrap()
.into(),
p2pk33: groups
.p2pk33
.count
.height
.collect_one(prev_height)
.unwrap()
.into(),
p2pkh: groups
.p2pkh
.count
.height
.collect_one(prev_height)
.unwrap()
.into(),
p2sh: groups
.p2sh
.count
.height
.collect_one(prev_height)
.unwrap()
.into(),
p2wpkh: groups
.p2wpkh
.count
.height
.collect_one(prev_height)
.unwrap()
.into(),
p2wsh: groups
.p2wsh
.count
.height
.collect_one(prev_height)
.unwrap()
.into(),
p2tr: groups
.p2tr
.count
.height
.collect_one(prev_height)
.unwrap()
.into(),
p2a: groups
.p2a
.count
.height
.collect_one(prev_height)
.unwrap()
@@ -133,7 +100,7 @@ impl From<(&AddressTypeToAddrCountVecs, Height)> for AddressTypeToAddressCount {
}
}
/// Address count per address type, with height + derived indexes + 1m change.
/// Address count per address type, with height + derived indexes.
#[derive(Deref, DerefMut, Traversable)]
pub struct AddressTypeToAddrCountVecs<M: StorageMode = Rw>(ByAddressType<AddrCountVecs<M>>);
@@ -159,7 +126,7 @@ impl AddressTypeToAddrCountVecs {
}
pub(crate) fn min_stateful_height(&self) -> usize {
self.0.values().map(|v| v.count.height.len()).min().unwrap()
self.0.values().map(|v| v.height.len()).min().unwrap()
}
pub(crate) fn par_iter_height_mut(
@@ -167,7 +134,7 @@ impl AddressTypeToAddrCountVecs {
) -> impl ParallelIterator<Item = &mut dyn AnyStoredVec> {
self.0
.par_values_mut()
.map(|v| &mut v.count.height as &mut dyn AnyStoredVec)
.map(|v| &mut v.height as &mut dyn AnyStoredVec)
}
pub(crate) fn truncate_push_height(
@@ -176,7 +143,7 @@ impl AddressTypeToAddrCountVecs {
addr_counts: &AddressTypeToAddressCount,
) -> Result<()> {
for (vecs, &count) in self.0.values_mut().zip(addr_counts.values()) {
vecs.count.height.truncate_push(height, count.into())?;
vecs.height.truncate_push(height, count.into())?;
}
Ok(())
}
@@ -184,25 +151,13 @@ impl AddressTypeToAddrCountVecs {
pub(crate) fn reset_height(&mut self) -> Result<()> {
use vecdb::WritableVec;
for v in self.0.values_mut() {
v.count.height.reset()?;
}
Ok(())
}
pub(crate) fn compute_rest(
&mut self,
blocks: &blocks::Vecs,
starting_indexes: &Indexes,
exit: &Exit,
) -> Result<()> {
for v in self.0.values_mut() {
v.compute_rest(blocks, starting_indexes, exit)?;
v.height.reset()?;
}
Ok(())
}
pub(crate) fn by_height(&self) -> Vec<&EagerVec<PcoVec<Height, StoredU64>>> {
self.0.values().map(|v| &v.count.height).collect()
self.0.values().map(|v| &v.height).collect()
}
}
@@ -227,22 +182,18 @@ impl AddrCountsVecs {
}
pub(crate) fn min_stateful_height(&self) -> usize {
self.all
.count
.height
.len()
.min(self.by_addresstype.min_stateful_height())
self.all.height.len().min(self.by_addresstype.min_stateful_height())
}
pub(crate) fn par_iter_height_mut(
&mut self,
) -> impl ParallelIterator<Item = &mut dyn AnyStoredVec> {
rayon::iter::once(&mut self.all.count.height as &mut dyn AnyStoredVec)
rayon::iter::once(&mut self.all.height as &mut dyn AnyStoredVec)
.chain(self.by_addresstype.par_iter_height_mut())
}
pub(crate) fn reset_height(&mut self) -> Result<()> {
self.all.count.height.reset()?;
self.all.height.reset()?;
self.by_addresstype.reset_height()?;
Ok(())
}
@@ -253,7 +204,7 @@ impl AddrCountsVecs {
total: u64,
addr_counts: &AddressTypeToAddressCount,
) -> Result<()> {
self.all.count.height.truncate_push(height, total.into())?;
self.all.height.truncate_push(height, total.into())?;
self.by_addresstype
.truncate_push_height(height, addr_counts)?;
Ok(())
@@ -261,26 +212,13 @@ impl AddrCountsVecs {
pub(crate) fn compute_rest(
&mut self,
blocks: &blocks::Vecs,
starting_indexes: &Indexes,
exit: &Exit,
) -> Result<()> {
self.by_addresstype
.compute_rest(blocks, starting_indexes, exit)?;
let sources = self.by_addresstype.by_height();
self.all
.count
.height
.compute_sum_of_others(starting_indexes.height, &sources, exit)?;
self.all.change_1m.height.compute_rolling_change(
starting_indexes.height,
&blocks.count.height_1m_ago,
&self.all.count.height,
exit,
)?;
Ok(())
}
}

View File

@@ -0,0 +1,61 @@
use brk_cohort::ByAddressType;
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{Height, StoredI64, StoredU64, Version};
use vecdb::{Database, Exit, Rw, StorageMode};
use crate::{
indexes,
internal::{WindowStarts, RollingDelta},
};
use super::AddrCountsVecs;
#[derive(Traversable)]
pub struct DeltaVecs<M: StorageMode = Rw> {
pub all: RollingDelta<StoredU64, StoredI64, M>,
#[traversable(flatten)]
pub by_addresstype: ByAddressType<RollingDelta<StoredU64, StoredI64, M>>,
}
impl DeltaVecs {
pub(crate) fn forced_import(
db: &Database,
version: Version,
indexes: &indexes::Vecs,
) -> Result<Self> {
let version = version + Version::ONE;
let all = RollingDelta::forced_import(db, "addr_count", version, indexes)?;
let by_addresstype = ByAddressType::new_with_name(|name| {
RollingDelta::forced_import(db, &format!("{name}_addr_count"), version, indexes)
})?;
Ok(Self {
all,
by_addresstype,
})
}
pub(crate) fn compute(
&mut self,
max_from: Height,
windows: &WindowStarts<'_>,
addr_count: &AddrCountsVecs,
exit: &Exit,
) -> Result<()> {
self.all
.compute(max_from, windows, &addr_count.all.height, exit)?;
for ((_, growth), (_, addr)) in self
.by_addresstype
.iter_mut()
.zip(addr_count.by_addresstype.iter())
{
growth.compute(max_from, windows, &addr.height, exit)?;
}
Ok(())
}
}

View File

@@ -1,99 +0,0 @@
use brk_cohort::ByAddressType;
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{BasisPoints16, Height, StoredU64, Version};
use vecdb::{Database, EagerVec, Exit, PcoVec, ReadableVec, Rw, StorageMode};
use crate::{
indexes,
internal::{PercentFromHeightDistribution, WindowStarts},
};
use super::{AddrCountsVecs, NewAddrCountVecs};
/// Growth rate: new_addr_count / addr_count (global + per-type)
#[derive(Traversable)]
pub struct GrowthRateVecs<M: StorageMode = Rw> {
pub all: PercentFromHeightDistribution<BasisPoints16, M>,
#[traversable(flatten)]
pub by_addresstype: ByAddressType<PercentFromHeightDistribution<BasisPoints16, M>>,
}
impl GrowthRateVecs {
pub(crate) fn forced_import(
db: &Database,
version: Version,
indexes: &indexes::Vecs,
) -> Result<Self> {
let all =
PercentFromHeightDistribution::forced_import(db, "growth_rate", version, indexes)?;
let by_addresstype = ByAddressType::new_with_name(|name| {
PercentFromHeightDistribution::forced_import(
db,
&format!("{name}_growth_rate"),
version,
indexes,
)
})?;
Ok(Self {
all,
by_addresstype,
})
}
pub(crate) fn compute(
&mut self,
max_from: Height,
windows: &WindowStarts<'_>,
new_addr_count: &NewAddrCountVecs,
addr_count: &AddrCountsVecs,
exit: &Exit,
) -> Result<()> {
self.all.compute(max_from, windows, exit, |target| {
compute_ratio(
target,
max_from,
&new_addr_count.all.height,
&addr_count.all.count.height,
exit,
)
})?;
for ((_, growth), ((_, new), (_, addr))) in self.by_addresstype.iter_mut().zip(
new_addr_count
.by_addresstype
.iter()
.zip(addr_count.by_addresstype.iter()),
) {
growth.compute(max_from, windows, exit, |target| {
compute_ratio(target, max_from, &new.height, &addr.count.height, exit)
})?;
}
Ok(())
}
}
fn compute_ratio(
target: &mut EagerVec<PcoVec<Height, BasisPoints16>>,
max_from: Height,
numerator: &impl ReadableVec<Height, StoredU64>,
denominator: &impl ReadableVec<Height, StoredU64>,
exit: &Exit,
) -> Result<()> {
target.compute_transform2(
max_from,
numerator,
denominator,
|(h, num, den, ..)| {
let n = *num as f64;
let d = *den as f64;
let ratio = if d == 0.0 { 0.0 } else { n / d };
(h, BasisPoints16::from(ratio))
},
exit,
)?;
Ok(())
}

View File

@@ -1,7 +1,7 @@
mod activity;
mod address_count;
mod data;
mod growth_rate;
mod delta;
mod indexes;
mod new_addr_count;
mod total_addr_count;
@@ -10,7 +10,7 @@ mod type_map;
pub use activity::{AddressActivityVecs, AddressTypeToActivityCounts};
pub use address_count::{AddrCountsVecs, AddressTypeToAddressCount};
pub use data::AddressesDataVecs;
pub use growth_rate::GrowthRateVecs;
pub use delta::DeltaVecs;
pub use indexes::AnyAddressIndexesVecs;
pub use new_addr_count::NewAddrCountVecs;
pub use total_addr_count::TotalAddrCountVecs;

View File

@@ -50,8 +50,8 @@ impl TotalAddrCountVecs {
) -> Result<()> {
self.all.height.compute_add(
max_from,
&addr_count.all.count.height,
&empty_addr_count.all.count.height,
&addr_count.all.height,
&empty_addr_count.all.height,
exit,
)?;
@@ -63,7 +63,7 @@ impl TotalAddrCountVecs {
) {
total
.height
.compute_add(max_from, &addr.count.height, &empty.count.height, exit)?;
.compute_add(max_from, &addr.height, &empty.height, exit)?;
}
Ok(())

View File

@@ -378,46 +378,6 @@ impl UTXOCohorts<Rw> {
.try_for_each(|v| v.compute_rest_part1(blocks, prices, starting_indexes, exit))?;
}
// 2. Compute net_sentiment.height for aggregate cohorts (weighted average).
// Separate cohorts already computed net_sentiment in step 1 (inside compute_rest_part1).
// Note: min_age, max_age, epoch, class are Core tier — no net_sentiment.
// Note: ge_amount, lt_amount, amount_range are Minimal tier — no net_sentiment.
{
let Self {
all,
sth,
lth,
age_range,
..
} = self;
let ar = &*age_range;
let si = starting_indexes;
let tasks: Vec<Box<dyn FnOnce() -> Result<()> + Send + '_>> = vec![
Box::new(|| {
let sources = filter_sources_from(ar.iter(), None);
all.metrics
.compute_net_sentiment_from_others(si, &sources, exit)
}),
Box::new(|| {
let sources = filter_sources_from(ar.iter(), Some(sth.metrics.filter()));
sth.metrics
.compute_net_sentiment_from_others(si, &sources, exit)
}),
Box::new(|| {
let sources = filter_sources_from(ar.iter(), Some(lth.metrics.filter()));
lth.metrics
.compute_net_sentiment_from_others(si, &sources, exit)
}),
];
tasks
.into_par_iter()
.map(|f| f())
.collect::<Result<Vec<_>>>()?;
}
Ok(())
}

View File

@@ -4,7 +4,7 @@ use brk_types::{Bitcoin, Height, Indexes, Sats, StoredF64, Version};
use derive_more::{Deref, DerefMut};
use vecdb::{AnyStoredVec, AnyVec, Exit, Rw, StorageMode, WritableVec};
use crate::internal::ComputedFromHeightCumulativeSum;
use crate::internal::{ComputedFromHeightCumulative, ComputedFromHeightCumulativeSum};
use crate::{blocks, distribution::metrics::ImportConfig, prices};
@@ -17,7 +17,7 @@ pub struct ActivityFull<M: StorageMode = Rw> {
#[traversable(flatten)]
pub base: ActivityBase<M>,
pub coinblocks_destroyed: ComputedFromHeightCumulativeSum<StoredF64, M>,
pub coinblocks_destroyed: ComputedFromHeightCumulative<StoredF64, M>,
pub coindays_destroyed: ComputedFromHeightCumulativeSum<StoredF64, M>,
}
@@ -94,11 +94,10 @@ impl ActivityFull {
self.base
.compute_rest_part1(blocks, prices, starting_indexes, exit)?;
let window_starts = blocks.count.window_starts();
self.coinblocks_destroyed
.compute_rest(starting_indexes.height, &window_starts, exit)?;
.compute_rest(starting_indexes.height, exit)?;
let window_starts = blocks.count.window_starts();
self.coindays_destroyed
.compute_rest(starting_indexes.height, &window_starts, exit)?;

View File

@@ -35,6 +35,7 @@ pub struct AllCohortMetrics<M: StorageMode = Rw> {
impl CohortMetricsBase for AllCohortMetrics {
type RealizedVecs = RealizedFull;
type UnrealizedVecs = UnrealizedFull;
type CostBasisVecs = CostBasisWithExtended;
impl_cohort_accessors!();

View File

@@ -8,7 +8,7 @@ use crate::{blocks, prices};
use crate::distribution::metrics::{
ActivityFull, CohortMetricsBase, CostBasisBase, ImportConfig, OutputsMetrics, RealizedBase,
RelativeWithRelToAll, SupplyMetrics, UnrealizedFull,
RelativeWithRelToAll, SupplyMetrics, UnrealizedBase,
};
/// Basic cohort metrics: no extensions, with relative (rel_to_all).
@@ -22,12 +22,13 @@ pub struct BasicCohortMetrics<M: StorageMode = Rw> {
pub activity: Box<ActivityFull<M>>,
pub realized: Box<RealizedBase<M>>,
pub cost_basis: Box<CostBasisBase<M>>,
pub unrealized: Box<UnrealizedFull<M>>,
pub unrealized: Box<UnrealizedBase<M>>,
pub relative: Box<RelativeWithRelToAll<M>>,
}
impl CohortMetricsBase for BasicCohortMetrics {
type RealizedVecs = RealizedBase;
type UnrealizedVecs = UnrealizedBase;
type CostBasisVecs = CostBasisBase;
impl_cohort_accessors!();
@@ -47,7 +48,7 @@ impl CohortMetricsBase for BasicCohortMetrics {
impl BasicCohortMetrics {
pub(crate) fn forced_import(cfg: &ImportConfig) -> Result<Self> {
let supply = SupplyMetrics::forced_import(cfg)?;
let unrealized = UnrealizedFull::forced_import(cfg)?;
let unrealized = UnrealizedBase::forced_import(cfg)?;
let realized = RealizedBase::forced_import(cfg)?;
let relative = RelativeWithRelToAll::forced_import(cfg)?;

View File

@@ -8,7 +8,7 @@ use crate::{blocks, prices};
use crate::distribution::metrics::{
ActivityBase, CohortMetricsBase, RealizedBase, ImportConfig, OutputsMetrics,
RelativeBaseWithRelToAll, SupplyMetrics, UnrealizedBase,
RelativeBaseWithRelToAll, SupplyMetrics, UnrealizedCore,
};
#[derive(Traversable)]
@@ -19,7 +19,7 @@ pub struct CoreCohortMetrics<M: StorageMode = Rw> {
pub outputs: Box<OutputsMetrics<M>>,
pub activity: Box<ActivityBase<M>>,
pub realized: Box<RealizedBase<M>>,
pub unrealized: Box<UnrealizedBase<M>>,
pub unrealized: Box<UnrealizedCore<M>>,
pub relative: Box<RelativeBaseWithRelToAll<M>>,
}
@@ -31,7 +31,7 @@ impl CoreCohortMetrics {
outputs: Box::new(OutputsMetrics::forced_import(cfg)?),
activity: Box::new(ActivityBase::forced_import(cfg)?),
realized: Box::new(RealizedBase::forced_import(cfg)?),
unrealized: Box::new(UnrealizedBase::forced_import(cfg)?),
unrealized: Box::new(UnrealizedCore::forced_import(cfg)?),
relative: Box::new(RelativeBaseWithRelToAll::forced_import(cfg)?),
})
}
@@ -90,7 +90,7 @@ impl CoreCohortMetrics {
)?;
self.unrealized.compute_from_stateful(
starting_indexes,
&others.iter().map(|v| &v.unrealized_full().base).collect::<Vec<_>>(),
&others.iter().map(|v| &v.unrealized_base().core).collect::<Vec<_>>(),
exit,
)?;

View File

@@ -33,6 +33,7 @@ pub struct ExtendedCohortMetrics<M: StorageMode = Rw> {
impl CohortMetricsBase for ExtendedCohortMetrics {
type RealizedVecs = RealizedFull;
type UnrealizedVecs = UnrealizedFull;
type CostBasisVecs = CostBasisWithExtended;
impl_cohort_accessors!();

View File

@@ -8,6 +8,7 @@ use crate::{blocks, prices};
use crate::distribution::metrics::{
CohortMetricsBase, CostBasisWithExtended, ImportConfig, RealizedAdjusted, RealizedFull,
UnrealizedFull,
};
use super::ExtendedCohortMetrics;
@@ -27,6 +28,7 @@ pub struct ExtendedAdjustedCohortMetrics<M: StorageMode = Rw> {
impl CohortMetricsBase for ExtendedAdjustedCohortMetrics {
type RealizedVecs = RealizedFull;
type UnrealizedVecs = UnrealizedFull;
type CostBasisVecs = CostBasisWithExtended;
impl_cohort_accessors!();

View File

@@ -27,8 +27,8 @@ macro_rules! impl_cohort_accessors {
fn activity_mut(&mut self) -> &mut $crate::distribution::metrics::ActivityFull { &mut self.activity }
fn realized(&self) -> &Self::RealizedVecs { &self.realized }
fn realized_mut(&mut self) -> &mut Self::RealizedVecs { &mut self.realized }
fn unrealized_full(&self) -> &$crate::distribution::metrics::UnrealizedFull { &self.unrealized }
fn unrealized_full_mut(&mut self) -> &mut $crate::distribution::metrics::UnrealizedFull { &mut self.unrealized }
fn unrealized(&self) -> &Self::UnrealizedVecs { &self.unrealized }
fn unrealized_mut(&mut self) -> &mut Self::UnrealizedVecs { &mut self.unrealized }
fn cost_basis(&self) -> &Self::CostBasisVecs { &self.cost_basis }
fn cost_basis_mut(&mut self) -> &mut Self::CostBasisVecs { &mut self.cost_basis }
};
@@ -58,7 +58,7 @@ pub use relative::{
RelativeBaseWithRelToAll, RelativeForAll, RelativeWithExtended, RelativeWithRelToAll,
};
pub use supply::SupplyMetrics;
pub use unrealized::{UnrealizedBase, UnrealizedFull};
pub use unrealized::{UnrealizedBase, UnrealizedCore, UnrealizedFull, UnrealizedLike};
use brk_cohort::Filter;
use brk_error::Result;
@@ -92,6 +92,7 @@ impl<M: StorageMode> CohortMetricsState for AllCohortMetrics<M> {
pub trait CohortMetricsBase: CohortMetricsState<Realized = RealizedState> + Send + Sync {
type RealizedVecs: RealizedLike;
type UnrealizedVecs: UnrealizedLike;
type CostBasisVecs: CostBasisLike;
fn filter(&self) -> &Filter;
@@ -103,8 +104,8 @@ pub trait CohortMetricsBase: CohortMetricsState<Realized = RealizedState> + Send
fn activity_mut(&mut self) -> &mut ActivityFull;
fn realized(&self) -> &Self::RealizedVecs;
fn realized_mut(&mut self) -> &mut Self::RealizedVecs;
fn unrealized_full(&self) -> &UnrealizedFull;
fn unrealized_full_mut(&mut self) -> &mut UnrealizedFull;
fn unrealized(&self) -> &Self::UnrealizedVecs;
fn unrealized_mut(&mut self) -> &mut Self::UnrealizedVecs;
fn cost_basis(&self) -> &Self::CostBasisVecs;
fn cost_basis_mut(&mut self) -> &mut Self::CostBasisVecs;
@@ -112,6 +113,10 @@ pub trait CohortMetricsBase: CohortMetricsState<Realized = RealizedState> + Send
fn realized_base(&self) -> &RealizedBase { self.realized().as_base() }
fn realized_base_mut(&mut self) -> &mut RealizedBase { self.realized_mut().as_base_mut() }
/// Convenience: access unrealized as `&UnrealizedBase` (via `UnrealizedLike::as_base`).
fn unrealized_base(&self) -> &UnrealizedBase { self.unrealized().as_base() }
fn unrealized_base_mut(&mut self) -> &mut UnrealizedBase { self.unrealized_mut().as_base_mut() }
/// Convenience: access cost basis as `&CostBasisBase` (via `CostBasisLike::as_base`).
fn cost_basis_base(&self) -> &CostBasisBase { self.cost_basis().as_base() }
fn cost_basis_base_mut(&mut self) -> &mut CostBasisBase { self.cost_basis_mut().as_base_mut() }
@@ -134,7 +139,7 @@ pub trait CohortMetricsBase: CohortMetricsState<Realized = RealizedState> + Send
self.cost_basis_base_mut()
.truncate_push_minmax(height, state)?;
let unrealized_state = state.compute_unrealized_state(height_price);
self.unrealized_full_mut()
self.unrealized_mut()
.truncate_push(height, &unrealized_state)?;
Ok(())
}
@@ -160,7 +165,7 @@ pub trait CohortMetricsBase: CohortMetricsState<Realized = RealizedState> + Send
.min(self.outputs().min_len())
.min(self.activity().min_len())
.min(self.realized().min_stateful_height_len())
.min(self.unrealized_full().min_stateful_height_len())
.min(self.unrealized().min_stateful_height_len())
.min(self.cost_basis_base().min_stateful_height_len())
}
@@ -180,31 +185,6 @@ pub trait CohortMetricsBase: CohortMetricsState<Realized = RealizedState> + Send
Ok(())
}
/// Compute net_sentiment.height as capital-weighted average of component cohorts.
fn compute_net_sentiment_from_others<T: CohortMetricsBase>(
&mut self,
starting_indexes: &Indexes,
others: &[&T],
exit: &Exit,
) -> Result<()> {
let weights: Vec<_> = others
.iter()
.map(|o| &o.realized_base().realized_cap.height)
.collect();
let values: Vec<_> = others
.iter()
.map(|o| &o.unrealized_full().net_sentiment.cents.height)
.collect();
self.unrealized_full_mut()
.net_sentiment
.cents
.height
.compute_weighted_average_of_others(starting_indexes.height, &weights, &values, exit)?;
Ok(())
}
/// First phase of computed metrics (indexes from height).
fn compute_rest_part1(
&mut self,
@@ -234,19 +214,18 @@ pub trait CohortMetricsBase: CohortMetricsState<Realized = RealizedState> + Send
self.realized_mut()
.compute_rest_part1(starting_indexes, exit)?;
self.unrealized_full_mut()
self.unrealized_mut()
.compute_rest(prices, starting_indexes, exit)?;
Ok(())
}
/// Compute net_sentiment.height for separate cohorts (greed - pain).
fn compute_net_sentiment_height(
&mut self,
starting_indexes: &Indexes,
exit: &Exit,
) -> Result<()> {
self.unrealized_full_mut()
self.unrealized_mut()
.compute_net_sentiment_height(starting_indexes, exit)?;
Ok(())
}
@@ -278,9 +257,9 @@ pub trait CohortMetricsBase: CohortMetricsState<Realized = RealizedState> + Send
&others.iter().map(|v| v.realized_base()).collect::<Vec<_>>(),
exit,
)?;
self.unrealized_full_mut().compute_from_stateful(
self.unrealized_base_mut().compute_from_stateful(
starting_indexes,
&others.iter().map(|v| v.unrealized_full()).collect::<Vec<_>>(),
&others.iter().map(|v| v.unrealized_base()).collect::<Vec<_>>(),
exit,
)?;
self.cost_basis_base_mut().compute_from_stateful(

View File

@@ -10,9 +10,8 @@ use crate::{
blocks,
distribution::state::RealizedOps,
internal::{
ComputedFromHeight, ComputedFromHeightCumulative,
LazyFromHeight, NegCentsUnsignedToDollars, RatioCents64,
RollingWindows, ValueFromHeightCumulative,
ByUnit, ComputedFromHeight, ComputedFromHeightCumulative, LazyFromHeight,
NegCentsUnsignedToDollars, RatioCents64, RollingWindows, SatsToCents,
},
prices,
};

View File

@@ -5,7 +5,7 @@ use vecdb::{Exit, ReadableVec, Rw, StorageMode};
use crate::internal::{PercentFromHeight, RatioSatsBp16};
use crate::distribution::metrics::{ImportConfig, UnrealizedBase};
use crate::distribution::metrics::{ImportConfig, UnrealizedCore};
/// Relative metrics for the Complete tier.
#[derive(Traversable)]
@@ -29,7 +29,7 @@ impl RelativeBase {
pub(crate) fn compute(
&mut self,
max_from: Height,
unrealized: &UnrealizedBase,
unrealized: &UnrealizedCore,
supply_total_sats: &impl ReadableVec<Height, Sats>,
exit: &Exit,
) -> Result<()> {

View File

@@ -5,7 +5,7 @@ use vecdb::{Exit, ReadableVec, Rw, StorageMode};
use crate::internal::{PercentFromHeight, RatioDollarsBp16, RatioDollarsBp32, RatioDollarsBps32};
use crate::distribution::metrics::{ImportConfig, UnrealizedFull};
use crate::distribution::metrics::{ImportConfig, UnrealizedBase};
/// Extended relative metrics for own market cap (extended && rel_to_all).
#[derive(Traversable)]
@@ -32,7 +32,7 @@ impl RelativeExtendedOwnMarketCap {
pub(crate) fn compute(
&mut self,
max_from: Height,
unrealized: &UnrealizedFull,
unrealized: &UnrealizedBase,
own_market_cap: &impl ReadableVec<Height, Dollars>,
exit: &Exit,
) -> Result<()> {

View File

@@ -5,7 +5,7 @@ use vecdb::{Exit, Rw, StorageMode};
use crate::internal::{PercentFromHeight, RatioDollarsBp16, RatioDollarsBps32};
use crate::distribution::metrics::{ImportConfig, UnrealizedFull};
use crate::distribution::metrics::{ImportConfig, UnrealizedBase};
/// Extended relative metrics for own total unrealized PnL (extended only).
#[derive(Traversable)]
@@ -32,7 +32,7 @@ impl RelativeExtendedOwnPnl {
pub(crate) fn compute(
&mut self,
max_from: Height,
unrealized: &UnrealizedFull,
unrealized: &UnrealizedBase,
exit: &Exit,
) -> Result<()> {
self.unrealized_profit_rel_to_own_gross_pnl

View File

@@ -4,7 +4,7 @@ use brk_types::{Dollars, Height, Sats};
use derive_more::{Deref, DerefMut};
use vecdb::{Exit, ReadableVec, Rw, StorageMode};
use crate::distribution::metrics::{ImportConfig, RealizedBase, UnrealizedFull};
use crate::distribution::metrics::{ImportConfig, RealizedBase, UnrealizedBase};
use super::{RelativeFull, RelativeExtendedOwnPnl};
@@ -30,7 +30,7 @@ impl RelativeForAll {
pub(crate) fn compute(
&mut self,
max_from: Height,
unrealized: &UnrealizedFull,
unrealized: &UnrealizedBase,
realized: &RealizedBase,
supply_total_sats: &impl ReadableVec<Height, Sats>,
market_cap: &impl ReadableVec<Height, Dollars>,

View File

@@ -10,7 +10,7 @@ use crate::internal::{
Bps32ToFloat, LazyFromHeight, PercentFromHeight, RatioDollarsBp16, RatioDollarsBps32,
};
use crate::distribution::metrics::{ImportConfig, RealizedBase, UnrealizedFull};
use crate::distribution::metrics::{ImportConfig, RealizedBase, UnrealizedBase};
use super::RelativeBase;
@@ -73,7 +73,7 @@ impl RelativeFull {
pub(crate) fn compute(
&mut self,
max_from: Height,
unrealized: &UnrealizedFull,
unrealized: &UnrealizedBase,
realized: &RealizedBase,
supply_total_sats: &impl ReadableVec<Height, Sats>,
market_cap: &impl ReadableVec<Height, Dollars>,
@@ -81,7 +81,7 @@ impl RelativeFull {
) -> Result<()> {
self.base.compute(
max_from,
&unrealized.base,
&unrealized.core,
supply_total_sats,
exit,
)?;

View File

@@ -4,7 +4,7 @@ use brk_types::{Dollars, Height, Sats};
use derive_more::{Deref, DerefMut};
use vecdb::{Exit, ReadableVec, Rw, StorageMode};
use crate::distribution::metrics::{ImportConfig, RealizedBase, UnrealizedFull};
use crate::distribution::metrics::{ImportConfig, RealizedBase, UnrealizedBase};
use super::{RelativeFull, RelativeExtendedOwnMarketCap, RelativeExtendedOwnPnl, RelativeToAll};
@@ -38,7 +38,7 @@ impl RelativeWithExtended {
pub(crate) fn compute(
&mut self,
max_from: Height,
unrealized: &UnrealizedFull,
unrealized: &UnrealizedBase,
realized: &RealizedBase,
supply_total_sats: &impl ReadableVec<Height, Sats>,
market_cap: &impl ReadableVec<Height, Dollars>,

View File

@@ -4,7 +4,7 @@ use brk_types::{Dollars, Height, Sats};
use derive_more::{Deref, DerefMut};
use vecdb::{Exit, ReadableVec, Rw, StorageMode};
use crate::distribution::metrics::{ImportConfig, RealizedBase, UnrealizedFull};
use crate::distribution::metrics::{ImportConfig, RealizedBase, UnrealizedBase};
use super::{RelativeFull, RelativeToAll};
@@ -32,7 +32,7 @@ impl RelativeWithRelToAll {
pub(crate) fn compute(
&mut self,
max_from: Height,
unrealized: &UnrealizedFull,
unrealized: &UnrealizedBase,
realized: &RealizedBase,
supply_total_sats: &impl ReadableVec<Height, Sats>,
market_cap: &impl ReadableVec<Height, Dollars>,

View File

@@ -4,7 +4,7 @@ use brk_types::{Height, Sats};
use derive_more::{Deref, DerefMut};
use vecdb::{Exit, ReadableVec, Rw, StorageMode};
use crate::distribution::metrics::{ImportConfig, UnrealizedBase};
use crate::distribution::metrics::{ImportConfig, UnrealizedCore};
use super::{RelativeBase, RelativeToAll};
@@ -31,7 +31,7 @@ impl RelativeBaseWithRelToAll {
pub(crate) fn compute(
&mut self,
max_from: Height,
unrealized: &UnrealizedBase,
unrealized: &UnrealizedCore,
supply_total_sats: &impl ReadableVec<Height, Sats>,
all_supply_sats: &impl ReadableVec<Height, Sats>,
exit: &Exit,

View File

@@ -1,73 +1,75 @@
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{Cents, CentsSigned, Height, Indexes, Version};
use vecdb::{AnyStoredVec, AnyVec, Exit, ReadableCloneableVec, Rw, StorageMode, WritableVec};
use brk_types::{Cents, CentsSats, CentsSquaredSats, Height, Indexes, Version};
use derive_more::{Deref, DerefMut};
use vecdb::{AnyStoredVec, AnyVec, BytesVec, Exit, ReadableVec, Rw, StorageMode, WritableVec};
use crate::{
distribution::state::UnrealizedState,
internal::{
CentsSubtractToCentsSigned, FiatFromHeight, LazyFromHeight, NegCentsUnsignedToDollars,
ValueFromHeight,
},
internal::FiatFromHeight,
prices,
};
use brk_types::Dollars;
use crate::distribution::metrics::ImportConfig;
/// Unrealized metrics for the Complete tier (~6 fields).
///
/// Excludes source-only fields (invested_capital, raw BytesVecs)
/// and extended-only fields (pain_index, greed_index, net_sentiment).
#[derive(Traversable)]
use super::UnrealizedCore;
#[derive(Deref, DerefMut, Traversable)]
pub struct UnrealizedBase<M: StorageMode = Rw> {
pub supply_in_profit: ValueFromHeight<M>,
pub supply_in_loss: ValueFromHeight<M>,
#[deref]
#[deref_mut]
#[traversable(flatten)]
pub core: UnrealizedCore<M>,
pub unrealized_profit: FiatFromHeight<Cents, M>,
pub unrealized_loss: FiatFromHeight<Cents, M>,
pub gross_pnl: FiatFromHeight<Cents, M>,
pub neg_unrealized_loss: LazyFromHeight<Dollars, Cents>,
pub invested_capital_in_profit: FiatFromHeight<Cents, M>,
pub invested_capital_in_loss: FiatFromHeight<Cents, M>,
pub net_unrealized_pnl: FiatFromHeight<CentsSigned, M>,
pub invested_capital_in_profit_raw: M::Stored<BytesVec<Height, CentsSats>>,
pub invested_capital_in_loss_raw: M::Stored<BytesVec<Height, CentsSats>>,
pub investor_cap_in_profit_raw: M::Stored<BytesVec<Height, CentsSquaredSats>>,
pub investor_cap_in_loss_raw: M::Stored<BytesVec<Height, CentsSquaredSats>>,
}
impl UnrealizedBase {
pub(crate) fn forced_import(cfg: &ImportConfig) -> Result<Self> {
let v0 = Version::ZERO;
let supply_in_profit = cfg.import("supply_in_profit", v0)?;
let supply_in_loss = cfg.import("supply_in_loss", v0)?;
let unrealized_profit = cfg.import("unrealized_profit", v0)?;
let unrealized_loss: FiatFromHeight<Cents> = cfg.import("unrealized_loss", v0)?;
let core = UnrealizedCore::forced_import(cfg)?;
let neg_unrealized_loss = LazyFromHeight::from_computed::<NegCentsUnsignedToDollars>(
&cfg.name("neg_unrealized_loss"),
cfg.version,
unrealized_loss.cents.height.read_only_boxed_clone(),
&unrealized_loss.cents,
);
let gross_pnl = cfg.import("unrealized_gross_pnl", v0)?;
let net_unrealized_pnl = cfg.import("net_unrealized_pnl", v0)?;
let invested_capital_in_profit = cfg.import("invested_capital_in_profit", v0)?;
let invested_capital_in_loss = cfg.import("invested_capital_in_loss", v0)?;
let invested_capital_in_profit_raw =
cfg.import("invested_capital_in_profit_raw", v0)?;
let invested_capital_in_loss_raw = cfg.import("invested_capital_in_loss_raw", v0)?;
let investor_cap_in_profit_raw = cfg.import("investor_cap_in_profit_raw", v0)?;
let investor_cap_in_loss_raw = cfg.import("investor_cap_in_loss_raw", v0)?;
Ok(Self {
supply_in_profit,
supply_in_loss,
unrealized_profit,
unrealized_loss,
neg_unrealized_loss,
net_unrealized_pnl,
core,
gross_pnl,
invested_capital_in_profit,
invested_capital_in_loss,
invested_capital_in_profit_raw,
invested_capital_in_loss_raw,
investor_cap_in_profit_raw,
investor_cap_in_loss_raw,
})
}
pub(crate) fn min_stateful_height_len(&self) -> usize {
self.supply_in_profit
.sats
.height
.len()
.min(self.supply_in_loss.sats.height.len())
.min(self.unrealized_profit.cents.height.len())
.min(self.unrealized_loss.cents.height.len())
self.core
.min_stateful_height_len()
.min(self.invested_capital_in_profit.cents.height.len())
.min(self.invested_capital_in_loss.cents.height.len())
.min(self.invested_capital_in_profit_raw.len())
.min(self.invested_capital_in_loss_raw.len())
.min(self.investor_cap_in_profit_raw.len())
.min(self.investor_cap_in_loss_raw.len())
}
pub(crate) fn truncate_push(
@@ -75,35 +77,46 @@ impl UnrealizedBase {
height: Height,
height_state: &UnrealizedState,
) -> Result<()> {
self.supply_in_profit
.sats
.height
.truncate_push(height, height_state.supply_in_profit)?;
self.supply_in_loss
.sats
.height
.truncate_push(height, height_state.supply_in_loss)?;
self.unrealized_profit
self.core.truncate_push(height, height_state)?;
self.invested_capital_in_profit
.cents
.height
.truncate_push(height, height_state.unrealized_profit)?;
self.unrealized_loss
.truncate_push(height, height_state.invested_capital_in_profit)?;
self.invested_capital_in_loss
.cents
.height
.truncate_push(height, height_state.unrealized_loss)?;
.truncate_push(height, height_state.invested_capital_in_loss)?;
self.invested_capital_in_profit_raw.truncate_push(
height,
CentsSats::new(height_state.invested_capital_in_profit_raw),
)?;
self.invested_capital_in_loss_raw.truncate_push(
height,
CentsSats::new(height_state.invested_capital_in_loss_raw),
)?;
self.investor_cap_in_profit_raw.truncate_push(
height,
CentsSquaredSats::new(height_state.investor_cap_in_profit_raw),
)?;
self.investor_cap_in_loss_raw.truncate_push(
height,
CentsSquaredSats::new(height_state.investor_cap_in_loss_raw),
)?;
Ok(())
}
pub(crate) fn collect_vecs_mut(&mut self) -> Vec<&mut dyn AnyStoredVec> {
vec![
&mut self.supply_in_profit.base.sats.height as &mut dyn AnyStoredVec,
&mut self.supply_in_profit.base.cents.height as &mut dyn AnyStoredVec,
&mut self.supply_in_loss.base.sats.height as &mut dyn AnyStoredVec,
&mut self.supply_in_loss.base.cents.height as &mut dyn AnyStoredVec,
&mut self.unrealized_profit.cents.height,
&mut self.unrealized_loss.cents.height,
]
let mut vecs = self.core.collect_vecs_mut();
vecs.push(&mut self.invested_capital_in_profit.cents.height as &mut dyn AnyStoredVec);
vecs.push(&mut self.invested_capital_in_loss.cents.height as &mut dyn AnyStoredVec);
vecs.push(&mut self.invested_capital_in_profit_raw as &mut dyn AnyStoredVec);
vecs.push(&mut self.invested_capital_in_loss_raw as &mut dyn AnyStoredVec);
vecs.push(&mut self.investor_cap_in_profit_raw as &mut dyn AnyStoredVec);
vecs.push(&mut self.investor_cap_in_loss_raw as &mut dyn AnyStoredVec);
vecs
}
pub(crate) fn compute_from_stateful(
@@ -112,29 +125,89 @@ impl UnrealizedBase {
others: &[&Self],
exit: &Exit,
) -> Result<()> {
sum_others!(self, starting_indexes, others, exit; supply_in_profit.sats.height);
sum_others!(self, starting_indexes, others, exit; supply_in_loss.sats.height);
sum_others!(self, starting_indexes, others, exit; unrealized_profit.cents.height);
sum_others!(self, starting_indexes, others, exit; unrealized_loss.cents.height);
let core_refs: Vec<&UnrealizedCore> =
others.iter().map(|o| &o.core).collect();
self.core
.compute_from_stateful(starting_indexes, &core_refs, exit)?;
sum_others!(self, starting_indexes, others, exit; invested_capital_in_profit.cents.height);
sum_others!(self, starting_indexes, others, exit; invested_capital_in_loss.cents.height);
let start = self
.invested_capital_in_profit_raw
.len()
.min(self.invested_capital_in_loss_raw.len())
.min(self.investor_cap_in_profit_raw.len())
.min(self.investor_cap_in_loss_raw.len());
let end = others
.iter()
.map(|o| o.invested_capital_in_profit_raw.len())
.min()
.unwrap_or(0);
let invested_profit_ranges: Vec<Vec<CentsSats>> = others
.iter()
.map(|o| {
o.invested_capital_in_profit_raw
.collect_range_at(start, end)
})
.collect();
let invested_loss_ranges: Vec<Vec<CentsSats>> = others
.iter()
.map(|o| o.invested_capital_in_loss_raw.collect_range_at(start, end))
.collect();
let investor_profit_ranges: Vec<Vec<CentsSquaredSats>> = others
.iter()
.map(|o| o.investor_cap_in_profit_raw.collect_range_at(start, end))
.collect();
let investor_loss_ranges: Vec<Vec<CentsSquaredSats>> = others
.iter()
.map(|o| o.investor_cap_in_loss_raw.collect_range_at(start, end))
.collect();
for i in start..end {
let height = Height::from(i);
let local_i = i - start;
let mut sum_invested_profit = CentsSats::ZERO;
let mut sum_invested_loss = CentsSats::ZERO;
let mut sum_investor_profit = CentsSquaredSats::ZERO;
let mut sum_investor_loss = CentsSquaredSats::ZERO;
for idx in 0..others.len() {
sum_invested_profit += invested_profit_ranges[idx][local_i];
sum_invested_loss += invested_loss_ranges[idx][local_i];
sum_investor_profit += investor_profit_ranges[idx][local_i];
sum_investor_loss += investor_loss_ranges[idx][local_i];
}
self.invested_capital_in_profit_raw
.truncate_push(height, sum_invested_profit)?;
self.invested_capital_in_loss_raw
.truncate_push(height, sum_invested_loss)?;
self.investor_cap_in_profit_raw
.truncate_push(height, sum_investor_profit)?;
self.investor_cap_in_loss_raw
.truncate_push(height, sum_investor_loss)?;
}
Ok(())
}
/// Compute derived metrics from stored values.
pub(crate) fn compute_rest(
&mut self,
_prices: &prices::Vecs,
starting_indexes: &Indexes,
exit: &Exit,
) -> Result<()> {
self.net_unrealized_pnl
.cents
.height
.compute_binary::<Cents, Cents, CentsSubtractToCentsSigned>(
starting_indexes.height,
&self.unrealized_profit.cents.height,
&self.unrealized_loss.cents.height,
exit,
)?;
self.core.compute_rest(starting_indexes, exit)?;
self.gross_pnl.cents.height.compute_add(
starting_indexes.height,
&self.core.unrealized_profit.cents.height,
&self.core.unrealized_loss.cents.height,
exit,
)?;
Ok(())
}

View File

@@ -0,0 +1,137 @@
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{Cents, CentsSigned, Height, Indexes, Version};
use vecdb::{AnyStoredVec, AnyVec, Exit, ReadableCloneableVec, Rw, StorageMode, WritableVec};
use crate::{
distribution::state::UnrealizedState,
internal::{
CentsSubtractToCentsSigned, FiatFromHeight, LazyFromHeight, NegCentsUnsignedToDollars,
ValueFromHeight,
},
};
use brk_types::Dollars;
use crate::distribution::metrics::ImportConfig;
#[derive(Traversable)]
pub struct UnrealizedCore<M: StorageMode = Rw> {
pub supply_in_profit: ValueFromHeight<M>,
pub supply_in_loss: ValueFromHeight<M>,
pub unrealized_profit: FiatFromHeight<Cents, M>,
pub unrealized_loss: FiatFromHeight<Cents, M>,
pub neg_unrealized_loss: LazyFromHeight<Dollars, Cents>,
pub net_unrealized_pnl: FiatFromHeight<CentsSigned, M>,
}
impl UnrealizedCore {
pub(crate) fn forced_import(cfg: &ImportConfig) -> Result<Self> {
let v0 = Version::ZERO;
let supply_in_profit = cfg.import("supply_in_profit", v0)?;
let supply_in_loss = cfg.import("supply_in_loss", v0)?;
let unrealized_profit = cfg.import("unrealized_profit", v0)?;
let unrealized_loss: FiatFromHeight<Cents> = cfg.import("unrealized_loss", v0)?;
let neg_unrealized_loss = LazyFromHeight::from_computed::<NegCentsUnsignedToDollars>(
&cfg.name("neg_unrealized_loss"),
cfg.version,
unrealized_loss.cents.height.read_only_boxed_clone(),
&unrealized_loss.cents,
);
let net_unrealized_pnl = cfg.import("net_unrealized_pnl", v0)?;
Ok(Self {
supply_in_profit,
supply_in_loss,
unrealized_profit,
unrealized_loss,
neg_unrealized_loss,
net_unrealized_pnl,
})
}
pub(crate) fn min_stateful_height_len(&self) -> usize {
self.supply_in_profit
.sats
.height
.len()
.min(self.supply_in_loss.sats.height.len())
.min(self.unrealized_profit.cents.height.len())
.min(self.unrealized_loss.cents.height.len())
}
pub(crate) fn truncate_push(
&mut self,
height: Height,
height_state: &UnrealizedState,
) -> Result<()> {
self.supply_in_profit
.sats
.height
.truncate_push(height, height_state.supply_in_profit)?;
self.supply_in_loss
.sats
.height
.truncate_push(height, height_state.supply_in_loss)?;
self.unrealized_profit
.cents
.height
.truncate_push(height, height_state.unrealized_profit)?;
self.unrealized_loss
.cents
.height
.truncate_push(height, height_state.unrealized_loss)?;
Ok(())
}
pub(crate) fn collect_vecs_mut(&mut self) -> Vec<&mut dyn AnyStoredVec> {
vec![
&mut self.supply_in_profit.base.sats.height as &mut dyn AnyStoredVec,
&mut self.supply_in_profit.base.cents.height as &mut dyn AnyStoredVec,
&mut self.supply_in_loss.base.sats.height as &mut dyn AnyStoredVec,
&mut self.supply_in_loss.base.cents.height as &mut dyn AnyStoredVec,
&mut self.unrealized_profit.cents.height,
&mut self.unrealized_loss.cents.height,
]
}
pub(crate) fn compute_from_stateful(
&mut self,
starting_indexes: &Indexes,
others: &[&Self],
exit: &Exit,
) -> Result<()> {
sum_others!(self, starting_indexes, others, exit; supply_in_profit.sats.height);
sum_others!(self, starting_indexes, others, exit; supply_in_loss.sats.height);
sum_others!(self, starting_indexes, others, exit; unrealized_profit.cents.height);
sum_others!(self, starting_indexes, others, exit; unrealized_loss.cents.height);
Ok(())
}
/// Compute derived metrics from stored values.
pub(crate) fn compute_rest(
&mut self,
starting_indexes: &Indexes,
exit: &Exit,
) -> Result<()> {
self.net_unrealized_pnl
.cents
.height
.compute_binary::<Cents, Cents, CentsSubtractToCentsSigned>(
starting_indexes.height,
&self.unrealized_profit.cents.height,
&self.unrealized_loss.cents.height,
exit,
)?;
Ok(())
}
}

View File

@@ -1,42 +1,23 @@
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{Cents, CentsSats, CentsSigned, CentsSquaredSats, Height, Indexes, Version};
use brk_types::{Cents, CentsSigned, Indexes, Version};
use derive_more::{Deref, DerefMut};
use vecdb::{AnyStoredVec, AnyVec, BytesVec, Exit, ReadableVec, Rw, StorageMode, WritableVec};
use vecdb::{Exit, Rw, StorageMode};
use crate::{
distribution::state::UnrealizedState,
internal::{CentsSubtractToCentsSigned, FiatFromHeight},
prices,
};
use crate::internal::{CentsSubtractToCentsSigned, FiatFromHeight};
use crate::prices;
use crate::distribution::metrics::ImportConfig;
use super::UnrealizedBase;
/// Full unrealized metrics (Source/Extended tier).
///
/// Contains all Complete-tier fields (via Deref to UnrealizedBase) plus:
/// - Source-only: invested_capital, raw BytesVecs
/// - Extended-only: pain_index, greed_index, net_sentiment
#[derive(Deref, DerefMut, Traversable)]
pub struct UnrealizedFull<M: StorageMode = Rw> {
#[deref]
#[deref_mut]
#[traversable(flatten)]
pub base: UnrealizedBase<M>,
pub inner: UnrealizedBase<M>,
pub gross_pnl: FiatFromHeight<Cents, M>,
pub invested_capital_in_profit: FiatFromHeight<Cents, M>,
pub invested_capital_in_loss: FiatFromHeight<Cents, M>,
pub invested_capital_in_profit_raw: M::Stored<BytesVec<Height, CentsSats>>,
pub invested_capital_in_loss_raw: M::Stored<BytesVec<Height, CentsSats>>,
pub investor_cap_in_profit_raw: M::Stored<BytesVec<Height, CentsSquaredSats>>,
pub investor_cap_in_loss_raw: M::Stored<BytesVec<Height, CentsSquaredSats>>,
// --- Extended-only fields ---
pub pain_index: FiatFromHeight<Cents, M>,
pub greed_index: FiatFromHeight<Cents, M>,
pub net_sentiment: FiatFromHeight<CentsSigned, M>,
@@ -44,198 +25,32 @@ pub struct UnrealizedFull<M: StorageMode = Rw> {
impl UnrealizedFull {
pub(crate) fn forced_import(cfg: &ImportConfig) -> Result<Self> {
let v0 = Version::ZERO;
let inner = UnrealizedBase::forced_import(cfg)?;
let base = UnrealizedBase::forced_import(cfg)?;
let gross_pnl = cfg.import("unrealized_gross_pnl", v0)?;
let invested_capital_in_profit = cfg.import("invested_capital_in_profit", v0)?;
let invested_capital_in_loss = cfg.import("invested_capital_in_loss", v0)?;
let invested_capital_in_profit_raw =
cfg.import("invested_capital_in_profit_raw", v0)?;
let invested_capital_in_loss_raw = cfg.import("invested_capital_in_loss_raw", v0)?;
let investor_cap_in_profit_raw = cfg.import("investor_cap_in_profit_raw", v0)?;
let investor_cap_in_loss_raw = cfg.import("investor_cap_in_loss_raw", v0)?;
let pain_index = cfg.import("pain_index", v0)?;
let greed_index = cfg.import("greed_index", v0)?;
let pain_index = cfg.import("pain_index", Version::ZERO)?;
let greed_index = cfg.import("greed_index", Version::ZERO)?;
let net_sentiment = cfg.import("net_sentiment", Version::ONE)?;
Ok(Self {
base,
gross_pnl,
invested_capital_in_profit,
invested_capital_in_loss,
invested_capital_in_profit_raw,
invested_capital_in_loss_raw,
investor_cap_in_profit_raw,
investor_cap_in_loss_raw,
inner,
pain_index,
greed_index,
net_sentiment,
})
}
pub(crate) fn min_stateful_height_len(&self) -> usize {
self.base
.min_stateful_height_len()
.min(self.invested_capital_in_profit.cents.height.len())
.min(self.invested_capital_in_loss.cents.height.len())
.min(self.invested_capital_in_profit_raw.len())
.min(self.invested_capital_in_loss_raw.len())
.min(self.investor_cap_in_profit_raw.len())
.min(self.investor_cap_in_loss_raw.len())
}
pub(crate) fn truncate_push(
&mut self,
height: Height,
height_state: &UnrealizedState,
) -> Result<()> {
self.base.truncate_push(height, height_state)?;
self.invested_capital_in_profit
.cents
.height
.truncate_push(height, height_state.invested_capital_in_profit)?;
self.invested_capital_in_loss
.cents
.height
.truncate_push(height, height_state.invested_capital_in_loss)?;
self.invested_capital_in_profit_raw.truncate_push(
height,
CentsSats::new(height_state.invested_capital_in_profit_raw),
)?;
self.invested_capital_in_loss_raw.truncate_push(
height,
CentsSats::new(height_state.invested_capital_in_loss_raw),
)?;
self.investor_cap_in_profit_raw.truncate_push(
height,
CentsSquaredSats::new(height_state.investor_cap_in_profit_raw),
)?;
self.investor_cap_in_loss_raw.truncate_push(
height,
CentsSquaredSats::new(height_state.investor_cap_in_loss_raw),
)?;
Ok(())
}
pub(crate) fn collect_vecs_mut(&mut self) -> Vec<&mut dyn AnyStoredVec> {
let mut vecs = self.base.collect_vecs_mut();
vecs.push(&mut self.invested_capital_in_profit.cents.height as &mut dyn AnyStoredVec);
vecs.push(&mut self.invested_capital_in_loss.cents.height as &mut dyn AnyStoredVec);
vecs.push(&mut self.invested_capital_in_profit_raw as &mut dyn AnyStoredVec);
vecs.push(&mut self.invested_capital_in_loss_raw as &mut dyn AnyStoredVec);
vecs.push(&mut self.investor_cap_in_profit_raw as &mut dyn AnyStoredVec);
vecs.push(&mut self.investor_cap_in_loss_raw as &mut dyn AnyStoredVec);
vecs
}
pub(crate) fn compute_from_stateful(
&mut self,
starting_indexes: &Indexes,
others: &[&Self],
exit: &Exit,
) -> Result<()> {
// Delegate Complete-tier aggregation
let base_refs: Vec<&UnrealizedBase> =
others.iter().map(|o| &o.base).collect();
self.base
.compute_from_stateful(starting_indexes, &base_refs, exit)?;
// Source-only: invested_capital
sum_others!(self, starting_indexes, others, exit; invested_capital_in_profit.cents.height);
sum_others!(self, starting_indexes, others, exit; invested_capital_in_loss.cents.height);
// Source-only: raw BytesVec aggregation
let start = self
.invested_capital_in_profit_raw
.len()
.min(self.invested_capital_in_loss_raw.len())
.min(self.investor_cap_in_profit_raw.len())
.min(self.investor_cap_in_loss_raw.len());
let end = others
.iter()
.map(|o| o.invested_capital_in_profit_raw.len())
.min()
.unwrap_or(0);
// Pre-collect all cohort data to avoid per-element BytesVec reads in nested loop
let invested_profit_ranges: Vec<Vec<CentsSats>> = others
.iter()
.map(|o| {
o.invested_capital_in_profit_raw
.collect_range_at(start, end)
})
.collect();
let invested_loss_ranges: Vec<Vec<CentsSats>> = others
.iter()
.map(|o| o.invested_capital_in_loss_raw.collect_range_at(start, end))
.collect();
let investor_profit_ranges: Vec<Vec<CentsSquaredSats>> = others
.iter()
.map(|o| o.investor_cap_in_profit_raw.collect_range_at(start, end))
.collect();
let investor_loss_ranges: Vec<Vec<CentsSquaredSats>> = others
.iter()
.map(|o| o.investor_cap_in_loss_raw.collect_range_at(start, end))
.collect();
for i in start..end {
let height = Height::from(i);
let local_i = i - start;
let mut sum_invested_profit = CentsSats::ZERO;
let mut sum_invested_loss = CentsSats::ZERO;
let mut sum_investor_profit = CentsSquaredSats::ZERO;
let mut sum_investor_loss = CentsSquaredSats::ZERO;
for idx in 0..others.len() {
sum_invested_profit += invested_profit_ranges[idx][local_i];
sum_invested_loss += invested_loss_ranges[idx][local_i];
sum_investor_profit += investor_profit_ranges[idx][local_i];
sum_investor_loss += investor_loss_ranges[idx][local_i];
}
self.invested_capital_in_profit_raw
.truncate_push(height, sum_invested_profit)?;
self.invested_capital_in_loss_raw
.truncate_push(height, sum_invested_loss)?;
self.investor_cap_in_profit_raw
.truncate_push(height, sum_investor_profit)?;
self.investor_cap_in_loss_raw
.truncate_push(height, sum_investor_loss)?;
}
Ok(())
}
/// Compute derived metrics from stored values + price.
pub(crate) fn compute_rest(
&mut self,
prices: &prices::Vecs,
starting_indexes: &Indexes,
exit: &Exit,
) -> Result<()> {
self.base.compute_rest(starting_indexes, exit)?;
self.inner.compute_rest(prices, starting_indexes, exit)?;
self.gross_pnl.cents.height.compute_add(
starting_indexes.height,
&self.base.unrealized_profit.cents.height,
&self.base.unrealized_loss.cents.height,
exit,
)?;
// Pain index (investor_price_of_losers - spot)
self.pain_index.cents.height.compute_transform3(
starting_indexes.height,
&self.investor_cap_in_loss_raw,
&self.invested_capital_in_loss_raw,
&self.inner.investor_cap_in_loss_raw,
&self.inner.invested_capital_in_loss_raw,
&prices.price.cents.height,
|(h, investor_cap, invested_cap, spot, ..)| {
if invested_cap.inner() == 0 {
@@ -248,11 +63,10 @@ impl UnrealizedFull {
exit,
)?;
// Extended-only: Greed index (spot - investor_price_of_winners)
self.greed_index.cents.height.compute_transform3(
starting_indexes.height,
&self.investor_cap_in_profit_raw,
&self.invested_capital_in_profit_raw,
&self.inner.investor_cap_in_profit_raw,
&self.inner.invested_capital_in_profit_raw,
&prices.price.cents.height,
|(h, investor_cap, invested_cap, spot, ..)| {
if invested_cap.inner() == 0 {
@@ -268,7 +82,6 @@ impl UnrealizedFull {
Ok(())
}
/// Compute net_sentiment.height for separate cohorts (greed - pain).
pub(crate) fn compute_net_sentiment_height(
&mut self,
starting_indexes: &Indexes,

View File

@@ -1,5 +1,52 @@
mod base;
mod core;
mod full;
pub use base::UnrealizedBase;
pub use self::core::UnrealizedCore;
pub use full::UnrealizedFull;
use brk_error::Result;
use brk_types::{Height, Indexes};
use vecdb::Exit;
use crate::{distribution::state::UnrealizedState, prices};
pub trait UnrealizedLike: Send + Sync {
fn as_base(&self) -> &UnrealizedBase;
fn as_base_mut(&mut self) -> &mut UnrealizedBase;
fn min_stateful_height_len(&self) -> usize;
fn truncate_push(&mut self, height: Height, state: &UnrealizedState) -> Result<()>;
fn compute_rest(&mut self, prices: &prices::Vecs, starting_indexes: &Indexes, exit: &Exit) -> Result<()>;
fn compute_net_sentiment_height(&mut self, starting_indexes: &Indexes, exit: &Exit) -> Result<()>;
}
impl UnrealizedLike for UnrealizedBase {
fn as_base(&self) -> &UnrealizedBase { self }
fn as_base_mut(&mut self) -> &mut UnrealizedBase { self }
fn min_stateful_height_len(&self) -> usize { self.min_stateful_height_len() }
fn truncate_push(&mut self, height: Height, state: &UnrealizedState) -> Result<()> {
self.truncate_push(height, state)
}
fn compute_rest(&mut self, prices: &prices::Vecs, starting_indexes: &Indexes, exit: &Exit) -> Result<()> {
self.compute_rest(prices, starting_indexes, exit)
}
fn compute_net_sentiment_height(&mut self, _starting_indexes: &Indexes, _exit: &Exit) -> Result<()> {
Ok(())
}
}
impl UnrealizedLike for UnrealizedFull {
fn as_base(&self) -> &UnrealizedBase { &self.inner }
fn as_base_mut(&mut self) -> &mut UnrealizedBase { &mut self.inner }
fn min_stateful_height_len(&self) -> usize { self.inner.min_stateful_height_len() }
fn truncate_push(&mut self, height: Height, state: &UnrealizedState) -> Result<()> {
self.inner.truncate_push(height, state)
}
fn compute_rest(&mut self, prices: &prices::Vecs, starting_indexes: &Indexes, exit: &Exit) -> Result<()> {
self.compute_rest(prices, starting_indexes, exit)
}
fn compute_net_sentiment_height(&mut self, starting_indexes: &Indexes, exit: &Exit) -> Result<()> {
self.compute_net_sentiment_height(starting_indexes, exit)
}
}

View File

@@ -30,7 +30,7 @@ use crate::{
use super::{
AddressCohorts, AddressesDataVecs, AnyAddressIndexesVecs, RangeMap, UTXOCohorts,
address::{
AddrCountsVecs, AddressActivityVecs, GrowthRateVecs, NewAddrCountVecs, TotalAddrCountVecs,
AddrCountsVecs, AddressActivityVecs, DeltaVecs, NewAddrCountVecs, TotalAddrCountVecs,
},
compute::aggregates,
};
@@ -57,8 +57,8 @@ pub struct Vecs<M: StorageMode = Rw> {
pub total_addr_count: TotalAddrCountVecs<M>,
/// New addresses per block (delta of total) - stored height + cumulative + rolling, global + per-type
pub new_addr_count: NewAddrCountVecs<M>,
/// Growth rate (new / addr_count) - stored ratio with distribution stats, global + per-type
pub growth_rate: GrowthRateVecs<M>,
/// Windowed change + growth rate for addr_count, global + per-type
pub delta: DeltaVecs<M>,
pub fundedaddressindex:
LazyVecFrom1<FundedAddressIndex, FundedAddressIndex, FundedAddressIndex, FundedAddressData>,
@@ -141,7 +141,7 @@ impl Vecs {
let new_addr_count = NewAddrCountVecs::forced_import(&db, version, indexes)?;
// Growth rate: new / addr_count (global + per-type)
let growth_rate = GrowthRateVecs::forced_import(&db, version, indexes)?;
let delta = DeltaVecs::forced_import(&db, version, indexes)?;
let this = Self {
supply_state: BytesVec::forced_import_with(
@@ -154,7 +154,7 @@ impl Vecs {
address_activity,
total_addr_count,
new_addr_count,
growth_rate,
delta,
utxo_cohorts,
address_cohorts,
@@ -400,11 +400,11 @@ impl Vecs {
exit,
)?;
// 6b. Compute address count day1 vecs (by addresstype + all)
// 6b. Compute address count sum (by addresstype all)
self.addr_count
.compute_rest(blocks, starting_indexes, exit)?;
.compute_rest(starting_indexes, exit)?;
self.empty_addr_count
.compute_rest(blocks, starting_indexes, exit)?;
.compute_rest(starting_indexes, exit)?;
// 6c. Compute total_addr_count = addr_count + empty_addr_count
self.total_addr_count.compute(
@@ -425,11 +425,9 @@ impl Vecs {
exit,
)?;
// 6e. Compute growth_rate = new_addr_count / addr_count
self.growth_rate.compute(
self.delta.compute(
starting_indexes.height,
&window_starts,
&self.new_addr_count,
&self.addr_count,
exit,
)?;

View File

@@ -0,0 +1,107 @@
//! RollingDelta - raw change + growth rate (%) across 4 time windows.
//!
//! For a monotonic source (e.g., cumulative address count):
//! - `change._24h` = count_now - count_24h_ago
//! - `rate._24h` = (count_now - count_24h_ago) / count_24h_ago in BPS
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{BasisPoints16, Height, Version};
use schemars::JsonSchema;
use vecdb::{Database, Exit, ReadableVec, Rw, StorageMode};
use crate::{
indexes,
internal::{NumericValue, PercentRollingWindows, RollingWindows, WindowStarts},
};
#[derive(Traversable)]
pub struct RollingDelta<S, C = S, M: StorageMode = Rw>
where
S: NumericValue + JsonSchema,
C: NumericValue + JsonSchema,
{
pub change: RollingWindows<C, M>,
pub rate: PercentRollingWindows<BasisPoints16, M>,
_phantom: std::marker::PhantomData<S>,
}
impl<S, C> RollingDelta<S, C>
where
S: NumericValue + JsonSchema,
C: NumericValue + JsonSchema,
{
pub(crate) fn forced_import(
db: &Database,
name: &str,
version: Version,
indexes: &indexes::Vecs,
) -> Result<Self> {
Ok(Self {
change: RollingWindows::forced_import(
db,
&format!("{name}_change"),
version,
indexes,
)?,
rate: PercentRollingWindows::forced_import(
db,
&format!("{name}_rate"),
version,
indexes,
)?,
_phantom: std::marker::PhantomData,
})
}
pub(crate) fn compute(
&mut self,
max_from: Height,
windows: &WindowStarts<'_>,
source: &impl ReadableVec<Height, S>,
exit: &Exit,
) -> Result<()>
where
S: Default,
{
// Step 1: change = current - ago
for (change_w, starts) in self.change.0.as_mut_array().into_iter().zip(windows.as_array())
{
change_w.height.compute_transform(
max_from,
*starts,
|(h, ago_h, ..)| {
let current: f64 = source.collect_one(h).unwrap_or_default().into();
let ago: f64 = source.collect_one(ago_h).unwrap_or_default().into();
(h, C::from(current - ago))
},
exit,
)?;
}
// Step 2: rate = change / ago = change / (current - change)
for (growth_w, change_w) in self
.rate
.0
.as_mut_array()
.into_iter()
.zip(self.change.0.as_array())
{
growth_w.bps.height.compute_transform2(
max_from,
source,
&change_w.height,
|(h, current, change, ..)| {
let current_f: f64 = current.into();
let change_f: f64 = change.into();
let ago = current_f - change_f;
let rate = if ago == 0.0 { 0.0 } else { change_f / ago };
(h, BasisPoints16::from(rate))
},
exit,
)?;
}
Ok(())
}
}

View File

@@ -1,75 +0,0 @@
//! 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>,
}
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 height: EagerVec<PcoVec<Height, T>> = EagerVec::forced_import(db, name, version)?;
let rolling = RollingDistribution::forced_import(db, name, version, 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.compute_rest(max_from, windows, exit)
}
/// Compute rolling distribution from already-populated height data.
pub(crate) fn compute_rest(
&mut self,
max_from: Height,
windows: &WindowStarts<'_>,
exit: &Exit,
) -> Result<()>
where
T: Copy + Ord + From<f64> + Default,
f64: From<T>,
{
self.rolling
.compute_distribution(max_from, windows, &self.height, exit)?;
Ok(())
}
}

View File

@@ -1,13 +1,13 @@
mod aggregated;
mod cumulative;
mod cumulative_sum;
mod distribution;
mod full;
mod delta;
mod rolling_average;
pub use aggregated::*;
pub use cumulative::*;
pub use cumulative_sum::*;
pub use distribution::*;
pub use full::*;
pub use delta::*;
pub use rolling_average::*;

View File

@@ -37,7 +37,7 @@ where
) -> Result<Self> {
let height: EagerVec<PcoVec<Height, T>> = EagerVec::forced_import(db, name, version)?;
let average =
RollingWindows::forced_import(db, &format!("{name}_average"), version, indexes)?;
RollingWindows::forced_import(db, &format!("{name}_average"), version + Version::ONE, indexes)?;
Ok(Self { height, average })
}

View File

@@ -1,69 +0,0 @@
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{Height, StoredF32, Version};
use vecdb::{Database, EagerVec, Exit, PcoVec, ReadableCloneableVec, Rw, StorageMode};
use crate::{
indexes,
internal::{BpsType, WindowStarts},
};
use crate::internal::{ComputedFromHeightDistribution, LazyFromHeight};
/// Like PercentFromHeight but with rolling distribution stats on the bps data.
#[derive(Traversable)]
pub struct PercentFromHeightDistribution<B: BpsType, M: StorageMode = Rw> {
pub bps: ComputedFromHeightDistribution<B, M>,
pub ratio: LazyFromHeight<StoredF32, B>,
pub percent: LazyFromHeight<StoredF32, B>,
}
impl<B: BpsType> PercentFromHeightDistribution<B> {
pub(crate) fn forced_import(
db: &Database,
name: &str,
version: Version,
indexes: &indexes::Vecs,
) -> Result<Self> {
let bps = ComputedFromHeightDistribution::forced_import(
db,
&format!("{name}_bps"),
version,
indexes,
)?;
let ratio = LazyFromHeight::from_height_source::<B::ToRatio>(
&format!("{name}_ratio"),
version,
bps.height.read_only_boxed_clone(),
indexes,
);
let percent = LazyFromHeight::from_height_source::<B::ToPercent>(
name,
version,
bps.height.read_only_boxed_clone(),
indexes,
);
Ok(Self {
bps,
ratio,
percent,
})
}
pub(crate) fn compute(
&mut self,
max_from: Height,
windows: &WindowStarts<'_>,
exit: &Exit,
compute_height: impl FnOnce(&mut EagerVec<PcoVec<Height, B>>) -> Result<()>,
) -> Result<()>
where
B: Copy + Ord + From<f64> + Default,
f64: From<B>,
{
self.bps.compute(max_from, windows, exit, compute_height)
}
}

View File

@@ -1,7 +1,5 @@
mod base;
mod distribution;
mod rolling_average;
pub use base::*;
pub use distribution::*;
pub use rolling_average::*;