diff --git a/crates/brk_computer/src/distribution/cohorts/utxo/groups.rs b/crates/brk_computer/src/distribution/cohorts/utxo/groups.rs index 2cb82b4f4..6a2f711cb 100644 --- a/crates/brk_computer/src/distribution/cohorts/utxo/groups.rs +++ b/crates/brk_computer/src/distribution/cohorts/utxo/groups.rs @@ -17,7 +17,7 @@ use crate::{ metrics::{ AllCohortMetrics, BasicCohortMetrics, CohortMetricsBase, CoreCohortMetrics, ExtendedAdjustedCohortMetrics, ExtendedCohortMetrics, ImportConfig, - MinimalCohortMetrics, ProfitabilityMetrics, SupplyMetrics, + MinimalCohortMetrics, ProfitabilityMetrics, RealizedFullAccum, SupplyMetrics, }, state::UTXOCohortState, }, @@ -44,6 +44,7 @@ pub struct UTXOCohorts { pub ge_amount: ByGreatEqualAmount>>, pub amount_range: ByAmountRange>>, pub lt_amount: ByLowerThanAmount>>, + #[traversable(rename = "type")] pub type_: BySpendableType>>, pub profitability: ProfitabilityMetrics, pub matured: ByAgeRange>, @@ -707,7 +708,7 @@ impl UTXOCohorts { .try_for_each(|v| v.write_state(height, cleanup)) } - /// Get minimum height from all separate cohorts' + profitability height-indexed vectors. + /// Get minimum height from all separate cohorts' + profitability + overlapping realized height-indexed vectors. pub(crate) fn min_separate_stateful_height_len(&self) -> Height { self.iter_separate() .map(|v| Height::from(v.min_stateful_height_len())) @@ -715,6 +716,9 @@ impl UTXOCohorts { .min() .unwrap_or_default() .min(Height::from(self.profitability.min_stateful_height_len())) + .min(Height::from(self.all.metrics.realized.min_stateful_height_len())) + .min(Height::from(self.sth.metrics.realized.min_stateful_height_len())) + .min(Height::from(self.lth.metrics.realized.min_stateful_height_len())) } /// Import state for all separate cohorts at or before given height. @@ -761,6 +765,38 @@ impl UTXOCohorts { } Ok(()) } + + /// Aggregate RealizedFull fields from age_range states and push to all/sth/lth. + /// Called during the block loop after separate cohorts' truncate_push but before reset. + pub(crate) fn push_overlapping_realized_full(&mut self, height: Height) -> Result<()> { + let Self { + all, sth, lth, age_range, .. + } = self; + + let sth_filter = &sth.metrics.filter; + + let mut all_acc = RealizedFullAccum::default(); + let mut sth_acc = RealizedFullAccum::default(); + let mut lth_acc = RealizedFullAccum::default(); + + for ar in age_range.iter() { + if let Some(state) = ar.state.as_ref() { + let r = &state.realized; + all_acc.add(r); + if sth_filter.includes(&ar.metrics.filter) { + sth_acc.add(r); + } else { + lth_acc.add(r); + } + } + } + + all.metrics.realized.push_from_accum(&all_acc, height)?; + sth.metrics.realized.push_from_accum(&sth_acc, height)?; + lth.metrics.realized.push_from_accum(<h_acc, height)?; + + Ok(()) + } } /// Filter source cohorts by an optional filter. diff --git a/crates/brk_computer/src/distribution/cohorts/utxo/vecs.rs b/crates/brk_computer/src/distribution/cohorts/utxo/vecs.rs index 96b119868..85e2a28dc 100644 --- a/crates/brk_computer/src/distribution/cohorts/utxo/vecs.rs +++ b/crates/brk_computer/src/distribution/cohorts/utxo/vecs.rs @@ -157,10 +157,6 @@ impl DynCohortVecs for UTXOCohortVecs { ) -> Result<()> { self.metrics .compute_rest_part1(blocks, prices, starting_indexes, exit)?; - if self.state.is_some() { - self.metrics - .compute_net_sentiment_height(starting_indexes, exit)?; - } Ok(()) } diff --git a/crates/brk_computer/src/distribution/compute/block_loop.rs b/crates/brk_computer/src/distribution/compute/block_loop.rs index a2afb7f2a..f60facb89 100644 --- a/crates/brk_computer/src/distribution/compute/block_loop.rs +++ b/crates/brk_computer/src/distribution/compute/block_loop.rs @@ -506,6 +506,7 @@ fn push_cohort_states( height_price: Cents, is_day_boundary: bool, ) -> Result<()> { + // Phase 1: push + unrealized (no reset yet — states still needed for aggregation) let (r1, r2) = rayon::join( || { utxo_cohorts @@ -517,7 +518,6 @@ fn push_cohort_states( height_price, is_day_boundary, )?; - v.reset_single_iteration_values(); Ok(()) }) }, @@ -531,12 +531,23 @@ fn push_cohort_states( height_price, is_day_boundary, )?; - v.reset_single_iteration_values(); Ok(()) }) }, ); r1?; r2?; + + // Phase 2: aggregate age_range realized states → push to overlapping cohorts' RealizedFull + utxo_cohorts.push_overlapping_realized_full(height)?; + + // Phase 3: reset per-block values + utxo_cohorts + .iter_separate_mut() + .for_each(|v| v.reset_single_iteration_values()); + address_cohorts + .iter_separate_mut() + .for_each(|v| v.reset_single_iteration_values()); + Ok(()) } 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 9d864d34f..ac2d07815 100644 --- a/crates/brk_computer/src/distribution/metrics/cohort/extended_adjusted.rs +++ b/crates/brk_computer/src/distribution/metrics/cohort/extended_adjusted.rs @@ -22,7 +22,6 @@ pub struct ExtendedAdjustedCohortMetrics { #[deref_mut] #[traversable(flatten)] pub inner: ExtendedCohortMetrics, - #[traversable(flatten)] pub adjusted: Box>, } diff --git a/crates/brk_computer/src/distribution/metrics/mod.rs b/crates/brk_computer/src/distribution/metrics/mod.rs index b8d40eb01..d3c08e25c 100644 --- a/crates/brk_computer/src/distribution/metrics/mod.rs +++ b/crates/brk_computer/src/distribution/metrics/mod.rs @@ -52,7 +52,8 @@ pub use cost_basis::CostBasis; pub use profitability::ProfitabilityMetrics; pub use outputs::OutputsMetrics; pub use realized::{ - RealizedAdjusted, RealizedBase, RealizedCore, RealizedFull, RealizedLike, RealizedMinimal, + RealizedAdjusted, RealizedBase, RealizedCore, RealizedFull, RealizedFullAccum, RealizedLike, + RealizedMinimal, }; pub use relative::{ RelativeForAll, RelativeToAll, RelativeWithExtended, @@ -189,16 +190,9 @@ pub trait CohortMetricsBase: CohortMetricsState + Send self.unrealized_mut() .compute_rest(prices, starting_indexes, exit)?; - Ok(()) - } - - fn compute_net_sentiment_height( - &mut self, - starting_indexes: &Indexes, - exit: &Exit, - ) -> Result<()> { self.unrealized_mut() .compute_net_sentiment_height(starting_indexes, 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 9c2df09da..616f5c93c 100644 --- a/crates/brk_computer/src/distribution/metrics/realized/full.rs +++ b/crates/brk_computer/src/distribution/metrics/realized/full.rs @@ -298,6 +298,47 @@ impl RealizedFull { Ok(()) } + pub(crate) fn push_from_accum( + &mut self, + accum: &RealizedFullAccum, + height: Height, + ) -> Result<()> { + self.profit_value_created + .height + .truncate_push(height, accum.profit_value_created)?; + self.profit_value_destroyed + .height + .truncate_push(height, accum.profit_value_destroyed)?; + self.loss_value_created + .height + .truncate_push(height, accum.loss_value_created)?; + self.loss_value_destroyed + .height + .truncate_push(height, accum.loss_value_destroyed)?; + self.cap_raw.truncate_push(height, accum.cap_raw)?; + self.investor_cap_raw + .truncate_push(height, accum.investor_cap_raw)?; + + let investor_price = { + let cap = accum.cap_raw.as_u128(); + if cap == 0 { + Cents::ZERO + } else { + Cents::new((accum.investor_cap_raw / cap) as u64) + } + }; + self.investor_price + .cents + .height + .truncate_push(height, investor_price)?; + + self.peak_regret + .height + .truncate_push(height, accum.peak_regret)?; + + Ok(()) + } + pub(crate) fn compute_rest_part1( &mut self, blocks: &blocks::Vecs, @@ -626,3 +667,26 @@ impl RealizedFull { Ok(()) } } + +#[derive(Default)] +pub struct RealizedFullAccum { + pub(crate) profit_value_created: Cents, + pub(crate) profit_value_destroyed: Cents, + pub(crate) loss_value_created: Cents, + pub(crate) loss_value_destroyed: Cents, + pub(crate) cap_raw: CentsSats, + pub(crate) investor_cap_raw: CentsSquaredSats, + pub(crate) peak_regret: Cents, +} + +impl RealizedFullAccum { + pub(crate) fn add(&mut self, state: &RealizedState) { + self.profit_value_created += state.profit_value_created(); + self.profit_value_destroyed += state.profit_value_destroyed(); + self.loss_value_created += state.loss_value_created(); + self.loss_value_destroyed += state.loss_value_destroyed(); + self.cap_raw += state.cap_raw(); + self.investor_cap_raw += state.investor_cap_raw(); + self.peak_regret += state.peak_regret(); + } +} diff --git a/crates/brk_computer/src/distribution/metrics/realized/mod.rs b/crates/brk_computer/src/distribution/metrics/realized/mod.rs index c76fae4b2..2d4d1af69 100644 --- a/crates/brk_computer/src/distribution/metrics/realized/mod.rs +++ b/crates/brk_computer/src/distribution/metrics/realized/mod.rs @@ -7,7 +7,7 @@ mod minimal; pub use adjusted::RealizedAdjusted; pub use base::RealizedBase; pub use self::core::RealizedCore; -pub use full::RealizedFull; +pub use full::{RealizedFull, RealizedFullAccum}; pub use minimal::RealizedMinimal; use brk_error::Result;