diff --git a/crates/brk_client/src/lib.rs b/crates/brk_client/src/lib.rs index 0af979ac5..2ae734b74 100644 --- a/crates/brk_client/src/lib.rs +++ b/crates/brk_client/src/lib.rs @@ -949,7 +949,6 @@ pub struct CapCapitulationGrossInvestorLossLowerMvrvNegNetPeakProfitRealizedSell pub investor_price: CentsSatsUsdPattern, pub investor_price_ratio: BpsRatioPattern, pub investor_price_ratio_percentiles: RatioPattern, - pub investor_price_ratio_std_dev: RatioPattern2, pub loss_value_created: MetricPattern1, pub loss_value_destroyed: MetricPattern1, pub lower_price_band: CentsSatsUsdPattern, diff --git a/crates/brk_computer/src/distribution/metrics/cohort/all.rs b/crates/brk_computer/src/distribution/metrics/cohort/all.rs index 8898526b1..1ed0045a7 100644 --- a/crates/brk_computer/src/distribution/metrics/cohort/all.rs +++ b/crates/brk_computer/src/distribution/metrics/cohort/all.rs @@ -2,6 +2,7 @@ use brk_cohort::Filter; use brk_error::Result; use brk_traversable::Traversable; use brk_types::{Bitcoin, Cents, Dollars, Height, Indexes, StoredF32, Version}; +use vecdb::AnyStoredVec; use vecdb::{Exit, ReadableVec, Rw, StorageMode}; use crate::{blocks, prices}; @@ -9,8 +10,8 @@ use crate::{blocks, prices}; use crate::internal::ComputedFromHeight; use crate::distribution::metrics::{ - ActivityFull, CostBasisWithExtended, ImportConfig, OutputsMetrics, RealizedAdjusted, - RealizedFull, RelativeForAll, SupplyMetrics, UnrealizedFull, + ActivityFull, CohortMetricsBase, CostBasisWithExtended, ImportConfig, OutputsMetrics, + RealizedAdjusted, RealizedFull, RelativeForAll, SupplyMetrics, UnrealizedFull, }; /// All-cohort metrics: extended realized + adjusted (as composable add-on), @@ -32,7 +33,25 @@ pub struct AllCohortMetrics { pub velocity: ComputedFromHeight, } -impl_cohort_metrics_base!(AllCohortMetrics, extended_cost_basis); +impl CohortMetricsBase for AllCohortMetrics { + type RealizedVecs = RealizedFull; + type CostBasisVecs = CostBasisWithExtended; + + impl_cohort_accessors!(); + + fn collect_all_vecs_mut(&mut self) -> Vec<&mut dyn AnyStoredVec> { + let mut vecs: Vec<&mut dyn AnyStoredVec> = Vec::new(); + vecs.extend(self.supply.collect_vecs_mut()); + vecs.extend(self.outputs.collect_vecs_mut()); + vecs.extend(self.activity.collect_vecs_mut()); + vecs.extend(self.realized.collect_vecs_mut()); + vecs.extend(self.cost_basis.collect_vecs_mut()); + vecs.extend(self.unrealized.collect_vecs_mut()); + vecs.push(&mut self.dormancy.height); + vecs.push(&mut self.velocity.height); + vecs + } +} impl AllCohortMetrics { /// Import the "all" cohort metrics with a pre-imported supply. diff --git a/crates/brk_computer/src/distribution/metrics/cohort/basic.rs b/crates/brk_computer/src/distribution/metrics/cohort/basic.rs index 5bb468575..66a417cc1 100644 --- a/crates/brk_computer/src/distribution/metrics/cohort/basic.rs +++ b/crates/brk_computer/src/distribution/metrics/cohort/basic.rs @@ -1,7 +1,7 @@ use brk_cohort::Filter; use brk_error::Result; use brk_traversable::Traversable; -use brk_types::{Dollars, Height, Indexes, Sats, Version}; +use brk_types::{Dollars, Height, Indexes, Sats}; use vecdb::{AnyStoredVec, Exit, ReadableVec, Rw, StorageMode}; use crate::{blocks, prices}; @@ -27,13 +27,10 @@ pub struct BasicCohortMetrics { } impl CohortMetricsBase for BasicCohortMetrics { - impl_cohort_metrics_base!(@accessors); + type RealizedVecs = RealizedBase; + type CostBasisVecs = CostBasisBase; - fn validate_computed_versions(&mut self, base_version: Version) -> Result<()> { - self.supply.validate_computed_versions(base_version)?; - self.activity.validate_computed_versions(base_version)?; - Ok(()) - } + impl_cohort_accessors!(); fn collect_all_vecs_mut(&mut self) -> Vec<&mut dyn AnyStoredVec> { let mut vecs: Vec<&mut dyn AnyStoredVec> = Vec::new(); diff --git a/crates/brk_computer/src/distribution/metrics/cohort/extended.rs b/crates/brk_computer/src/distribution/metrics/cohort/extended.rs index 56e2cd63e..3582d1018 100644 --- a/crates/brk_computer/src/distribution/metrics/cohort/extended.rs +++ b/crates/brk_computer/src/distribution/metrics/cohort/extended.rs @@ -2,6 +2,7 @@ use brk_cohort::Filter; use brk_error::Result; use brk_traversable::Traversable; use brk_types::{Bitcoin, Dollars, Height, Indexes, Sats, StoredF32, Version}; +use vecdb::AnyStoredVec; use vecdb::{Exit, ReadableVec, Rw, StorageMode}; use crate::{blocks, prices}; @@ -9,7 +10,7 @@ use crate::{blocks, prices}; use crate::internal::ComputedFromHeight; use crate::distribution::metrics::{ - ActivityFull, CostBasisWithExtended, ImportConfig, OutputsMetrics, + ActivityFull, CohortMetricsBase, CostBasisWithExtended, ImportConfig, OutputsMetrics, RealizedFull, RelativeWithExtended, SupplyMetrics, UnrealizedFull, }; @@ -30,7 +31,25 @@ pub struct ExtendedCohortMetrics { pub velocity: ComputedFromHeight, } -impl_cohort_metrics_base!(ExtendedCohortMetrics, extended_cost_basis); +impl CohortMetricsBase for ExtendedCohortMetrics { + type RealizedVecs = RealizedFull; + type CostBasisVecs = CostBasisWithExtended; + + impl_cohort_accessors!(); + + fn collect_all_vecs_mut(&mut self) -> Vec<&mut dyn AnyStoredVec> { + let mut vecs: Vec<&mut dyn AnyStoredVec> = Vec::new(); + vecs.extend(self.supply.collect_vecs_mut()); + vecs.extend(self.outputs.collect_vecs_mut()); + vecs.extend(self.activity.collect_vecs_mut()); + vecs.extend(self.realized.collect_vecs_mut()); + vecs.extend(self.cost_basis.collect_vecs_mut()); + vecs.extend(self.unrealized.collect_vecs_mut()); + vecs.push(&mut self.dormancy.height); + vecs.push(&mut self.velocity.height); + vecs + } +} impl ExtendedCohortMetrics { pub(crate) fn forced_import(cfg: &ImportConfig) -> Result { diff --git a/crates/brk_computer/src/distribution/metrics/cohort/extended_adjusted.rs b/crates/brk_computer/src/distribution/metrics/cohort/extended_adjusted.rs index dc1c081aa..cf752afac 100644 --- a/crates/brk_computer/src/distribution/metrics/cohort/extended_adjusted.rs +++ b/crates/brk_computer/src/distribution/metrics/cohort/extended_adjusted.rs @@ -2,11 +2,13 @@ use brk_error::Result; use brk_traversable::Traversable; use brk_types::{Cents, Dollars, Height, Indexes, Sats}; use derive_more::{Deref, DerefMut}; -use vecdb::{Exit, ReadableVec, Rw, StorageMode}; +use vecdb::{AnyStoredVec, Exit, ReadableVec, Rw, StorageMode}; use crate::{blocks, prices}; -use crate::distribution::metrics::{ImportConfig, RealizedAdjusted}; +use crate::distribution::metrics::{ + CohortMetricsBase, CostBasisWithExtended, ImportConfig, RealizedAdjusted, RealizedFull, +}; use super::ExtendedCohortMetrics; @@ -23,7 +25,16 @@ pub struct ExtendedAdjustedCohortMetrics { pub adjusted: Box>, } -impl_cohort_metrics_base!(ExtendedAdjustedCohortMetrics, deref_extended_cost_basis); +impl CohortMetricsBase for ExtendedAdjustedCohortMetrics { + type RealizedVecs = RealizedFull; + type CostBasisVecs = CostBasisWithExtended; + + impl_cohort_accessors!(); + + fn collect_all_vecs_mut(&mut self) -> Vec<&mut dyn AnyStoredVec> { + self.inner.collect_all_vecs_mut() + } +} impl ExtendedAdjustedCohortMetrics { pub(crate) fn forced_import(cfg: &ImportConfig) -> Result { diff --git a/crates/brk_computer/src/distribution/metrics/cost_basis/mod.rs b/crates/brk_computer/src/distribution/metrics/cost_basis/mod.rs index 1434bda00..bf8278078 100644 --- a/crates/brk_computer/src/distribution/metrics/cost_basis/mod.rs +++ b/crates/brk_computer/src/distribution/metrics/cost_basis/mod.rs @@ -5,3 +5,47 @@ mod with_extended; pub use base::CostBasisBase; pub use extended::CostBasisExtended; pub use with_extended::CostBasisWithExtended; + +use brk_error::Result; +use brk_types::{Height, Version}; + +use crate::distribution::state::{CohortState, RealizedState}; + +/// Polymorphic dispatch for cost basis metric types. +/// +/// `CostBasisBase` has no version validation or percentiles (no-op defaults). +/// `CostBasisWithExtended` validates versions and pushes percentiles. +pub trait CostBasisLike: Send + Sync { + fn as_base(&self) -> &CostBasisBase; + fn as_base_mut(&mut self) -> &mut CostBasisBase; + fn validate_computed_versions(&mut self, _base_version: Version) -> Result<()> { Ok(()) } + fn truncate_push_percentiles( + &mut self, + _height: Height, + _state: &mut CohortState, + _is_day_boundary: bool, + ) -> Result<()> { + Ok(()) + } +} + +impl CostBasisLike for CostBasisBase { + fn as_base(&self) -> &CostBasisBase { self } + fn as_base_mut(&mut self) -> &mut CostBasisBase { self } +} + +impl CostBasisLike for CostBasisWithExtended { + fn as_base(&self) -> &CostBasisBase { &self.base } + fn as_base_mut(&mut self) -> &mut CostBasisBase { &mut self.base } + fn validate_computed_versions(&mut self, base_version: Version) -> Result<()> { + self.extended.validate_computed_versions(base_version) + } + fn truncate_push_percentiles( + &mut self, + height: Height, + state: &mut CohortState, + is_day_boundary: bool, + ) -> Result<()> { + self.extended.truncate_push_percentiles(height, state, is_day_boundary) + } +} diff --git a/crates/brk_computer/src/distribution/metrics/cost_basis/with_extended.rs b/crates/brk_computer/src/distribution/metrics/cost_basis/with_extended.rs index f089c6c8d..f8f6b3e4b 100644 --- a/crates/brk_computer/src/distribution/metrics/cost_basis/with_extended.rs +++ b/crates/brk_computer/src/distribution/metrics/cost_basis/with_extended.rs @@ -1,6 +1,5 @@ use brk_error::Result; use brk_traversable::Traversable; -use brk_types::Version; use derive_more::{Deref, DerefMut}; use vecdb::{AnyStoredVec, Rw, StorageMode}; @@ -32,8 +31,4 @@ impl CostBasisWithExtended { vecs.extend(self.extended.collect_vecs_mut()); vecs } - - pub(crate) fn validate_computed_versions(&mut self, base_version: Version) -> Result<()> { - self.extended.validate_computed_versions(base_version) - } } diff --git a/crates/brk_computer/src/distribution/metrics/mod.rs b/crates/brk_computer/src/distribution/metrics/mod.rs index 5f4a1280a..87ae46fdf 100644 --- a/crates/brk_computer/src/distribution/metrics/mod.rs +++ b/crates/brk_computer/src/distribution/metrics/mod.rs @@ -11,212 +11,13 @@ macro_rules! sum_others { mod activity; -/// DRY macro for `CohortMetricsBase` impl on cohort metric types. +/// Accessor methods for `CohortMetricsBase` implementations. /// -/// Two variants handle extended cost basis types that need direct field access -/// (bypassing Deref to call methods on concrete `RealizedFull`): -/// -/// - `extended_cost_basis`: `CostBasisWithExtended` (percentiles + cost_basis version check) -/// - `deref_extended_cost_basis`: Deref wrapper delegating to `self.inner` (avoids DerefMut borrow conflicts) -/// -/// The `@accessors` helper is also used directly by `BasicCohortMetrics`. -macro_rules! impl_cohort_metrics_base { - ($type:ident, extended_cost_basis) => { - impl $crate::distribution::metrics::CohortMetricsBase for $type { - impl_cohort_metrics_base!(@accessors); - - fn validate_computed_versions(&mut self, base_version: brk_types::Version) -> brk_error::Result<()> { - self.supply.validate_computed_versions(base_version)?; - self.activity.validate_computed_versions(base_version)?; - self.cost_basis.validate_computed_versions(base_version)?; - Ok(()) - } - - fn compute_then_truncate_push_unrealized_states( - &mut self, - height: brk_types::Height, - height_price: brk_types::Cents, - state: &mut $crate::distribution::state::CohortState<$crate::distribution::state::RealizedState>, - is_day_boundary: bool, - ) -> brk_error::Result<()> { - self.compute_and_push_unrealized_base(height, height_price, state)?; - self.cost_basis - .extended - .truncate_push_percentiles(height, state, is_day_boundary)?; - Ok(()) - } - - fn min_stateful_height_len(&self) -> usize { - self.supply.min_len() - .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.cost_basis_base().min_stateful_height_len()) - } - - fn truncate_push( - &mut self, - height: brk_types::Height, - state: &$crate::distribution::state::CohortState<$crate::distribution::state::RealizedState>, - ) -> brk_error::Result<()> { - self.supply_mut() - .truncate_push(height, state.supply.value)?; - self.outputs_mut() - .truncate_push(height, state.supply.utxo_count)?; - self.activity_mut().truncate_push( - height, - state.sent, - state.satblocks_destroyed, - state.satdays_destroyed, - )?; - self.realized.truncate_push(height, &state.realized)?; - Ok(()) - } - - fn compute_base_from_others( - &mut self, - starting_indexes: &brk_types::Indexes, - others: &[&T], - exit: &vecdb::Exit, - ) -> brk_error::Result<()> { - self.supply_mut().compute_from_stateful( - starting_indexes, - &others.iter().map(|v| v.supply()).collect::>(), - exit, - )?; - self.outputs_mut().compute_from_stateful( - starting_indexes, - &others.iter().map(|v| v.outputs()).collect::>(), - exit, - )?; - self.activity_mut().compute_from_stateful( - starting_indexes, - &others.iter().map(|v| v.activity()).collect::>(), - exit, - )?; - self.realized.compute_from_stateful( - starting_indexes, - &others.iter().map(|v| v.realized_base()).collect::>(), - exit, - )?; - self.unrealized_full_mut().compute_from_stateful( - starting_indexes, - &others.iter().map(|v| v.unrealized_full()).collect::>(), - exit, - )?; - self.cost_basis_base_mut().compute_from_stateful( - starting_indexes, - &others.iter().map(|v| v.cost_basis_base()).collect::>(), - exit, - )?; - Ok(()) - } - - fn compute_rest_part1( - &mut self, - blocks: &$crate::blocks::Vecs, - prices: &$crate::prices::Vecs, - starting_indexes: &brk_types::Indexes, - exit: &vecdb::Exit, - ) -> brk_error::Result<()> { - self.supply_mut() - .compute(prices, starting_indexes.height, exit)?; - self.supply_mut() - .compute_rest_part1(blocks, starting_indexes, exit)?; - self.outputs_mut() - .compute_rest(blocks, starting_indexes, exit)?; - self.activity_mut() - .sent - .compute(prices, starting_indexes.height, exit)?; - self.activity_mut() - .compute_rest_part1(blocks, prices, starting_indexes, exit)?; - - self.realized.sent_in_profit - .compute(prices, starting_indexes.height, exit)?; - self.realized.sent_in_loss - .compute(prices, starting_indexes.height, exit)?; - self.realized.compute_rest_part1(starting_indexes, exit)?; - - self.unrealized_full_mut() - .compute_rest(prices, starting_indexes, exit)?; - - Ok(()) - } - - fn collect_all_vecs_mut(&mut self) -> Vec<&mut dyn vecdb::AnyStoredVec> { - let mut vecs: Vec<&mut dyn vecdb::AnyStoredVec> = Vec::new(); - vecs.extend(self.supply.collect_vecs_mut()); - vecs.extend(self.outputs.collect_vecs_mut()); - vecs.extend(self.activity.collect_vecs_mut()); - vecs.extend(self.realized.collect_vecs_mut()); - vecs.extend(self.cost_basis.collect_vecs_mut()); - vecs.extend(self.unrealized.collect_vecs_mut()); - vecs.push(&mut self.dormancy.height); - vecs.push(&mut self.velocity.height); - vecs - } - } - }; - - ($type:ident, deref_extended_cost_basis) => { - impl $crate::distribution::metrics::CohortMetricsBase for $type { - impl_cohort_metrics_base!(@deref_accessors); - - fn validate_computed_versions(&mut self, base_version: brk_types::Version) -> brk_error::Result<()> { - self.inner.validate_computed_versions(base_version) - } - - fn compute_then_truncate_push_unrealized_states( - &mut self, - height: brk_types::Height, - height_price: brk_types::Cents, - state: &mut $crate::distribution::state::CohortState<$crate::distribution::state::RealizedState>, - is_day_boundary: bool, - ) -> brk_error::Result<()> { - self.inner.compute_then_truncate_push_unrealized_states( - height, height_price, state, is_day_boundary, - ) - } - - fn min_stateful_height_len(&self) -> usize { - self.inner.min_stateful_height_len() - } - - fn truncate_push( - &mut self, - height: brk_types::Height, - state: &$crate::distribution::state::CohortState<$crate::distribution::state::RealizedState>, - ) -> brk_error::Result<()> { - self.inner.truncate_push(height, state) - } - - fn compute_rest_part1( - &mut self, - blocks: &$crate::blocks::Vecs, - prices: &$crate::prices::Vecs, - starting_indexes: &brk_types::Indexes, - exit: &vecdb::Exit, - ) -> brk_error::Result<()> { - self.inner.compute_rest_part1(blocks, prices, starting_indexes, exit) - } - - fn compute_base_from_others( - &mut self, - starting_indexes: &brk_types::Indexes, - others: &[&T], - exit: &vecdb::Exit, - ) -> brk_error::Result<()> { - self.inner.compute_base_from_others(starting_indexes, others, exit) - } - - fn collect_all_vecs_mut(&mut self) -> Vec<&mut dyn vecdb::AnyStoredVec> { - self.inner.collect_all_vecs_mut() - } - } - }; - - (@accessors) => { +/// All cohort metric types share the same field names (`filter`, `supply`, `outputs`, +/// `activity`, `realized`, `unrealized`, `cost_basis`). For wrapper types like +/// `ExtendedAdjustedCohortMetrics`, Rust's auto-deref resolves these through `Deref`. +macro_rules! impl_cohort_accessors { + () => { fn filter(&self) -> &brk_cohort::Filter { &self.filter } fn supply(&self) -> &$crate::distribution::metrics::SupplyMetrics { &self.supply } fn supply_mut(&mut self) -> &mut $crate::distribution::metrics::SupplyMetrics { &mut self.supply } @@ -224,28 +25,12 @@ macro_rules! impl_cohort_metrics_base { fn outputs_mut(&mut self) -> &mut $crate::distribution::metrics::OutputsMetrics { &mut self.outputs } fn activity(&self) -> &$crate::distribution::metrics::ActivityFull { &self.activity } fn activity_mut(&mut self) -> &mut $crate::distribution::metrics::ActivityFull { &mut self.activity } - fn realized_base(&self) -> &$crate::distribution::metrics::RealizedBase { &self.realized } - fn realized_base_mut(&mut self) -> &mut $crate::distribution::metrics::RealizedBase { &mut self.realized } + 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 cost_basis_base(&self) -> &$crate::distribution::metrics::CostBasisBase { &self.cost_basis } - fn cost_basis_base_mut(&mut self) -> &mut $crate::distribution::metrics::CostBasisBase { &mut self.cost_basis } - }; - - (@deref_accessors) => { - fn filter(&self) -> &brk_cohort::Filter { self.inner.filter() } - fn supply(&self) -> &$crate::distribution::metrics::SupplyMetrics { self.inner.supply() } - fn supply_mut(&mut self) -> &mut $crate::distribution::metrics::SupplyMetrics { self.inner.supply_mut() } - fn outputs(&self) -> &$crate::distribution::metrics::OutputsMetrics { self.inner.outputs() } - fn outputs_mut(&mut self) -> &mut $crate::distribution::metrics::OutputsMetrics { self.inner.outputs_mut() } - fn activity(&self) -> &$crate::distribution::metrics::ActivityFull { self.inner.activity() } - fn activity_mut(&mut self) -> &mut $crate::distribution::metrics::ActivityFull { self.inner.activity_mut() } - fn realized_base(&self) -> &$crate::distribution::metrics::RealizedBase { self.inner.realized_base() } - fn realized_base_mut(&mut self) -> &mut $crate::distribution::metrics::RealizedBase { self.inner.realized_base_mut() } - fn unrealized_full(&self) -> &$crate::distribution::metrics::UnrealizedFull { self.inner.unrealized_full() } - fn unrealized_full_mut(&mut self) -> &mut $crate::distribution::metrics::UnrealizedFull { self.inner.unrealized_full_mut() } - fn cost_basis_base(&self) -> &$crate::distribution::metrics::CostBasisBase { self.inner.cost_basis_base() } - fn cost_basis_base_mut(&mut self) -> &mut $crate::distribution::metrics::CostBasisBase { self.inner.cost_basis_base_mut() } + fn cost_basis(&self) -> &Self::CostBasisVecs { &self.cost_basis } + fn cost_basis_mut(&mut self) -> &mut Self::CostBasisVecs { &mut self.cost_basis } }; } @@ -264,9 +49,11 @@ pub use cohort::{ ExtendedCohortMetrics, MinimalCohortMetrics, }; pub use config::ImportConfig; -pub use cost_basis::{CostBasisBase, CostBasisExtended, CostBasisWithExtended}; +pub use cost_basis::{CostBasisBase, CostBasisExtended, CostBasisLike, CostBasisWithExtended}; pub use outputs::OutputsMetrics; -pub use realized::{RealizedAdjusted, RealizedBase, RealizedFull, RealizedMinimal}; +pub use realized::{ + RealizedAdjusted, RealizedBase, RealizedFull, RealizedLike, RealizedMinimal, +}; pub use relative::{ RelativeBaseWithRelToAll, RelativeForAll, RelativeWithExtended, RelativeWithRelToAll, }; @@ -304,6 +91,9 @@ impl CohortMetricsState for AllCohortMetrics { } pub trait CohortMetricsBase: CohortMetricsState + Send + Sync { + type RealizedVecs: RealizedLike; + type CostBasisVecs: CostBasisLike; + fn filter(&self) -> &Filter; fn supply(&self) -> &SupplyMetrics; fn supply_mut(&mut self) -> &mut SupplyMetrics; @@ -311,14 +101,27 @@ pub trait CohortMetricsBase: CohortMetricsState + Send fn outputs_mut(&mut self) -> &mut OutputsMetrics; fn activity(&self) -> &ActivityFull; fn activity_mut(&mut self) -> &mut ActivityFull; - fn realized_base(&self) -> &RealizedBase; - fn realized_base_mut(&mut self) -> &mut RealizedBase; + 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 cost_basis_base(&self) -> &CostBasisBase; - fn cost_basis_base_mut(&mut self) -> &mut CostBasisBase; + fn cost_basis(&self) -> &Self::CostBasisVecs; + fn cost_basis_mut(&mut self) -> &mut Self::CostBasisVecs; - fn validate_computed_versions(&mut self, base_version: Version) -> Result<()>; + /// Convenience: access realized as `&RealizedBase` (via `RealizedLike::as_base`). + 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 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() } + + fn validate_computed_versions(&mut self, base_version: Version) -> Result<()> { + self.supply_mut().validate_computed_versions(base_version)?; + self.activity_mut().validate_computed_versions(base_version)?; + self.cost_basis_mut().validate_computed_versions(base_version)?; + Ok(()) + } /// Apply pending, push min/max cost basis, compute and push unrealized state. fn compute_and_push_unrealized_base( @@ -336,15 +139,17 @@ pub trait CohortMetricsBase: CohortMetricsState + Send Ok(()) } - /// Compute and push unrealized states. Extended types override to also push percentiles. + /// Compute and push unrealized states + cost basis percentiles (no-op for base types). fn compute_then_truncate_push_unrealized_states( &mut self, height: Height, height_price: Cents, state: &mut CohortState, - _is_day_boundary: bool, + is_day_boundary: bool, ) -> Result<()> { - self.compute_and_push_unrealized_base(height, height_price, state) + self.compute_and_push_unrealized_base(height, height_price, state)?; + self.cost_basis_mut().truncate_push_percentiles(height, state, is_day_boundary)?; + Ok(()) } fn collect_all_vecs_mut(&mut self) -> Vec<&mut dyn AnyStoredVec>; @@ -354,7 +159,7 @@ pub trait CohortMetricsBase: CohortMetricsState + Send .min_len() .min(self.outputs().min_len()) .min(self.activity().min_len()) - .min(self.realized_base().min_stateful_height_len()) + .min(self.realized().min_stateful_height_len()) .min(self.unrealized_full().min_stateful_height_len()) .min(self.cost_basis_base().min_stateful_height_len()) } @@ -370,7 +175,7 @@ pub trait CohortMetricsBase: CohortMetricsState + Send state.satblocks_destroyed, state.satdays_destroyed, )?; - self.realized_base_mut() + self.realized_mut() .truncate_push(height, &state.realized)?; Ok(()) } @@ -426,7 +231,7 @@ pub trait CohortMetricsBase: CohortMetricsState + Send self.realized_base_mut() .sent_in_loss .compute(prices, starting_indexes.height, exit)?; - self.realized_base_mut() + self.realized_mut() .compute_rest_part1(starting_indexes, exit)?; self.unrealized_full_mut() @@ -453,22 +258,36 @@ pub trait CohortMetricsBase: CohortMetricsState + Send others: &[&T], exit: &Exit, ) -> Result<()> { - macro_rules! aggregate { - ($self_mut:ident, $accessor:ident) => { - self.$self_mut().compute_from_stateful( - starting_indexes, - &others.iter().map(|v| v.$accessor()).collect::>(), - exit, - )? - }; - } - - aggregate!(supply_mut, supply); - aggregate!(outputs_mut, outputs); - aggregate!(activity_mut, activity); - aggregate!(realized_base_mut, realized_base); - aggregate!(unrealized_full_mut, unrealized_full); - aggregate!(cost_basis_base_mut, cost_basis_base); + self.supply_mut().compute_from_stateful( + starting_indexes, + &others.iter().map(|v| v.supply()).collect::>(), + exit, + )?; + self.outputs_mut().compute_from_stateful( + starting_indexes, + &others.iter().map(|v| v.outputs()).collect::>(), + exit, + )?; + self.activity_mut().compute_from_stateful( + starting_indexes, + &others.iter().map(|v| v.activity()).collect::>(), + exit, + )?; + self.realized_mut().compute_from_stateful( + starting_indexes, + &others.iter().map(|v| v.realized_base()).collect::>(), + exit, + )?; + self.unrealized_full_mut().compute_from_stateful( + starting_indexes, + &others.iter().map(|v| v.unrealized_full()).collect::>(), + exit, + )?; + self.cost_basis_base_mut().compute_from_stateful( + starting_indexes, + &others.iter().map(|v| v.cost_basis_base()).collect::>(), + exit, + )?; Ok(()) } } diff --git a/crates/brk_computer/src/distribution/metrics/realized/full.rs b/crates/brk_computer/src/distribution/metrics/realized/full.rs index c302d64a0..8785e777f 100644 --- a/crates/brk_computer/src/distribution/metrics/realized/full.rs +++ b/crates/brk_computer/src/distribution/metrics/realized/full.rs @@ -72,7 +72,6 @@ pub struct RealizedFull { pub realized_price_ratio_std_dev: ComputedFromHeightRatioStdDevBands, pub investor_price_ratio_percentiles: ComputedFromHeightRatioPercentiles, - pub investor_price_ratio_std_dev: ComputedFromHeightRatioStdDevBands, } impl RealizedFull { @@ -168,12 +167,6 @@ impl RealizedFull { investor_price_version, cfg.indexes, )?, - investor_price_ratio_std_dev: ComputedFromHeightRatioStdDevBands::forced_import( - cfg.db, - &investor_price_name, - investor_price_version, - cfg.indexes, - )?, }) } @@ -426,7 +419,7 @@ impl RealizedFull { &self.core.minimal.realized_price.cents.height, )?; - // Investor price: percentiles + stddev bands + // Investor price: percentiles let investor_price = &self.investor_price.cents.height; self.investor_price_ratio_percentiles.compute( blocks, @@ -435,13 +428,6 @@ impl RealizedFull { &self.investor_price_ratio.ratio.height, investor_price, )?; - self.investor_price_ratio_std_dev.compute( - blocks, - starting_indexes, - exit, - &self.investor_price_ratio.ratio.height, - investor_price, - )?; Ok(()) } diff --git a/crates/brk_computer/src/distribution/metrics/realized/mod.rs b/crates/brk_computer/src/distribution/metrics/realized/mod.rs index 80db494ae..23917e32b 100644 --- a/crates/brk_computer/src/distribution/metrics/realized/mod.rs +++ b/crates/brk_computer/src/distribution/metrics/realized/mod.rs @@ -7,3 +7,58 @@ pub use adjusted::RealizedAdjusted; pub use base::RealizedBase; pub use full::RealizedFull; pub use minimal::RealizedMinimal; + +use brk_error::Result; +use brk_types::{Height, Indexes}; +use vecdb::Exit; + +use crate::distribution::state::RealizedState; + +/// Polymorphic dispatch for realized metric types. +/// +/// Both `RealizedBase` and `RealizedFull` have the same inherent methods +/// but with different behavior (Full checks/pushes more fields). +/// This trait enables `CohortMetricsBase` to dispatch correctly via associated type. +pub trait RealizedLike: Send + Sync { + fn as_base(&self) -> &RealizedBase; + fn as_base_mut(&mut self) -> &mut RealizedBase; + fn min_stateful_height_len(&self) -> usize; + fn truncate_push(&mut self, height: Height, state: &RealizedState) -> Result<()>; + fn compute_rest_part1(&mut self, starting_indexes: &Indexes, exit: &Exit) -> Result<()>; + fn compute_from_stateful( + &mut self, + starting_indexes: &Indexes, + others: &[&RealizedBase], + exit: &Exit, + ) -> Result<()>; +} + +impl RealizedLike for RealizedBase { + fn as_base(&self) -> &RealizedBase { self } + fn as_base_mut(&mut self) -> &mut RealizedBase { self } + fn min_stateful_height_len(&self) -> usize { self.min_stateful_height_len() } + fn truncate_push(&mut self, height: Height, state: &RealizedState) -> Result<()> { + self.truncate_push(height, state) + } + fn compute_rest_part1(&mut self, starting_indexes: &Indexes, exit: &Exit) -> Result<()> { + self.compute_rest_part1(starting_indexes, exit) + } + fn compute_from_stateful(&mut self, starting_indexes: &Indexes, others: &[&RealizedBase], exit: &Exit) -> Result<()> { + self.compute_from_stateful(starting_indexes, others, exit) + } +} + +impl RealizedLike for RealizedFull { + fn as_base(&self) -> &RealizedBase { &self.core } + fn as_base_mut(&mut self) -> &mut RealizedBase { &mut self.core } + fn min_stateful_height_len(&self) -> usize { self.min_stateful_height_len() } + fn truncate_push(&mut self, height: Height, state: &RealizedState) -> Result<()> { + self.truncate_push(height, state) + } + fn compute_rest_part1(&mut self, starting_indexes: &Indexes, exit: &Exit) -> Result<()> { + self.compute_rest_part1(starting_indexes, exit) + } + fn compute_from_stateful(&mut self, starting_indexes: &Indexes, others: &[&RealizedBase], exit: &Exit) -> Result<()> { + self.compute_from_stateful(starting_indexes, others, exit) + } +}