diff --git a/crates/brk_computer/src/stateful/cohorts/address.rs b/crates/brk_computer/src/stateful/cohorts/address.rs index d462ebf0d..b6c0bcf45 100644 --- a/crates/brk_computer/src/stateful/cohorts/address.rs +++ b/crates/brk_computer/src/stateful/cohorts/address.rs @@ -212,13 +212,13 @@ impl DynCohortVecs for AddressCohortVecs { dateindex: Option, date_price: Option>, ) -> Result<()> { - if let Some(state) = self.state.as_ref() { + if let Some(state) = self.state.as_mut() { self.metrics.compute_then_truncate_push_unrealized_states( height, height_price, dateindex, date_price, - &state.inner, + &mut state.inner, )?; } Ok(()) diff --git a/crates/brk_computer/src/stateful/cohorts/state.rs b/crates/brk_computer/src/stateful/cohorts/state.rs index 14131af67..ed1b6c993 100644 --- a/crates/brk_computer/src/stateful/cohorts/state.rs +++ b/crates/brk_computer/src/stateful/cohorts/state.rs @@ -2,14 +2,13 @@ //! //! This state is maintained in memory during block processing and periodically flushed. -use std::cmp::Ordering; use std::path::Path; use brk_error::Result; -use brk_types::{CheckedSub, Dollars, Height, Sats}; +use brk_types::{Dollars, Height, Sats}; use crate::{ - PriceToAmount, RealizedState, SupplyState, UnrealizedState, + CachedUnrealizedState, PriceToAmount, RealizedState, SupplyState, UnrealizedState, grouped::{PERCENTILES, PERCENTILES_LEN}, utils::OptionExt, }; @@ -34,6 +33,9 @@ pub struct CohortState { /// Price distribution for percentile calculations (requires price data) price_to_amount: Option, + + /// Cached unrealized state for O(k) incremental updates. + cached_unrealized: Option, } impl CohortState { @@ -46,11 +48,15 @@ impl CohortState { satblocks_destroyed: Sats::ZERO, satdays_destroyed: Sats::ZERO, price_to_amount: compute_dollars.then_some(PriceToAmount::create(path, name)), + cached_unrealized: None, } } /// Import state from checkpoint. pub fn import_at_or_before(&mut self, height: Height) -> Result { + // Invalidate cache when importing new data + self.cached_unrealized = None; + match self.price_to_amount.as_mut() { Some(p) => p.import_at_or_before(height), None => Ok(height), @@ -63,6 +69,8 @@ impl CohortState { p.clean()?; p.init(); } + // Invalidate cache when data is reset + self.cached_unrealized = None; Ok(()) } @@ -91,11 +99,20 @@ impl CohortState { self.supply += supply; if supply.value > Sats::ZERO - && let Some(realized) = self.realized.as_mut() { - let price = price.unwrap(); - realized.increment(supply, price); - self.price_to_amount.as_mut().unwrap().increment(price, supply); + && let Some(realized) = self.realized.as_mut() + { + let price = price.unwrap(); + realized.increment(supply, price); + self.price_to_amount + .as_mut() + .unwrap() + .increment(price, supply); + + // Update cache for added supply + if let Some(cache) = self.cached_unrealized.as_mut() { + cache.on_receive(price, supply.value); } + } } /// Add supply with pre-computed realized cap (for address cohorts). @@ -108,10 +125,19 @@ impl CohortState { self.supply += supply; if supply.value > Sats::ZERO - && let Some(realized) = self.realized.as_mut() { - realized.increment_(realized_cap); - self.price_to_amount.as_mut().unwrap().increment(realized_price, supply); + && let Some(realized) = self.realized.as_mut() + { + realized.increment_(realized_cap); + self.price_to_amount + .as_mut() + .unwrap() + .increment(realized_price, supply); + + // Update cache for added supply + if let Some(cache) = self.cached_unrealized.as_mut() { + cache.on_receive(realized_price, supply.value); } + } } /// Remove supply from this cohort (e.g., when UTXO ages out of cohort). @@ -119,11 +145,20 @@ impl CohortState { self.supply -= supply; if supply.value > Sats::ZERO - && let Some(realized) = self.realized.as_mut() { - let price = price.unwrap(); - realized.decrement(supply, price); - self.price_to_amount.as_mut().unwrap().decrement(price, supply); + && let Some(realized) = self.realized.as_mut() + { + let price = price.unwrap(); + realized.decrement(supply, price); + self.price_to_amount + .as_mut() + .unwrap() + .decrement(price, supply); + + // Update cache for removed supply + if let Some(cache) = self.cached_unrealized.as_mut() { + cache.on_send(price, supply.value); } + } } /// Remove supply with pre-computed realized cap (for address cohorts). @@ -136,20 +171,24 @@ impl CohortState { self.supply -= supply; if supply.value > Sats::ZERO - && let Some(realized) = self.realized.as_mut() { - realized.decrement_(realized_cap); - self.price_to_amount.as_mut().unwrap().decrement(realized_price, supply); + && let Some(realized) = self.realized.as_mut() + { + realized.decrement_(realized_cap); + self.price_to_amount + .as_mut() + .unwrap() + .decrement(realized_price, supply); + + // Update cache for removed supply + if let Some(cache) = self.cached_unrealized.as_mut() { + cache.on_send(realized_price, supply.value); } + } } /// Process received output (new UTXO in cohort). pub fn receive(&mut self, supply: &SupplyState, price: Option) { - self.receive_( - supply, - price, - price.map(|price| (price, supply)), - None, - ); + self.receive_(supply, price, price.map(|price| (price, supply)), None); } /// Process received output with custom price_to_amount updates (for address cohorts). @@ -163,20 +202,39 @@ impl CohortState { self.supply += supply; if supply.value > Sats::ZERO - && let Some(realized) = self.realized.as_mut() { - let price = price.unwrap(); - realized.receive(supply, price); + && let Some(realized) = self.realized.as_mut() + { + let price = price.unwrap(); + realized.receive(supply, price); - if let Some((price, supply)) = price_to_amount_increment - && supply.value.is_not_zero() { - self.price_to_amount.as_mut().unwrap().increment(price, supply); - } + if let Some((price, supply)) = price_to_amount_increment + && supply.value.is_not_zero() + { + self.price_to_amount + .as_mut() + .unwrap() + .increment(price, supply); - if let Some((price, supply)) = price_to_amount_decrement - && supply.value.is_not_zero() { - self.price_to_amount.as_mut().unwrap().decrement(price, supply); - } + // Update cache for added supply + if let Some(cache) = self.cached_unrealized.as_mut() { + cache.on_receive(price, supply.value); + } } + + if let Some((price, supply)) = price_to_amount_decrement + && supply.value.is_not_zero() + { + self.price_to_amount + .as_mut() + .unwrap() + .decrement(price, supply); + + // Update cache for removed supply + if let Some(cache) = self.cached_unrealized.as_mut() { + cache.on_send(price, supply.value); + } + } + } } /// Process spent input (UTXO leaving cohort). @@ -232,14 +290,32 @@ impl CohortState { realized.send(supply, current_price, prev_price, older_than_hour); if let Some((price, supply)) = price_to_amount_increment - && supply.value.is_not_zero() { - self.price_to_amount.as_mut().unwrap().increment(price, supply); + && supply.value.is_not_zero() + { + self.price_to_amount + .as_mut() + .unwrap() + .increment(price, supply); + + // Update cache for added supply + if let Some(cache) = self.cached_unrealized.as_mut() { + cache.on_receive(price, supply.value); } + } if let Some((price, supply)) = price_to_amount_decrement - && supply.value.is_not_zero() { - self.price_to_amount.as_mut().unwrap().decrement(price, supply); + && supply.value.is_not_zero() + { + self.price_to_amount + .as_mut() + .unwrap() + .decrement(price, supply); + + // Update cache for removed supply + if let Some(cache) = self.cached_unrealized.as_mut() { + cache.on_send(price, supply.value); } + } } } } @@ -279,57 +355,41 @@ impl CohortState { result } - /// Compute unrealized profit/loss at current price (alias for compatibility). - pub fn compute_unrealized_states( - &self, - height_price: Dollars, - date_price: Option, - ) -> (UnrealizedState, Option) { - self.compute_unrealized(height_price, date_price) - } - /// Compute unrealized profit/loss at current price. - pub fn compute_unrealized( - &self, + /// Uses O(k) incremental updates for height_price where k = flip range size. + pub fn compute_unrealized_states( + &mut self, height_price: Dollars, date_price: Option, ) -> (UnrealizedState, Option) { let price_to_amount = match self.price_to_amount.as_ref() { Some(p) if !p.is_empty() => p, - _ => return (UnrealizedState::NAN, date_price.map(|_| UnrealizedState::NAN)), + _ => { + return ( + UnrealizedState::NAN, + date_price.map(|_| UnrealizedState::NAN), + ); + } }; - let mut height_state = UnrealizedState::ZERO; - let mut date_state = date_price.map(|_| UnrealizedState::ZERO); + // Height unrealized: use incremental cache (O(k) where k = flip range) + let height_state = if let Some(cache) = self.cached_unrealized.as_mut() { + cache.get_at_price(height_price, price_to_amount).clone() + } else { + let cache = CachedUnrealizedState::compute_fresh(height_price, price_to_amount); + let state = cache.state.clone(); + self.cached_unrealized = Some(cache); + state + }; - for (&price, &sats) in price_to_amount.iter() { - Self::update_unrealized(price, height_price, sats, &mut height_state); - - if let Some(date_price) = date_price { - Self::update_unrealized(price, date_price, sats, date_state.um()); - } - } + // Date unrealized: compute from scratch (only at date boundaries, ~144x less frequent) + let date_state = date_price.map(|date_price| { + CachedUnrealizedState::compute_full_standalone(date_price, price_to_amount) + }); (height_state, date_state) } - fn update_unrealized(price: Dollars, current: Dollars, sats: Sats, state: &mut UnrealizedState) { - match price.cmp(¤t) { - Ordering::Less | Ordering::Equal => { - state.supply_in_profit += sats; - if price < current && price > Dollars::ZERO && current > Dollars::ZERO { - state.unrealized_profit += current.checked_sub(price).unwrap() * sats; - } - } - Ordering::Greater => { - state.supply_in_loss += sats; - if price > Dollars::ZERO && current > Dollars::ZERO { - state.unrealized_loss += price.checked_sub(current).unwrap() * sats; - } - } - } - } - /// Flush state to disk at checkpoint. pub fn commit(&mut self, height: Height) -> Result<()> { if let Some(p) = self.price_to_amount.as_mut() { @@ -340,12 +400,18 @@ impl CohortState { /// Get first (lowest) price in distribution. pub fn min_price(&self) -> Option<&Dollars> { - self.price_to_amount.as_ref()?.first_key_value().map(|(k, _)| k) + self.price_to_amount + .as_ref()? + .first_key_value() + .map(|(k, _)| k) } /// Get last (highest) price in distribution. pub fn max_price(&self) -> Option<&Dollars> { - self.price_to_amount.as_ref()?.last_key_value().map(|(k, _)| k) + self.price_to_amount + .as_ref()? + .last_key_value() + .map(|(k, _)| k) } /// Get iterator over price_to_amount for merged percentile computation. diff --git a/crates/brk_computer/src/stateful/cohorts/utxo.rs b/crates/brk_computer/src/stateful/cohorts/utxo.rs index f38eb4201..080e409d8 100644 --- a/crates/brk_computer/src/stateful/cohorts/utxo.rs +++ b/crates/brk_computer/src/stateful/cohorts/utxo.rs @@ -167,7 +167,7 @@ impl DynCohortVecs for UTXOCohortVecs { dateindex: Option, date_price: Option>, ) -> Result<()> { - if let Some(state) = self.state.as_ref() { + if let Some(state) = self.state.as_mut() { self.metrics.compute_then_truncate_push_unrealized_states( height, height_price, diff --git a/crates/brk_computer/src/stateful/metrics/mod.rs b/crates/brk_computer/src/stateful/metrics/mod.rs index 5afe0e331..844f16b73 100644 --- a/crates/brk_computer/src/stateful/metrics/mod.rs +++ b/crates/brk_computer/src/stateful/metrics/mod.rs @@ -149,7 +149,7 @@ impl CohortMetrics { height_price: Option, dateindex: Option, date_price: Option>, - state: &CohortState, + state: &mut CohortState, ) -> Result<()> { if let (Some(unrealized), Some(price_paid), Some(height_price)) = ( self.unrealized.as_mut(), diff --git a/crates/brk_computer/src/stateful_old/address_cohort.rs b/crates/brk_computer/src/stateful_old/address_cohort.rs deleted file mode 100644 index 5eb4349bd..000000000 --- a/crates/brk_computer/src/stateful_old/address_cohort.rs +++ /dev/null @@ -1,239 +0,0 @@ -use std::path::Path; - -use brk_error::Result; -use brk_grouper::{CohortContext, Filter, Filtered}; -use brk_traversable::Traversable; -use brk_types::{Bitcoin, DateIndex, Dollars, Height, StoredU64, Version}; -use vecdb::{ - AnyStoredVec, AnyVec, Database, EagerVec, Exit, GenericStoredVec, ImportableVec, IterableVec, - PcoVec, TypedVecIterator, -}; - -use crate::{ - Indexes, - grouped::{ComputedVecsFromHeight, Source, VecBuilderOptions}, - indexes, price, - stateful::{ - common, - r#trait::{CohortVecs, DynCohortVecs}, - }, - states::AddressCohortState, - utils::OptionExt, -}; - -const VERSION: Version = Version::ZERO; - -#[derive(Clone, Traversable)] -pub struct Vecs { - starting_height: Option, - - #[traversable(skip)] - pub state: Option, - - #[traversable(flatten)] - pub inner: common::Vecs, - - pub height_to_addr_count: EagerVec>, - pub indexes_to_addr_count: ComputedVecsFromHeight, -} - -impl Vecs { - pub fn forced_import( - db: &Database, - filter: Filter, - version: Version, - indexes: &indexes::Vecs, - price: Option<&price::Vecs>, - states_path: Option<&Path>, - ) -> Result { - let compute_dollars = price.is_some(); - - let full_name = filter.to_full_name(CohortContext::Address); - let suffix = |s: &str| { - if full_name.is_empty() { - s.to_string() - } else { - format!("{full_name}_{s}") - } - }; - - Ok(Self { - starting_height: None, - state: states_path.map(|states_path| { - AddressCohortState::new(states_path, &full_name, compute_dollars) - }), - height_to_addr_count: EagerVec::forced_import( - db, - &suffix("addr_count"), - version + VERSION + Version::ZERO, - )?, - indexes_to_addr_count: ComputedVecsFromHeight::forced_import( - db, - &suffix("addr_count"), - Source::None, - version + VERSION + Version::ZERO, - indexes, - VecBuilderOptions::default().add_last(), - )?, - inner: common::Vecs::forced_import( - db, - filter, - CohortContext::Address, - version, - indexes, - price, - )?, - }) - } -} - -impl DynCohortVecs for Vecs { - fn min_height_vecs_len(&self) -> usize { - std::cmp::min( - self.height_to_addr_count.len(), - self.inner.min_height_vecs_len(), - ) - } - - fn reset_state_starting_height(&mut self) { - self.starting_height = Some(Height::ZERO); - } - - fn import_state(&mut self, starting_height: Height) -> Result { - let starting_height = self - .inner - .import_state(starting_height, &mut self.state.um().inner)?; - - self.starting_height = Some(starting_height); - - if let Some(prev_height) = starting_height.decremented() { - self.state.um().addr_count = *self - .height_to_addr_count - .into_iter() - .get_unwrap(prev_height); - } - - Ok(starting_height) - } - - fn validate_computed_versions(&mut self, base_version: Version) -> Result<()> { - self.height_to_addr_count - .validate_computed_version_or_reset( - base_version + self.height_to_addr_count.inner_version(), - )?; - - self.inner.validate_computed_versions(base_version) - } - - fn truncate_push(&mut self, height: Height) -> Result<()> { - if self.starting_height.unwrap() > height { - return Ok(()); - } - - self.height_to_addr_count - .truncate_push(height, self.state.u().addr_count.into())?; - - self.inner - .truncate_push(height, &self.state.u().inner) - } - - fn compute_then_truncate_push_unrealized_states( - &mut self, - height: Height, - height_price: Option, - dateindex: Option, - date_price: Option>, - ) -> Result<()> { - self.inner.compute_then_truncate_push_unrealized_states( - height, - height_price, - dateindex, - date_price, - &self.state.u().inner, - ) - } - - fn safe_flush_stateful_vecs(&mut self, height: Height, exit: &Exit) -> Result<()> { - self.height_to_addr_count.safe_write(exit)?; - - self.inner - .safe_flush_stateful_vecs(height, exit, &mut self.state.um().inner) - } - - #[allow(clippy::too_many_arguments)] - fn compute_rest_part1( - &mut self, - indexes: &indexes::Vecs, - price: Option<&price::Vecs>, - starting_indexes: &Indexes, - exit: &Exit, - ) -> Result<()> { - self.indexes_to_addr_count.compute_rest( - indexes, - starting_indexes, - exit, - Some(&self.height_to_addr_count), - )?; - - self.inner - .compute_rest_part1(indexes, price, starting_indexes, exit) - } -} - -impl CohortVecs for Vecs { - fn compute_from_stateful( - &mut self, - starting_indexes: &Indexes, - others: &[&Self], - exit: &Exit, - ) -> Result<()> { - self.height_to_addr_count.compute_sum_of_others( - starting_indexes.height, - others - .iter() - .map(|v| &v.height_to_addr_count) - .collect::>() - .as_slice(), - exit, - )?; - self.inner.compute_from_stateful( - starting_indexes, - &others.iter().map(|v| &v.inner).collect::>(), - exit, - ) - } - - #[allow(clippy::too_many_arguments)] - fn compute_rest_part2( - &mut self, - indexes: &indexes::Vecs, - price: Option<&price::Vecs>, - starting_indexes: &Indexes, - height_to_supply: &impl IterableVec, - dateindex_to_supply: &impl IterableVec, - height_to_market_cap: Option<&impl IterableVec>, - dateindex_to_market_cap: Option<&impl IterableVec>, - height_to_realized_cap: Option<&impl IterableVec>, - dateindex_to_realized_cap: Option<&impl IterableVec>, - exit: &Exit, - ) -> Result<()> { - self.inner.compute_rest_part2( - indexes, - price, - starting_indexes, - height_to_supply, - dateindex_to_supply, - height_to_market_cap, - dateindex_to_market_cap, - height_to_realized_cap, - dateindex_to_realized_cap, - exit, - ) - } -} - -impl Filtered for Vecs { - fn filter(&self) -> &Filter { - &self.inner.filter - } -} diff --git a/crates/brk_computer/src/stateful_old/address_cohorts.rs b/crates/brk_computer/src/stateful_old/address_cohorts.rs deleted file mode 100644 index e6e37fa09..000000000 --- a/crates/brk_computer/src/stateful_old/address_cohorts.rs +++ /dev/null @@ -1,139 +0,0 @@ -use std::path::Path; - -use brk_error::Result; -use brk_grouper::{AddressGroups, AmountFilter, Filter, Filtered}; -use brk_traversable::Traversable; -use brk_types::{Bitcoin, DateIndex, Dollars, Height, Version}; -use derive_deref::{Deref, DerefMut}; -use rayon::prelude::*; -use vecdb::{Database, Exit, IterableVec}; - -use crate::{ - Indexes, indexes, price, - stateful::{ - address_cohort, - r#trait::{CohortVecs, DynCohortVecs}, - }, -}; - -const VERSION: Version = Version::new(0); - -#[derive(Clone, Deref, DerefMut, Traversable)] -pub struct Vecs(AddressGroups); - -impl Vecs { - pub fn forced_import( - db: &Database, - version: Version, - indexes: &indexes::Vecs, - price: Option<&price::Vecs>, - states_path: &Path, - ) -> Result { - Ok(Self(AddressGroups::new(|filter| { - let states_path = match &filter { - Filter::Amount(AmountFilter::Range(_)) => Some(states_path), - _ => None, - }; - - address_cohort::Vecs::forced_import( - db, - filter, - version + VERSION + Version::ZERO, - indexes, - price, - states_path, - ) - .unwrap() - }))) - } - - pub fn compute_overlapping_vecs( - &mut self, - starting_indexes: &Indexes, - exit: &Exit, - ) -> Result<()> { - let by_size_range = &self.0.amount_range; - - [ - self.0 - .ge_amount - .par_iter_mut() - .map(|vecs| { - let filter = vecs.filter().clone(); - ( - vecs, - by_size_range - .iter() - .filter(|other| filter.includes(other.filter())) - .collect::>(), - ) - }) - .collect::>(), - self.0 - .lt_amount - .par_iter_mut() - .map(|vecs| { - let filter = vecs.filter().clone(); - ( - vecs, - by_size_range - .iter() - .filter(|other| filter.includes(other.filter())) - .collect::>(), - ) - }) - .collect::>(), - ] - .into_iter() - .flatten() - .try_for_each(|(vecs, stateful)| { - vecs.compute_from_stateful(starting_indexes, &stateful, exit) - }) - } - - pub fn compute_rest_part1( - &mut self, - indexes: &indexes::Vecs, - price: Option<&price::Vecs>, - starting_indexes: &Indexes, - exit: &Exit, - ) -> Result<()> { - self.par_iter_mut() - .try_for_each(|v| v.compute_rest_part1(indexes, price, starting_indexes, exit)) - } - - #[allow(clippy::too_many_arguments)] - pub fn compute_rest_part2( - &mut self, - indexes: &indexes::Vecs, - price: Option<&price::Vecs>, - starting_indexes: &Indexes, - height_to_supply: &impl IterableVec, - dateindex_to_supply: &impl IterableVec, - height_to_market_cap: Option<&impl IterableVec>, - dateindex_to_market_cap: Option<&impl IterableVec>, - height_to_realized_cap: Option<&impl IterableVec>, - dateindex_to_realized_cap: Option<&impl IterableVec>, - exit: &Exit, - ) -> Result<()> { - self.0.par_iter_mut().try_for_each(|v| { - v.compute_rest_part2( - indexes, - price, - starting_indexes, - height_to_supply, - dateindex_to_supply, - height_to_market_cap, - dateindex_to_market_cap, - height_to_realized_cap, - dateindex_to_realized_cap, - exit, - ) - }) - } - - pub fn safe_flush_stateful_vecs(&mut self, height: Height, exit: &Exit) -> Result<()> { - self.par_iter_separate_mut() - .try_for_each(|v| v.safe_flush_stateful_vecs(height, exit)) - } -} diff --git a/crates/brk_computer/src/stateful_old/address_indexes.rs b/crates/brk_computer/src/stateful_old/address_indexes.rs deleted file mode 100644 index fbc6b1cc1..000000000 --- a/crates/brk_computer/src/stateful_old/address_indexes.rs +++ /dev/null @@ -1,226 +0,0 @@ -use brk_error::{Error, Result}; -use brk_traversable::Traversable; -use brk_types::{ - AnyAddressIndex, EmptyAddressData, EmptyAddressIndex, Height, LoadedAddressData, - LoadedAddressIndex, OutputType, P2AAddressIndex, P2PK33AddressIndex, P2PK65AddressIndex, - P2PKHAddressIndex, P2SHAddressIndex, P2TRAddressIndex, P2WPKHAddressIndex, P2WSHAddressIndex, - TypeIndex, -}; -use vecdb::{AnyStoredVec, BytesVec, GenericStoredVec, Reader, Stamp}; - -#[derive(Clone, Traversable)] -pub struct AnyAddressIndexesVecs { - pub p2pk33: BytesVec, - pub p2pk65: BytesVec, - pub p2pkh: BytesVec, - pub p2sh: BytesVec, - pub p2tr: BytesVec, - pub p2wpkh: BytesVec, - pub p2wsh: BytesVec, - pub p2a: BytesVec, -} - -impl AnyAddressIndexesVecs { - pub fn min_stamped_height(&self) -> Height { - Height::from(self.p2pk33.stamp()) - .incremented() - .min(Height::from(self.p2pk65.stamp()).incremented()) - .min(Height::from(self.p2pkh.stamp()).incremented()) - .min(Height::from(self.p2sh.stamp()).incremented()) - .min(Height::from(self.p2tr.stamp()).incremented()) - .min(Height::from(self.p2wpkh.stamp()).incremented()) - .min(Height::from(self.p2wsh.stamp()).incremented()) - .min(Height::from(self.p2a.stamp()).incremented()) - } - - pub fn rollback_before(&mut self, stamp: Stamp) -> Result<[Stamp; 8]> { - Ok([ - self.p2pk33.rollback_before(stamp)?, - self.p2pk65.rollback_before(stamp)?, - self.p2pkh.rollback_before(stamp)?, - self.p2sh.rollback_before(stamp)?, - self.p2tr.rollback_before(stamp)?, - self.p2wpkh.rollback_before(stamp)?, - self.p2wsh.rollback_before(stamp)?, - self.p2a.rollback_before(stamp)?, - ]) - } - - pub fn reset(&mut self) -> Result<()> { - self.p2pk33.reset()?; - self.p2pk65.reset()?; - self.p2pkh.reset()?; - self.p2sh.reset()?; - self.p2tr.reset()?; - self.p2wpkh.reset()?; - self.p2wsh.reset()?; - self.p2a.reset()?; - Ok(()) - } - - pub fn get_anyaddressindex( - &self, - address_type: OutputType, - typeindex: TypeIndex, - reader: &Reader, - ) -> AnyAddressIndex { - match address_type { - OutputType::P2PK33 => self - .p2pk33 - .get_pushed_or_read_at_unwrap(typeindex.into(), reader), - OutputType::P2PK65 => self - .p2pk65 - .get_pushed_or_read_at_unwrap(typeindex.into(), reader), - OutputType::P2PKH => self - .p2pkh - .get_pushed_or_read_at_unwrap(typeindex.into(), reader), - OutputType::P2SH => self - .p2sh - .get_pushed_or_read_at_unwrap(typeindex.into(), reader), - OutputType::P2TR => self - .p2tr - .get_pushed_or_read_at_unwrap(typeindex.into(), reader), - OutputType::P2WPKH => self - .p2wpkh - .get_pushed_or_read_at_unwrap(typeindex.into(), reader), - OutputType::P2WSH => self - .p2wsh - .get_pushed_or_read_at_unwrap(typeindex.into(), reader), - OutputType::P2A => self - .p2a - .get_pushed_or_read_at_unwrap(typeindex.into(), reader), - _ => unreachable!(), - } - } - - pub fn get_anyaddressindex_once( - &self, - address_type: OutputType, - typeindex: TypeIndex, - ) -> Result { - match address_type { - OutputType::P2PK33 => self - .p2pk33 - .read_at_once(typeindex.into()) - .map_err(|e| e.into()), - OutputType::P2PK65 => self - .p2pk65 - .read_at_once(typeindex.into()) - .map_err(|e| e.into()), - OutputType::P2PKH => self - .p2pkh - .read_at_once(typeindex.into()) - .map_err(|e| e.into()), - OutputType::P2SH => self - .p2sh - .read_at_once(typeindex.into()) - .map_err(|e| e.into()), - OutputType::P2TR => self - .p2tr - .read_at_once(typeindex.into()) - .map_err(|e| e.into()), - OutputType::P2WPKH => self - .p2wpkh - .read_at_once(typeindex.into()) - .map_err(|e| e.into()), - OutputType::P2WSH => self - .p2wsh - .read_at_once(typeindex.into()) - .map_err(|e| e.into()), - OutputType::P2A => self - .p2a - .read_at_once(typeindex.into()) - .map_err(|e| e.into()), - _ => Err(Error::UnsupportedType(address_type.to_string())), - } - } - - pub fn update_or_push( - &mut self, - address_type: OutputType, - typeindex: TypeIndex, - anyaddressindex: AnyAddressIndex, - ) -> Result<()> { - (match address_type { - OutputType::P2PK33 => self - .p2pk33 - .update_or_push(typeindex.into(), anyaddressindex), - OutputType::P2PK65 => self - .p2pk65 - .update_or_push(typeindex.into(), anyaddressindex), - OutputType::P2PKH => self.p2pkh.update_or_push(typeindex.into(), anyaddressindex), - OutputType::P2SH => self.p2sh.update_or_push(typeindex.into(), anyaddressindex), - OutputType::P2TR => self.p2tr.update_or_push(typeindex.into(), anyaddressindex), - OutputType::P2WPKH => self - .p2wpkh - .update_or_push(typeindex.into(), anyaddressindex), - OutputType::P2WSH => self.p2wsh.update_or_push(typeindex.into(), anyaddressindex), - OutputType::P2A => self.p2a.update_or_push(typeindex.into(), anyaddressindex), - _ => unreachable!(), - })?; - Ok(()) - } - - pub fn stamped_flush_maybe_with_changes( - &mut self, - stamp: Stamp, - with_changes: bool, - ) -> Result<()> { - self.p2pk33 - .stamped_flush_maybe_with_changes(stamp, with_changes)?; - self.p2pk65 - .stamped_flush_maybe_with_changes(stamp, with_changes)?; - self.p2pkh - .stamped_flush_maybe_with_changes(stamp, with_changes)?; - self.p2sh - .stamped_flush_maybe_with_changes(stamp, with_changes)?; - self.p2tr - .stamped_flush_maybe_with_changes(stamp, with_changes)?; - self.p2wpkh - .stamped_flush_maybe_with_changes(stamp, with_changes)?; - self.p2wsh - .stamped_flush_maybe_with_changes(stamp, with_changes)?; - self.p2a - .stamped_flush_maybe_with_changes(stamp, with_changes)?; - Ok(()) - } -} - -#[derive(Clone, Traversable)] -pub struct AddressesDataVecs { - pub loaded: BytesVec, - pub empty: BytesVec, -} - -impl AddressesDataVecs { - pub fn min_stamped_height(&self) -> Height { - Height::from(self.loaded.stamp()) - .incremented() - .min(Height::from(self.empty.stamp()).incremented()) - } - - pub fn rollback_before(&mut self, stamp: Stamp) -> Result<[Stamp; 2]> { - Ok([ - self.loaded.rollback_before(stamp)?, - self.empty.rollback_before(stamp)?, - ]) - } - - pub fn reset(&mut self) -> Result<()> { - self.loaded.reset()?; - self.empty.reset()?; - Ok(()) - } - - pub fn stamped_flush_maybe_with_changes( - &mut self, - stamp: Stamp, - with_changes: bool, - ) -> Result<()> { - self.loaded - .stamped_flush_maybe_with_changes(stamp, with_changes)?; - self.empty - .stamped_flush_maybe_with_changes(stamp, with_changes)?; - Ok(()) - } -} diff --git a/crates/brk_computer/src/stateful_old/addresstype/addresscount.rs b/crates/brk_computer/src/stateful_old/addresstype/addresscount.rs deleted file mode 100644 index 1cdd92eb5..000000000 --- a/crates/brk_computer/src/stateful_old/addresstype/addresscount.rs +++ /dev/null @@ -1,29 +0,0 @@ -use brk_grouper::ByAddressType; -use brk_types::Height; -use derive_deref::{Deref, DerefMut}; -use vecdb::TypedVecIterator; - -use super::AddressTypeToHeightToAddressCount; - -#[derive(Debug, Default, Deref, DerefMut)] -pub struct AddressTypeToAddressCount(ByAddressType); - -impl From<(&AddressTypeToHeightToAddressCount, Height)> for AddressTypeToAddressCount { - #[inline] - fn from((groups, starting_height): (&AddressTypeToHeightToAddressCount, Height)) -> Self { - if let Some(prev_height) = starting_height.decremented() { - Self(ByAddressType { - p2pk65: groups.p2pk65.into_iter().get_unwrap(prev_height).into(), - p2pk33: groups.p2pk33.into_iter().get_unwrap(prev_height).into(), - p2pkh: groups.p2pkh.into_iter().get_unwrap(prev_height).into(), - p2sh: groups.p2sh.into_iter().get_unwrap(prev_height).into(), - p2wpkh: groups.p2wpkh.into_iter().get_unwrap(prev_height).into(), - p2wsh: groups.p2wsh.into_iter().get_unwrap(prev_height).into(), - p2tr: groups.p2tr.into_iter().get_unwrap(prev_height).into(), - p2a: groups.p2a.into_iter().get_unwrap(prev_height).into(), - }) - } else { - Default::default() - } - } -} diff --git a/crates/brk_computer/src/stateful_old/addresstype/height_to_addresscount.rs b/crates/brk_computer/src/stateful_old/addresstype/height_to_addresscount.rs deleted file mode 100644 index b9e753ea2..000000000 --- a/crates/brk_computer/src/stateful_old/addresstype/height_to_addresscount.rs +++ /dev/null @@ -1,45 +0,0 @@ -use brk_error::Result; -use brk_grouper::ByAddressType; -use brk_traversable::Traversable; -use brk_types::{Height, StoredU64}; -use derive_deref::{Deref, DerefMut}; -use vecdb::{PcoVec, EagerVec, GenericStoredVec}; - -use super::AddressTypeToAddressCount; - -#[derive(Debug, Clone, Deref, DerefMut, Traversable)] -pub struct AddressTypeToHeightToAddressCount(ByAddressType>>); - -impl From>>> for AddressTypeToHeightToAddressCount { - #[inline] - fn from(value: ByAddressType>>) -> Self { - Self(value) - } -} - -impl AddressTypeToHeightToAddressCount { - pub fn truncate_push( - &mut self, - height: Height, - addresstype_to_usize: &AddressTypeToAddressCount, - ) -> Result<()> { - self.p2pk65 - .truncate_push(height, addresstype_to_usize.p2pk65.into())?; - self.p2pk33 - .truncate_push(height, addresstype_to_usize.p2pk33.into())?; - self.p2pkh - .truncate_push(height, addresstype_to_usize.p2pkh.into())?; - self.p2sh - .truncate_push(height, addresstype_to_usize.p2sh.into())?; - self.p2wpkh - .truncate_push(height, addresstype_to_usize.p2wpkh.into())?; - self.p2wsh - .truncate_push(height, addresstype_to_usize.p2wsh.into())?; - self.p2tr - .truncate_push(height, addresstype_to_usize.p2tr.into())?; - self.p2a - .truncate_push(height, addresstype_to_usize.p2a.into())?; - - Ok(()) - } -} diff --git a/crates/brk_computer/src/stateful_old/addresstype/height_to_vec.rs b/crates/brk_computer/src/stateful_old/addresstype/height_to_vec.rs deleted file mode 100644 index db471d658..000000000 --- a/crates/brk_computer/src/stateful_old/addresstype/height_to_vec.rs +++ /dev/null @@ -1,9 +0,0 @@ -use std::collections::BTreeMap; - -use brk_types::Height; -use derive_deref::{Deref, DerefMut}; - -use crate::stateful::AddressTypeToVec; - -#[derive(Debug, Default, Deref, DerefMut)] -pub struct HeightToAddressTypeToVec(pub BTreeMap>); diff --git a/crates/brk_computer/src/stateful_old/addresstype/indexes_to_addresscount.rs b/crates/brk_computer/src/stateful_old/addresstype/indexes_to_addresscount.rs deleted file mode 100644 index ae8e32af5..000000000 --- a/crates/brk_computer/src/stateful_old/addresstype/indexes_to_addresscount.rs +++ /dev/null @@ -1,80 +0,0 @@ -use brk_error::Result; -use brk_grouper::ByAddressType; -use brk_traversable::Traversable; -use brk_types::StoredU64; -use derive_deref::{Deref, DerefMut}; -use vecdb::Exit; - -use crate::{Indexes, grouped::ComputedVecsFromHeight, indexes}; - -use super::AddressTypeToHeightToAddressCount; - -#[derive(Clone, Deref, DerefMut, Traversable)] -pub struct AddressTypeToIndexesToAddressCount(ByAddressType>); - -impl From>> for AddressTypeToIndexesToAddressCount { - #[inline] - fn from(value: ByAddressType>) -> Self { - Self(value) - } -} - -impl AddressTypeToIndexesToAddressCount { - pub fn compute( - &mut self, - indexes: &indexes::Vecs, - starting_indexes: &Indexes, - exit: &Exit, - addresstype_to_height_to_addresscount: &AddressTypeToHeightToAddressCount, - ) -> Result<()> { - self.p2pk65.compute_rest( - indexes, - starting_indexes, - exit, - Some(&addresstype_to_height_to_addresscount.p2pk65), - )?; - self.p2pk33.compute_rest( - indexes, - starting_indexes, - exit, - Some(&addresstype_to_height_to_addresscount.p2pk33), - )?; - self.p2pkh.compute_rest( - indexes, - starting_indexes, - exit, - Some(&addresstype_to_height_to_addresscount.p2pkh), - )?; - self.p2sh.compute_rest( - indexes, - starting_indexes, - exit, - Some(&addresstype_to_height_to_addresscount.p2sh), - )?; - self.p2wpkh.compute_rest( - indexes, - starting_indexes, - exit, - Some(&addresstype_to_height_to_addresscount.p2wpkh), - )?; - self.p2wsh.compute_rest( - indexes, - starting_indexes, - exit, - Some(&addresstype_to_height_to_addresscount.p2wsh), - )?; - self.p2tr.compute_rest( - indexes, - starting_indexes, - exit, - Some(&addresstype_to_height_to_addresscount.p2tr), - )?; - self.p2a.compute_rest( - indexes, - starting_indexes, - exit, - Some(&addresstype_to_height_to_addresscount.p2a), - )?; - Ok(()) - } -} diff --git a/crates/brk_computer/src/stateful_old/addresstype/mod.rs b/crates/brk_computer/src/stateful_old/addresstype/mod.rs deleted file mode 100644 index 8b2fc4845..000000000 --- a/crates/brk_computer/src/stateful_old/addresstype/mod.rs +++ /dev/null @@ -1,13 +0,0 @@ -mod addresscount; -mod height_to_addresscount; -mod height_to_vec; -mod indexes_to_addresscount; -mod typeindex_map; -mod vec; - -pub use addresscount::*; -pub use height_to_addresscount::*; -pub use height_to_vec::*; -pub use indexes_to_addresscount::*; -pub use typeindex_map::*; -pub use vec::*; diff --git a/crates/brk_computer/src/stateful_old/addresstype/typeindex_map.rs b/crates/brk_computer/src/stateful_old/addresstype/typeindex_map.rs deleted file mode 100644 index a6a9cce6c..000000000 --- a/crates/brk_computer/src/stateful_old/addresstype/typeindex_map.rs +++ /dev/null @@ -1,100 +0,0 @@ -use std::{collections::hash_map::Entry, mem}; - -use brk_grouper::ByAddressType; -use brk_types::{OutputType, TypeIndex}; -use derive_deref::{Deref, DerefMut}; -use rustc_hash::FxHashMap; -use smallvec::{Array, SmallVec}; - -#[derive(Debug, Deref, DerefMut)] -pub struct AddressTypeToTypeIndexMap(ByAddressType>); - -impl AddressTypeToTypeIndexMap { - pub fn merge(mut self, mut other: Self) -> Self { - Self::merge_(&mut self.p2pk65, &mut other.p2pk65); - Self::merge_(&mut self.p2pk33, &mut other.p2pk33); - Self::merge_(&mut self.p2pkh, &mut other.p2pkh); - Self::merge_(&mut self.p2sh, &mut other.p2sh); - Self::merge_(&mut self.p2wpkh, &mut other.p2wpkh); - Self::merge_(&mut self.p2wsh, &mut other.p2wsh); - Self::merge_(&mut self.p2tr, &mut other.p2tr); - Self::merge_(&mut self.p2a, &mut other.p2a); - self - } - - fn merge_(own: &mut FxHashMap, other: &mut FxHashMap) { - if own.len() < other.len() { - mem::swap(own, other); - } - own.extend(other.drain()); - } - - // pub fn get_for_type(&self, address_type: OutputType, typeindex: &TypeIndex) -> Option<&T> { - // self.get(address_type).unwrap().get(typeindex) - // } - - pub fn insert_for_type(&mut self, address_type: OutputType, typeindex: TypeIndex, value: T) { - self.get_mut(address_type).unwrap().insert(typeindex, value); - } - - pub fn remove_for_type(&mut self, address_type: OutputType, typeindex: &TypeIndex) -> T { - self.get_mut(address_type) - .unwrap() - .remove(typeindex) - .unwrap() - } - - pub fn into_sorted_iter(self) -> impl Iterator)> { - self.0.into_iter().map(|(output_type, map)| { - let mut sorted: Vec<_> = map.into_iter().collect(); - sorted.sort_unstable_by_key(|(typeindex, _)| *typeindex); - (output_type, sorted) - }) - } - - #[allow(clippy::should_implement_trait)] - pub fn into_iter(self) -> impl Iterator)> { - self.0.into_iter() - } -} - -impl Default for AddressTypeToTypeIndexMap { - fn default() -> Self { - Self(ByAddressType { - p2pk65: FxHashMap::default(), - p2pk33: FxHashMap::default(), - p2pkh: FxHashMap::default(), - p2sh: FxHashMap::default(), - p2wpkh: FxHashMap::default(), - p2wsh: FxHashMap::default(), - p2tr: FxHashMap::default(), - p2a: FxHashMap::default(), - }) - } -} - -impl AddressTypeToTypeIndexMap> -where - T: Array, -{ - pub fn merge_vec(mut self, other: Self) -> Self { - for (address_type, other_map) in other.0.into_iter() { - let self_map = self.0.get_mut_unwrap(address_type); - for (typeindex, mut other_vec) in other_map { - match self_map.entry(typeindex) { - Entry::Occupied(mut entry) => { - let self_vec = entry.get_mut(); - if other_vec.len() > self_vec.len() { - mem::swap(self_vec, &mut other_vec); - } - self_vec.extend(other_vec); - } - Entry::Vacant(entry) => { - entry.insert(other_vec); - } - } - } - } - self - } -} diff --git a/crates/brk_computer/src/stateful_old/addresstype/vec.rs b/crates/brk_computer/src/stateful_old/addresstype/vec.rs deleted file mode 100644 index 8ba575292..000000000 --- a/crates/brk_computer/src/stateful_old/addresstype/vec.rs +++ /dev/null @@ -1,60 +0,0 @@ -use std::mem; - -use brk_grouper::ByAddressType; -use derive_deref::{Deref, DerefMut}; - -#[derive(Debug, Deref, DerefMut)] -pub struct AddressTypeToVec(ByAddressType>); - -impl AddressTypeToVec { - pub fn merge(mut self, mut other: Self) -> Self { - Self::merge_(&mut self.p2pk65, &mut other.p2pk65); - Self::merge_(&mut self.p2pk33, &mut other.p2pk33); - Self::merge_(&mut self.p2pkh, &mut other.p2pkh); - Self::merge_(&mut self.p2sh, &mut other.p2sh); - Self::merge_(&mut self.p2wpkh, &mut other.p2wpkh); - Self::merge_(&mut self.p2wsh, &mut other.p2wsh); - Self::merge_(&mut self.p2tr, &mut other.p2tr); - Self::merge_(&mut self.p2a, &mut other.p2a); - self - } - - pub fn merge_mut(&mut self, mut other: Self) { - Self::merge_(&mut self.p2pk65, &mut other.p2pk65); - Self::merge_(&mut self.p2pk33, &mut other.p2pk33); - Self::merge_(&mut self.p2pkh, &mut other.p2pkh); - Self::merge_(&mut self.p2sh, &mut other.p2sh); - Self::merge_(&mut self.p2wpkh, &mut other.p2wpkh); - Self::merge_(&mut self.p2wsh, &mut other.p2wsh); - Self::merge_(&mut self.p2tr, &mut other.p2tr); - Self::merge_(&mut self.p2a, &mut other.p2a); - } - - fn merge_(own: &mut Vec, other: &mut Vec) { - if own.len() >= other.len() { - own.append(other); - } else { - other.append(own); - mem::swap(own, other); - } - } - - pub fn unwrap(self) -> ByAddressType> { - self.0 - } -} - -impl Default for AddressTypeToVec { - fn default() -> Self { - Self(ByAddressType { - p2pk65: vec![], - p2pk33: vec![], - p2pkh: vec![], - p2sh: vec![], - p2wpkh: vec![], - p2wsh: vec![], - p2tr: vec![], - p2a: vec![], - }) - } -} diff --git a/crates/brk_computer/src/stateful_old/common/compute.rs b/crates/brk_computer/src/stateful_old/common/compute.rs deleted file mode 100644 index 6df334f19..000000000 --- a/crates/brk_computer/src/stateful_old/common/compute.rs +++ /dev/null @@ -1,1241 +0,0 @@ -//! Compute methods for Vecs. -//! -//! This module contains methods for post-processing computations: -//! - `compute_from_stateful`: Compute aggregate cohort values from separate cohorts -//! - `compute_rest_part1`: First phase of computed metrics -//! - `compute_rest_part2`: Second phase of computed metrics - -use brk_error::Result; -use brk_types::{Bitcoin, DateIndex, Dollars, Height, StoredF64, StoredU64}; -use vecdb::{Exit, IterableVec, TypedVecIterator}; - -use crate::{Indexes, indexes, price, utils::OptionExt}; - -use super::Vecs; - -impl Vecs { - pub fn compute_from_stateful( - &mut self, - starting_indexes: &Indexes, - others: &[&Self], - exit: &Exit, - ) -> Result<()> { - self.height_to_supply.compute_sum_of_others( - starting_indexes.height, - others - .iter() - .map(|v| &v.height_to_supply) - .collect::>() - .as_slice(), - exit, - )?; - self.height_to_utxo_count.compute_sum_of_others( - starting_indexes.height, - others - .iter() - .map(|v| &v.height_to_utxo_count) - .collect::>() - .as_slice(), - exit, - )?; - self.height_to_sent.compute_sum_of_others( - starting_indexes.height, - others - .iter() - .map(|v| &v.height_to_sent) - .collect::>() - .as_slice(), - exit, - )?; - self.height_to_satblocks_destroyed.compute_sum_of_others( - starting_indexes.height, - others - .iter() - .map(|v| &v.height_to_satblocks_destroyed) - .collect::>() - .as_slice(), - exit, - )?; - self.height_to_satdays_destroyed.compute_sum_of_others( - starting_indexes.height, - others - .iter() - .map(|v| &v.height_to_satdays_destroyed) - .collect::>() - .as_slice(), - exit, - )?; - - if let Some(height_to_realized_cap) = &mut self.height_to_realized_cap { - height_to_realized_cap.compute_sum_of_others( - starting_indexes.height, - others - .iter() - .map(|v| v.height_to_realized_cap.u()) - .collect::>() - .as_slice(), - exit, - )?; - - self.height_to_min_price_paid.um().compute_min_of_others( - starting_indexes.height, - others - .iter() - .map(|v| v.height_to_min_price_paid.u()) - .collect::>() - .as_slice(), - exit, - )?; - self.height_to_max_price_paid.um().compute_max_of_others( - starting_indexes.height, - others - .iter() - .map(|v| v.height_to_max_price_paid.u()) - .collect::>() - .as_slice(), - exit, - )?; - self.height_to_realized_profit.um().compute_sum_of_others( - starting_indexes.height, - others - .iter() - .map(|v| v.height_to_realized_profit.u()) - .collect::>() - .as_slice(), - exit, - )?; - self.height_to_realized_loss.um().compute_sum_of_others( - starting_indexes.height, - others - .iter() - .map(|v| v.height_to_realized_loss.u()) - .collect::>() - .as_slice(), - exit, - )?; - self.height_to_value_created.um().compute_sum_of_others( - starting_indexes.height, - others - .iter() - .map(|v| v.height_to_value_created.u()) - .collect::>() - .as_slice(), - exit, - )?; - self.height_to_value_destroyed.um().compute_sum_of_others( - starting_indexes.height, - others - .iter() - .map(|v| v.height_to_value_destroyed.u()) - .collect::>() - .as_slice(), - exit, - )?; - self.height_to_supply_in_profit.um().compute_sum_of_others( - starting_indexes.height, - others - .iter() - .map(|v| v.height_to_supply_in_profit.u()) - .collect::>() - .as_slice(), - exit, - )?; - self.height_to_supply_in_loss.um().compute_sum_of_others( - starting_indexes.height, - others - .iter() - .map(|v| v.height_to_supply_in_loss.u()) - .collect::>() - .as_slice(), - exit, - )?; - self.height_to_unrealized_profit - .um() - .compute_sum_of_others( - starting_indexes.height, - others - .iter() - .map(|v| v.height_to_unrealized_profit.u()) - .collect::>() - .as_slice(), - exit, - )?; - self.height_to_unrealized_loss.um().compute_sum_of_others( - starting_indexes.height, - others - .iter() - .map(|v| v.height_to_unrealized_loss.u()) - .collect::>() - .as_slice(), - exit, - )?; - self.dateindex_to_supply_in_profit - .um() - .compute_sum_of_others( - starting_indexes.dateindex, - others - .iter() - .map(|v| v.dateindex_to_supply_in_profit.u()) - .collect::>() - .as_slice(), - exit, - )?; - self.dateindex_to_supply_in_loss - .um() - .compute_sum_of_others( - starting_indexes.dateindex, - others - .iter() - .map(|v| v.dateindex_to_supply_in_loss.u()) - .collect::>() - .as_slice(), - exit, - )?; - self.dateindex_to_unrealized_profit - .um() - .compute_sum_of_others( - starting_indexes.dateindex, - others - .iter() - .map(|v| v.dateindex_to_unrealized_profit.u()) - .collect::>() - .as_slice(), - exit, - )?; - self.dateindex_to_unrealized_loss - .um() - .compute_sum_of_others( - starting_indexes.dateindex, - others - .iter() - .map(|v| v.dateindex_to_unrealized_loss.u()) - .collect::>() - .as_slice(), - exit, - )?; - self.height_to_min_price_paid.um().compute_min_of_others( - starting_indexes.height, - others - .iter() - .map(|v| v.height_to_min_price_paid.u()) - .collect::>() - .as_slice(), - exit, - )?; - self.height_to_max_price_paid.um().compute_max_of_others( - starting_indexes.height, - others - .iter() - .map(|v| v.height_to_max_price_paid.u()) - .collect::>() - .as_slice(), - exit, - )?; - - if self.height_to_adjusted_value_created.is_some() { - self.height_to_adjusted_value_created - .um() - .compute_sum_of_others( - starting_indexes.height, - others - .iter() - .map(|v| { - v.height_to_adjusted_value_created - .as_ref() - .unwrap_or(v.height_to_value_created.u()) - }) - .collect::>() - .as_slice(), - exit, - )?; - self.height_to_adjusted_value_destroyed - .um() - .compute_sum_of_others( - starting_indexes.height, - others - .iter() - .map(|v| { - v.height_to_adjusted_value_destroyed - .as_ref() - .unwrap_or(v.height_to_value_destroyed.u()) - }) - .collect::>() - .as_slice(), - exit, - )?; - } - } - - Ok(()) - } - - #[allow(clippy::too_many_arguments)] - pub fn compute_rest_part1( - &mut self, - indexes: &indexes::Vecs, - price: Option<&price::Vecs>, - starting_indexes: &Indexes, - exit: &Exit, - ) -> Result<()> { - self.height_to_supply_value.compute_rest( - price, - starting_indexes, - exit, - Some(&self.height_to_supply), - )?; - - self.indexes_to_supply - .compute_all(price, starting_indexes, exit, |v| { - let mut dateindex_to_height_count_iter = - indexes.dateindex_to_height_count.into_iter(); - let mut height_to_supply_iter = self.height_to_supply.into_iter(); - v.compute_transform( - starting_indexes.dateindex, - &indexes.dateindex_to_first_height, - |(i, height, ..)| { - let count = dateindex_to_height_count_iter.get_unwrap(i); - if count == StoredU64::default() { - unreachable!() - } - let supply = height_to_supply_iter.get_unwrap(height + (*count - 1)); - (i, supply) - }, - exit, - )?; - Ok(()) - })?; - - self.indexes_to_utxo_count.compute_rest( - indexes, - starting_indexes, - exit, - Some(&self.height_to_utxo_count), - )?; - - self.height_to_supply_half_value - .compute_all(price, starting_indexes, exit, |v| { - v.compute_transform( - starting_indexes.height, - &self.height_to_supply, - |(h, v, ..)| (h, v / 2), - exit, - )?; - Ok(()) - })?; - - self.indexes_to_supply_half - .compute_all(price, starting_indexes, exit, |v| { - v.compute_transform( - starting_indexes.dateindex, - self.indexes_to_supply.sats.dateindex.u(), - |(i, sats, ..)| (i, sats / 2), - exit, - )?; - Ok(()) - })?; - - self.indexes_to_sent.compute_rest( - indexes, - price, - starting_indexes, - exit, - Some(&self.height_to_sent), - )?; - - self.indexes_to_coinblocks_destroyed - .compute_all(indexes, starting_indexes, exit, |v| { - v.compute_transform( - starting_indexes.height, - &self.height_to_satblocks_destroyed, - |(i, v, ..)| (i, StoredF64::from(Bitcoin::from(v))), - exit, - )?; - Ok(()) - })?; - - self.indexes_to_coindays_destroyed - .compute_all(indexes, starting_indexes, exit, |v| { - v.compute_transform( - starting_indexes.height, - &self.height_to_satdays_destroyed, - |(i, v, ..)| (i, StoredF64::from(Bitcoin::from(v))), - exit, - )?; - Ok(()) - })?; - - Ok(()) - } - - #[allow(clippy::too_many_arguments)] - pub fn compute_rest_part2( - &mut self, - indexes: &indexes::Vecs, - price: Option<&price::Vecs>, - starting_indexes: &Indexes, - height_to_supply: &impl IterableVec, - dateindex_to_supply: &impl IterableVec, - height_to_market_cap: Option<&impl IterableVec>, - dateindex_to_market_cap: Option<&impl IterableVec>, - height_to_realized_cap: Option<&impl IterableVec>, - dateindex_to_realized_cap: Option<&impl IterableVec>, - exit: &Exit, - ) -> Result<()> { - if let Some(v) = self.indexes_to_supply_rel_to_circulating_supply.as_mut() { - v.compute_all(indexes, starting_indexes, exit, |v| { - v.compute_percentage( - starting_indexes.height, - &self.height_to_supply_value.bitcoin, - height_to_supply, - exit, - )?; - Ok(()) - })?; - } - - if let Some(indexes_to_realized_cap) = self.indexes_to_realized_cap.as_mut() { - let height_to_market_cap = height_to_market_cap.unwrap(); - let dateindex_to_market_cap = dateindex_to_market_cap.unwrap(); - - indexes_to_realized_cap.compute_rest( - indexes, - starting_indexes, - exit, - Some(self.height_to_realized_cap.u()), - )?; - - self.indexes_to_realized_price.um().compute_all( - indexes, - starting_indexes, - exit, - |vec| { - vec.compute_divide( - starting_indexes.height, - self.height_to_realized_cap.u(), - &self.height_to_supply_value.bitcoin, - exit, - )?; - Ok(()) - }, - )?; - - self.indexes_to_realized_price_extra.um().compute_rest( - price.u(), - starting_indexes, - exit, - Some(self.indexes_to_realized_price.u().dateindex.unwrap_last()), - )?; - - self.indexes_to_realized_profit.um().compute_rest( - indexes, - starting_indexes, - exit, - Some(self.height_to_realized_profit.u()), - )?; - - self.indexes_to_realized_loss.um().compute_rest( - indexes, - starting_indexes, - exit, - Some(self.height_to_realized_loss.u()), - )?; - - self.indexes_to_neg_realized_loss.um().compute_all( - indexes, - starting_indexes, - exit, - |vec| { - vec.compute_transform( - starting_indexes.height, - self.height_to_realized_loss.u(), - |(i, v, ..)| (i, v * -1_i64), - exit, - )?; - Ok(()) - }, - )?; - - self.indexes_to_value_created.um().compute_rest( - indexes, - starting_indexes, - exit, - Some(self.height_to_value_created.u()), - )?; - - self.indexes_to_value_destroyed.um().compute_rest( - indexes, - starting_indexes, - exit, - Some(self.height_to_value_destroyed.u()), - )?; - - self.indexes_to_realized_cap_30d_delta.um().compute_all( - starting_indexes, - exit, - |vec| { - vec.compute_change( - starting_indexes.dateindex, - self.indexes_to_realized_cap.u().dateindex.unwrap_last(), - 30, - exit, - )?; - Ok(()) - }, - )?; - - self.indexes_to_net_realized_pnl.um().compute_all( - indexes, - starting_indexes, - exit, - |vec| { - vec.compute_subtract( - starting_indexes.height, - self.height_to_realized_profit.u(), - self.height_to_realized_loss.u(), - exit, - )?; - Ok(()) - }, - )?; - - self.indexes_to_realized_value.um().compute_all( - indexes, - starting_indexes, - exit, - |vec| { - vec.compute_add( - starting_indexes.height, - self.height_to_realized_profit.u(), - self.height_to_realized_loss.u(), - exit, - )?; - Ok(()) - }, - )?; - - self.dateindex_to_sopr.um().compute_divide( - starting_indexes.dateindex, - self.indexes_to_value_created.u().dateindex.unwrap_sum(), - self.indexes_to_value_destroyed.u().dateindex.unwrap_sum(), - exit, - )?; - - self.dateindex_to_sopr_7d_ema.um().compute_ema( - starting_indexes.dateindex, - self.dateindex_to_sopr.u(), - 7, - exit, - )?; - - self.dateindex_to_sopr_30d_ema.um().compute_ema( - starting_indexes.dateindex, - self.dateindex_to_sopr.u(), - 30, - exit, - )?; - - self.dateindex_to_sell_side_risk_ratio - .um() - .compute_percentage( - starting_indexes.dateindex, - self.indexes_to_realized_value.u().dateindex.unwrap_sum(), - self.indexes_to_realized_cap.u().dateindex.unwrap_last(), - exit, - )?; - - self.dateindex_to_sell_side_risk_ratio_7d_ema - .um() - .compute_ema( - starting_indexes.dateindex, - self.dateindex_to_sell_side_risk_ratio.u(), - 7, - exit, - )?; - - self.dateindex_to_sell_side_risk_ratio_30d_ema - .um() - .compute_ema( - starting_indexes.dateindex, - self.dateindex_to_sell_side_risk_ratio.u(), - 30, - exit, - )?; - - self.indexes_to_supply_in_profit.um().compute_rest( - price, - starting_indexes, - exit, - Some(self.dateindex_to_supply_in_profit.u()), - )?; - self.indexes_to_supply_in_loss.um().compute_rest( - price, - starting_indexes, - exit, - Some(self.dateindex_to_supply_in_loss.u()), - )?; - self.indexes_to_unrealized_profit.um().compute_rest( - starting_indexes, - exit, - Some(self.dateindex_to_unrealized_profit.u()), - )?; - self.indexes_to_unrealized_loss.um().compute_rest( - starting_indexes, - exit, - Some(self.dateindex_to_unrealized_loss.u()), - )?; - self.height_to_total_unrealized_pnl.um().compute_add( - starting_indexes.height, - self.height_to_unrealized_profit.u(), - self.height_to_unrealized_loss.u(), - exit, - )?; - self.indexes_to_total_unrealized_pnl.um().compute_all( - starting_indexes, - exit, - |vec| { - vec.compute_add( - starting_indexes.dateindex, - self.dateindex_to_unrealized_profit.u(), - self.dateindex_to_unrealized_loss.u(), - exit, - )?; - Ok(()) - }, - )?; - self.height_to_total_realized_pnl.um().compute_add( - starting_indexes.height, - self.height_to_realized_profit.u(), - self.height_to_realized_loss.u(), - exit, - )?; - self.indexes_to_total_realized_pnl - .um() - .compute_all(starting_indexes, exit, |vec| { - vec.compute_add( - starting_indexes.dateindex, - self.indexes_to_realized_profit.u().dateindex.unwrap_sum(), - self.indexes_to_realized_loss.u().dateindex.unwrap_sum(), - exit, - )?; - Ok(()) - })?; - - self.indexes_to_min_price_paid.um().compute_rest( - indexes, - starting_indexes, - exit, - Some(self.height_to_min_price_paid.u()), - )?; - self.indexes_to_max_price_paid.um().compute_rest( - indexes, - starting_indexes, - exit, - Some(self.height_to_max_price_paid.u()), - )?; - - self.height_to_neg_unrealized_loss.um().compute_transform( - starting_indexes.height, - self.height_to_unrealized_loss.u(), - |(h, v, ..)| (h, v * -1_i64), - exit, - )?; - self.indexes_to_neg_unrealized_loss - .um() - .compute_all(starting_indexes, exit, |v| { - v.compute_transform( - starting_indexes.dateindex, - self.dateindex_to_unrealized_loss.u(), - |(h, v, ..)| (h, v * -1_i64), - exit, - )?; - Ok(()) - })?; - self.height_to_net_unrealized_pnl.um().compute_subtract( - starting_indexes.height, - self.height_to_unrealized_profit.u(), - self.height_to_unrealized_loss.u(), - exit, - )?; - - self.indexes_to_net_unrealized_pnl - .um() - .compute_all(starting_indexes, exit, |vec| { - vec.compute_subtract( - starting_indexes.dateindex, - self.dateindex_to_unrealized_profit.u(), - self.dateindex_to_unrealized_loss.u(), - exit, - )?; - Ok(()) - })?; - self.height_to_unrealized_profit_rel_to_market_cap - .um() - .compute_percentage( - starting_indexes.height, - self.height_to_unrealized_profit.u(), - height_to_market_cap, - exit, - )?; - self.height_to_unrealized_loss_rel_to_market_cap - .um() - .compute_percentage( - starting_indexes.height, - self.height_to_unrealized_loss.u(), - height_to_market_cap, - exit, - )?; - self.height_to_neg_unrealized_loss_rel_to_market_cap - .um() - .compute_percentage( - starting_indexes.height, - self.height_to_neg_unrealized_loss.u(), - height_to_market_cap, - exit, - )?; - self.height_to_net_unrealized_pnl_rel_to_market_cap - .um() - .compute_percentage( - starting_indexes.height, - self.height_to_net_unrealized_pnl.u(), - height_to_market_cap, - exit, - )?; - self.indexes_to_unrealized_profit_rel_to_market_cap - .um() - .compute_all(starting_indexes, exit, |vec| { - vec.compute_percentage( - starting_indexes.dateindex, - self.dateindex_to_unrealized_profit.u(), - dateindex_to_market_cap, - exit, - )?; - Ok(()) - })?; - self.indexes_to_unrealized_loss_rel_to_market_cap - .um() - .compute_all(starting_indexes, exit, |vec| { - vec.compute_percentage( - starting_indexes.dateindex, - self.dateindex_to_unrealized_loss.u(), - dateindex_to_market_cap, - exit, - )?; - Ok(()) - })?; - self.indexes_to_neg_unrealized_loss_rel_to_market_cap - .um() - .compute_all(starting_indexes, exit, |vec| { - vec.compute_percentage( - starting_indexes.dateindex, - self.indexes_to_neg_unrealized_loss.u().dateindex.u(), - dateindex_to_market_cap, - exit, - )?; - Ok(()) - })?; - self.indexes_to_net_unrealized_pnl_rel_to_market_cap - .um() - .compute_all(starting_indexes, exit, |vec| { - vec.compute_percentage( - starting_indexes.dateindex, - self.indexes_to_net_unrealized_pnl.u().dateindex.u(), - dateindex_to_market_cap, - exit, - )?; - Ok(()) - })?; - - if self - .height_to_unrealized_profit_rel_to_own_market_cap - .is_some() - { - self.height_to_unrealized_profit_rel_to_own_market_cap - .um() - .compute_percentage( - starting_indexes.height, - self.height_to_unrealized_profit.u(), - self.height_to_supply_value.dollars.u(), - exit, - )?; - self.height_to_unrealized_loss_rel_to_own_market_cap - .um() - .compute_percentage( - starting_indexes.height, - self.height_to_unrealized_loss.u(), - self.height_to_supply_value.dollars.u(), - exit, - )?; - self.height_to_neg_unrealized_loss_rel_to_own_market_cap - .um() - .compute_percentage( - starting_indexes.height, - self.height_to_neg_unrealized_loss.u(), - self.height_to_supply_value.dollars.u(), - exit, - )?; - self.height_to_net_unrealized_pnl_rel_to_own_market_cap - .um() - .compute_percentage( - starting_indexes.height, - self.height_to_net_unrealized_pnl.u(), - self.height_to_supply_value.dollars.u(), - exit, - )?; - self.indexes_to_unrealized_profit_rel_to_own_market_cap - .um() - .compute_all(starting_indexes, exit, |vec| { - vec.compute_percentage( - starting_indexes.dateindex, - self.dateindex_to_unrealized_profit.u(), - self.indexes_to_supply - .dollars - .as_ref() - .unwrap() - .dateindex - .as_ref() - .unwrap(), - exit, - )?; - Ok(()) - })?; - self.indexes_to_unrealized_loss_rel_to_own_market_cap - .um() - .compute_all(starting_indexes, exit, |vec| { - vec.compute_percentage( - starting_indexes.dateindex, - self.dateindex_to_unrealized_loss.u(), - self.indexes_to_supply - .dollars - .as_ref() - .unwrap() - .dateindex - .as_ref() - .unwrap(), - exit, - )?; - Ok(()) - })?; - self.indexes_to_neg_unrealized_loss_rel_to_own_market_cap - .um() - .compute_all(starting_indexes, exit, |vec| { - vec.compute_percentage( - starting_indexes.dateindex, - self.indexes_to_neg_unrealized_loss - .as_ref() - .unwrap() - .dateindex - .as_ref() - .unwrap(), - self.indexes_to_supply - .dollars - .as_ref() - .unwrap() - .dateindex - .as_ref() - .unwrap(), - exit, - )?; - Ok(()) - })?; - self.indexes_to_net_unrealized_pnl_rel_to_own_market_cap - .um() - .compute_all(starting_indexes, exit, |vec| { - vec.compute_percentage( - starting_indexes.dateindex, - self.indexes_to_net_unrealized_pnl - .as_ref() - .unwrap() - .dateindex - .as_ref() - .unwrap(), - self.indexes_to_supply - .dollars - .as_ref() - .unwrap() - .dateindex - .as_ref() - .unwrap(), - exit, - )?; - Ok(()) - })?; - } - - if self - .height_to_unrealized_profit_rel_to_own_total_unrealized_pnl - .is_some() - { - self.height_to_unrealized_profit_rel_to_own_total_unrealized_pnl - .um() - .compute_percentage( - starting_indexes.height, - self.height_to_unrealized_profit.u(), - self.height_to_total_unrealized_pnl.u(), - exit, - )?; - self.height_to_unrealized_loss_rel_to_own_total_unrealized_pnl - .um() - .compute_percentage( - starting_indexes.height, - self.height_to_unrealized_loss.u(), - self.height_to_total_unrealized_pnl.u(), - exit, - )?; - self.height_to_neg_unrealized_loss_rel_to_own_total_unrealized_pnl - .um() - .compute_percentage( - starting_indexes.height, - self.height_to_neg_unrealized_loss.u(), - self.height_to_total_unrealized_pnl.u(), - exit, - )?; - self.height_to_net_unrealized_pnl_rel_to_own_total_unrealized_pnl - .um() - .compute_percentage( - starting_indexes.height, - self.height_to_net_unrealized_pnl.u(), - self.height_to_total_unrealized_pnl.u(), - exit, - )?; - self.indexes_to_unrealized_profit_rel_to_own_total_unrealized_pnl - .um() - .compute_all(starting_indexes, exit, |vec| { - vec.compute_percentage( - starting_indexes.dateindex, - self.dateindex_to_unrealized_profit.u(), - self.indexes_to_total_unrealized_pnl - .as_ref() - .unwrap() - .dateindex - .as_ref() - .unwrap(), - exit, - )?; - Ok(()) - })?; - self.indexes_to_unrealized_loss_rel_to_own_total_unrealized_pnl - .um() - .compute_all(starting_indexes, exit, |vec| { - vec.compute_percentage( - starting_indexes.dateindex, - self.dateindex_to_unrealized_loss.u(), - self.indexes_to_total_unrealized_pnl - .as_ref() - .unwrap() - .dateindex - .as_ref() - .unwrap(), - exit, - )?; - Ok(()) - })?; - self.indexes_to_neg_unrealized_loss_rel_to_own_total_unrealized_pnl - .um() - .compute_all(starting_indexes, exit, |vec| { - vec.compute_percentage( - starting_indexes.dateindex, - self.indexes_to_neg_unrealized_loss - .as_ref() - .unwrap() - .dateindex - .as_ref() - .unwrap(), - self.indexes_to_total_unrealized_pnl - .as_ref() - .unwrap() - .dateindex - .as_ref() - .unwrap(), - exit, - )?; - Ok(()) - })?; - self.indexes_to_net_unrealized_pnl_rel_to_own_total_unrealized_pnl - .um() - .compute_all(starting_indexes, exit, |vec| { - vec.compute_percentage( - starting_indexes.dateindex, - self.indexes_to_net_unrealized_pnl - .as_ref() - .unwrap() - .dateindex - .as_ref() - .unwrap(), - self.indexes_to_total_unrealized_pnl - .as_ref() - .unwrap() - .dateindex - .as_ref() - .unwrap(), - exit, - )?; - Ok(()) - })?; - } - - self.indexes_to_realized_profit_rel_to_realized_cap - .um() - .compute_all(indexes, starting_indexes, exit, |vec| { - vec.compute_percentage( - starting_indexes.height, - self.height_to_realized_profit.u(), - *height_to_realized_cap.u(), - exit, - )?; - Ok(()) - })?; - - self.indexes_to_realized_loss_rel_to_realized_cap - .um() - .compute_all(indexes, starting_indexes, exit, |vec| { - vec.compute_percentage( - starting_indexes.height, - self.height_to_realized_loss.u(), - *height_to_realized_cap.u(), - exit, - )?; - Ok(()) - })?; - - self.indexes_to_net_realized_pnl_rel_to_realized_cap - .um() - .compute_all(indexes, starting_indexes, exit, |vec| { - vec.compute_percentage( - starting_indexes.height, - self.indexes_to_net_realized_pnl.u().height.u(), - *height_to_realized_cap.u(), - exit, - )?; - Ok(()) - })?; - - self.height_to_supply_in_loss_value.um().compute_rest( - price, - starting_indexes, - exit, - Some(self.height_to_supply_in_loss.u()), - )?; - self.height_to_supply_in_profit_value.um().compute_rest( - price, - starting_indexes, - exit, - Some(self.height_to_supply_in_profit.u()), - )?; - self.height_to_supply_in_loss_rel_to_own_supply - .um() - .compute_percentage( - starting_indexes.height, - &self.height_to_supply_in_loss_value.u().bitcoin, - &self.height_to_supply_value.bitcoin, - exit, - )?; - self.height_to_supply_in_profit_rel_to_own_supply - .um() - .compute_percentage( - starting_indexes.height, - &self.height_to_supply_in_profit_value.u().bitcoin, - &self.height_to_supply_value.bitcoin, - exit, - )?; - self.indexes_to_supply_in_loss_rel_to_own_supply - .um() - .compute_all(starting_indexes, exit, |v| { - v.compute_percentage( - starting_indexes.dateindex, - self.indexes_to_supply_in_loss.u().bitcoin.dateindex.u(), - self.indexes_to_supply.bitcoin.dateindex.u(), - exit, - )?; - Ok(()) - })?; - self.indexes_to_supply_in_profit_rel_to_own_supply - .um() - .compute_all(starting_indexes, exit, |v| { - v.compute_percentage( - starting_indexes.dateindex, - self.indexes_to_supply_in_profit.u().bitcoin.dateindex.u(), - self.indexes_to_supply.bitcoin.dateindex.u(), - exit, - )?; - Ok(()) - })?; - - self.indexes_to_net_realized_pnl_cumulative_30d_delta - .um() - .compute_all(starting_indexes, exit, |v| { - v.compute_change( - starting_indexes.dateindex, - self.indexes_to_net_realized_pnl - .u() - .dateindex - .unwrap_cumulative(), - 30, - exit, - )?; - Ok(()) - })?; - - self.indexes_to_net_realized_pnl_cumulative_30d_delta_rel_to_realized_cap - .um() - .compute_all(starting_indexes, exit, |v| { - v.compute_percentage( - starting_indexes.dateindex, - self.indexes_to_net_realized_pnl_cumulative_30d_delta - .u() - .dateindex - .u(), - *dateindex_to_realized_cap.u(), - exit, - )?; - Ok(()) - })?; - - self.indexes_to_net_realized_pnl_cumulative_30d_delta_rel_to_market_cap - .um() - .compute_all(starting_indexes, exit, |v| { - v.compute_percentage( - starting_indexes.dateindex, - self.indexes_to_net_realized_pnl_cumulative_30d_delta - .u() - .dateindex - .u(), - dateindex_to_market_cap, - exit, - )?; - Ok(()) - })?; - - if self - .height_to_supply_in_profit_rel_to_circulating_supply - .as_mut() - .is_some() - { - self.height_to_supply_in_loss_rel_to_circulating_supply - .um() - .compute_percentage( - starting_indexes.height, - &self.height_to_supply_in_loss_value.u().bitcoin, - height_to_supply, - exit, - )?; - self.height_to_supply_in_profit_rel_to_circulating_supply - .um() - .compute_percentage( - starting_indexes.height, - &self.height_to_supply_in_profit_value.u().bitcoin, - height_to_supply, - exit, - )?; - self.indexes_to_supply_in_loss_rel_to_circulating_supply - .um() - .compute_all(starting_indexes, exit, |v| { - v.compute_percentage( - starting_indexes.dateindex, - self.indexes_to_supply_in_loss - .as_ref() - .unwrap() - .bitcoin - .dateindex - .as_ref() - .unwrap(), - dateindex_to_supply, - exit, - )?; - Ok(()) - })?; - self.indexes_to_supply_in_profit_rel_to_circulating_supply - .um() - .compute_all(starting_indexes, exit, |v| { - v.compute_percentage( - starting_indexes.dateindex, - self.indexes_to_supply_in_profit - .as_ref() - .unwrap() - .bitcoin - .dateindex - .as_ref() - .unwrap(), - dateindex_to_supply, - exit, - )?; - Ok(()) - })?; - } - - if self.indexes_to_adjusted_value_created.is_some() { - self.indexes_to_adjusted_value_created.um().compute_rest( - indexes, - starting_indexes, - exit, - Some(self.height_to_adjusted_value_created.u()), - )?; - - self.indexes_to_adjusted_value_destroyed.um().compute_rest( - indexes, - starting_indexes, - exit, - Some(self.height_to_adjusted_value_destroyed.u()), - )?; - - self.dateindex_to_adjusted_sopr.um().compute_divide( - starting_indexes.dateindex, - self.indexes_to_adjusted_value_created - .u() - .dateindex - .unwrap_sum(), - self.indexes_to_adjusted_value_destroyed - .u() - .dateindex - .unwrap_sum(), - exit, - )?; - - self.dateindex_to_adjusted_sopr_7d_ema.um().compute_ema( - starting_indexes.dateindex, - self.dateindex_to_adjusted_sopr.u(), - 7, - exit, - )?; - - self.dateindex_to_adjusted_sopr_30d_ema.um().compute_ema( - starting_indexes.dateindex, - self.dateindex_to_adjusted_sopr.u(), - 30, - exit, - )?; - } - - if let Some(indexes_to_realized_cap_rel_to_own_market_cap) = - self.indexes_to_realized_cap_rel_to_own_market_cap.as_mut() - { - indexes_to_realized_cap_rel_to_own_market_cap.compute_all( - indexes, - starting_indexes, - exit, - |v| { - v.compute_percentage( - starting_indexes.height, - self.height_to_realized_cap.u(), - self.height_to_supply_value.dollars.u(), - exit, - )?; - Ok(()) - }, - )?; - } - } - - if let Some(dateindex_to_realized_profit_to_loss_ratio) = - self.dateindex_to_realized_profit_to_loss_ratio.as_mut() - { - dateindex_to_realized_profit_to_loss_ratio.compute_divide( - starting_indexes.dateindex, - self.indexes_to_realized_profit.u().dateindex.unwrap_sum(), - self.indexes_to_realized_loss.u().dateindex.unwrap_sum(), - exit, - )?; - } - - Ok(()) - } -} diff --git a/crates/brk_computer/src/stateful_old/common/import.rs b/crates/brk_computer/src/stateful_old/common/import.rs deleted file mode 100644 index 51973be6b..000000000 --- a/crates/brk_computer/src/stateful_old/common/import.rs +++ /dev/null @@ -1,914 +0,0 @@ -//! Import and validation methods for Vecs. -//! -//! This module contains methods for: -//! - `forced_import`: Creating a new Vecs instance from database -//! - `import_state`: Importing state when resuming from checkpoint -//! - `validate_computed_versions`: Version validation -//! - `min_height_vecs_len`: Finding minimum vector length - -use brk_error::{Error, Result}; -use brk_grouper::{CohortContext, Filter}; -use brk_types::{DateIndex, Dollars, Height, Sats, StoredF32, StoredF64, Version}; -use vecdb::{ - AnyVec, Database, EagerVec, GenericStoredVec, ImportableVec, IterableCloneableVec, PcoVec, - StoredVec, TypedVecIterator, -}; - -use crate::{ - grouped::{ - ComputedHeightValueVecs, ComputedRatioVecsFromDateIndex, ComputedValueVecsFromDateIndex, - ComputedValueVecsFromHeight, ComputedVecsFromDateIndex, ComputedVecsFromHeight, - PricePercentiles, Source, VecBuilderOptions, - }, - indexes, price, - states::CohortState, - utils::OptionExt, -}; - -use super::Vecs; - -impl Vecs { - #[allow(clippy::too_many_arguments)] - pub fn forced_import( - db: &Database, - filter: Filter, - context: CohortContext, - parent_version: Version, - indexes: &indexes::Vecs, - price: Option<&price::Vecs>, - ) -> Result { - let compute_dollars = price.is_some(); - let extended = filter.is_extended(context); - let compute_rel_to_all = filter.compute_rel_to_all(); - let compute_adjusted = filter.compute_adjusted(context); - - let version = parent_version + Version::ZERO; - - let name_prefix = filter.to_full_name(context); - let suffix = |s: &str| { - if name_prefix.is_empty() { - s.to_string() - } else { - format!("{name_prefix}_{s}") - } - }; - - // Helper macros for imports - macro_rules! eager { - ($idx:ty, $val:ty, $name:expr, $v:expr) => { - EagerVec::>::forced_import(db, &suffix($name), version + $v) - .unwrap() - }; - } - macro_rules! computed_h { - ($name:expr, $source:expr, $v:expr, $opts:expr $(,)?) => { - ComputedVecsFromHeight::forced_import( - db, - &suffix($name), - $source, - version + $v, - indexes, - $opts, - ) - .unwrap() - }; - } - macro_rules! computed_di { - ($name:expr, $source:expr, $v:expr, $opts:expr $(,)?) => { - ComputedVecsFromDateIndex::forced_import( - db, - &suffix($name), - $source, - version + $v, - indexes, - $opts, - ) - .unwrap() - }; - } - - // Common version patterns - let v0 = Version::ZERO; - let v1 = Version::ONE; - let v2 = Version::TWO; - let v3 = Version::new(3); - let last = || VecBuilderOptions::default().add_last(); - let sum = || VecBuilderOptions::default().add_sum(); - let sum_cum = || VecBuilderOptions::default().add_sum().add_cumulative(); - - // Pre-create dateindex vecs that are used in computed vecs - let dateindex_to_supply_in_profit = - compute_dollars.then(|| eager!(DateIndex, Sats, "supply_in_profit", v0)); - let dateindex_to_supply_in_loss = - compute_dollars.then(|| eager!(DateIndex, Sats, "supply_in_loss", v0)); - let dateindex_to_unrealized_profit = - compute_dollars.then(|| eager!(DateIndex, Dollars, "unrealized_profit", v0)); - let dateindex_to_unrealized_loss = - compute_dollars.then(|| eager!(DateIndex, Dollars, "unrealized_loss", v0)); - - Ok(Self { - filter, - - // ==================== SUPPLY & UTXO COUNT ==================== - height_to_supply: EagerVec::forced_import(db, &suffix("supply"), version + v0)?, - height_to_supply_value: ComputedHeightValueVecs::forced_import( - db, - &suffix("supply"), - Source::None, - version + v0, - compute_dollars, - )?, - indexes_to_supply: ComputedValueVecsFromDateIndex::forced_import( - db, - &suffix("supply"), - Source::Compute, - version + v1, - last(), - compute_dollars, - indexes, - )?, - height_to_utxo_count: EagerVec::forced_import(db, &suffix("utxo_count"), version + v0)?, - indexes_to_utxo_count: computed_h!("utxo_count", Source::None, v0, last()), - height_to_supply_half_value: ComputedHeightValueVecs::forced_import( - db, - &suffix("supply_half"), - Source::Compute, - version + v0, - compute_dollars, - )?, - indexes_to_supply_half: ComputedValueVecsFromDateIndex::forced_import( - db, - &suffix("supply_half"), - Source::Compute, - version + v0, - last(), - compute_dollars, - indexes, - )?, - - // ==================== ACTIVITY ==================== - height_to_sent: EagerVec::forced_import(db, &suffix("sent"), version + v0)?, - indexes_to_sent: ComputedValueVecsFromHeight::forced_import( - db, - &suffix("sent"), - Source::None, - version + v0, - sum(), - compute_dollars, - indexes, - )?, - height_to_satblocks_destroyed: EagerVec::forced_import( - db, - &suffix("satblocks_destroyed"), - version + v0, - )?, - height_to_satdays_destroyed: EagerVec::forced_import( - db, - &suffix("satdays_destroyed"), - version + v0, - )?, - indexes_to_coinblocks_destroyed: computed_h!( - "coinblocks_destroyed", - Source::Compute, - v2, - sum_cum(), - ), - indexes_to_coindays_destroyed: computed_h!( - "coindays_destroyed", - Source::Compute, - v2, - sum_cum(), - ), - - // ==================== REALIZED CAP & PRICE ==================== - height_to_realized_cap: compute_dollars - .then(|| eager!(Height, Dollars, "realized_cap", v0)), - indexes_to_realized_cap: compute_dollars - .then(|| computed_h!("realized_cap", Source::None, v0, last())), - indexes_to_realized_price: compute_dollars - .then(|| computed_h!("realized_price", Source::Compute, v0, last())), - indexes_to_realized_price_extra: compute_dollars.then(|| { - ComputedRatioVecsFromDateIndex::forced_import( - db, - &suffix("realized_price"), - Source::None, - version + v0, - indexes, - extended, - ) - .unwrap() - }), - indexes_to_realized_cap_rel_to_own_market_cap: (compute_dollars && extended).then( - || { - computed_h!( - "realized_cap_rel_to_own_market_cap", - Source::Compute, - v0, - last() - ) - }, - ), - indexes_to_realized_cap_30d_delta: compute_dollars - .then(|| computed_di!("realized_cap_30d_delta", Source::Compute, v0, last())), - - // ==================== REALIZED PROFIT & LOSS ==================== - height_to_realized_profit: compute_dollars - .then(|| eager!(Height, Dollars, "realized_profit", v0)), - indexes_to_realized_profit: compute_dollars - .then(|| computed_h!("realized_profit", Source::None, v0, sum_cum())), - height_to_realized_loss: compute_dollars - .then(|| eager!(Height, Dollars, "realized_loss", v0)), - indexes_to_realized_loss: compute_dollars - .then(|| computed_h!("realized_loss", Source::None, v0, sum_cum())), - indexes_to_neg_realized_loss: compute_dollars - .then(|| computed_h!("neg_realized_loss", Source::Compute, v1, sum_cum())), - indexes_to_net_realized_pnl: compute_dollars - .then(|| computed_h!("net_realized_pnl", Source::Compute, v0, sum_cum())), - indexes_to_realized_value: compute_dollars - .then(|| computed_h!("realized_value", Source::Compute, v0, sum())), - indexes_to_realized_profit_rel_to_realized_cap: compute_dollars.then(|| { - computed_h!( - "realized_profit_rel_to_realized_cap", - Source::Compute, - v0, - sum() - ) - }), - indexes_to_realized_loss_rel_to_realized_cap: compute_dollars.then(|| { - computed_h!( - "realized_loss_rel_to_realized_cap", - Source::Compute, - v0, - sum() - ) - }), - indexes_to_net_realized_pnl_rel_to_realized_cap: compute_dollars.then(|| { - computed_h!( - "net_realized_pnl_rel_to_realized_cap", - Source::Compute, - v1, - sum() - ) - }), - height_to_total_realized_pnl: compute_dollars - .then(|| eager!(Height, Dollars, "total_realized_pnl", v0)), - indexes_to_total_realized_pnl: compute_dollars - .then(|| computed_di!("total_realized_pnl", Source::Compute, v1, sum())), - dateindex_to_realized_profit_to_loss_ratio: (compute_dollars && extended) - .then(|| eager!(DateIndex, StoredF64, "realized_profit_to_loss_ratio", v1)), - - // ==================== VALUE CREATED & DESTROYED ==================== - height_to_value_created: compute_dollars - .then(|| eager!(Height, Dollars, "value_created", v0)), - indexes_to_value_created: compute_dollars - .then(|| computed_h!("value_created", Source::None, v0, sum())), - height_to_value_destroyed: compute_dollars - .then(|| eager!(Height, Dollars, "value_destroyed", v0)), - indexes_to_value_destroyed: compute_dollars - .then(|| computed_h!("value_destroyed", Source::None, v0, sum())), - height_to_adjusted_value_created: (compute_dollars && compute_adjusted) - .then(|| eager!(Height, Dollars, "adjusted_value_created", v0)), - indexes_to_adjusted_value_created: (compute_dollars && compute_adjusted) - .then(|| computed_h!("adjusted_value_created", Source::None, v0, sum())), - height_to_adjusted_value_destroyed: (compute_dollars && compute_adjusted) - .then(|| eager!(Height, Dollars, "adjusted_value_destroyed", v0)), - indexes_to_adjusted_value_destroyed: (compute_dollars && compute_adjusted) - .then(|| computed_h!("adjusted_value_destroyed", Source::None, v0, sum())), - - // ==================== SOPR ==================== - dateindex_to_sopr: compute_dollars.then(|| eager!(DateIndex, StoredF64, "sopr", v1)), - dateindex_to_sopr_7d_ema: compute_dollars - .then(|| eager!(DateIndex, StoredF64, "sopr_7d_ema", v1)), - dateindex_to_sopr_30d_ema: compute_dollars - .then(|| eager!(DateIndex, StoredF64, "sopr_30d_ema", v1)), - dateindex_to_adjusted_sopr: (compute_dollars && compute_adjusted) - .then(|| eager!(DateIndex, StoredF64, "adjusted_sopr", v1)), - dateindex_to_adjusted_sopr_7d_ema: (compute_dollars && compute_adjusted) - .then(|| eager!(DateIndex, StoredF64, "adjusted_sopr_7d_ema", v1)), - dateindex_to_adjusted_sopr_30d_ema: (compute_dollars && compute_adjusted) - .then(|| eager!(DateIndex, StoredF64, "adjusted_sopr_30d_ema", v1)), - - // ==================== SELL SIDE RISK ==================== - dateindex_to_sell_side_risk_ratio: compute_dollars - .then(|| eager!(DateIndex, StoredF32, "sell_side_risk_ratio", v1)), - dateindex_to_sell_side_risk_ratio_7d_ema: compute_dollars - .then(|| eager!(DateIndex, StoredF32, "sell_side_risk_ratio_7d_ema", v1)), - dateindex_to_sell_side_risk_ratio_30d_ema: compute_dollars - .then(|| eager!(DateIndex, StoredF32, "sell_side_risk_ratio_30d_ema", v1)), - - // ==================== SUPPLY IN PROFIT/LOSS ==================== - height_to_supply_in_profit: compute_dollars - .then(|| eager!(Height, Sats, "supply_in_profit", v0)), - indexes_to_supply_in_profit: compute_dollars.then(|| { - ComputedValueVecsFromDateIndex::forced_import( - db, - &suffix("supply_in_profit"), - dateindex_to_supply_in_profit - .as_ref() - .map(|v| v.boxed_clone()) - .into(), - version + v0, - last(), - compute_dollars, - indexes, - ) - .unwrap() - }), - height_to_supply_in_loss: compute_dollars - .then(|| eager!(Height, Sats, "supply_in_loss", v0)), - indexes_to_supply_in_loss: compute_dollars.then(|| { - ComputedValueVecsFromDateIndex::forced_import( - db, - &suffix("supply_in_loss"), - dateindex_to_supply_in_loss - .as_ref() - .map(|v| v.boxed_clone()) - .into(), - version + v0, - last(), - compute_dollars, - indexes, - ) - .unwrap() - }), - dateindex_to_supply_in_profit, - dateindex_to_supply_in_loss, - height_to_supply_in_profit_value: compute_dollars.then(|| { - ComputedHeightValueVecs::forced_import( - db, - &suffix("supply_in_profit"), - Source::None, - version + v0, - compute_dollars, - ) - .unwrap() - }), - height_to_supply_in_loss_value: compute_dollars.then(|| { - ComputedHeightValueVecs::forced_import( - db, - &suffix("supply_in_loss"), - Source::None, - version + v0, - compute_dollars, - ) - .unwrap() - }), - - // ==================== UNREALIZED PROFIT & LOSS ==================== - height_to_unrealized_profit: compute_dollars - .then(|| eager!(Height, Dollars, "unrealized_profit", v0)), - indexes_to_unrealized_profit: compute_dollars.then(|| { - ComputedVecsFromDateIndex::forced_import( - db, - &suffix("unrealized_profit"), - dateindex_to_unrealized_profit - .as_ref() - .map(|v| v.boxed_clone()) - .into(), - version + v0, - indexes, - last(), - ) - .unwrap() - }), - height_to_unrealized_loss: compute_dollars - .then(|| eager!(Height, Dollars, "unrealized_loss", v0)), - indexes_to_unrealized_loss: compute_dollars.then(|| { - ComputedVecsFromDateIndex::forced_import( - db, - &suffix("unrealized_loss"), - dateindex_to_unrealized_loss - .as_ref() - .map(|v| v.boxed_clone()) - .into(), - version + v0, - indexes, - last(), - ) - .unwrap() - }), - dateindex_to_unrealized_profit, - dateindex_to_unrealized_loss, - height_to_neg_unrealized_loss: compute_dollars - .then(|| eager!(Height, Dollars, "neg_unrealized_loss", v0)), - indexes_to_neg_unrealized_loss: compute_dollars - .then(|| computed_di!("neg_unrealized_loss", Source::Compute, v0, last())), - height_to_net_unrealized_pnl: compute_dollars - .then(|| eager!(Height, Dollars, "net_unrealized_pnl", v0)), - indexes_to_net_unrealized_pnl: compute_dollars - .then(|| computed_di!("net_unrealized_pnl", Source::Compute, v0, last())), - height_to_total_unrealized_pnl: compute_dollars - .then(|| eager!(Height, Dollars, "total_unrealized_pnl", v0)), - indexes_to_total_unrealized_pnl: compute_dollars - .then(|| computed_di!("total_unrealized_pnl", Source::Compute, v0, last())), - - // ==================== PRICE PAID ==================== - height_to_min_price_paid: compute_dollars - .then(|| eager!(Height, Dollars, "min_price_paid", v0)), - indexes_to_min_price_paid: compute_dollars - .then(|| computed_h!("min_price_paid", Source::None, v0, last())), - height_to_max_price_paid: compute_dollars - .then(|| eager!(Height, Dollars, "max_price_paid", v0)), - indexes_to_max_price_paid: compute_dollars - .then(|| computed_h!("max_price_paid", Source::None, v0, last())), - price_percentiles: (compute_dollars && extended).then(|| { - PricePercentiles::forced_import(db, &suffix(""), version + v0, indexes, true) - .unwrap() - }), - - // ==================== RELATIVE METRICS: UNREALIZED vs MARKET CAP ==================== - height_to_unrealized_profit_rel_to_market_cap: compute_dollars - .then(|| eager!(Height, StoredF32, "unrealized_profit_rel_to_market_cap", v0)), - height_to_unrealized_loss_rel_to_market_cap: compute_dollars - .then(|| eager!(Height, StoredF32, "unrealized_loss_rel_to_market_cap", v0)), - height_to_neg_unrealized_loss_rel_to_market_cap: compute_dollars.then(|| { - eager!( - Height, - StoredF32, - "neg_unrealized_loss_rel_to_market_cap", - v0 - ) - }), - height_to_net_unrealized_pnl_rel_to_market_cap: compute_dollars.then(|| { - eager!( - Height, - StoredF32, - "net_unrealized_pnl_rel_to_market_cap", - v1 - ) - }), - indexes_to_unrealized_profit_rel_to_market_cap: compute_dollars.then(|| { - computed_di!( - "unrealized_profit_rel_to_market_cap", - Source::Compute, - v1, - last() - ) - }), - indexes_to_unrealized_loss_rel_to_market_cap: compute_dollars.then(|| { - computed_di!( - "unrealized_loss_rel_to_market_cap", - Source::Compute, - v1, - last() - ) - }), - indexes_to_neg_unrealized_loss_rel_to_market_cap: compute_dollars.then(|| { - computed_di!( - "neg_unrealized_loss_rel_to_market_cap", - Source::Compute, - v1, - last() - ) - }), - indexes_to_net_unrealized_pnl_rel_to_market_cap: compute_dollars.then(|| { - computed_di!( - "net_unrealized_pnl_rel_to_market_cap", - Source::Compute, - v1, - last() - ) - }), - - // ==================== RELATIVE METRICS: UNREALIZED vs OWN MARKET CAP ==================== - height_to_unrealized_profit_rel_to_own_market_cap: (compute_dollars - && extended - && compute_rel_to_all) - .then(|| { - eager!( - Height, - StoredF32, - "unrealized_profit_rel_to_own_market_cap", - v1 - ) - }), - height_to_unrealized_loss_rel_to_own_market_cap: (compute_dollars - && extended - && compute_rel_to_all) - .then(|| { - eager!( - Height, - StoredF32, - "unrealized_loss_rel_to_own_market_cap", - v1 - ) - }), - height_to_neg_unrealized_loss_rel_to_own_market_cap: (compute_dollars - && extended - && compute_rel_to_all) - .then(|| { - eager!( - Height, - StoredF32, - "neg_unrealized_loss_rel_to_own_market_cap", - v1 - ) - }), - height_to_net_unrealized_pnl_rel_to_own_market_cap: (compute_dollars - && extended - && compute_rel_to_all) - .then(|| { - eager!( - Height, - StoredF32, - "net_unrealized_pnl_rel_to_own_market_cap", - v2 - ) - }), - indexes_to_unrealized_profit_rel_to_own_market_cap: (compute_dollars - && extended - && compute_rel_to_all) - .then(|| { - computed_di!( - "unrealized_profit_rel_to_own_market_cap", - Source::Compute, - v2, - last() - ) - }), - indexes_to_unrealized_loss_rel_to_own_market_cap: (compute_dollars - && extended - && compute_rel_to_all) - .then(|| { - computed_di!( - "unrealized_loss_rel_to_own_market_cap", - Source::Compute, - v2, - last() - ) - }), - indexes_to_neg_unrealized_loss_rel_to_own_market_cap: (compute_dollars - && extended - && compute_rel_to_all) - .then(|| { - computed_di!( - "neg_unrealized_loss_rel_to_own_market_cap", - Source::Compute, - v2, - last() - ) - }), - indexes_to_net_unrealized_pnl_rel_to_own_market_cap: (compute_dollars - && extended - && compute_rel_to_all) - .then(|| { - computed_di!( - "net_unrealized_pnl_rel_to_own_market_cap", - Source::Compute, - v2, - last() - ) - }), - - // ==================== RELATIVE METRICS: UNREALIZED vs OWN TOTAL UNREALIZED ==================== - height_to_unrealized_profit_rel_to_own_total_unrealized_pnl: (compute_dollars - && extended) - .then(|| { - eager!( - Height, - StoredF32, - "unrealized_profit_rel_to_own_total_unrealized_pnl", - v0 - ) - }), - height_to_unrealized_loss_rel_to_own_total_unrealized_pnl: (compute_dollars - && extended) - .then(|| { - eager!( - Height, - StoredF32, - "unrealized_loss_rel_to_own_total_unrealized_pnl", - v0 - ) - }), - height_to_neg_unrealized_loss_rel_to_own_total_unrealized_pnl: (compute_dollars - && extended) - .then(|| { - eager!( - Height, - StoredF32, - "neg_unrealized_loss_rel_to_own_total_unrealized_pnl", - v0 - ) - }), - height_to_net_unrealized_pnl_rel_to_own_total_unrealized_pnl: (compute_dollars - && extended) - .then(|| { - eager!( - Height, - StoredF32, - "net_unrealized_pnl_rel_to_own_total_unrealized_pnl", - v1 - ) - }), - indexes_to_unrealized_profit_rel_to_own_total_unrealized_pnl: (compute_dollars - && extended) - .then(|| { - computed_di!( - "unrealized_profit_rel_to_own_total_unrealized_pnl", - Source::Compute, - v1, - last() - ) - }), - indexes_to_unrealized_loss_rel_to_own_total_unrealized_pnl: (compute_dollars - && extended) - .then(|| { - computed_di!( - "unrealized_loss_rel_to_own_total_unrealized_pnl", - Source::Compute, - v1, - last() - ) - }), - indexes_to_neg_unrealized_loss_rel_to_own_total_unrealized_pnl: (compute_dollars - && extended) - .then(|| { - computed_di!( - "neg_unrealized_loss_rel_to_own_total_unrealized_pnl", - Source::Compute, - v1, - last() - ) - }), - indexes_to_net_unrealized_pnl_rel_to_own_total_unrealized_pnl: (compute_dollars - && extended) - .then(|| { - computed_di!( - "net_unrealized_pnl_rel_to_own_total_unrealized_pnl", - Source::Compute, - v1, - last() - ) - }), - - // ==================== RELATIVE METRICS: SUPPLY vs CIRCULATING/OWN ==================== - indexes_to_supply_rel_to_circulating_supply: compute_rel_to_all.then(|| { - computed_h!( - "supply_rel_to_circulating_supply", - Source::Compute, - v1, - last() - ) - }), - height_to_supply_in_profit_rel_to_own_supply: compute_dollars - .then(|| eager!(Height, StoredF64, "supply_in_profit_rel_to_own_supply", v1)), - height_to_supply_in_loss_rel_to_own_supply: compute_dollars - .then(|| eager!(Height, StoredF64, "supply_in_loss_rel_to_own_supply", v1)), - indexes_to_supply_in_profit_rel_to_own_supply: compute_dollars.then(|| { - computed_di!( - "supply_in_profit_rel_to_own_supply", - Source::Compute, - v1, - last() - ) - }), - indexes_to_supply_in_loss_rel_to_own_supply: compute_dollars.then(|| { - computed_di!( - "supply_in_loss_rel_to_own_supply", - Source::Compute, - v1, - last() - ) - }), - height_to_supply_in_profit_rel_to_circulating_supply: (compute_rel_to_all - && compute_dollars) - .then(|| { - eager!( - Height, - StoredF64, - "supply_in_profit_rel_to_circulating_supply", - v1 - ) - }), - height_to_supply_in_loss_rel_to_circulating_supply: (compute_rel_to_all - && compute_dollars) - .then(|| { - eager!( - Height, - StoredF64, - "supply_in_loss_rel_to_circulating_supply", - v1 - ) - }), - indexes_to_supply_in_profit_rel_to_circulating_supply: (compute_rel_to_all - && compute_dollars) - .then(|| { - computed_di!( - "supply_in_profit_rel_to_circulating_supply", - Source::Compute, - v1, - last() - ) - }), - indexes_to_supply_in_loss_rel_to_circulating_supply: (compute_rel_to_all - && compute_dollars) - .then(|| { - computed_di!( - "supply_in_loss_rel_to_circulating_supply", - Source::Compute, - v1, - last() - ) - }), - - // ==================== NET REALIZED PNL DELTAS ==================== - indexes_to_net_realized_pnl_cumulative_30d_delta: compute_dollars.then(|| { - computed_di!( - "net_realized_pnl_cumulative_30d_delta", - Source::Compute, - v3, - last() - ) - }), - indexes_to_net_realized_pnl_cumulative_30d_delta_rel_to_realized_cap: compute_dollars - .then(|| { - computed_di!( - "net_realized_pnl_cumulative_30d_delta_rel_to_realized_cap", - Source::Compute, - v3, - last() - ) - }), - indexes_to_net_realized_pnl_cumulative_30d_delta_rel_to_market_cap: compute_dollars - .then(|| { - computed_di!( - "net_realized_pnl_cumulative_30d_delta_rel_to_market_cap", - Source::Compute, - v3, - last() - ) - }), - }) - } - - /// Returns the minimum length of all height-indexed vectors. - /// Used to determine the starting point for processing. - pub fn min_height_vecs_len(&self) -> usize { - [ - self.height_to_supply.len(), - self.height_to_utxo_count.len(), - self.height_to_realized_cap - .as_ref() - .map_or(usize::MAX, |v| v.len()), - self.height_to_realized_profit - .as_ref() - .map_or(usize::MAX, |v| v.len()), - self.height_to_realized_loss - .as_ref() - .map_or(usize::MAX, |v| v.len()), - self.height_to_value_created - .as_ref() - .map_or(usize::MAX, |v| v.len()), - self.height_to_adjusted_value_created - .as_ref() - .map_or(usize::MAX, |v| v.len()), - self.height_to_value_destroyed - .as_ref() - .map_or(usize::MAX, |v| v.len()), - self.height_to_adjusted_value_destroyed - .as_ref() - .map_or(usize::MAX, |v| v.len()), - self.height_to_supply_in_profit - .as_ref() - .map_or(usize::MAX, |v| v.len()), - self.height_to_supply_in_loss - .as_ref() - .map_or(usize::MAX, |v| v.len()), - self.height_to_unrealized_profit - .as_ref() - .map_or(usize::MAX, |v| v.len()), - self.height_to_unrealized_loss - .as_ref() - .map_or(usize::MAX, |v| v.len()), - self.height_to_min_price_paid - .as_ref() - .map_or(usize::MAX, |v| v.len()), - self.height_to_max_price_paid - .as_ref() - .map_or(usize::MAX, |v| v.len()), - self.height_to_sent.len(), - self.height_to_satdays_destroyed.len(), - self.height_to_satblocks_destroyed.len(), - ] - .into_iter() - .min() - .unwrap() - } - - /// Import state from a checkpoint when resuming processing. - /// Returns the next height to process from. - pub fn import_state( - &mut self, - starting_height: Height, - state: &mut CohortState, - ) -> Result { - if let Some(mut prev_height) = starting_height.decremented() { - if self.height_to_realized_cap.as_mut().is_some() { - prev_height = state.import_at_or_before(prev_height)?; - } - - state.supply.value = self.height_to_supply.into_iter().get_unwrap(prev_height); - state.supply.utxo_count = *self - .height_to_utxo_count - .into_iter() - .get_unwrap(prev_height); - - if let Some(height_to_realized_cap) = self.height_to_realized_cap.as_mut() { - state.realized.um().cap = - height_to_realized_cap.into_iter().get_unwrap(prev_height); - } - - Ok(prev_height.incremented()) - } else { - Err(Error::Internal("No previous height to import state from")) - } - } - - /// Validate that all computed versions match expected values, resetting if needed. - pub fn validate_computed_versions(&mut self, base_version: Version) -> Result<()> { - // Always-present vecs - self.height_to_supply.validate_computed_version_or_reset( - base_version + self.height_to_supply.inner_version(), - )?; - self.height_to_utxo_count - .validate_computed_version_or_reset( - base_version + self.height_to_utxo_count.inner_version(), - )?; - self.height_to_sent.validate_computed_version_or_reset( - base_version + self.height_to_sent.inner_version(), - )?; - self.height_to_satblocks_destroyed - .validate_computed_version_or_reset( - base_version + self.height_to_satblocks_destroyed.inner_version(), - )?; - self.height_to_satdays_destroyed - .validate_computed_version_or_reset( - base_version + self.height_to_satdays_destroyed.inner_version(), - )?; - - // Dollar-dependent vecs - if let Some(height_to_realized_cap) = self.height_to_realized_cap.as_mut().as_mut() { - height_to_realized_cap.validate_computed_version_or_reset( - base_version + height_to_realized_cap.inner_version(), - )?; - - Self::validate_optional_vec_version(&mut self.height_to_realized_profit, base_version)?; - Self::validate_optional_vec_version(&mut self.height_to_realized_loss, base_version)?; - Self::validate_optional_vec_version(&mut self.height_to_value_created, base_version)?; - Self::validate_optional_vec_version(&mut self.height_to_value_destroyed, base_version)?; - Self::validate_optional_vec_version( - &mut self.height_to_supply_in_profit, - base_version, - )?; - Self::validate_optional_vec_version(&mut self.height_to_supply_in_loss, base_version)?; - Self::validate_optional_vec_version( - &mut self.height_to_unrealized_profit, - base_version, - )?; - Self::validate_optional_vec_version(&mut self.height_to_unrealized_loss, base_version)?; - Self::validate_optional_vec_version( - &mut self.dateindex_to_supply_in_profit, - base_version, - )?; - Self::validate_optional_vec_version( - &mut self.dateindex_to_supply_in_loss, - base_version, - )?; - Self::validate_optional_vec_version( - &mut self.dateindex_to_unrealized_profit, - base_version, - )?; - Self::validate_optional_vec_version( - &mut self.dateindex_to_unrealized_loss, - base_version, - )?; - Self::validate_optional_vec_version(&mut self.height_to_min_price_paid, base_version)?; - Self::validate_optional_vec_version(&mut self.height_to_max_price_paid, base_version)?; - - if self.height_to_adjusted_value_created.is_some() { - Self::validate_optional_vec_version( - &mut self.height_to_adjusted_value_created, - base_version, - )?; - Self::validate_optional_vec_version( - &mut self.height_to_adjusted_value_destroyed, - base_version, - )?; - } - } - - Ok(()) - } - - /// Helper to validate an optional vec's version. - fn validate_optional_vec_version( - vec: &mut Option>, - base_version: Version, - ) -> Result<()> { - if let Some(v) = vec.as_mut() { - v.validate_computed_version_or_reset(base_version + v.inner_version())?; - } - Ok(()) - } -} diff --git a/crates/brk_computer/src/stateful_old/common/mod.rs b/crates/brk_computer/src/stateful_old/common/mod.rs deleted file mode 100644 index fc02e4666..000000000 --- a/crates/brk_computer/src/stateful_old/common/mod.rs +++ /dev/null @@ -1,19 +0,0 @@ -//! Common vector structs and logic shared between UTXO and Address cohorts. -//! -//! This module contains the `Vecs` struct which holds all the computed vectors -//! for a single cohort, along with methods for importing, flushing, and computing. -//! -//! ## Module Organization -//! -//! The implementation is split across multiple files for maintainability: -//! - `vecs.rs`: Struct definition with field documentation -//! - `import.rs`: Import, validation, and initialization methods -//! - `push.rs`: Per-block push and flush methods -//! - `compute.rs`: Post-processing computation methods - -mod compute; -mod import; -mod push; -mod vecs; - -pub use vecs::Vecs; diff --git a/crates/brk_computer/src/stateful_old/common/push.rs b/crates/brk_computer/src/stateful_old/common/push.rs deleted file mode 100644 index b28b9e3df..000000000 --- a/crates/brk_computer/src/stateful_old/common/push.rs +++ /dev/null @@ -1,178 +0,0 @@ -//! Push and flush methods for Vecs. -//! -//! This module contains methods for: -//! - `truncate_push`: Push state values to height-indexed vectors -//! - `compute_then_truncate_push_unrealized_states`: Compute and push unrealized states -//! - `safe_flush_stateful_vecs`: Safely flush all stateful vectors - -use brk_error::Result; -use brk_types::{DateIndex, Dollars, Height, StoredU64}; -use vecdb::{AnyStoredVec, Exit, GenericStoredVec}; - -use crate::{stateful::Flushable, states::CohortState, utils::OptionExt}; - -use super::Vecs; - -impl Vecs { - pub fn truncate_push(&mut self, height: Height, state: &CohortState) -> Result<()> { - self.height_to_supply - .truncate_push(height, state.supply.value)?; - - self.height_to_utxo_count - .truncate_push(height, StoredU64::from(state.supply.utxo_count))?; - - self.height_to_sent.truncate_push(height, state.sent)?; - - self.height_to_satblocks_destroyed - .truncate_push(height, state.satblocks_destroyed)?; - - self.height_to_satdays_destroyed - .truncate_push(height, state.satdays_destroyed)?; - - if let Some(height_to_realized_cap) = self.height_to_realized_cap.as_mut() { - let realized = state.realized.as_ref().unwrap_or_else(|| { - dbg!((&state.realized, &state.supply)); - panic!(); - }); - - height_to_realized_cap.truncate_push(height, realized.cap)?; - - self.height_to_realized_profit - .um() - .truncate_push(height, realized.profit)?; - self.height_to_realized_loss - .um() - .truncate_push(height, realized.loss)?; - self.height_to_value_created - .um() - .truncate_push(height, realized.value_created)?; - self.height_to_value_destroyed - .um() - .truncate_push(height, realized.value_destroyed)?; - - if self.height_to_adjusted_value_created.is_some() { - self.height_to_adjusted_value_created - .um() - .truncate_push(height, realized.adj_value_created)?; - self.height_to_adjusted_value_destroyed - .um() - .truncate_push(height, realized.adj_value_destroyed)?; - } - } - Ok(()) - } - - pub fn compute_then_truncate_push_unrealized_states( - &mut self, - height: Height, - height_price: Option, - dateindex: Option, - date_price: Option>, - state: &CohortState, - ) -> Result<()> { - if let Some(height_price) = height_price { - self.height_to_min_price_paid.um().truncate_push( - height, - state - .price_to_amount_first_key_value() - .map(|(&dollars, _)| dollars) - .unwrap_or(Dollars::NAN), - )?; - self.height_to_max_price_paid.um().truncate_push( - height, - state - .price_to_amount_last_key_value() - .map(|(&dollars, _)| dollars) - .unwrap_or(Dollars::NAN), - )?; - - let (height_unrealized_state, date_unrealized_state) = - state.compute_unrealized_states(height_price, date_price.unwrap()); - - self.height_to_supply_in_profit - .um() - .truncate_push(height, height_unrealized_state.supply_in_profit)?; - self.height_to_supply_in_loss - .um() - .truncate_push(height, height_unrealized_state.supply_in_loss)?; - self.height_to_unrealized_profit - .um() - .truncate_push(height, height_unrealized_state.unrealized_profit)?; - self.height_to_unrealized_loss - .um() - .truncate_push(height, height_unrealized_state.unrealized_loss)?; - - if let Some(date_unrealized_state) = date_unrealized_state { - let dateindex = dateindex.unwrap(); - - self.dateindex_to_supply_in_profit - .um() - .truncate_push(dateindex, date_unrealized_state.supply_in_profit)?; - self.dateindex_to_supply_in_loss - .um() - .truncate_push(dateindex, date_unrealized_state.supply_in_loss)?; - self.dateindex_to_unrealized_profit - .um() - .truncate_push(dateindex, date_unrealized_state.unrealized_profit)?; - self.dateindex_to_unrealized_loss - .um() - .truncate_push(dateindex, date_unrealized_state.unrealized_loss)?; - } - - // Compute and push price percentiles - if let Some(price_percentiles) = self.price_percentiles.as_mut() { - let percentile_prices = state.compute_percentile_prices(); - price_percentiles.truncate_push(height, &percentile_prices)?; - } - } - - Ok(()) - } - - pub fn safe_flush_stateful_vecs( - &mut self, - height: Height, - exit: &Exit, - state: &mut CohortState, - ) -> Result<()> { - self.height_to_supply.safe_write(exit)?; - self.height_to_utxo_count.safe_write(exit)?; - self.height_to_sent.safe_write(exit)?; - self.height_to_satdays_destroyed.safe_write(exit)?; - self.height_to_satblocks_destroyed.safe_write(exit)?; - - if let Some(height_to_realized_cap) = self.height_to_realized_cap.as_mut() { - height_to_realized_cap.safe_write(exit)?; - self.height_to_realized_profit.um().safe_write(exit)?; - self.height_to_realized_loss.um().safe_write(exit)?; - self.height_to_value_created.um().safe_write(exit)?; - self.height_to_value_destroyed.um().safe_write(exit)?; - self.height_to_supply_in_profit.um().safe_write(exit)?; - self.height_to_supply_in_loss.um().safe_write(exit)?; - self.height_to_unrealized_profit.um().safe_write(exit)?; - self.height_to_unrealized_loss.um().safe_write(exit)?; - self.dateindex_to_supply_in_profit.um().safe_write(exit)?; - self.dateindex_to_supply_in_loss.um().safe_write(exit)?; - self.dateindex_to_unrealized_profit.um().safe_write(exit)?; - self.dateindex_to_unrealized_loss.um().safe_write(exit)?; - self.height_to_min_price_paid.um().safe_write(exit)?; - self.height_to_max_price_paid.um().safe_write(exit)?; - - if self.height_to_adjusted_value_created.is_some() { - self.height_to_adjusted_value_created - .um() - .safe_write(exit)?; - self.height_to_adjusted_value_destroyed - .um() - .safe_write(exit)?; - } - - // Uses Flushable trait - Option impl handles None case - self.price_percentiles.safe_write(exit)?; - } - - state.commit(height)?; - - Ok(()) - } -} diff --git a/crates/brk_computer/src/stateful_old/common/vecs.rs b/crates/brk_computer/src/stateful_old/common/vecs.rs deleted file mode 100644 index 1ea0d06f9..000000000 --- a/crates/brk_computer/src/stateful_old/common/vecs.rs +++ /dev/null @@ -1,210 +0,0 @@ -use brk_grouper::Filter; -use brk_traversable::Traversable; -use brk_types::{DateIndex, Dollars, Height, Sats, StoredF32, StoredF64, StoredU64}; -use vecdb::{EagerVec, PcoVec}; - -use crate::grouped::{ - ComputedHeightValueVecs, ComputedRatioVecsFromDateIndex, ComputedValueVecsFromDateIndex, - ComputedValueVecsFromHeight, ComputedVecsFromDateIndex, ComputedVecsFromHeight, - PricePercentiles, -}; - -/// Common vectors shared between UTXO and Address cohorts. -/// -/// This struct contains all the computed vectors for a single cohort. The fields are -/// organized into logical groups matching the initialization order in `forced_import`. -/// -/// ## Field Groups -/// - **Supply & UTXO count**: Basic supply metrics (always computed) -/// - **Activity**: Sent amounts, satblocks/satdays destroyed -/// - **Realized**: Realized cap, profit/loss, value created/destroyed, SOPR -/// - **Unrealized**: Unrealized profit/loss, supply in profit/loss -/// - **Price**: Min/max price paid, price percentiles -/// - **Relative metrics**: Ratios relative to market cap, realized cap, etc. -#[derive(Clone, Traversable)] -pub struct Vecs { - #[traversable(skip)] - pub filter: Filter, - - // ==================== SUPPLY & UTXO COUNT ==================== - // Always computed - core supply metrics - pub height_to_supply: EagerVec>, - pub height_to_supply_value: ComputedHeightValueVecs, - pub indexes_to_supply: ComputedValueVecsFromDateIndex, - pub height_to_utxo_count: EagerVec>, - pub indexes_to_utxo_count: ComputedVecsFromHeight, - pub height_to_supply_half_value: ComputedHeightValueVecs, - pub indexes_to_supply_half: ComputedValueVecsFromDateIndex, - - // ==================== ACTIVITY ==================== - // Always computed - transaction activity metrics - pub height_to_sent: EagerVec>, - pub indexes_to_sent: ComputedValueVecsFromHeight, - pub height_to_satblocks_destroyed: EagerVec>, - pub height_to_satdays_destroyed: EagerVec>, - pub indexes_to_coinblocks_destroyed: ComputedVecsFromHeight, - pub indexes_to_coindays_destroyed: ComputedVecsFromHeight, - - // ==================== REALIZED CAP & PRICE ==================== - // Conditional on compute_dollars - pub height_to_realized_cap: Option>>, - pub indexes_to_realized_cap: Option>, - pub indexes_to_realized_price: Option>, - pub indexes_to_realized_price_extra: Option, - pub indexes_to_realized_cap_rel_to_own_market_cap: Option>, - pub indexes_to_realized_cap_30d_delta: Option>, - - // ==================== REALIZED PROFIT & LOSS ==================== - // Conditional on compute_dollars - pub height_to_realized_profit: Option>>, - pub indexes_to_realized_profit: Option>, - pub height_to_realized_loss: Option>>, - pub indexes_to_realized_loss: Option>, - pub indexes_to_neg_realized_loss: Option>, - pub indexes_to_net_realized_pnl: Option>, - pub indexes_to_realized_value: Option>, - pub indexes_to_realized_profit_rel_to_realized_cap: Option>, - pub indexes_to_realized_loss_rel_to_realized_cap: Option>, - pub indexes_to_net_realized_pnl_rel_to_realized_cap: Option>, - pub height_to_total_realized_pnl: Option>>, - pub indexes_to_total_realized_pnl: Option>, - pub dateindex_to_realized_profit_to_loss_ratio: Option>>, - - // ==================== VALUE CREATED & DESTROYED ==================== - // Conditional on compute_dollars - pub height_to_value_created: Option>>, - pub indexes_to_value_created: Option>, - pub height_to_value_destroyed: Option>>, - pub indexes_to_value_destroyed: Option>, - pub height_to_adjusted_value_created: Option>>, - pub indexes_to_adjusted_value_created: Option>, - pub height_to_adjusted_value_destroyed: Option>>, - pub indexes_to_adjusted_value_destroyed: Option>, - - // ==================== SOPR ==================== - // Spent Output Profit Ratio - conditional on compute_dollars - pub dateindex_to_sopr: Option>>, - pub dateindex_to_sopr_7d_ema: Option>>, - pub dateindex_to_sopr_30d_ema: Option>>, - pub dateindex_to_adjusted_sopr: Option>>, - pub dateindex_to_adjusted_sopr_7d_ema: Option>>, - pub dateindex_to_adjusted_sopr_30d_ema: Option>>, - - // ==================== SELL SIDE RISK ==================== - // Conditional on compute_dollars - pub dateindex_to_sell_side_risk_ratio: Option>>, - pub dateindex_to_sell_side_risk_ratio_7d_ema: Option>>, - pub dateindex_to_sell_side_risk_ratio_30d_ema: Option>>, - - // ==================== SUPPLY IN PROFIT/LOSS ==================== - // Conditional on compute_dollars - pub height_to_supply_in_profit: Option>>, - pub indexes_to_supply_in_profit: Option, - pub height_to_supply_in_loss: Option>>, - pub indexes_to_supply_in_loss: Option, - pub dateindex_to_supply_in_profit: Option>>, - pub dateindex_to_supply_in_loss: Option>>, - pub height_to_supply_in_profit_value: Option, - pub height_to_supply_in_loss_value: Option, - - // ==================== UNREALIZED PROFIT & LOSS ==================== - // Conditional on compute_dollars - pub height_to_unrealized_profit: Option>>, - pub indexes_to_unrealized_profit: Option>, - pub height_to_unrealized_loss: Option>>, - pub indexes_to_unrealized_loss: Option>, - pub dateindex_to_unrealized_profit: Option>>, - pub dateindex_to_unrealized_loss: Option>>, - pub height_to_neg_unrealized_loss: Option>>, - pub indexes_to_neg_unrealized_loss: Option>, - pub height_to_net_unrealized_pnl: Option>>, - pub indexes_to_net_unrealized_pnl: Option>, - pub height_to_total_unrealized_pnl: Option>>, - pub indexes_to_total_unrealized_pnl: Option>, - - // ==================== PRICE PAID ==================== - // Conditional on compute_dollars - pub height_to_min_price_paid: Option>>, - pub indexes_to_min_price_paid: Option>, - pub height_to_max_price_paid: Option>>, - pub indexes_to_max_price_paid: Option>, - pub price_percentiles: Option, - - // ==================== RELATIVE METRICS: UNREALIZED vs MARKET CAP ==================== - // Conditional on compute_dollars - pub height_to_unrealized_profit_rel_to_market_cap: Option>>, - pub height_to_unrealized_loss_rel_to_market_cap: Option>>, - pub height_to_neg_unrealized_loss_rel_to_market_cap: - Option>>, - pub height_to_net_unrealized_pnl_rel_to_market_cap: Option>>, - pub indexes_to_unrealized_profit_rel_to_market_cap: - Option>, - pub indexes_to_unrealized_loss_rel_to_market_cap: Option>, - pub indexes_to_neg_unrealized_loss_rel_to_market_cap: - Option>, - pub indexes_to_net_unrealized_pnl_rel_to_market_cap: - Option>, - - // ==================== RELATIVE METRICS: UNREALIZED vs OWN MARKET CAP ==================== - // Conditional on compute_dollars && extended && compute_rel_to_all - pub height_to_unrealized_profit_rel_to_own_market_cap: - Option>>, - pub height_to_unrealized_loss_rel_to_own_market_cap: - Option>>, - pub height_to_neg_unrealized_loss_rel_to_own_market_cap: - Option>>, - pub height_to_net_unrealized_pnl_rel_to_own_market_cap: - Option>>, - pub indexes_to_unrealized_profit_rel_to_own_market_cap: - Option>, - pub indexes_to_unrealized_loss_rel_to_own_market_cap: - Option>, - pub indexes_to_neg_unrealized_loss_rel_to_own_market_cap: - Option>, - pub indexes_to_net_unrealized_pnl_rel_to_own_market_cap: - Option>, - - // ==================== RELATIVE METRICS: UNREALIZED vs OWN TOTAL UNREALIZED ==================== - // Conditional on compute_dollars && extended - pub height_to_unrealized_profit_rel_to_own_total_unrealized_pnl: - Option>>, - pub height_to_unrealized_loss_rel_to_own_total_unrealized_pnl: - Option>>, - pub height_to_neg_unrealized_loss_rel_to_own_total_unrealized_pnl: - Option>>, - pub height_to_net_unrealized_pnl_rel_to_own_total_unrealized_pnl: - Option>>, - pub indexes_to_unrealized_profit_rel_to_own_total_unrealized_pnl: - Option>, - pub indexes_to_unrealized_loss_rel_to_own_total_unrealized_pnl: - Option>, - pub indexes_to_neg_unrealized_loss_rel_to_own_total_unrealized_pnl: - Option>, - pub indexes_to_net_unrealized_pnl_rel_to_own_total_unrealized_pnl: - Option>, - - // ==================== RELATIVE METRICS: SUPPLY vs CIRCULATING/OWN ==================== - // Conditional on compute_dollars - pub indexes_to_supply_rel_to_circulating_supply: Option>, - pub height_to_supply_in_profit_rel_to_own_supply: Option>>, - pub height_to_supply_in_loss_rel_to_own_supply: Option>>, - pub indexes_to_supply_in_profit_rel_to_own_supply: Option>, - pub indexes_to_supply_in_loss_rel_to_own_supply: Option>, - pub height_to_supply_in_profit_rel_to_circulating_supply: - Option>>, - pub height_to_supply_in_loss_rel_to_circulating_supply: - Option>>, - pub indexes_to_supply_in_profit_rel_to_circulating_supply: - Option>, - pub indexes_to_supply_in_loss_rel_to_circulating_supply: - Option>, - - // ==================== NET REALIZED PNL DELTAS ==================== - // Conditional on compute_dollars - pub indexes_to_net_realized_pnl_cumulative_30d_delta: - Option>, - pub indexes_to_net_realized_pnl_cumulative_30d_delta_rel_to_realized_cap: - Option>, - pub indexes_to_net_realized_pnl_cumulative_30d_delta_rel_to_market_cap: - Option>, -} diff --git a/crates/brk_computer/src/stateful_old/flushable.rs b/crates/brk_computer/src/stateful_old/flushable.rs deleted file mode 100644 index 449e93f39..000000000 --- a/crates/brk_computer/src/stateful_old/flushable.rs +++ /dev/null @@ -1,80 +0,0 @@ -//! Traits for consistent state flushing and importing. -//! -//! These traits ensure all stateful components follow the same patterns -//! for checkpoint/resume operations, preventing bugs where new fields -//! are forgotten during flush operations. - -use brk_error::Result; -use brk_types::Height; -use vecdb::Exit; - -/// Trait for components that can be flushed to disk. -/// -/// This is for simple flush operations that don't require height tracking. -pub trait Flushable { - /// Safely flush data to disk. - fn safe_flush(&mut self, exit: &Exit) -> Result<()>; - - /// Write to mmap without fsync. Data visible to readers immediately but not durable. - fn safe_write(&mut self, exit: &Exit) -> Result<()>; -} - -/// Trait for stateful components that track data indexed by height. -/// -/// This ensures consistent patterns for: -/// - Flushing state at checkpoints -/// - Importing state when resuming from a checkpoint -/// - Resetting state when starting from scratch -pub trait HeightFlushable { - /// Flush state to disk at the given height checkpoint. - fn flush_at_height(&mut self, height: Height, exit: &Exit) -> Result<()>; - - /// Import state from the most recent checkpoint at or before the given height. - /// Returns the actual height that was imported. - fn import_at_or_before(&mut self, height: Height) -> Result; - - /// Reset state for starting from scratch. - fn reset(&mut self) -> Result<()>; -} - -/// Blanket implementation for Option where T: Flushable -impl Flushable for Option { - fn safe_flush(&mut self, exit: &Exit) -> Result<()> { - if let Some(inner) = self.as_mut() { - inner.safe_flush(exit)?; - } - Ok(()) - } - - fn safe_write(&mut self, exit: &Exit) -> Result<()> { - if let Some(inner) = self.as_mut() { - inner.safe_write(exit)?; - } - Ok(()) - } -} - -/// Blanket implementation for Option where T: HeightFlushable -impl HeightFlushable for Option { - fn flush_at_height(&mut self, height: Height, exit: &Exit) -> Result<()> { - if let Some(inner) = self.as_mut() { - inner.flush_at_height(height, exit)?; - } - Ok(()) - } - - fn import_at_or_before(&mut self, height: Height) -> Result { - if let Some(inner) = self.as_mut() { - inner.import_at_or_before(height) - } else { - Ok(height) - } - } - - fn reset(&mut self) -> Result<()> { - if let Some(inner) = self.as_mut() { - inner.reset()?; - } - Ok(()) - } -} diff --git a/crates/brk_computer/src/stateful_old/mod.rs b/crates/brk_computer/src/stateful_old/mod.rs deleted file mode 100644 index 11bafdaf0..000000000 --- a/crates/brk_computer/src/stateful_old/mod.rs +++ /dev/null @@ -1,1702 +0,0 @@ -//! Stateful computation module for Bitcoin UTXO and address cohort analysis. -//! -//! This module contains the main computation loop that processes blocks and computes -//! various metrics for UTXO cohorts (grouped by age, amount, etc.) and address cohorts. -//! -//! ## Architecture -//! -//! The module is organized as follows: -//! -//! - **`Vecs`**: Main struct holding all computed vectors and state -//! - **Cohort Types**: -//! - **Separate cohorts**: Have full state tracking (e.g., UTXOs 1-2 years old) -//! - **Aggregate cohorts**: Computed from separate cohorts (e.g., all, sth, lth) -//! -//! ## Checkpoint/Resume -//! -//! The computation supports checkpointing via `flush_states()` which saves: -//! - All separate cohorts' state (via `safe_flush_stateful_vecs`) -//! - Aggregate cohorts' `price_to_amount` (via `HeightFlushable` trait) -//! - Aggregate cohorts' `price_percentiles` (via `Flushable` trait) -//! -//! Resume is handled by: -//! - `import_state()` for separate cohorts -//! - `import_aggregate_price_to_amount()` for aggregate cohorts -//! -//! ## Key Traits -//! -//! - `Flushable`: Simple flush operations (no height tracking) -//! - `HeightFlushable`: Height-indexed state (flush, import, reset) - -use std::{cmp::Ordering, collections::BTreeSet, mem, path::Path, thread}; - -use brk_error::Result; -use brk_grouper::ByAddressType; -use brk_indexer::Indexer; -use brk_traversable::Traversable; -use brk_types::{ - AnyAddressDataIndexEnum, AnyAddressIndex, DateIndex, Dollars, EmptyAddressData, - EmptyAddressIndex, Height, LoadedAddressData, LoadedAddressIndex, OutputType, Sats, StoredU64, - TxInIndex, TxIndex, TxOutIndex, TypeIndex, Version, -}; -use log::info; -use rayon::prelude::*; -use rustc_hash::FxHashMap; -use smallvec::SmallVec; -use vecdb::{ - AnyStoredVec, AnyVec, BytesVec, CollectableVec, Database, EagerVec, Exit, GenericStoredVec, - ImportOptions, ImportableVec, IterableCloneableVec, IterableVec, LazyVecFrom1, PAGE_SIZE, - PcoVec, Stamp, TypedVecIterator, VecIndex, -}; - -use crate::{ - BlockState, Indexes, SupplyState, Transacted, chain, - grouped::{ - ComputedValueVecsFromHeight, ComputedVecsFromDateIndex, ComputedVecsFromHeight, Source, - VecBuilderOptions, - }, - indexes, price, - utils::OptionExt, -}; - -mod address_cohort; -mod address_cohorts; -mod address_indexes; -mod addresstype; -mod common; -// mod flushable; -mod range_map; -mod readers; -mod r#trait; -mod transaction_processing; -mod utxo_cohort; -mod utxo_cohorts; -mod withaddressdatasource; - -pub use crate::states::{Flushable, HeightFlushable}; - -use address_indexes::{AddressesDataVecs, AnyAddressIndexesVecs}; -use addresstype::*; -use range_map::*; -use readers::{ - IndexerReaders, VecsReaders, build_txinindex_to_txindex, build_txoutindex_to_txindex, -}; -use r#trait::*; -use withaddressdatasource::*; - -type TxIndexVec = SmallVec<[TxIndex; 4]>; - -const VERSION: Version = Version::new(21); - -const BIP30_DUPLICATE_COINBASE_HEIGHT_1: u32 = 91_842; -const BIP30_DUPLICATE_COINBASE_HEIGHT_2: u32 = 91_880; -const BIP30_ORIGINAL_COINBASE_HEIGHT_1: u32 = 91_812; -const BIP30_ORIGINAL_COINBASE_HEIGHT_2: u32 = 91_722; -const FLUSH_INTERVAL: usize = 10_000; - -#[derive(Clone, Traversable)] -pub struct Vecs { - db: Database, - - // --- - // States - // --- - pub chain_state: BytesVec, - pub txoutindex_to_txinindex: BytesVec, - pub any_address_indexes: AnyAddressIndexesVecs, - pub addresses_data: AddressesDataVecs, - pub utxo_cohorts: utxo_cohorts::Vecs, - pub address_cohorts: address_cohorts::Vecs, - - pub height_to_unspendable_supply: EagerVec>, - pub height_to_opreturn_supply: EagerVec>, - pub addresstype_to_height_to_addr_count: AddressTypeToHeightToAddressCount, - pub addresstype_to_height_to_empty_addr_count: AddressTypeToHeightToAddressCount, - - // --- - // Computed - // --- - pub addresstype_to_indexes_to_addr_count: AddressTypeToIndexesToAddressCount, - pub addresstype_to_indexes_to_empty_addr_count: AddressTypeToIndexesToAddressCount, - pub indexes_to_unspendable_supply: ComputedValueVecsFromHeight, - pub indexes_to_opreturn_supply: ComputedValueVecsFromHeight, - pub indexes_to_addr_count: ComputedVecsFromHeight, - pub indexes_to_empty_addr_count: ComputedVecsFromHeight, - pub height_to_market_cap: Option>, - pub indexes_to_market_cap: Option>, - pub loadedaddressindex_to_loadedaddressindex: - LazyVecFrom1, - pub emptyaddressindex_to_emptyaddressindex: - LazyVecFrom1, -} - -const SAVED_STAMPED_CHANGES: u16 = 10; - -impl Vecs { - pub fn forced_import( - parent: &Path, - version: Version, - indexes: &indexes::Vecs, - price: Option<&price::Vecs>, - ) -> Result { - let db_path = parent.join("stateful"); - let states_path = db_path.join("states"); - - let db = Database::open(&db_path)?; - db.set_min_len(PAGE_SIZE * 20_000_000)?; - db.set_min_regions(50_000)?; - - let compute_dollars = price.is_some(); - let v0 = version + VERSION + Version::ZERO; - let v1 = version + VERSION + Version::ONE; - let v2 = version + VERSION + Version::TWO; - - let utxo_cohorts = - utxo_cohorts::Vecs::forced_import(&db, version, indexes, price, &states_path)?; - - let loadedaddressindex_to_loadedaddressdata = BytesVec::forced_import_with( - ImportOptions::new(&db, "loadedaddressdata", v0) - .with_saved_stamped_changes(SAVED_STAMPED_CHANGES), - )?; - let emptyaddressindex_to_emptyaddressdata = BytesVec::forced_import_with( - ImportOptions::new(&db, "emptyaddressdata", v0) - .with_saved_stamped_changes(SAVED_STAMPED_CHANGES), - )?; - let loadedaddressindex_to_loadedaddressindex = LazyVecFrom1::init( - "loadedaddressindex", - v0, - loadedaddressindex_to_loadedaddressdata.boxed_clone(), - |index, _| Some(index), - ); - let emptyaddressindex_to_emptyaddressindex = LazyVecFrom1::init( - "emptyaddressindex", - v0, - emptyaddressindex_to_emptyaddressdata.boxed_clone(), - |index, _| Some(index), - ); - - let this = Self { - chain_state: BytesVec::forced_import_with( - ImportOptions::new(&db, "chain", v0) - .with_saved_stamped_changes(SAVED_STAMPED_CHANGES), - )?, - txoutindex_to_txinindex: BytesVec::forced_import_with( - ImportOptions::new(&db, "txinindex", v0) - .with_saved_stamped_changes(SAVED_STAMPED_CHANGES), - )?, - - height_to_unspendable_supply: EagerVec::forced_import(&db, "unspendable_supply", v0)?, - indexes_to_unspendable_supply: ComputedValueVecsFromHeight::forced_import( - &db, - "unspendable_supply", - Source::None, - v0, - VecBuilderOptions::default().add_last(), - compute_dollars, - indexes, - )?, - height_to_opreturn_supply: EagerVec::forced_import(&db, "opreturn_supply", v0)?, - indexes_to_opreturn_supply: ComputedValueVecsFromHeight::forced_import( - &db, - "opreturn_supply", - Source::None, - v0, - VecBuilderOptions::default().add_last(), - compute_dollars, - indexes, - )?, - indexes_to_addr_count: ComputedVecsFromHeight::forced_import( - &db, - "addr_count", - Source::Compute, - v0, - indexes, - VecBuilderOptions::default().add_last(), - )?, - indexes_to_empty_addr_count: ComputedVecsFromHeight::forced_import( - &db, - "empty_addr_count", - Source::Compute, - v0, - indexes, - VecBuilderOptions::default().add_last(), - )?, - height_to_market_cap: compute_dollars.then(|| { - LazyVecFrom1::init( - "market_cap", - v1, - utxo_cohorts - .all - .inner - .height_to_supply_value - .dollars - .as_ref() - .unwrap() - .boxed_clone(), - |height: Height, iter| iter.get(height), - ) - }), - indexes_to_market_cap: compute_dollars.then(|| { - ComputedVecsFromDateIndex::forced_import( - &db, - "market_cap", - Source::Compute, - v2, - indexes, - VecBuilderOptions::default().add_last(), - ) - .unwrap() - }), - addresstype_to_height_to_addr_count: AddressTypeToHeightToAddressCount::from( - ByAddressType::new_with_name(|name| { - Ok(EagerVec::forced_import( - &db, - &format!("{name}_addr_count"), - v0, - )?) - })?, - ), - addresstype_to_height_to_empty_addr_count: AddressTypeToHeightToAddressCount::from( - ByAddressType::new_with_name(|name| { - Ok(EagerVec::forced_import( - &db, - &format!("{name}_empty_addr_count"), - v0, - )?) - })?, - ), - addresstype_to_indexes_to_addr_count: AddressTypeToIndexesToAddressCount::from( - ByAddressType::new_with_name(|name| { - ComputedVecsFromHeight::forced_import( - &db, - &format!("{name}_addr_count"), - Source::None, - v0, - indexes, - VecBuilderOptions::default().add_last(), - ) - })?, - ), - addresstype_to_indexes_to_empty_addr_count: AddressTypeToIndexesToAddressCount::from( - ByAddressType::new_with_name(|name| { - ComputedVecsFromHeight::forced_import( - &db, - &format!("{name}_empty_addr_count"), - Source::None, - v0, - indexes, - VecBuilderOptions::default().add_last(), - ) - })?, - ), - utxo_cohorts, - address_cohorts: address_cohorts::Vecs::forced_import( - &db, - version, - indexes, - price, - &states_path, - )?, - - any_address_indexes: AnyAddressIndexesVecs { - p2a: BytesVec::forced_import_with( - ImportOptions::new(&db, "anyaddressindex", v0) - .with_saved_stamped_changes(SAVED_STAMPED_CHANGES), - )?, - p2pk33: BytesVec::forced_import_with( - ImportOptions::new(&db, "anyaddressindex", v0) - .with_saved_stamped_changes(SAVED_STAMPED_CHANGES), - )?, - p2pk65: BytesVec::forced_import_with( - ImportOptions::new(&db, "anyaddressindex", v0) - .with_saved_stamped_changes(SAVED_STAMPED_CHANGES), - )?, - p2pkh: BytesVec::forced_import_with( - ImportOptions::new(&db, "anyaddressindex", v0) - .with_saved_stamped_changes(SAVED_STAMPED_CHANGES), - )?, - p2sh: BytesVec::forced_import_with( - ImportOptions::new(&db, "anyaddressindex", v0) - .with_saved_stamped_changes(SAVED_STAMPED_CHANGES), - )?, - p2tr: BytesVec::forced_import_with( - ImportOptions::new(&db, "anyaddressindex", v0) - .with_saved_stamped_changes(SAVED_STAMPED_CHANGES), - )?, - p2wpkh: BytesVec::forced_import_with( - ImportOptions::new(&db, "anyaddressindex", v0) - .with_saved_stamped_changes(SAVED_STAMPED_CHANGES), - )?, - p2wsh: BytesVec::forced_import_with( - ImportOptions::new(&db, "anyaddressindex", v0) - .with_saved_stamped_changes(SAVED_STAMPED_CHANGES), - )?, - }, - addresses_data: AddressesDataVecs { - loaded: loadedaddressindex_to_loadedaddressdata, - empty: emptyaddressindex_to_emptyaddressdata, - }, - loadedaddressindex_to_loadedaddressindex, - emptyaddressindex_to_emptyaddressindex, - - db, - }; - - this.db.retain_regions( - this.iter_any_exportable() - .flat_map(|v| v.region_names()) - .collect(), - )?; - - this.db.compact()?; - - Ok(this) - } - - #[allow(clippy::too_many_arguments)] - pub fn compute( - &mut self, - indexer: &Indexer, - indexes: &indexes::Vecs, - chain: &chain::Vecs, - price: Option<&price::Vecs>, - // Must take ownership as its indexes will be updated for this specific function - starting_indexes: &mut Indexes, - exit: &Exit, - ) -> Result<()> { - self.compute_(indexer, indexes, chain, price, starting_indexes, exit)?; - self.db.compact()?; - Ok(()) - } - - #[allow(clippy::too_many_arguments)] - fn compute_( - &mut self, - indexer: &Indexer, - indexes: &indexes::Vecs, - chain: &chain::Vecs, - price: Option<&price::Vecs>, - // Must take ownership as its indexes will be updated for this specific function - starting_indexes: &mut Indexes, - exit: &Exit, - ) -> Result<()> { - let dateindex_to_first_height = &indexes.dateindex_to_first_height; - let dateindex_to_height_count = &indexes.dateindex_to_height_count; - let dateindex_to_price_close = price - .as_ref() - .map(|price| price.timeindexes_to_price_close.dateindex.u()); - let height_to_date_fixed = &indexes.height_to_date_fixed; - let height_to_first_p2aaddressindex = &indexer.vecs.address.height_to_first_p2aaddressindex; - let height_to_first_p2pk33addressindex = - &indexer.vecs.address.height_to_first_p2pk33addressindex; - let height_to_first_p2pk65addressindex = - &indexer.vecs.address.height_to_first_p2pk65addressindex; - let height_to_first_p2pkhaddressindex = - &indexer.vecs.address.height_to_first_p2pkhaddressindex; - let height_to_first_p2shaddressindex = - &indexer.vecs.address.height_to_first_p2shaddressindex; - let height_to_first_p2traddressindex = - &indexer.vecs.address.height_to_first_p2traddressindex; - let height_to_first_p2wpkhaddressindex = - &indexer.vecs.address.height_to_first_p2wpkhaddressindex; - let height_to_first_p2wshaddressindex = - &indexer.vecs.address.height_to_first_p2wshaddressindex; - let height_to_first_txindex = &indexer.vecs.tx.height_to_first_txindex; - let height_to_txindex_count = chain.indexes_to_tx_count.height.u(); - let height_to_first_txinindex = &indexer.vecs.txin.height_to_first_txinindex; - let height_to_first_txoutindex = &indexer.vecs.txout.height_to_first_txoutindex; - let height_to_input_count = chain.indexes_to_input_count.height.unwrap_sum(); - let height_to_output_count = chain.indexes_to_output_count.height.unwrap_sum(); - let height_to_price_close = price - .as_ref() - .map(|price| &price.chainindexes_to_price_close.height); - let height_to_timestamp_fixed = &indexes.height_to_timestamp_fixed; - let height_to_tx_count = chain.indexes_to_tx_count.height.u(); - let height_to_unclaimed_rewards = chain - .indexes_to_unclaimed_rewards - .sats - .height - .as_ref() - .unwrap(); - let txindex_to_first_txoutindex = &indexer.vecs.tx.txindex_to_first_txoutindex; - let txindex_to_height = &indexer.vecs.tx.txindex_to_height; - let txindex_to_input_count = &indexes.txindex_to_input_count; - let txindex_to_output_count = &indexes.txindex_to_output_count; - let txinindex_to_outpoint = &indexer.vecs.txin.txinindex_to_outpoint; - let txoutindex_to_outputtype = &indexer.vecs.txout.txoutindex_to_outputtype; - let txoutindex_to_txindex = &indexer.vecs.txout.txoutindex_to_txindex; - let txoutindex_to_typeindex = &indexer.vecs.txout.txoutindex_to_typeindex; - let txoutindex_to_value = &indexer.vecs.txout.txoutindex_to_value; - - let mut height_to_price_close_iter = height_to_price_close.as_ref().map(|v| v.into_iter()); - let mut height_to_timestamp_fixed_iter = height_to_timestamp_fixed.into_iter(); - - let base_version = Version::ZERO - + dateindex_to_first_height.version() - + dateindex_to_height_count.version() - + dateindex_to_price_close - .as_ref() - .map_or(Version::ZERO, |v| v.version()) - + height_to_date_fixed.version() - + height_to_first_p2aaddressindex.version() - + height_to_first_p2pk33addressindex.version() - + height_to_first_p2pk65addressindex.version() - + height_to_first_p2pkhaddressindex.version() - + height_to_first_p2shaddressindex.version() - + height_to_first_p2traddressindex.version() - + height_to_first_p2wpkhaddressindex.version() - + height_to_first_p2wshaddressindex.version() - + height_to_first_txindex.version() - + height_to_txindex_count.version() - + height_to_first_txinindex.version() - + height_to_first_txoutindex.version() - + height_to_input_count.version() - + height_to_output_count.version() - + height_to_price_close - .as_ref() - .map_or(Version::ZERO, |v| v.version()) - + height_to_timestamp_fixed.version() - + height_to_tx_count.version() - + height_to_unclaimed_rewards.version() - + txindex_to_first_txoutindex.version() - + txindex_to_height.version() - + txindex_to_input_count.version() - + txindex_to_output_count.version() - + txinindex_to_outpoint.version() - + txoutindex_to_outputtype.version() - + txoutindex_to_txindex.version() - + txoutindex_to_typeindex.version() - + txoutindex_to_value.version(); - - let mut separate_utxo_vecs = self.utxo_cohorts.iter_separate_mut().collect::>(); - let mut separate_address_vecs = - self.address_cohorts.iter_separate_mut().collect::>(); - - separate_utxo_vecs - .par_iter_mut() - .try_for_each(|v| v.validate_computed_versions(base_version))?; - separate_address_vecs - .par_iter_mut() - .try_for_each(|v| v.validate_computed_versions(base_version))?; - self.height_to_unspendable_supply - .validate_computed_version_or_reset( - base_version + self.height_to_unspendable_supply.inner_version(), - )?; - self.height_to_opreturn_supply - .validate_computed_version_or_reset( - base_version + self.height_to_opreturn_supply.inner_version(), - )?; - - let mut chain_state_starting_height = Height::from(self.chain_state.len()); - let stateful_starting_height = match separate_utxo_vecs - .par_iter_mut() - .map(|v| Height::from(v.min_height_vecs_len())) - .min() - .unwrap_or_default() - .min( - separate_address_vecs - .par_iter_mut() - .map(|v| Height::from(v.min_height_vecs_len())) - .min() - .unwrap_or_default(), - ) - .min(chain_state_starting_height) - .min(Height::from(self.txoutindex_to_txinindex.stamp()).incremented()) - .min(self.any_address_indexes.min_stamped_height()) - .min(self.addresses_data.min_stamped_height()) - .min(Height::from(self.height_to_unspendable_supply.len())) - .min(Height::from(self.height_to_opreturn_supply.len())) - .cmp(&chain_state_starting_height) - { - Ordering::Greater => unreachable!(), - Ordering::Equal => chain_state_starting_height, - Ordering::Less => Height::ZERO, - }; - - let starting_height = starting_indexes.height.min(stateful_starting_height); - let last_height = Height::from(indexer.vecs.block.height_to_blockhash.stamp()); - if starting_height <= last_height { - let stamp = starting_height.into(); - let starting_height = if starting_height.is_not_zero() { - let mut set = [self.chain_state.rollback_before(stamp)?] - .into_iter() - .chain([self.txoutindex_to_txinindex.rollback_before(stamp)?]) - .chain(self.any_address_indexes.rollback_before(stamp)?) - .chain(self.addresses_data.rollback_before(stamp)?) - .map(Height::from) - .map(Height::incremented) - .collect::>(); - - if set.len() == 1 { - set.pop_first().unwrap() - } else { - Height::ZERO - } - } else { - Height::ZERO - }; - - let starting_height = if starting_height.is_not_zero() - && separate_utxo_vecs - .iter_mut() - .map(|v| v.import_state(starting_height).unwrap_or_default()) - .all(|h| h == starting_height) - { - starting_height - } else { - Height::ZERO - }; - - let starting_height = if starting_height.is_not_zero() - && separate_address_vecs - .iter_mut() - .map(|v| v.import_state(starting_height).unwrap_or_default()) - .all(|h| h == starting_height) - { - starting_height - } else { - Height::ZERO - }; - - // Import aggregate cohorts' price_to_amount. - // Need to temporarily release the separate vecs borrows since iter_aggregate_mut - // borrows the whole UTXOGroups struct, even though it accesses non-overlapping fields. - let starting_height = { - drop(separate_utxo_vecs); - drop(separate_address_vecs); - let imported_height = self - .utxo_cohorts - .import_aggregate_price_to_amount(starting_height)?; - let result = if starting_height.is_not_zero() && imported_height == starting_height - { - starting_height - } else { - Height::ZERO - }; - separate_utxo_vecs = self.utxo_cohorts.iter_separate_mut().collect(); - separate_address_vecs = self.address_cohorts.iter_separate_mut().collect(); - result - }; - - let mut chain_state: Vec; - if starting_height.is_not_zero() { - chain_state = self - .chain_state - .collect_range(None, None) - .into_iter() - .enumerate() - .map(|(height, supply)| { - let height = Height::from(height); - let timestamp = height_to_timestamp_fixed_iter.get_unwrap(height); - let price = height_to_price_close_iter - .as_mut() - .map(|i| *i.get_unwrap(height)); - BlockState { - timestamp, - price, - supply, - } - }) - .collect::>(); - } else { - info!("Starting processing utxos from the start"); - - chain_state = vec![]; - - self.txoutindex_to_txinindex.reset()?; - self.any_address_indexes.reset()?; - self.addresses_data.reset()?; - - separate_utxo_vecs.par_iter_mut().try_for_each(|v| { - v.reset_state_starting_height(); - v.state.um().reset_price_to_amount_if_needed() - })?; - - // Reset aggregate cohorts' price_to_amount - self.utxo_cohorts.reset_aggregate_price_to_amount()?; - - separate_address_vecs.par_iter_mut().try_for_each(|v| { - v.reset_state_starting_height(); - v.state.um().reset_price_to_amount_if_needed() - })?; - } - - chain_state_starting_height = starting_height; - - starting_indexes.update_from_height(starting_height, indexes); - - let ir = IndexerReaders::new(indexer); - - let mut dateindex_to_first_height_iter = dateindex_to_first_height.into_iter(); - let mut dateindex_to_height_count_iter = dateindex_to_height_count.into_iter(); - let mut dateindex_to_price_close_iter = - dateindex_to_price_close.as_ref().map(|v| v.into_iter()); - let mut height_to_date_fixed_iter = height_to_date_fixed.into_iter(); - let mut height_to_first_p2aaddressindex_iter = - height_to_first_p2aaddressindex.into_iter(); - let mut height_to_first_p2pk33addressindex_iter = - height_to_first_p2pk33addressindex.into_iter(); - let mut height_to_first_p2pk65addressindex_iter = - height_to_first_p2pk65addressindex.into_iter(); - let mut height_to_first_p2pkhaddressindex_iter = - height_to_first_p2pkhaddressindex.into_iter(); - let mut height_to_first_p2shaddressindex_iter = - height_to_first_p2shaddressindex.into_iter(); - let mut height_to_first_p2traddressindex_iter = - height_to_first_p2traddressindex.into_iter(); - let mut height_to_first_p2wpkhaddressindex_iter = - height_to_first_p2wpkhaddressindex.into_iter(); - let mut height_to_first_p2wshaddressindex_iter = - height_to_first_p2wshaddressindex.into_iter(); - let mut height_to_first_txindex_iter = height_to_first_txindex.into_iter(); - let mut height_to_first_txinindex_iter = height_to_first_txinindex.into_iter(); - let mut height_to_first_txoutindex_iter = height_to_first_txoutindex.into_iter(); - let mut height_to_input_count_iter = height_to_input_count.into_iter(); - let mut height_to_output_count_iter = height_to_output_count.into_iter(); - let mut height_to_tx_count_iter = height_to_tx_count.into_iter(); - let mut height_to_unclaimed_rewards_iter = height_to_unclaimed_rewards.into_iter(); - let mut txindex_to_input_count_iter = txindex_to_input_count.iter(); - let mut txindex_to_output_count_iter = txindex_to_output_count.iter(); - - let height_to_price_close_vec = - height_to_price_close.map(|height_to_price_close| height_to_price_close.collect()); - - let height_to_timestamp_fixed_vec = height_to_timestamp_fixed.collect(); - let txoutindex_range_to_height = RangeMap::from(height_to_first_txoutindex); - - let mut unspendable_supply = if let Some(prev_height) = starting_height.decremented() { - self.height_to_unspendable_supply - .into_iter() - .get_unwrap(prev_height) - } else { - Sats::ZERO - }; - let mut opreturn_supply = if let Some(prev_height) = starting_height.decremented() { - self.height_to_opreturn_supply - .into_iter() - .get_unwrap(prev_height) - } else { - Sats::ZERO - }; - let mut addresstype_to_addr_count = AddressTypeToAddressCount::from(( - &self.addresstype_to_height_to_addr_count, - starting_height, - )); - let mut addresstype_to_empty_addr_count = AddressTypeToAddressCount::from(( - &self.addresstype_to_height_to_empty_addr_count, - starting_height, - )); - - let mut height = starting_height; - - let mut addresstype_to_typeindex_to_loadedaddressdata = - AddressTypeToTypeIndexMap::>::default(); - let mut addresstype_to_typeindex_to_emptyaddressdata = - AddressTypeToTypeIndexMap::>::default(); - - let mut vr = VecsReaders::new(self); - - let last_height = Height::from( - height_to_date_fixed - .len() - .checked_sub(1) - .unwrap_or_default(), - ); - - for _height in (height.to_usize()..height_to_date_fixed.len()).map(Height::from) { - height = _height; - - info!("Processing chain at {height}..."); - - self.utxo_cohorts - .iter_separate_mut() - .for_each(|v| v.state.um().reset_single_iteration_values()); - - self.address_cohorts - .iter_separate_mut() - .for_each(|v| v.state.um().reset_single_iteration_values()); - - let timestamp = height_to_timestamp_fixed_iter.get_unwrap(height); - let price = height_to_price_close_iter - .as_mut() - .map(|i| *i.get_unwrap(height)); - let first_txindex = height_to_first_txindex_iter.get_unwrap(height); - let first_txoutindex = height_to_first_txoutindex_iter - .get_unwrap(height) - .to_usize(); - let first_txinindex = height_to_first_txinindex_iter.get_unwrap(height).to_usize(); - let tx_count = height_to_tx_count_iter.get_unwrap(height); - let output_count = height_to_output_count_iter.get_unwrap(height); - let input_count = height_to_input_count_iter.get_unwrap(height); - - let txoutindex_to_txindex = build_txoutindex_to_txindex( - first_txindex, - u64::from(tx_count), - &mut txindex_to_output_count_iter, - ); - - let txinindex_to_txindex = build_txinindex_to_txindex( - first_txindex, - u64::from(tx_count), - &mut txindex_to_input_count_iter, - ); - - let first_addressindexes: ByAddressType = ByAddressType { - p2a: height_to_first_p2aaddressindex_iter - .get_unwrap(height) - .into(), - p2pk33: height_to_first_p2pk33addressindex_iter - .get_unwrap(height) - .into(), - p2pk65: height_to_first_p2pk65addressindex_iter - .get_unwrap(height) - .into(), - p2pkh: height_to_first_p2pkhaddressindex_iter - .get_unwrap(height) - .into(), - p2sh: height_to_first_p2shaddressindex_iter - .get_unwrap(height) - .into(), - p2tr: height_to_first_p2traddressindex_iter - .get_unwrap(height) - .into(), - p2wpkh: height_to_first_p2wpkhaddressindex_iter - .get_unwrap(height) - .into(), - p2wsh: height_to_first_p2wshaddressindex_iter - .get_unwrap(height) - .into(), - }; - - let ( - mut transacted, - addresstype_to_typedindex_to_received_data, - mut height_to_sent, - addresstype_to_typedindex_to_sent_data, - mut stored_or_new_addresstype_to_typeindex_to_addressdatawithsource, - mut combined_txindex_vecs, - txoutindex_to_txinindex_updates, - ) = thread::scope(|scope| { - scope.spawn(|| { - self.utxo_cohorts - .tick_tock_next_block(&chain_state, timestamp); - }); - - let (transacted, addresstype_to_typedindex_to_received_data, receiving_addresstype_to_typeindex_to_addressdatawithsource, output_txindex_vecs) = (first_txoutindex..first_txoutindex + usize::from(output_count)) - .into_par_iter() - .map(|i| { - let txoutindex = TxOutIndex::from(i); - - let local_idx = i - first_txoutindex; - let txindex = txoutindex_to_txindex[local_idx]; - - let value = txoutindex_to_value - .read_unwrap(txoutindex, &ir.txoutindex_to_value); - - let output_type = txoutindex_to_outputtype - .read_unwrap(txoutindex, &ir.txoutindex_to_outputtype); - - if output_type.is_not_address() { - return (txindex, value, output_type, None); - } - - let typeindex = txoutindex_to_typeindex - .read_unwrap(txoutindex, &ir.txoutindex_to_typeindex); - - let addressdata_opt = Self::get_addressdatawithsource( - output_type, - typeindex, - &first_addressindexes, - &addresstype_to_typeindex_to_loadedaddressdata, - &addresstype_to_typeindex_to_emptyaddressdata, - &vr, - &self.any_address_indexes, - &self.addresses_data, - ); - - (txindex, value, output_type, Some(( typeindex, addressdata_opt))) - }).fold( - || { - ( - Transacted::default(), - AddressTypeToVec::<(TypeIndex, Sats)>::default(), - AddressTypeToTypeIndexMap::default(), - AddressTypeToTypeIndexMap::::default(), - ) - }, - |(mut transacted, mut addresstype_to_typedindex_to_data, mut addresstype_to_typeindex_to_addressdatawithsource, mut txindex_vecs), - ( - txindex, - value, - output_type, - typeindex_with_addressdata_opt, - )| { - transacted.iterate(value, output_type); - - if let Some((typeindex, addressdata_opt)) = typeindex_with_addressdata_opt { - if let Some(addressdata) = addressdata_opt - { - addresstype_to_typeindex_to_addressdatawithsource - .insert_for_type(output_type, typeindex, addressdata); - } - - let addr_type = output_type; - - addresstype_to_typedindex_to_data - .get_mut(addr_type) - .unwrap() - .push((typeindex, value)); - - txindex_vecs - .get_mut(addr_type) - .unwrap() - .entry(typeindex) - .or_insert_with(TxIndexVec::new) - .push(txindex); - } - - (transacted, addresstype_to_typedindex_to_data, addresstype_to_typeindex_to_addressdatawithsource, txindex_vecs) - }).reduce( - || { - ( - Transacted::default(), - AddressTypeToVec::<(TypeIndex, Sats)>::default(), - AddressTypeToTypeIndexMap::default(), - AddressTypeToTypeIndexMap::::default(), - ) - }, - |(transacted, addresstype_to_typedindex_to_data, addresstype_to_typeindex_to_addressdatawithsource, txindex_vecs), (transacted2, addresstype_to_typedindex_to_data2, addresstype_to_typeindex_to_addressdatawithsource2, txindex_vecs2)| { - (transacted + transacted2, addresstype_to_typedindex_to_data.merge(addresstype_to_typedindex_to_data2), addresstype_to_typeindex_to_addressdatawithsource.merge(addresstype_to_typeindex_to_addressdatawithsource2), txindex_vecs.merge_vec(txindex_vecs2)) - }, - ); - - // Skip coinbase - let ( - height_to_sent, - addresstype_to_typedindex_to_sent_data, - sending_addresstype_to_typeindex_to_addressdatawithsource, - input_txindex_vecs, - txoutindex_to_txinindex_updates, - ) = (first_txinindex + 1..first_txinindex + usize::from(input_count)) - .into_par_iter() - .map(|i| { - let txinindex = TxInIndex::from(i); - - let local_idx = i - first_txinindex; - let txindex = txinindex_to_txindex[local_idx]; - - let outpoint = txinindex_to_outpoint - .read_unwrap(txinindex, &ir.txinindex_to_outpoint); - - let txoutindex = txindex_to_first_txoutindex - .read_unwrap(outpoint.txindex(), &ir.txindex_to_first_txoutindex) - + outpoint.vout(); - - let value = txoutindex_to_value - .read_unwrap(txoutindex, &ir.txoutindex_to_value); - - let input_type = txoutindex_to_outputtype - .read_unwrap(txoutindex, &ir.txoutindex_to_outputtype); - - let prev_height = *txoutindex_range_to_height.get(txoutindex).unwrap(); - - if input_type.is_not_address() { - return ( - txinindex, - txoutindex, - txindex, - prev_height, - value, - input_type, - None, - ); - } - - let typeindex = txoutindex_to_typeindex - .read_unwrap(txoutindex, &ir.txoutindex_to_typeindex); - - let addressdata_opt = Self::get_addressdatawithsource( - input_type, - typeindex, - &first_addressindexes, - &addresstype_to_typeindex_to_loadedaddressdata, - &addresstype_to_typeindex_to_emptyaddressdata, - &vr, - &self.any_address_indexes, - &self.addresses_data, - ); - - ( - txinindex, - txoutindex, - txindex, - prev_height, - value, - input_type, - Some((typeindex, addressdata_opt)), - ) - }) - .fold( - || { - ( - FxHashMap::::default(), - HeightToAddressTypeToVec::<(TypeIndex, Sats)>::default(), - AddressTypeToTypeIndexMap::default(), - AddressTypeToTypeIndexMap::::default(), - Vec::<(TxOutIndex, TxInIndex)>::new(), - ) - }, - |( - mut height_to_transacted, - mut height_to_addresstype_to_typedindex_to_data, - mut addresstype_to_typeindex_to_addressdatawithsource, - mut txindex_vecs, - mut txoutindex_to_txinindex_updates, - ), - ( - txinindex, - txoutindex, - txindex, - prev_height, - value, - output_type, - typeindex_with_addressdata_opt, - )| { - height_to_transacted - .entry(prev_height) - .or_default() - .iterate(value, output_type); - - txoutindex_to_txinindex_updates.push((txoutindex, txinindex)); - - if let Some((typeindex, addressdata_opt)) = - typeindex_with_addressdata_opt - { - if let Some(addressdata) = addressdata_opt { - addresstype_to_typeindex_to_addressdatawithsource - .insert_for_type(output_type, typeindex, addressdata); - } - - let addr_type = output_type; - - height_to_addresstype_to_typedindex_to_data - .entry(prev_height) - .or_default() - .get_mut(addr_type) - .unwrap() - .push((typeindex, value)); - - txindex_vecs - .get_mut(addr_type) - .unwrap() - .entry(typeindex) - .or_insert_with(TxIndexVec::new) - .push(txindex); - } - - ( - height_to_transacted, - height_to_addresstype_to_typedindex_to_data, - addresstype_to_typeindex_to_addressdatawithsource, - txindex_vecs, - txoutindex_to_txinindex_updates, - ) - }, - ) - .reduce( - || { - ( - FxHashMap::::default(), - HeightToAddressTypeToVec::<(TypeIndex, Sats)>::default(), - AddressTypeToTypeIndexMap::default(), - AddressTypeToTypeIndexMap::::default(), - Vec::<(TxOutIndex, TxInIndex)>::new(), - ) - }, - |( - height_to_transacted, - addresstype_to_typedindex_to_data, - addresstype_to_typeindex_to_addressdatawithsource, - txindex_vecs, - txoutindex_to_txinindex_updates, - ), - ( - height_to_transacted2, - addresstype_to_typedindex_to_data2, - addresstype_to_typeindex_to_addressdatawithsource2, - txindex_vecs2, - txoutindex_to_txinindex_updates2, - )| { - let (mut height_to_transacted, height_to_transacted_consumed) = - if height_to_transacted.len() > height_to_transacted2.len() { - (height_to_transacted, height_to_transacted2) - } else { - (height_to_transacted2, height_to_transacted) - }; - height_to_transacted_consumed - .into_iter() - .for_each(|(k, v)| { - *height_to_transacted.entry(k).or_default() += v; - }); - - let ( - mut addresstype_to_typedindex_to_data, - addresstype_to_typedindex_to_data_consumed, - ) = if addresstype_to_typedindex_to_data.len() - > addresstype_to_typedindex_to_data2.len() - { - ( - addresstype_to_typedindex_to_data, - addresstype_to_typedindex_to_data2, - ) - } else { - ( - addresstype_to_typedindex_to_data2, - addresstype_to_typedindex_to_data, - ) - }; - addresstype_to_typedindex_to_data_consumed - .0 - .into_iter() - .for_each(|(k, v)| { - addresstype_to_typedindex_to_data - .entry(k) - .or_default() - .merge_mut(v); - }); - - let ( - mut txoutindex_to_txinindex_updates, - txoutindex_to_txinindex_updates_consumed, - ) = if txoutindex_to_txinindex_updates.len() - > txoutindex_to_txinindex_updates2.len() - { - ( - txoutindex_to_txinindex_updates, - txoutindex_to_txinindex_updates2, - ) - } else { - ( - txoutindex_to_txinindex_updates2, - txoutindex_to_txinindex_updates, - ) - }; - txoutindex_to_txinindex_updates - .extend(txoutindex_to_txinindex_updates_consumed); - - ( - height_to_transacted, - addresstype_to_typedindex_to_data, - addresstype_to_typeindex_to_addressdatawithsource - .merge(addresstype_to_typeindex_to_addressdatawithsource2), - txindex_vecs.merge_vec(txindex_vecs2), - txoutindex_to_txinindex_updates, - ) - }, - ); - - let addresstype_to_typeindex_to_addressdatawithsource = - receiving_addresstype_to_typeindex_to_addressdatawithsource - .merge(sending_addresstype_to_typeindex_to_addressdatawithsource); - - let combined_txindex_vecs = output_txindex_vecs.merge_vec(input_txindex_vecs); - - ( - transacted, - addresstype_to_typedindex_to_received_data, - height_to_sent, - addresstype_to_typedindex_to_sent_data, - addresstype_to_typeindex_to_addressdatawithsource, - combined_txindex_vecs, - txoutindex_to_txinindex_updates, - ) - }); - - combined_txindex_vecs - .par_values_mut() - .flat_map(|typeindex_to_txindexes| typeindex_to_txindexes.par_iter_mut()) - .map(|(_, v)| v) - .filter(|txindex_vec| txindex_vec.len() > 1) - .for_each(|txindex_vec| { - txindex_vec.sort_unstable(); - txindex_vec.dedup(); - }); - - for (address_type, typeindex, txindex_vec) in combined_txindex_vecs - .into_iter() - .flat_map(|(t, m)| m.into_iter().map(move |(i, v)| (t, i, v))) - { - let tx_count = txindex_vec.len() as u32; - - if let Some(addressdata) = addresstype_to_typeindex_to_loadedaddressdata - .get_mut_unwrap(address_type) - .get_mut(&typeindex) - { - addressdata.deref_mut().tx_count += tx_count; - } else if let Some(addressdata) = addresstype_to_typeindex_to_emptyaddressdata - .get_mut_unwrap(address_type) - .get_mut(&typeindex) - { - addressdata.deref_mut().tx_count += tx_count; - } else if let Some(addressdata) = - stored_or_new_addresstype_to_typeindex_to_addressdatawithsource - .get_mut_unwrap(address_type) - .get_mut(&typeindex) - { - addressdata.deref_mut().tx_count += tx_count; - } - } - - thread::scope(|scope| { - scope.spawn(|| { - addresstype_to_typedindex_to_received_data.process_received( - &mut self.address_cohorts, - &mut addresstype_to_typeindex_to_loadedaddressdata, - &mut addresstype_to_typeindex_to_emptyaddressdata, - price, - &mut addresstype_to_addr_count, - &mut addresstype_to_empty_addr_count, - &mut stored_or_new_addresstype_to_typeindex_to_addressdatawithsource, - ); - - addresstype_to_typedindex_to_sent_data - .process_sent( - &mut self.address_cohorts, - &mut addresstype_to_typeindex_to_loadedaddressdata, - &mut addresstype_to_typeindex_to_emptyaddressdata, - price, - &mut addresstype_to_addr_count, - &mut addresstype_to_empty_addr_count, - height_to_price_close_vec.as_ref(), - &height_to_timestamp_fixed_vec, - height, - timestamp, - &mut stored_or_new_addresstype_to_typeindex_to_addressdatawithsource, - ) - .unwrap(); - }); - - debug_assert!( - chain_state_starting_height <= height, - "chain_state_starting_height ({chain_state_starting_height}) > height ({height})" - ); - - // NOTE: If ByUnspendableType gains more fields, change to .as_vec().into_iter().map(|s| s.value).sum() - unspendable_supply += transacted.by_type.unspendable.opreturn.value - + height_to_unclaimed_rewards_iter.get_unwrap(height); - - opreturn_supply += transacted.by_type.unspendable.opreturn.value; - - if height == Height::ZERO { - transacted = Transacted::default(); - unspendable_supply += Sats::FIFTY_BTC; - } else if height == Height::new(BIP30_DUPLICATE_COINBASE_HEIGHT_1) - || height == Height::new(BIP30_DUPLICATE_COINBASE_HEIGHT_2) - { - if height == Height::new(BIP30_DUPLICATE_COINBASE_HEIGHT_1) { - height_to_sent - .entry(Height::new(BIP30_ORIGINAL_COINBASE_HEIGHT_1)) - .or_default() - } else { - height_to_sent - .entry(Height::new(BIP30_ORIGINAL_COINBASE_HEIGHT_2)) - .or_default() - } - .iterate(Sats::FIFTY_BTC, OutputType::P2PK65); - } - - // Push current block state before processing sends and receives - chain_state.push(BlockState { - supply: transacted.spendable_supply.clone(), - price, - timestamp, - }); - - self.utxo_cohorts.receive(transacted, height, price); - - self.utxo_cohorts.send(height_to_sent, &mut chain_state); - }); - - // Update txoutindex_to_txinindex - self.update_txoutindex_to_txinindex( - usize::from(output_count), - txoutindex_to_txinindex_updates, - )?; - - self.height_to_unspendable_supply - .truncate_push(height, unspendable_supply)?; - - self.height_to_opreturn_supply - .truncate_push(height, opreturn_supply)?; - - self.addresstype_to_height_to_addr_count - .truncate_push(height, &addresstype_to_addr_count)?; - - self.addresstype_to_height_to_empty_addr_count - .truncate_push(height, &addresstype_to_empty_addr_count)?; - - let date = height_to_date_fixed_iter.get_unwrap(height); - let dateindex = DateIndex::try_from(date).unwrap(); - let date_first_height = dateindex_to_first_height_iter.get_unwrap(dateindex); - let date_height_count = dateindex_to_height_count_iter.get_unwrap(dateindex); - let is_date_last_height = date_first_height - + Height::from(date_height_count).decremented().unwrap() - == height; - let date_price = dateindex_to_price_close_iter - .as_mut() - .map(|v| is_date_last_height.then(|| *v.get_unwrap(dateindex))); - - let dateindex = is_date_last_height.then_some(dateindex); - - self.utxo_cohorts - .par_iter_separate_mut() - .map(|v| v as &mut dyn DynCohortVecs) - .chain( - self.address_cohorts - .par_iter_separate_mut() - .map(|v| v as &mut dyn DynCohortVecs), - ) - .try_for_each(|v| { - v.truncate_push(height)?; - v.compute_then_truncate_push_unrealized_states( - height, price, dateindex, date_price, - ) - })?; - - // Compute and push percentiles for aggregate cohorts (all, sth, lth) - self.utxo_cohorts - .truncate_push_aggregate_percentiles(height)?; - - if height != last_height - && height != Height::ZERO - && height.to_usize() % FLUSH_INTERVAL == 0 - { - let _lock = exit.lock(); - - drop(vr); - - self.flush_states( - height, - &chain_state, - mem::take(&mut addresstype_to_typeindex_to_loadedaddressdata), - mem::take(&mut addresstype_to_typeindex_to_emptyaddressdata), - false, - exit, - )?; - - vr = VecsReaders::new(self); - } - } - - drop(vr); - - let _lock = exit.lock(); - self.flush_states( - height, - &chain_state, - mem::take(&mut addresstype_to_typeindex_to_loadedaddressdata), - mem::take(&mut addresstype_to_typeindex_to_emptyaddressdata), - true, - exit, - )?; - } - - info!("Computing overlapping..."); - - self.utxo_cohorts - .compute_overlapping_vecs(starting_indexes, exit)?; - - self.address_cohorts - .compute_overlapping_vecs(starting_indexes, exit)?; - - info!("Computing rest part 1..."); - - self.indexes_to_addr_count - .compute_all(indexes, starting_indexes, exit, |v| { - v.compute_sum_of_others( - starting_indexes.height, - &self - .addresstype_to_height_to_addr_count - .iter() - .map(|(_, v)| v) - .collect::>(), - exit, - )?; - Ok(()) - })?; - - self.indexes_to_empty_addr_count - .compute_all(indexes, starting_indexes, exit, |v| { - v.compute_sum_of_others( - starting_indexes.height, - &self - .addresstype_to_height_to_empty_addr_count - .iter() - .map(|(_, v)| v) - .collect::>(), - exit, - )?; - Ok(()) - })?; - - self.indexes_to_unspendable_supply.compute_rest( - indexes, - price, - starting_indexes, - exit, - Some(&self.height_to_unspendable_supply), - )?; - self.indexes_to_opreturn_supply.compute_rest( - indexes, - price, - starting_indexes, - exit, - Some(&self.height_to_opreturn_supply), - )?; - - self.addresstype_to_indexes_to_addr_count.compute( - indexes, - starting_indexes, - exit, - &self.addresstype_to_height_to_addr_count, - )?; - - self.addresstype_to_indexes_to_empty_addr_count.compute( - indexes, - starting_indexes, - exit, - &self.addresstype_to_height_to_empty_addr_count, - )?; - - self.utxo_cohorts - .compute_rest_part1(indexes, price, starting_indexes, exit)?; - - self.address_cohorts - .compute_rest_part1(indexes, price, starting_indexes, exit)?; - - if let Some(indexes_to_market_cap) = self.indexes_to_market_cap.as_mut() { - indexes_to_market_cap.compute_all(starting_indexes, exit, |v| { - v.compute_transform( - starting_indexes.dateindex, - self.utxo_cohorts - .all - .inner - .indexes_to_supply - .dollars - .as_ref() - .unwrap() - .dateindex - .as_ref() - .unwrap(), - |(i, v, ..)| (i, v), - exit, - )?; - Ok(()) - })?; - } - - info!("Computing rest part 2..."); - - let height_to_supply = &self - .utxo_cohorts - .all - .inner - .height_to_supply_value - .bitcoin - .clone(); - let dateindex_to_supply = self - .utxo_cohorts - .all - .inner - .indexes_to_supply - .bitcoin - .dateindex - .clone(); - let height_to_market_cap = self.height_to_market_cap.clone(); - let dateindex_to_market_cap = self - .indexes_to_market_cap - .as_ref() - .map(|v| v.dateindex.u().clone()); - let height_to_realized_cap = self.utxo_cohorts.all.inner.height_to_realized_cap.clone(); - let dateindex_to_realized_cap = self - .utxo_cohorts - .all - .inner - .indexes_to_realized_cap - .as_ref() - .map(|v| v.dateindex.unwrap_last().clone()); - let dateindex_to_supply_ref = dateindex_to_supply.u(); - let height_to_market_cap_ref = height_to_market_cap.as_ref(); - let dateindex_to_market_cap_ref = dateindex_to_market_cap.as_ref(); - let height_to_realized_cap_ref = height_to_realized_cap.as_ref(); - let dateindex_to_realized_cap_ref = dateindex_to_realized_cap.as_ref(); - - self.utxo_cohorts.compute_rest_part2( - indexes, - price, - starting_indexes, - height_to_supply, - dateindex_to_supply_ref, - height_to_market_cap_ref, - dateindex_to_market_cap_ref, - height_to_realized_cap_ref, - dateindex_to_realized_cap_ref, - exit, - )?; - - self.address_cohorts.compute_rest_part2( - indexes, - price, - starting_indexes, - height_to_supply, - dateindex_to_supply_ref, - height_to_market_cap_ref, - dateindex_to_market_cap_ref, - height_to_realized_cap_ref, - dateindex_to_realized_cap_ref, - exit, - )?; - - Ok(()) - } - - #[allow(clippy::too_many_arguments)] - fn get_addressdatawithsource( - address_type: OutputType, - typeindex: TypeIndex, - first_addressindexes: &ByAddressType, - addresstype_to_typeindex_to_loadedaddressdata: &AddressTypeToTypeIndexMap< - WithAddressDataSource, - >, - addresstype_to_typeindex_to_emptyaddressdata: &AddressTypeToTypeIndexMap< - WithAddressDataSource, - >, - vr: &VecsReaders, - any_address_indexes: &AnyAddressIndexesVecs, - addresses_data: &AddressesDataVecs, - ) -> Option> { - let first = *first_addressindexes.get(address_type).unwrap(); - if first <= typeindex { - return Some(WithAddressDataSource::New(LoadedAddressData::default())); - } - - if addresstype_to_typeindex_to_loadedaddressdata - .get(address_type) - .unwrap() - .contains_key(&typeindex) - || addresstype_to_typeindex_to_emptyaddressdata - .get(address_type) - .unwrap() - .contains_key(&typeindex) - { - return None; - } - - let reader = vr.get_anyaddressindex_reader(address_type); - - let anyaddressindex = - any_address_indexes.get_anyaddressindex(address_type, typeindex, reader); - - Some(match anyaddressindex.to_enum() { - AnyAddressDataIndexEnum::Loaded(loadedaddressindex) => { - let reader = &vr.anyaddressindex_to_anyaddressdata.loaded; - - let loadedaddressdata = addresses_data - .loaded - .get_pushed_or_read_unwrap(loadedaddressindex, reader); - - WithAddressDataSource::FromLoadedAddressDataVec(( - loadedaddressindex, - loadedaddressdata, - )) - } - AnyAddressDataIndexEnum::Empty(emtpyaddressindex) => { - let reader = &vr.anyaddressindex_to_anyaddressdata.empty; - - let emptyaddressdata = addresses_data - .empty - .get_pushed_or_read_unwrap(emtpyaddressindex, reader); - - WithAddressDataSource::FromEmptyAddressDataVec(( - emtpyaddressindex, - emptyaddressdata.into(), - )) - } - }) - } - - fn flush_states( - &mut self, - height: Height, - chain_state: &[BlockState], - addresstype_to_typeindex_to_loadedaddressdata: AddressTypeToTypeIndexMap< - WithAddressDataSource, - >, - addresstype_to_typeindex_to_emptyaddressdata: AddressTypeToTypeIndexMap< - WithAddressDataSource, - >, - with_changes: bool, - exit: &Exit, - ) -> Result<()> { - info!("Flushing..."); - - self.utxo_cohorts.safe_flush_stateful_vecs(height, exit)?; - self.address_cohorts - .safe_flush_stateful_vecs(height, exit)?; - self.height_to_unspendable_supply.safe_write(exit)?; - self.height_to_opreturn_supply.safe_write(exit)?; - self.addresstype_to_height_to_addr_count - .values_mut() - .try_for_each(|v| v.safe_flush(exit))?; - self.addresstype_to_height_to_empty_addr_count - .values_mut() - .try_for_each(|v| v.safe_flush(exit))?; - - let mut addresstype_to_typeindex_to_new_or_updated_anyaddressindex = - AddressTypeToTypeIndexMap::default(); - - for (address_type, sorted) in - addresstype_to_typeindex_to_emptyaddressdata.into_sorted_iter() - { - for (typeindex, emptyaddressdata_with_source) in sorted.into_iter() { - match emptyaddressdata_with_source { - WithAddressDataSource::New(emptyaddressdata) => { - let emptyaddressindex = self - .addresses_data - .empty - .fill_first_hole_or_push(emptyaddressdata)?; - - let anyaddressindex = AnyAddressIndex::from(emptyaddressindex); - - addresstype_to_typeindex_to_new_or_updated_anyaddressindex - .get_mut(address_type) - .unwrap() - .insert(typeindex, anyaddressindex); - } - WithAddressDataSource::FromEmptyAddressDataVec(( - emptyaddressindex, - emptyaddressdata, - )) => self - .addresses_data - .empty - .update(emptyaddressindex, emptyaddressdata)?, - WithAddressDataSource::FromLoadedAddressDataVec(( - loadedaddressindex, - emptyaddressdata, - )) => { - self.addresses_data.loaded.delete(loadedaddressindex); - - let emptyaddressindex = self - .addresses_data - .empty - .fill_first_hole_or_push(emptyaddressdata)?; - - let anyaddressindex = emptyaddressindex.into(); - - addresstype_to_typeindex_to_new_or_updated_anyaddressindex - .get_mut(address_type) - .unwrap() - .insert(typeindex, anyaddressindex); - } - } - } - } - - for (address_type, sorted) in - addresstype_to_typeindex_to_loadedaddressdata.into_sorted_iter() - { - for (typeindex, loadedaddressdata_with_source) in sorted.into_iter() { - match loadedaddressdata_with_source { - WithAddressDataSource::New(loadedaddressdata) => { - let loadedaddressindex = self - .addresses_data - .loaded - .fill_first_hole_or_push(loadedaddressdata)?; - - let anyaddressindex = AnyAddressIndex::from(loadedaddressindex); - - addresstype_to_typeindex_to_new_or_updated_anyaddressindex - .get_mut(address_type) - .unwrap() - .insert(typeindex, anyaddressindex); - } - WithAddressDataSource::FromLoadedAddressDataVec(( - loadedaddressindex, - loadedaddressdata, - )) => self - .addresses_data - .loaded - .update(loadedaddressindex, loadedaddressdata)?, - WithAddressDataSource::FromEmptyAddressDataVec(( - emptyaddressindex, - loadedaddressdata, - )) => { - self.addresses_data.empty.delete(emptyaddressindex); - - let loadedaddressindex = self - .addresses_data - .loaded - .fill_first_hole_or_push(loadedaddressdata)?; - - let anyaddressindex = loadedaddressindex.into(); - - addresstype_to_typeindex_to_new_or_updated_anyaddressindex - .get_mut(address_type) - .unwrap() - .insert(typeindex, anyaddressindex); - } - } - } - } - - for (address_type, sorted) in - addresstype_to_typeindex_to_new_or_updated_anyaddressindex.into_sorted_iter() - { - for (typeindex, anyaddressindex) in sorted { - self.any_address_indexes.update_or_push( - address_type, - typeindex, - anyaddressindex, - )?; - } - } - - let stamp = Stamp::from(height); - - self.txoutindex_to_txinindex - .stamped_flush_maybe_with_changes(stamp, with_changes)?; - self.any_address_indexes - .stamped_flush_maybe_with_changes(stamp, with_changes)?; - self.addresses_data - .stamped_flush_maybe_with_changes(stamp, with_changes)?; - - self.chain_state.truncate_if_needed(Height::ZERO)?; - chain_state.iter().for_each(|block_state| { - self.chain_state.push(block_state.supply.clone()); - }); - self.chain_state - .stamped_flush_maybe_with_changes(stamp, with_changes)?; - - Ok(()) - } - - /// Update txoutindex_to_txinindex for a block. - /// - /// 1. Push UNSPENT for all new outputs in the block - /// 2. Update spent outputs with their spending txinindex - pub fn update_txoutindex_to_txinindex( - &mut self, - output_count: usize, - updates: Vec<(TxOutIndex, TxInIndex)>, - ) -> Result<()> { - // Push UNSPENT for all new outputs in this block - for _ in 0..output_count { - self.txoutindex_to_txinindex.push(TxInIndex::UNSPENT); - } - // Update spent outputs with their spending txinindex - for (txoutindex, txinindex) in updates { - self.txoutindex_to_txinindex.update(txoutindex, txinindex)?; - } - Ok(()) - } -} diff --git a/crates/brk_computer/src/stateful_old/range_map.rs b/crates/brk_computer/src/stateful_old/range_map.rs deleted file mode 100644 index 87076dc32..000000000 --- a/crates/brk_computer/src/stateful_old/range_map.rs +++ /dev/null @@ -1,53 +0,0 @@ -use std::collections::BTreeMap; - -use vecdb::{BytesVec, BytesVecValue, PcoVec, PcoVecValue, VecIndex}; - -#[derive(Debug)] -pub struct RangeMap(BTreeMap); - -impl RangeMap -where - I: VecIndex, - T: VecIndex, -{ - pub fn get(&self, key: I) -> Option<&T> { - self.0.range(..=key).next_back().map(|(&min, value)| { - if min > key { - unreachable!() - } - value - }) - } -} - -impl From<&BytesVec> for RangeMap -where - I: VecIndex, - T: VecIndex + BytesVecValue, -{ - #[inline] - fn from(vec: &BytesVec) -> Self { - Self( - vec.into_iter() - .enumerate() - .map(|(i, v)| (v, I::from(i))) - .collect::>(), - ) - } -} - -impl From<&PcoVec> for RangeMap -where - I: VecIndex, - T: VecIndex + PcoVecValue, -{ - #[inline] - fn from(vec: &PcoVec) -> Self { - Self( - vec.into_iter() - .enumerate() - .map(|(i, v)| (v, I::from(i))) - .collect::>(), - ) - } -} diff --git a/crates/brk_computer/src/stateful_old/readers.rs b/crates/brk_computer/src/stateful_old/readers.rs deleted file mode 100644 index d63e54d43..000000000 --- a/crates/brk_computer/src/stateful_old/readers.rs +++ /dev/null @@ -1,111 +0,0 @@ -use brk_grouper::{ByAddressType, ByAnyAddress}; -use brk_indexer::Indexer; -use brk_types::{OutputType, StoredU64, TxIndex}; -use vecdb::{BoxedVecIterator, GenericStoredVec, Reader, VecIndex}; - -use super::Vecs; - -pub struct IndexerReaders { - pub txinindex_to_outpoint: Reader, - pub txindex_to_first_txoutindex: Reader, - pub txoutindex_to_value: Reader, - pub txoutindex_to_outputtype: Reader, - pub txoutindex_to_typeindex: Reader, -} - -impl IndexerReaders { - pub fn new(indexer: &Indexer) -> Self { - Self { - txinindex_to_outpoint: indexer.vecs.txin.txinindex_to_outpoint.create_reader(), - txindex_to_first_txoutindex: indexer.vecs.tx.txindex_to_first_txoutindex.create_reader(), - txoutindex_to_value: indexer.vecs.txout.txoutindex_to_value.create_reader(), - txoutindex_to_outputtype: indexer.vecs.txout.txoutindex_to_outputtype.create_reader(), - txoutindex_to_typeindex: indexer.vecs.txout.txoutindex_to_typeindex.create_reader(), - } - } -} - -pub struct VecsReaders { - pub addresstypeindex_to_anyaddressindex: ByAddressType, - pub anyaddressindex_to_anyaddressdata: ByAnyAddress, -} - -impl VecsReaders { - pub fn new(vecs: &Vecs) -> Self { - Self { - addresstypeindex_to_anyaddressindex: ByAddressType { - p2pk33: vecs.any_address_indexes.p2pk33.create_reader(), - p2pk65: vecs.any_address_indexes.p2pk65.create_reader(), - p2pkh: vecs.any_address_indexes.p2pkh.create_reader(), - p2sh: vecs.any_address_indexes.p2sh.create_reader(), - p2tr: vecs.any_address_indexes.p2tr.create_reader(), - p2wpkh: vecs.any_address_indexes.p2wpkh.create_reader(), - p2wsh: vecs.any_address_indexes.p2wsh.create_reader(), - p2a: vecs.any_address_indexes.p2a.create_reader(), - }, - anyaddressindex_to_anyaddressdata: ByAnyAddress { - loaded: vecs.addresses_data.loaded.create_reader(), - empty: vecs.addresses_data.empty.create_reader(), - }, - } - } - - pub fn get_anyaddressindex_reader(&self, address_type: OutputType) -> &Reader { - self.addresstypeindex_to_anyaddressindex - .get_unwrap(address_type) - } -} - -pub fn build_txoutindex_to_txindex<'a>( - block_first_txindex: TxIndex, - block_tx_count: u64, - txindex_to_output_count: &mut BoxedVecIterator<'a, TxIndex, StoredU64>, -) -> Vec { - let block_first_txindex = block_first_txindex.to_usize(); - - let counts: Vec<_> = (0..block_tx_count as usize) - .map(|tx_offset| { - let txindex = TxIndex::from(block_first_txindex + tx_offset); - u64::from(txindex_to_output_count.get_unwrap(txindex)) - }) - .collect(); - - let total: u64 = counts.iter().sum(); - let mut vec = Vec::with_capacity(total as usize); - - for (tx_offset, &output_count) in counts.iter().enumerate() { - let txindex = TxIndex::from(block_first_txindex + tx_offset); - for _ in 0..output_count { - vec.push(txindex); - } - } - - vec -} - -pub fn build_txinindex_to_txindex<'a>( - block_first_txindex: TxIndex, - block_tx_count: u64, - txindex_to_input_count: &mut BoxedVecIterator<'a, TxIndex, StoredU64>, -) -> Vec { - let block_first_txindex = block_first_txindex.to_usize(); - - let counts: Vec<_> = (0..block_tx_count as usize) - .map(|tx_offset| { - let txindex = TxIndex::from(block_first_txindex + tx_offset); - u64::from(txindex_to_input_count.get_unwrap(txindex)) - }) - .collect(); - - let total: u64 = counts.iter().sum(); - let mut vec = Vec::with_capacity(total as usize); - - for (tx_offset, &input_count) in counts.iter().enumerate() { - let txindex = TxIndex::from(block_first_txindex + tx_offset); - for _ in 0..input_count { - vec.push(txindex); - } - } - - vec -} diff --git a/crates/brk_computer/src/stateful_old/trait.rs b/crates/brk_computer/src/stateful_old/trait.rs deleted file mode 100644 index 64005bbae..000000000 --- a/crates/brk_computer/src/stateful_old/trait.rs +++ /dev/null @@ -1,59 +0,0 @@ -use brk_error::Result; -use brk_types::{Bitcoin, DateIndex, Dollars, Height, Version}; -use vecdb::{Exit, IterableVec}; - -use crate::{Indexes, indexes, price}; - -pub trait DynCohortVecs: Send + Sync { - fn min_height_vecs_len(&self) -> usize; - fn reset_state_starting_height(&mut self); - - fn import_state(&mut self, starting_height: Height) -> Result; - - fn validate_computed_versions(&mut self, base_version: Version) -> Result<()>; - - fn truncate_push(&mut self, height: Height) -> Result<()>; - - fn compute_then_truncate_push_unrealized_states( - &mut self, - height: Height, - height_price: Option, - dateindex: Option, - date_price: Option>, - ) -> Result<()>; - - fn safe_flush_stateful_vecs(&mut self, height: Height, exit: &Exit) -> Result<()>; - - #[allow(clippy::too_many_arguments)] - fn compute_rest_part1( - &mut self, - indexes: &indexes::Vecs, - price: Option<&price::Vecs>, - starting_indexes: &Indexes, - exit: &Exit, - ) -> Result<()>; -} - -pub trait CohortVecs: DynCohortVecs { - fn compute_from_stateful( - &mut self, - starting_indexes: &Indexes, - others: &[&Self], - exit: &Exit, - ) -> Result<()>; - - #[allow(clippy::too_many_arguments)] - fn compute_rest_part2( - &mut self, - indexes: &indexes::Vecs, - price: Option<&price::Vecs>, - starting_indexes: &Indexes, - height_to_supply: &impl IterableVec, - dateindex_to_supply: &impl IterableVec, - height_to_market_cap: Option<&impl IterableVec>, - dateindex_to_market_cap: Option<&impl IterableVec>, - height_to_realized_cap: Option<&impl IterableVec>, - dateindex_to_realized_cap: Option<&impl IterableVec>, - exit: &Exit, - ) -> Result<()>; -} diff --git a/crates/brk_computer/src/stateful_old/transaction_processing.rs b/crates/brk_computer/src/stateful_old/transaction_processing.rs deleted file mode 100644 index c425c0d0d..000000000 --- a/crates/brk_computer/src/stateful_old/transaction_processing.rs +++ /dev/null @@ -1,217 +0,0 @@ -use brk_error::Result; -use brk_grouper::{ByAddressType, Filtered}; -use brk_types::{ - CheckedSub, Dollars, EmptyAddressData, Height, LoadedAddressData, Sats, Timestamp, TypeIndex, -}; -use vecdb::VecIndex; - -use crate::utils::OptionExt; - -use super::{ - address_cohorts, - addresstype::{AddressTypeToTypeIndexMap, AddressTypeToVec, HeightToAddressTypeToVec}, - withaddressdatasource::WithAddressDataSource, -}; - -impl AddressTypeToVec<(TypeIndex, Sats)> { - #[allow(clippy::too_many_arguments)] - pub fn process_received( - self, - vecs: &mut address_cohorts::Vecs, - addresstype_to_typeindex_to_loadedaddressdata: &mut AddressTypeToTypeIndexMap< - WithAddressDataSource, - >, - addresstype_to_typeindex_to_emptyaddressdata: &mut AddressTypeToTypeIndexMap< - WithAddressDataSource, - >, - price: Option, - addresstype_to_addr_count: &mut ByAddressType, - addresstype_to_empty_addr_count: &mut ByAddressType, - stored_or_new_addresstype_to_typeindex_to_addressdatawithsource: &mut AddressTypeToTypeIndexMap< - WithAddressDataSource, - >, - ) { - self.unwrap().into_iter().for_each(|(_type, vec)| { - vec.into_iter().for_each(|(type_index, value)| { - let mut is_new = false; - let mut from_any_empty = false; - - let addressdata_withsource = addresstype_to_typeindex_to_loadedaddressdata - .get_mut(_type) - .unwrap() - .entry(type_index) - .or_insert_with(|| { - addresstype_to_typeindex_to_emptyaddressdata - .get_mut(_type) - .unwrap() - .remove(&type_index) - .map(|ad| { - from_any_empty = true; - ad.into() - }) - .unwrap_or_else(|| { - let addressdata = - stored_or_new_addresstype_to_typeindex_to_addressdatawithsource - .remove_for_type(_type, &type_index); - is_new = addressdata.is_new(); - from_any_empty = addressdata.is_from_emptyaddressdata(); - addressdata - }) - }); - - if is_new || from_any_empty { - (*addresstype_to_addr_count.get_mut(_type).unwrap()) += 1; - if from_any_empty { - (*addresstype_to_empty_addr_count.get_mut(_type).unwrap()) -= 1; - } - } - - let addressdata = addressdata_withsource.deref_mut(); - - let prev_amount = addressdata.balance(); - - let amount = prev_amount + value; - - let filters_differ = vecs.amount_range.get(amount).filter() - != vecs.amount_range.get(prev_amount).filter(); - - if is_new || from_any_empty || filters_differ { - if !is_new && !from_any_empty { - vecs.amount_range - .get_mut(prev_amount) - .state - .um() - .subtract(addressdata); - } - - addressdata.receive(value, price); - - vecs.amount_range - .get_mut(amount) - .state - .um() - .add(addressdata); - } else { - vecs.amount_range - .get_mut(amount) - .state - .um() - .receive(addressdata, value, price); - } - }); - }); - } -} - -impl HeightToAddressTypeToVec<(TypeIndex, Sats)> { - #[allow(clippy::too_many_arguments)] - pub fn process_sent( - self, - vecs: &mut address_cohorts::Vecs, - addresstype_to_typeindex_to_loadedaddressdata: &mut AddressTypeToTypeIndexMap< - WithAddressDataSource, - >, - addresstype_to_typeindex_to_emptyaddressdata: &mut AddressTypeToTypeIndexMap< - WithAddressDataSource, - >, - price: Option, - addresstype_to_addr_count: &mut ByAddressType, - addresstype_to_empty_addr_count: &mut ByAddressType, - height_to_price_close_vec: Option<&Vec>>, - height_to_timestamp_fixed_vec: &[Timestamp], - height: Height, - timestamp: Timestamp, - stored_or_new_addresstype_to_typeindex_to_addressdatawithsource: &mut AddressTypeToTypeIndexMap< - WithAddressDataSource, - >, - ) -> Result<()> { - self.0.into_iter().try_for_each(|(prev_height, v)| { - let prev_price = height_to_price_close_vec - .as_ref() - .map(|v| **v.get(prev_height.to_usize()).unwrap()); - - let prev_timestamp = *height_to_timestamp_fixed_vec - .get(prev_height.to_usize()) - .unwrap(); - - let blocks_old = height.to_usize() - prev_height.to_usize(); - - let days_old = timestamp.difference_in_days_between_float(prev_timestamp); - - let older_than_hour = timestamp - .checked_sub(prev_timestamp) - .unwrap() - .is_more_than_hour(); - - v.unwrap().into_iter().try_for_each(|(_type, vec)| { - vec.into_iter().try_for_each(|(type_index, value)| { - let typeindex_to_loadedaddressdata = - addresstype_to_typeindex_to_loadedaddressdata.get_mut_unwrap(_type); - - let addressdata_withsource = typeindex_to_loadedaddressdata - .entry(type_index) - .or_insert_with(|| { - stored_or_new_addresstype_to_typeindex_to_addressdatawithsource - .remove_for_type(_type, &type_index) - }); - - let addressdata = addressdata_withsource.deref_mut(); - - let prev_amount = addressdata.balance(); - - let amount = prev_amount.checked_sub(value).unwrap(); - - let will_be_empty = addressdata.has_1_utxos(); - - let filters_differ = vecs.amount_range.get(amount).filter() - != vecs.amount_range.get(prev_amount).filter(); - - if will_be_empty || filters_differ { - vecs.amount_range - .get_mut(prev_amount) - .state - .um() - .subtract(addressdata); - - addressdata.send(value, prev_price)?; - - if will_be_empty { - if amount.is_not_zero() { - unreachable!() - } - - (*addresstype_to_addr_count.get_mut(_type).unwrap()) -= 1; - (*addresstype_to_empty_addr_count.get_mut(_type).unwrap()) += 1; - - let addressdata = - typeindex_to_loadedaddressdata.remove(&type_index).unwrap(); - - addresstype_to_typeindex_to_emptyaddressdata - .get_mut(_type) - .unwrap() - .insert(type_index, addressdata.into()); - } else { - vecs.amount_range - .get_mut(amount) - .state - .um() - .add(addressdata); - } - } else { - vecs.amount_range.get_mut(amount).state.um().send( - addressdata, - value, - price, - prev_price, - blocks_old, - days_old, - older_than_hour, - )?; - } - - Ok(()) - }) - }) - }) - } -} diff --git a/crates/brk_computer/src/stateful_old/utxo_cohort.rs b/crates/brk_computer/src/stateful_old/utxo_cohort.rs deleted file mode 100644 index aebe57d9b..000000000 --- a/crates/brk_computer/src/stateful_old/utxo_cohort.rs +++ /dev/null @@ -1,241 +0,0 @@ -use std::{ops::Deref, path::Path}; - -use brk_error::Result; -use brk_grouper::{CohortContext, Filter, Filtered, StateLevel}; -use brk_traversable::Traversable; -use brk_types::{Bitcoin, DateIndex, Dollars, Height, Sats, Version}; -use vecdb::{Database, Exit, IterableVec}; - -use crate::{ - Indexes, PriceToAmount, UTXOCohortState, - grouped::{PERCENTILES, PERCENTILES_LEN}, - indexes, price, - stateful::{ - common, - r#trait::{CohortVecs, DynCohortVecs}, - }, - utils::OptionExt, -}; - -#[derive(Clone, Traversable)] -pub struct Vecs { - state_starting_height: Option, - - #[traversable(skip)] - pub state: Option, - - /// For aggregate cohorts (all, sth, lth) that only need price_to_amount for percentiles - #[traversable(skip)] - pub price_to_amount: Option, - - #[traversable(flatten)] - pub inner: common::Vecs, -} - -impl Vecs { - pub fn forced_import( - db: &Database, - filter: Filter, - version: Version, - indexes: &indexes::Vecs, - price: Option<&price::Vecs>, - states_path: &Path, - state_level: StateLevel, - ) -> Result { - let compute_dollars = price.is_some(); - - let full_name = filter.to_full_name(CohortContext::Utxo); - - Ok(Self { - state_starting_height: None, - - state: if state_level.is_full() { - Some(UTXOCohortState::new( - states_path, - &full_name, - compute_dollars, - )) - } else { - None - }, - - price_to_amount: if state_level.is_price_only() && compute_dollars { - Some(PriceToAmount::create(states_path, &full_name)) - } else { - None - }, - - inner: common::Vecs::forced_import( - db, - filter, - CohortContext::Utxo, - version, - indexes, - price, - )?, - }) - } -} - -impl DynCohortVecs for Vecs { - fn min_height_vecs_len(&self) -> usize { - self.inner.min_height_vecs_len() - } - - fn reset_state_starting_height(&mut self) { - self.state_starting_height = Some(Height::ZERO); - } - - fn import_state(&mut self, starting_height: Height) -> Result { - let starting_height = self - .inner - .import_state(starting_height, self.state.um())?; - - self.state_starting_height = Some(starting_height); - - Ok(starting_height) - } - - fn validate_computed_versions(&mut self, base_version: Version) -> Result<()> { - self.inner.validate_computed_versions(base_version) - } - - fn truncate_push(&mut self, height: Height) -> Result<()> { - if self.state_starting_height.unwrap() > height { - return Ok(()); - } - - self.inner - .truncate_push(height, self.state.u()) - } - - fn compute_then_truncate_push_unrealized_states( - &mut self, - height: Height, - height_price: Option, - dateindex: Option, - date_price: Option>, - ) -> Result<()> { - self.inner.compute_then_truncate_push_unrealized_states( - height, - height_price, - dateindex, - date_price, - self.state.um(), - ) - } - - fn safe_flush_stateful_vecs(&mut self, height: Height, exit: &Exit) -> Result<()> { - self.inner - .safe_flush_stateful_vecs(height, exit, self.state.um()) - } - - #[allow(clippy::too_many_arguments)] - fn compute_rest_part1( - &mut self, - indexes: &indexes::Vecs, - price: Option<&price::Vecs>, - starting_indexes: &Indexes, - exit: &Exit, - ) -> Result<()> { - self.inner - .compute_rest_part1(indexes, price, starting_indexes, exit) - } -} - -impl CohortVecs for Vecs { - fn compute_from_stateful( - &mut self, - starting_indexes: &Indexes, - others: &[&Self], - exit: &Exit, - ) -> Result<()> { - self.inner.compute_from_stateful( - starting_indexes, - &others.iter().map(|v| &v.inner).collect::>(), - exit, - ) - } - - #[allow(clippy::too_many_arguments)] - fn compute_rest_part2( - &mut self, - indexes: &indexes::Vecs, - price: Option<&price::Vecs>, - starting_indexes: &Indexes, - height_to_supply: &impl IterableVec, - dateindex_to_supply: &impl IterableVec, - height_to_market_cap: Option<&impl IterableVec>, - dateindex_to_market_cap: Option<&impl IterableVec>, - height_to_realized_cap: Option<&impl IterableVec>, - dateindex_to_realized_cap: Option<&impl IterableVec>, - exit: &Exit, - ) -> Result<()> { - self.inner.compute_rest_part2( - indexes, - price, - starting_indexes, - height_to_supply, - dateindex_to_supply, - height_to_market_cap, - dateindex_to_market_cap, - height_to_realized_cap, - dateindex_to_realized_cap, - exit, - ) - } -} - -impl Vecs { - /// Compute percentile prices for aggregate cohorts that have standalone price_to_amount. - /// Returns NaN array if price_to_amount is None or empty. - pub fn compute_percentile_prices_from_standalone( - &self, - supply: Sats, - ) -> [Dollars; PERCENTILES_LEN] { - let mut result = [Dollars::NAN; PERCENTILES_LEN]; - - let price_to_amount = match self.price_to_amount.as_ref() { - Some(p) => p, - None => return result, - }; - - if price_to_amount.is_empty() || supply == Sats::ZERO { - return result; - } - - let total = supply; - let targets = PERCENTILES.map(|p| total * p / 100); - - let mut accumulated = Sats::ZERO; - let mut pct_idx = 0; - - for (&price, &sats) in price_to_amount.iter() { - accumulated += sats; - - while pct_idx < PERCENTILES_LEN && accumulated >= targets[pct_idx] { - result[pct_idx] = price; - pct_idx += 1; - } - - if pct_idx >= PERCENTILES_LEN { - break; - } - } - - result - } -} - -impl Deref for Vecs { - type Target = common::Vecs; - fn deref(&self) -> &Self::Target { - &self.inner - } -} - -impl Filtered for Vecs { - fn filter(&self) -> &Filter { - &self.inner.filter - } -} diff --git a/crates/brk_computer/src/stateful_old/utxo_cohorts.rs b/crates/brk_computer/src/stateful_old/utxo_cohorts.rs deleted file mode 100644 index 459acc8ce..000000000 --- a/crates/brk_computer/src/stateful_old/utxo_cohorts.rs +++ /dev/null @@ -1,697 +0,0 @@ -use std::path::Path; - -use brk_error::Result; -use brk_grouper::{ - AmountFilter, ByAgeRange, ByAmountRange, ByEpoch, ByGreatEqualAmount, ByLowerThanAmount, - ByMaxAge, ByMinAge, BySpendableType, ByTerm, Filter, Filtered, StateLevel, Term, TimeFilter, - UTXOGroups, -}; -use brk_traversable::Traversable; -use brk_types::{ - Bitcoin, CheckedSub, DateIndex, Dollars, HalvingEpoch, Height, ONE_DAY_IN_SEC, OutputType, - Sats, Timestamp, Version, -}; -use derive_deref::{Deref, DerefMut}; -use rayon::prelude::*; -use rustc_hash::FxHashMap; -use vecdb::{Database, Exit, IterableVec, VecIndex}; - -use crate::{ - Indexes, indexes, price, - stateful::{Flushable, HeightFlushable, r#trait::DynCohortVecs}, - states::{BlockState, Transacted}, - utils::OptionExt, -}; - -use super::{r#trait::CohortVecs, utxo_cohort}; - -const VERSION: Version = Version::new(0); - -#[derive(Clone, Deref, DerefMut, Traversable)] -pub struct Vecs(UTXOGroups); - -impl Vecs { - pub fn forced_import( - db: &Database, - version: Version, - indexes: &indexes::Vecs, - price: Option<&price::Vecs>, - states_path: &Path, - ) -> Result { - let v = version + VERSION + Version::ZERO; - - // Helper to create a cohort - booleans are now derived from filter - let create = |filter: Filter, state_level: StateLevel| -> Result { - utxo_cohort::Vecs::forced_import( - db, - filter, - v, - indexes, - price, - states_path, - state_level, - ) - }; - - let full = |f: Filter| create(f, StateLevel::Full); - let none = |f: Filter| create(f, StateLevel::None); - - Ok(Self(UTXOGroups { - // Special case: all uses Version::ONE - all: utxo_cohort::Vecs::forced_import( - db, - Filter::All, - version + VERSION + Version::ONE, - indexes, - price, - states_path, - StateLevel::PriceOnly, - )?, - - term: ByTerm { - short: create(Filter::Term(Term::Sth), StateLevel::PriceOnly)?, - long: create(Filter::Term(Term::Lth), StateLevel::PriceOnly)?, - }, - - epoch: ByEpoch { - _0: full(Filter::Epoch(HalvingEpoch::new(0)))?, - _1: full(Filter::Epoch(HalvingEpoch::new(1)))?, - _2: full(Filter::Epoch(HalvingEpoch::new(2)))?, - _3: full(Filter::Epoch(HalvingEpoch::new(3)))?, - _4: full(Filter::Epoch(HalvingEpoch::new(4)))?, - }, - - type_: BySpendableType { - p2pk65: full(Filter::Type(OutputType::P2PK65))?, - p2pk33: full(Filter::Type(OutputType::P2PK33))?, - p2pkh: full(Filter::Type(OutputType::P2PKH))?, - p2sh: full(Filter::Type(OutputType::P2SH))?, - p2wpkh: full(Filter::Type(OutputType::P2WPKH))?, - p2wsh: full(Filter::Type(OutputType::P2WSH))?, - p2tr: full(Filter::Type(OutputType::P2TR))?, - p2a: full(Filter::Type(OutputType::P2A))?, - p2ms: full(Filter::Type(OutputType::P2MS))?, - empty: full(Filter::Type(OutputType::Empty))?, - unknown: full(Filter::Type(OutputType::Unknown))?, - }, - - max_age: ByMaxAge { - _1w: none(Filter::Time(TimeFilter::LowerThan(7)))?, - _1m: none(Filter::Time(TimeFilter::LowerThan(30)))?, - _2m: none(Filter::Time(TimeFilter::LowerThan(2 * 30)))?, - _3m: none(Filter::Time(TimeFilter::LowerThan(3 * 30)))?, - _4m: none(Filter::Time(TimeFilter::LowerThan(4 * 30)))?, - _5m: none(Filter::Time(TimeFilter::LowerThan(5 * 30)))?, - _6m: none(Filter::Time(TimeFilter::LowerThan(6 * 30)))?, - _1y: none(Filter::Time(TimeFilter::LowerThan(365)))?, - _2y: none(Filter::Time(TimeFilter::LowerThan(2 * 365)))?, - _3y: none(Filter::Time(TimeFilter::LowerThan(3 * 365)))?, - _4y: none(Filter::Time(TimeFilter::LowerThan(4 * 365)))?, - _5y: none(Filter::Time(TimeFilter::LowerThan(5 * 365)))?, - _6y: none(Filter::Time(TimeFilter::LowerThan(6 * 365)))?, - _7y: none(Filter::Time(TimeFilter::LowerThan(7 * 365)))?, - _8y: none(Filter::Time(TimeFilter::LowerThan(8 * 365)))?, - _10y: none(Filter::Time(TimeFilter::LowerThan(10 * 365)))?, - _12y: none(Filter::Time(TimeFilter::LowerThan(12 * 365)))?, - _15y: none(Filter::Time(TimeFilter::LowerThan(15 * 365)))?, - }, - - min_age: ByMinAge { - _1d: none(Filter::Time(TimeFilter::GreaterOrEqual(1)))?, - _1w: none(Filter::Time(TimeFilter::GreaterOrEqual(7)))?, - _1m: none(Filter::Time(TimeFilter::GreaterOrEqual(30)))?, - _2m: none(Filter::Time(TimeFilter::GreaterOrEqual(2 * 30)))?, - _3m: none(Filter::Time(TimeFilter::GreaterOrEqual(3 * 30)))?, - _4m: none(Filter::Time(TimeFilter::GreaterOrEqual(4 * 30)))?, - _5m: none(Filter::Time(TimeFilter::GreaterOrEqual(5 * 30)))?, - _6m: none(Filter::Time(TimeFilter::GreaterOrEqual(6 * 30)))?, - _1y: none(Filter::Time(TimeFilter::GreaterOrEqual(365)))?, - _2y: none(Filter::Time(TimeFilter::GreaterOrEqual(2 * 365)))?, - _3y: none(Filter::Time(TimeFilter::GreaterOrEqual(3 * 365)))?, - _4y: none(Filter::Time(TimeFilter::GreaterOrEqual(4 * 365)))?, - _5y: none(Filter::Time(TimeFilter::GreaterOrEqual(5 * 365)))?, - _6y: none(Filter::Time(TimeFilter::GreaterOrEqual(6 * 365)))?, - _7y: none(Filter::Time(TimeFilter::GreaterOrEqual(7 * 365)))?, - _8y: none(Filter::Time(TimeFilter::GreaterOrEqual(8 * 365)))?, - _10y: none(Filter::Time(TimeFilter::GreaterOrEqual(10 * 365)))?, - _12y: none(Filter::Time(TimeFilter::GreaterOrEqual(12 * 365)))?, - }, - - age_range: ByAgeRange { - up_to_1d: full(Filter::Time(TimeFilter::Range(0..1)))?, - _1d_to_1w: full(Filter::Time(TimeFilter::Range(1..7)))?, - _1w_to_1m: full(Filter::Time(TimeFilter::Range(7..30)))?, - _1m_to_2m: full(Filter::Time(TimeFilter::Range(30..60)))?, - _2m_to_3m: full(Filter::Time(TimeFilter::Range(60..90)))?, - _3m_to_4m: full(Filter::Time(TimeFilter::Range(90..120)))?, - _4m_to_5m: full(Filter::Time(TimeFilter::Range(120..150)))?, - _5m_to_6m: full(Filter::Time(TimeFilter::Range(150..180)))?, - _6m_to_1y: full(Filter::Time(TimeFilter::Range(180..365)))?, - _1y_to_2y: full(Filter::Time(TimeFilter::Range(365..730)))?, - _2y_to_3y: full(Filter::Time(TimeFilter::Range(730..1095)))?, - _3y_to_4y: full(Filter::Time(TimeFilter::Range(1095..1460)))?, - _4y_to_5y: full(Filter::Time(TimeFilter::Range(1460..1825)))?, - _5y_to_6y: full(Filter::Time(TimeFilter::Range(1825..2190)))?, - _6y_to_7y: full(Filter::Time(TimeFilter::Range(2190..2555)))?, - _7y_to_8y: full(Filter::Time(TimeFilter::Range(2555..2920)))?, - _8y_to_10y: full(Filter::Time(TimeFilter::Range(2920..3650)))?, - _10y_to_12y: full(Filter::Time(TimeFilter::Range(3650..4380)))?, - _12y_to_15y: full(Filter::Time(TimeFilter::Range(4380..5475)))?, - from_15y: full(Filter::Time(TimeFilter::GreaterOrEqual(15 * 365)))?, - }, - - amount_range: ByAmountRange { - _0sats: full(Filter::Amount(AmountFilter::LowerThan(Sats::_1)))?, - _1sat_to_10sats: full(Filter::Amount(AmountFilter::Range(Sats::_1..Sats::_10)))?, - _10sats_to_100sats: full(Filter::Amount(AmountFilter::Range( - Sats::_10..Sats::_100, - )))?, - _100sats_to_1k_sats: full(Filter::Amount(AmountFilter::Range( - Sats::_100..Sats::_1K, - )))?, - _1k_sats_to_10k_sats: full(Filter::Amount(AmountFilter::Range( - Sats::_1K..Sats::_10K, - )))?, - _10k_sats_to_100k_sats: full(Filter::Amount(AmountFilter::Range( - Sats::_10K..Sats::_100K, - )))?, - _100k_sats_to_1m_sats: full(Filter::Amount(AmountFilter::Range( - Sats::_100K..Sats::_1M, - )))?, - _1m_sats_to_10m_sats: full(Filter::Amount(AmountFilter::Range( - Sats::_1M..Sats::_10M, - )))?, - _10m_sats_to_1btc: full(Filter::Amount(AmountFilter::Range( - Sats::_10M..Sats::_1BTC, - )))?, - _1btc_to_10btc: full(Filter::Amount(AmountFilter::Range( - Sats::_1BTC..Sats::_10BTC, - )))?, - _10btc_to_100btc: full(Filter::Amount(AmountFilter::Range( - Sats::_10BTC..Sats::_100BTC, - )))?, - _100btc_to_1k_btc: full(Filter::Amount(AmountFilter::Range( - Sats::_100BTC..Sats::_1K_BTC, - )))?, - _1k_btc_to_10k_btc: full(Filter::Amount(AmountFilter::Range( - Sats::_1K_BTC..Sats::_10K_BTC, - )))?, - _10k_btc_to_100k_btc: full(Filter::Amount(AmountFilter::Range( - Sats::_10K_BTC..Sats::_100K_BTC, - )))?, - _100k_btc_or_more: full(Filter::Amount(AmountFilter::GreaterOrEqual( - Sats::_100K_BTC, - )))?, - }, - - lt_amount: ByLowerThanAmount { - _10sats: none(Filter::Amount(AmountFilter::LowerThan(Sats::_10)))?, - _100sats: none(Filter::Amount(AmountFilter::LowerThan(Sats::_100)))?, - _1k_sats: none(Filter::Amount(AmountFilter::LowerThan(Sats::_1K)))?, - _10k_sats: none(Filter::Amount(AmountFilter::LowerThan(Sats::_10K)))?, - _100k_sats: none(Filter::Amount(AmountFilter::LowerThan(Sats::_100K)))?, - _1m_sats: none(Filter::Amount(AmountFilter::LowerThan(Sats::_1M)))?, - _10m_sats: none(Filter::Amount(AmountFilter::LowerThan(Sats::_10M)))?, - _1btc: none(Filter::Amount(AmountFilter::LowerThan(Sats::_1BTC)))?, - _10btc: none(Filter::Amount(AmountFilter::LowerThan(Sats::_10BTC)))?, - _100btc: none(Filter::Amount(AmountFilter::LowerThan(Sats::_100BTC)))?, - _1k_btc: none(Filter::Amount(AmountFilter::LowerThan(Sats::_1K_BTC)))?, - _10k_btc: none(Filter::Amount(AmountFilter::LowerThan(Sats::_10K_BTC)))?, - _100k_btc: none(Filter::Amount(AmountFilter::LowerThan(Sats::_100K_BTC)))?, - }, - - ge_amount: ByGreatEqualAmount { - _1sat: none(Filter::Amount(AmountFilter::GreaterOrEqual(Sats::_1)))?, - _10sats: none(Filter::Amount(AmountFilter::GreaterOrEqual(Sats::_10)))?, - _100sats: none(Filter::Amount(AmountFilter::GreaterOrEqual(Sats::_100)))?, - _1k_sats: none(Filter::Amount(AmountFilter::GreaterOrEqual(Sats::_1K)))?, - _10k_sats: none(Filter::Amount(AmountFilter::GreaterOrEqual(Sats::_10K)))?, - _100k_sats: none(Filter::Amount(AmountFilter::GreaterOrEqual(Sats::_100K)))?, - _1m_sats: none(Filter::Amount(AmountFilter::GreaterOrEqual(Sats::_1M)))?, - _10m_sats: none(Filter::Amount(AmountFilter::GreaterOrEqual(Sats::_10M)))?, - _1btc: none(Filter::Amount(AmountFilter::GreaterOrEqual(Sats::_1BTC)))?, - _10btc: none(Filter::Amount(AmountFilter::GreaterOrEqual(Sats::_10BTC)))?, - _100btc: none(Filter::Amount(AmountFilter::GreaterOrEqual(Sats::_100BTC)))?, - _1k_btc: none(Filter::Amount(AmountFilter::GreaterOrEqual(Sats::_1K_BTC)))?, - _10k_btc: none(Filter::Amount(AmountFilter::GreaterOrEqual(Sats::_10K_BTC)))?, - }, - })) - } - - pub fn tick_tock_next_block(&mut self, chain_state: &[BlockState], timestamp: Timestamp) { - if chain_state.is_empty() { - return; - } - - let prev_timestamp = chain_state.last().unwrap().timestamp; - - // Only blocks whose age % ONE_DAY >= threshold can cross a day boundary. - // Saves 1 subtraction + 2 divisions per block vs computing days_old directly. - let elapsed = (*timestamp).saturating_sub(*prev_timestamp); - let threshold = ONE_DAY_IN_SEC.saturating_sub(elapsed); - - // Extract all mutable references upfront to avoid borrow checker issues - // Use a single destructuring to get non-overlapping mutable borrows - let UTXOGroups { - all, - term, - age_range, - .. - } = &mut self.0; - - let mut vecs = age_range - .iter_mut() - .map(|v| (v.filter().clone(), &mut v.state)) - .collect::>(); - - // Collect aggregate cohorts' filter and p2a for age transitions - let mut aggregate_p2a: Vec<(Filter, Option<&mut crate::PriceToAmount>)> = vec![ - (all.filter().clone(), all.price_to_amount.as_mut()), - ( - term.short.filter().clone(), - term.short.price_to_amount.as_mut(), - ), - ( - term.long.filter().clone(), - term.long.price_to_amount.as_mut(), - ), - ]; - - chain_state - .iter() - .filter(|block_state| { - let age = (*prev_timestamp).saturating_sub(*block_state.timestamp); - age % ONE_DAY_IN_SEC >= threshold - }) - .for_each(|block_state| { - let prev_days_old = - prev_timestamp.difference_in_days_between(block_state.timestamp); - let days_old = timestamp.difference_in_days_between(block_state.timestamp); - - if prev_days_old == days_old { - return; - } - - vecs.iter_mut().for_each(|(filter, state)| { - let is = filter.contains_time(days_old); - let was = filter.contains_time(prev_days_old); - - if is && !was { - state - .as_mut() - .unwrap() - .increment(&block_state.supply, block_state.price); - } else if was && !is { - state - .as_mut() - .unwrap() - .decrement(&block_state.supply, block_state.price); - } - }); - - // Handle age transitions for aggregate cohorts' price_to_amount - // Check which cohorts the UTXO was in vs is now in, and increment/decrement accordingly - // Only process if there's remaining supply (like CohortState::increment/decrement do) - if let Some(price) = block_state.price - && block_state.supply.value > Sats::ZERO - { - aggregate_p2a.iter_mut().for_each(|(filter, p2a)| { - let is = filter.contains_time(days_old); - let was = filter.contains_time(prev_days_old); - - if is && !was { - p2a.um().increment(price, &block_state.supply); - } else if was && !is { - p2a.um().decrement(price, &block_state.supply); - } - }); - } - }); - } - - pub fn send( - &mut self, - height_to_sent: FxHashMap, - chain_state: &mut [BlockState], - ) { - // Extract all mutable references upfront to avoid borrow checker issues - let UTXOGroups { - all, - term, - age_range, - epoch, - type_, - amount_range, - .. - } = &mut self.0; - - let mut time_based_vecs = age_range - .iter_mut() - .chain(epoch.iter_mut()) - .collect::>(); - - // Collect aggregate cohorts' filter and p2a for iteration - let mut aggregate_p2a: Vec<(Filter, Option<&mut crate::PriceToAmount>)> = vec![ - (all.filter().clone(), all.price_to_amount.as_mut()), - ( - term.short.filter().clone(), - term.short.price_to_amount.as_mut(), - ), - ( - term.long.filter().clone(), - term.long.price_to_amount.as_mut(), - ), - ]; - - let last_block = chain_state.last().unwrap(); - let last_timestamp = last_block.timestamp; - let current_price = last_block.price; - - let chain_state_len = chain_state.len(); - - height_to_sent.into_iter().for_each(|(height, sent)| { - chain_state[height.to_usize()].supply -= &sent.spendable_supply; - - let block_state = chain_state.get(height.to_usize()).unwrap(); - - let prev_price = block_state.price; - - let blocks_old = chain_state_len - 1 - height.to_usize(); - - let days_old = last_timestamp.difference_in_days_between(block_state.timestamp); - let days_old_float = - last_timestamp.difference_in_days_between_float(block_state.timestamp); - - let older_than_hour = last_timestamp - .checked_sub(block_state.timestamp) - .unwrap() - .is_more_than_hour(); - - time_based_vecs - .iter_mut() - .filter(|v| match v.filter() { - Filter::Time(TimeFilter::GreaterOrEqual(from)) => *from <= days_old, - Filter::Time(TimeFilter::LowerThan(to)) => *to > days_old, - Filter::Time(TimeFilter::Range(range)) => range.contains(&days_old), - Filter::Epoch(epoch) => *epoch == HalvingEpoch::from(height), - _ => unreachable!(), - }) - .for_each(|vecs| { - vecs.state.um().send( - &sent.spendable_supply, - current_price, - prev_price, - blocks_old, - days_old_float, - older_than_hour, - ); - }); - - sent.by_type - .spendable - .iter_typed() - .for_each(|(output_type, supply_state)| { - type_.get_mut(output_type).state.um().send( - supply_state, - current_price, - prev_price, - blocks_old, - days_old_float, - older_than_hour, - ) - }); - - sent.by_size_group - .iter_typed() - .for_each(|(group, supply_state)| { - amount_range.get_mut(group).state.um().send( - supply_state, - current_price, - prev_price, - blocks_old, - days_old_float, - older_than_hour, - ); - }); - - // Update aggregate cohorts' price_to_amount using filter.contains_time() - if let Some(prev_price) = prev_price { - let supply_state = &sent.spendable_supply; - if supply_state.value.is_not_zero() { - aggregate_p2a - .iter_mut() - .filter(|(f, _)| f.contains_time(days_old)) - .map(|(_, p2a)| p2a) - .for_each(|p2a| { - p2a.um().decrement(prev_price, supply_state); - }); - } - } - }); - } - - pub fn receive(&mut self, received: Transacted, height: Height, price: Option) { - let supply_state = received.spendable_supply; - - [ - &mut self.0.age_range.up_to_1d, - self.0.epoch.mut_vec_from_height(height), - ] - .into_iter() - .for_each(|v| { - v.state.um().receive(&supply_state, price); - }); - - // Update aggregate cohorts' price_to_amount - // New UTXOs have days_old = 0, so use filter.contains_time(0) to check applicability - if let Some(price) = price - && supply_state.value.is_not_zero() - { - self.0 - .iter_aggregate_mut() - .filter(|v| v.filter().contains_time(0)) - .for_each(|v| { - v.price_to_amount - .as_mut() - .unwrap() - .increment(price, &supply_state); - }); - } - - self.type_.iter_mut().for_each(|vecs| { - let output_type = match vecs.filter() { - Filter::Type(output_type) => *output_type, - _ => unreachable!(), - }; - vecs.state - .as_mut() - .unwrap() - .receive(received.by_type.get(output_type), price) - }); - - received - .by_size_group - .iter_typed() - .for_each(|(group, supply_state)| { - self.amount_range - .get_mut(group) - .state - .as_mut() - .unwrap() - .receive(supply_state, price); - }); - } - - pub fn compute_overlapping_vecs( - &mut self, - starting_indexes: &Indexes, - exit: &Exit, - ) -> Result<()> { - let by_date_range = &self.0.age_range; - let by_size_range = &self.0.amount_range; - - [(&mut self.0.all, by_date_range.iter().collect::>())] - .into_par_iter() - .chain(self.0.min_age.par_iter_mut().map(|vecs| { - let filter = vecs.filter().clone(); - ( - vecs, - by_date_range - .iter() - .filter(|other| filter.includes(other.filter())) - .collect::>(), - ) - })) - .chain(self.0.max_age.par_iter_mut().map(|vecs| { - let filter = vecs.filter().clone(); - ( - vecs, - by_date_range - .iter() - .filter(|other| filter.includes(other.filter())) - .collect::>(), - ) - })) - .chain(self.0.term.par_iter_mut().map(|vecs| { - let filter = vecs.filter().clone(); - ( - vecs, - by_date_range - .iter() - .filter(|other| filter.includes(other.filter())) - .collect::>(), - ) - })) - .chain(self.0.ge_amount.par_iter_mut().map(|vecs| { - let filter = vecs.filter().clone(); - ( - vecs, - by_size_range - .iter() - .filter(|other| filter.includes(other.filter())) - .collect::>(), - ) - })) - .chain(self.0.lt_amount.par_iter_mut().map(|vecs| { - let filter = vecs.filter().clone(); - ( - vecs, - by_size_range - .iter() - .filter(|other| filter.includes(other.filter())) - .collect::>(), - ) - })) - .try_for_each(|(vecs, stateful)| { - vecs.compute_from_stateful(starting_indexes, &stateful, exit) - }) - } - - pub fn compute_rest_part1( - &mut self, - indexes: &indexes::Vecs, - price: Option<&price::Vecs>, - starting_indexes: &Indexes, - exit: &Exit, - ) -> Result<()> { - self.par_iter_mut() - .try_for_each(|v| v.compute_rest_part1(indexes, price, starting_indexes, exit)) - } - - #[allow(clippy::too_many_arguments)] - pub fn compute_rest_part2( - &mut self, - indexes: &indexes::Vecs, - price: Option<&price::Vecs>, - starting_indexes: &Indexes, - height_to_supply: &impl IterableVec, - dateindex_to_supply: &impl IterableVec, - height_to_market_cap: Option<&impl IterableVec>, - dateindex_to_market_cap: Option<&impl IterableVec>, - height_to_realized_cap: Option<&impl IterableVec>, - dateindex_to_realized_cap: Option<&impl IterableVec>, - exit: &Exit, - ) -> Result<()> { - self.par_iter_mut().try_for_each(|v| { - v.compute_rest_part2( - indexes, - price, - starting_indexes, - height_to_supply, - dateindex_to_supply, - height_to_market_cap, - dateindex_to_market_cap, - height_to_realized_cap, - dateindex_to_realized_cap, - exit, - ) - }) - } - - pub fn safe_flush_stateful_vecs(&mut self, height: Height, exit: &Exit) -> Result<()> { - // Flush stateful cohorts - self.par_iter_separate_mut() - .try_for_each(|v| v.safe_flush_stateful_vecs(height, exit))?; - - // Flush aggregate cohorts' price_to_amount and price_percentiles - // Using traits ensures we can't forget to flush any field - self.0.par_iter_aggregate_mut().try_for_each(|v| { - v.price_to_amount.flush_at_height(height, exit)?; - v.inner.price_percentiles.safe_write(exit)?; - Ok(()) - }) - } - - /// Reset aggregate cohorts' price_to_amount when starting from scratch - pub fn reset_aggregate_price_to_amount(&mut self) -> Result<()> { - self.0 - .iter_aggregate_mut() - .try_for_each(|v| v.price_to_amount.reset()) - } - - /// Import aggregate cohorts' price_to_amount from disk when resuming from a checkpoint. - /// Returns the height to start processing from (checkpoint_height + 1), matching the - /// behavior of `common::import_state` for separate cohorts. - /// - /// Note: We don't check inner.min_height_vecs_len() for aggregate cohorts because their - /// inner vecs (height_to_supply, etc.) are computed post-hoc by compute_overlapping_vecs, - /// not maintained during the main processing loop. - pub fn import_aggregate_price_to_amount(&mut self, height: Height) -> Result { - // Match separate vecs behavior: decrement height to get prev_height - let Some(mut prev_height) = height.decremented() else { - // height is 0, return ZERO (caller will handle this) - return Ok(Height::ZERO); - }; - - for v in self.0.iter_aggregate_mut() { - // Using HeightFlushable trait - if price_to_amount is None, returns height unchanged - prev_height = prev_height.min(v.price_to_amount.import_at_or_before(prev_height)?); - } - // Return prev_height + 1, matching separate vecs behavior - Ok(prev_height.incremented()) - } - - /// Compute and push percentiles for aggregate cohorts (all, sth, lth). - /// Must be called after receive()/send() when price_to_amount is up to date. - pub fn truncate_push_aggregate_percentiles(&mut self, height: Height) -> Result<()> { - let age_range_data: Vec<_> = self - .0 - .age_range - .iter() - .map(|sub| (sub.filter().clone(), sub.state.u().supply.value)) - .collect(); - - let results: Vec<_> = self - .0 - .par_iter_aggregate() - .map(|v| { - if v.price_to_amount.is_none() { - panic!(); - } - let filter = v.filter().clone(); - let supply = age_range_data - .iter() - .filter(|(sub_filter, _)| filter.includes(sub_filter)) - .map(|(_, value)| *value) - .fold(Sats::ZERO, |acc, v| acc + v); - let percentiles = v.compute_percentile_prices_from_standalone(supply); - (filter, percentiles) - }) - .collect(); - - // Push results sequentially (requires &mut) - for (filter, percentiles) in results { - let v = self - .0 - .iter_aggregate_mut() - .find(|v| v.filter() == &filter) - .unwrap(); - - if let Some(pp) = v.inner.price_percentiles.as_mut() { - pp.truncate_push(height, &percentiles)?; - } - } - - Ok(()) - } -} diff --git a/crates/brk_computer/src/stateful_old/withaddressdatasource.rs b/crates/brk_computer/src/stateful_old/withaddressdatasource.rs deleted file mode 100644 index 79ac4b7da..000000000 --- a/crates/brk_computer/src/stateful_old/withaddressdatasource.rs +++ /dev/null @@ -1,56 +0,0 @@ -use brk_types::{EmptyAddressData, EmptyAddressIndex, LoadedAddressData, LoadedAddressIndex}; - -#[derive(Debug)] -pub enum WithAddressDataSource { - New(T), - FromLoadedAddressDataVec((LoadedAddressIndex, T)), - FromEmptyAddressDataVec((EmptyAddressIndex, T)), -} - -impl WithAddressDataSource { - pub fn is_new(&self) -> bool { - matches!(self, Self::New(_)) - } - - pub fn is_from_emptyaddressdata(&self) -> bool { - matches!(self, Self::FromEmptyAddressDataVec(_)) - } - - pub fn deref_mut(&mut self) -> &mut T { - match self { - Self::New(v) => v, - Self::FromLoadedAddressDataVec((_, v)) => v, - Self::FromEmptyAddressDataVec((_, v)) => v, - } - } -} - -impl From> for WithAddressDataSource { - #[inline] - fn from(value: WithAddressDataSource) -> Self { - match value { - WithAddressDataSource::New(v) => Self::New(v.into()), - WithAddressDataSource::FromLoadedAddressDataVec((i, v)) => { - Self::FromLoadedAddressDataVec((i, v.into())) - } - WithAddressDataSource::FromEmptyAddressDataVec((i, v)) => { - Self::FromEmptyAddressDataVec((i, v.into())) - } - } - } -} - -impl From> for WithAddressDataSource { - #[inline] - fn from(value: WithAddressDataSource) -> Self { - match value { - WithAddressDataSource::New(v) => Self::New(v.into()), - WithAddressDataSource::FromLoadedAddressDataVec((i, v)) => { - Self::FromLoadedAddressDataVec((i, v.into())) - } - WithAddressDataSource::FromEmptyAddressDataVec((i, v)) => { - Self::FromEmptyAddressDataVec((i, v.into())) - } - } - } -} diff --git a/crates/brk_computer/src/states/cohorts/address.rs b/crates/brk_computer/src/states/cohorts/address.rs deleted file mode 100644 index 65bea84f4..000000000 --- a/crates/brk_computer/src/states/cohorts/address.rs +++ /dev/null @@ -1,128 +0,0 @@ -use std::path::Path; - -use brk_error::Result; -use brk_types::{Dollars, Height, LoadedAddressData, Sats}; - -use crate::SupplyState; - -use super::CohortState; - -#[derive(Clone)] -pub struct AddressCohortState { - pub addr_count: u64, - pub inner: CohortState, -} - -impl AddressCohortState { - pub fn new(path: &Path, name: &str, compute_dollars: bool) -> Self { - Self { - addr_count: 0, - inner: CohortState::new(path, name, compute_dollars), - } - } - - pub fn reset_price_to_amount_if_needed(&mut self) -> Result<()> { - self.inner.reset_price_to_amount_if_needed() - } - - pub fn reset_single_iteration_values(&mut self) { - self.inner.reset_single_iteration_values(); - } - - #[allow(clippy::too_many_arguments)] - pub fn send( - &mut self, - addressdata: &mut LoadedAddressData, - value: Sats, - current_price: Option, - prev_price: Option, - blocks_old: usize, - days_old: f64, - older_than_hour: bool, - ) -> Result<()> { - let compute_price = current_price.is_some(); - - let prev_realized_price = compute_price.then(|| addressdata.realized_price()); - let prev_supply_state = SupplyState { - utxo_count: addressdata.utxo_count() as u64, - value: addressdata.balance(), - }; - - addressdata.send(value, prev_price)?; - - let supply_state = SupplyState { - utxo_count: addressdata.utxo_count() as u64, - value: addressdata.balance(), - }; - - self.inner.send_( - &SupplyState { - utxo_count: 1, - value, - }, - current_price, - prev_price, - blocks_old, - days_old, - older_than_hour, - compute_price.then(|| (addressdata.realized_price(), &supply_state)), - prev_realized_price.map(|prev_price| (prev_price, &prev_supply_state)), - ); - - Ok(()) - } - - pub fn receive( - &mut self, - address_data: &mut LoadedAddressData, - value: Sats, - price: Option, - ) { - let compute_price = price.is_some(); - - let prev_realized_price = compute_price.then(|| address_data.realized_price()); - let prev_supply_state = SupplyState { - utxo_count: address_data.utxo_count() as u64, - value: address_data.balance(), - }; - - address_data.receive(value, price); - - let supply_state = SupplyState { - utxo_count: address_data.utxo_count() as u64, - value: address_data.balance(), - }; - - self.inner.receive_( - &SupplyState { - utxo_count: 1, - value, - }, - price, - compute_price.then(|| (address_data.realized_price(), &supply_state)), - prev_realized_price.map(|prev_price| (prev_price, &prev_supply_state)), - ); - } - - pub fn add(&mut self, addressdata: &LoadedAddressData) { - self.addr_count += 1; - self.inner.increment_( - &addressdata.into(), - addressdata.realized_cap, - addressdata.realized_price(), - ); - } - - pub fn subtract(&mut self, addressdata: &LoadedAddressData) { - self.addr_count = self.addr_count.checked_sub(1).unwrap(); - self.inner.decrement_( - &addressdata.into(), - addressdata.realized_cap, - addressdata.realized_price(), - ); - } - - pub fn commit(&mut self, height: Height) -> Result<()> { - self.inner.commit(height) - } -} diff --git a/crates/brk_computer/src/states/cohorts/common.rs b/crates/brk_computer/src/states/cohorts/common.rs deleted file mode 100644 index dc0a49788..000000000 --- a/crates/brk_computer/src/states/cohorts/common.rs +++ /dev/null @@ -1,353 +0,0 @@ -use std::{cmp::Ordering, path::Path}; - -use brk_error::Result; -use brk_types::{CheckedSub, Dollars, Height, Sats}; - -use crate::{ - PriceToAmount, RealizedState, SupplyState, UnrealizedState, - grouped::{PERCENTILES, PERCENTILES_LEN}, - utils::OptionExt, -}; - -#[derive(Clone)] -pub struct CohortState { - pub supply: SupplyState, - - pub realized: Option, - pub sent: Sats, - pub satblocks_destroyed: Sats, - pub satdays_destroyed: Sats, - - price_to_amount: Option, -} - -impl CohortState { - pub fn new(path: &Path, name: &str, compute_dollars: bool) -> Self { - Self { - supply: SupplyState::default(), - realized: compute_dollars.then_some(RealizedState::NAN), - sent: Sats::ZERO, - satblocks_destroyed: Sats::ZERO, - satdays_destroyed: Sats::ZERO, - price_to_amount: compute_dollars.then_some(PriceToAmount::create(path, name)), - } - } - - pub fn import_at_or_before(&mut self, height: Height) -> Result { - if let Some(price_to_amount) = self.price_to_amount.as_mut() { - price_to_amount.import_at_or_before(height) - } else { - Ok(height) - } - } - - pub fn reset_price_to_amount_if_needed(&mut self) -> Result<()> { - if let Some(price_to_amount) = self.price_to_amount.as_mut() { - price_to_amount.clean()?; - price_to_amount.init(); - } - Ok(()) - } - - pub fn price_to_amount_first_key_value(&self) -> Option<(&Dollars, &Sats)> { - self.price_to_amount.u().first_key_value() - } - - pub fn price_to_amount_last_key_value(&self) -> Option<(&Dollars, &Sats)> { - self.price_to_amount.u().last_key_value() - } - - pub fn reset_single_iteration_values(&mut self) { - self.sent = Sats::ZERO; - self.satdays_destroyed = Sats::ZERO; - self.satblocks_destroyed = Sats::ZERO; - if let Some(realized) = self.realized.as_mut() { - realized.reset_single_iteration_values(); - } - } - - pub fn increment(&mut self, supply_state: &SupplyState, price: Option) { - self.supply += supply_state; - - if supply_state.value > Sats::ZERO - && let Some(realized) = self.realized.as_mut() - { - let price = price.unwrap(); - realized.increment(supply_state, price); - self.price_to_amount - .as_mut() - .unwrap() - .increment(price, supply_state); - } - } - - pub fn increment_( - &mut self, - supply_state: &SupplyState, - realized_cap: Dollars, - realized_price: Dollars, - ) { - self.supply += supply_state; - - if supply_state.value > Sats::ZERO - && let Some(realized) = self.realized.as_mut() - { - realized.increment_(realized_cap); - self.price_to_amount - .as_mut() - .unwrap() - .increment(realized_price, supply_state); - } - } - - pub fn decrement(&mut self, supply_state: &SupplyState, price: Option) { - self.supply -= supply_state; - - if supply_state.value > Sats::ZERO - && let Some(realized) = self.realized.as_mut() - { - let price = price.unwrap(); - realized.decrement(supply_state, price); - self.price_to_amount - .as_mut() - .unwrap() - .decrement(price, supply_state); - } - } - - pub fn decrement_( - &mut self, - supply_state: &SupplyState, - realized_cap: Dollars, - realized_price: Dollars, - ) { - self.supply -= supply_state; - - if supply_state.value > Sats::ZERO - && let Some(realized) = self.realized.as_mut() - { - realized.decrement_(realized_cap); - self.price_to_amount - .as_mut() - .unwrap() - .decrement(realized_price, supply_state); - } - } - - pub fn receive(&mut self, supply_state: &SupplyState, price: Option) { - self.receive_( - supply_state, - price, - price.map(|price| (price, supply_state)), - None, - ); - } - - pub fn receive_( - &mut self, - supply_state: &SupplyState, - price: Option, - price_to_amount_increment: Option<(Dollars, &SupplyState)>, - price_to_amount_decrement: Option<(Dollars, &SupplyState)>, - ) { - self.supply += supply_state; - - if supply_state.value > Sats::ZERO - && let Some(realized) = self.realized.as_mut() - { - let price = price.unwrap(); - realized.receive(supply_state, price); - - if let Some((price, supply)) = price_to_amount_increment - && supply.value.is_not_zero() - { - self.price_to_amount - .as_mut() - .unwrap() - .increment(price, supply); - } - - if let Some((price, supply)) = price_to_amount_decrement - && supply.value.is_not_zero() - { - self.price_to_amount - .as_mut() - .unwrap() - .decrement(price, supply); - } - } - } - - pub fn send( - &mut self, - supply_state: &SupplyState, - current_price: Option, - prev_price: Option, - blocks_old: usize, - days_old: f64, - older_than_hour: bool, - ) { - self.send_( - supply_state, - current_price, - prev_price, - blocks_old, - days_old, - older_than_hour, - None, - prev_price.map(|prev_price| (prev_price, supply_state)), - ); - } - - #[allow(clippy::too_many_arguments)] - pub fn send_( - &mut self, - supply_state: &SupplyState, - current_price: Option, - prev_price: Option, - blocks_old: usize, - days_old: f64, - older_than_hour: bool, - price_to_amount_increment: Option<(Dollars, &SupplyState)>, - price_to_amount_decrement: Option<(Dollars, &SupplyState)>, - ) { - if supply_state.utxo_count == 0 { - return; - } - - self.supply -= supply_state; - - if supply_state.value > Sats::ZERO { - self.sent += supply_state.value; - self.satblocks_destroyed += supply_state.value * blocks_old; - self.satdays_destroyed += - Sats::from((u64::from(supply_state.value) as f64 * days_old).floor() as u64); - - if let Some(realized) = self.realized.as_mut() { - let current_price = current_price.unwrap(); - let prev_price = prev_price.unwrap(); - realized.send(supply_state, current_price, prev_price, older_than_hour); - if let Some((price, supply)) = price_to_amount_increment - && supply.value.is_not_zero() - { - self.price_to_amount - .as_mut() - .unwrap() - .increment(price, supply); - } - if let Some((price, supply)) = price_to_amount_decrement - && supply.value.is_not_zero() - { - self.price_to_amount - .as_mut() - .unwrap() - .decrement(price, supply); - } - } - } - } - - /// Computes prices at PERCENTILES in a single pass. - /// Returns an array of prices corresponding to each percentile. - pub fn compute_percentile_prices(&self) -> [Dollars; PERCENTILES_LEN] { - let mut result = [Dollars::NAN; PERCENTILES_LEN]; - - let price_to_amount = match self.price_to_amount.as_ref() { - Some(p) => p, - None => return result, - }; - - if price_to_amount.is_empty() || self.supply.value == Sats::ZERO { - return result; - } - - let total = u64::from(self.supply.value); - let targets = PERCENTILES.map(|p| total * u64::from(p) / 100); - - let mut accumulated = 0u64; - let mut pct_idx = 0; - - for (&price, &sats) in price_to_amount.iter() { - accumulated += u64::from(sats); - - while pct_idx < PERCENTILES_LEN && accumulated >= targets[pct_idx] { - result[pct_idx] = price; - pct_idx += 1; - } - - if pct_idx >= PERCENTILES_LEN { - break; - } - } - - result - } - - pub fn compute_unrealized_states( - &self, - height_price: Dollars, - date_price: Option, - ) -> (UnrealizedState, Option) { - if self.price_to_amount.u().is_empty() { - return ( - UnrealizedState::NAN, - date_price.map(|_| UnrealizedState::NAN), - ); - } - - let mut height_unrealized_state = UnrealizedState::ZERO; - let mut date_unrealized_state = date_price.map(|_| UnrealizedState::ZERO); - - let update_state = - |price: Dollars, current_price: Dollars, sats: Sats, state: &mut UnrealizedState| { - let cmp = price.cmp(¤t_price); - match cmp { - Ordering::Equal | Ordering::Less => { - state.supply_in_profit += sats; - if !cmp.is_eq() && price > Dollars::ZERO && current_price > Dollars::ZERO { - let diff = current_price.checked_sub(price).unwrap(); - // Add back once in a while to verify, but generally not needed - // if diff <= Dollars::ZERO { - // dbg!(price, current_price, diff, sats); - // panic!(); - // } - state.unrealized_profit += diff * sats; - } - } - Ordering::Greater => { - state.supply_in_loss += sats; - if price > Dollars::ZERO && current_price > Dollars::ZERO { - let diff = price.checked_sub(current_price).unwrap(); - // Add back once in a while to verify, but generally not needed - // if diff <= Dollars::ZERO { - // dbg!(price, current_price, diff, sats); - // panic!(); - // } - state.unrealized_loss += diff * sats; - } - } - } - }; - - self.price_to_amount - .as_ref() - .unwrap() - .iter() - .for_each(|(&price, &sats)| { - update_state(price, height_price, sats, &mut height_unrealized_state); - - if let Some(date_price) = date_price { - update_state(price, date_price, sats, date_unrealized_state.um()) - } - }); - - (height_unrealized_state, date_unrealized_state) - } - - pub fn commit(&mut self, height: Height) -> Result<()> { - if let Some(price_to_amount) = self.price_to_amount.as_mut() { - price_to_amount.flush(height)?; - } - Ok(()) - } -} diff --git a/crates/brk_computer/src/states/cohorts/mod.rs b/crates/brk_computer/src/states/cohorts/mod.rs deleted file mode 100644 index b3dc7b22f..000000000 --- a/crates/brk_computer/src/states/cohorts/mod.rs +++ /dev/null @@ -1,7 +0,0 @@ -mod address; -mod common; -mod utxo; - -pub use address::*; -pub use common::*; -pub use utxo::*; diff --git a/crates/brk_computer/src/states/cohorts/utxo.rs b/crates/brk_computer/src/states/cohorts/utxo.rs deleted file mode 100644 index c633ba41f..000000000 --- a/crates/brk_computer/src/states/cohorts/utxo.rs +++ /dev/null @@ -1,19 +0,0 @@ -use std::path::Path; - -use brk_error::Result; -use derive_deref::{Deref, DerefMut}; - -use super::CohortState; - -#[derive(Clone, Deref, DerefMut)] -pub struct UTXOCohortState(CohortState); - -impl UTXOCohortState { - pub fn new(path: &Path, name: &str, compute_dollars: bool) -> Self { - Self(CohortState::new(path, name, compute_dollars)) - } - - pub fn reset_price_to_amount_if_needed(&mut self) -> Result<()> { - self.0.reset_price_to_amount_if_needed() - } -} diff --git a/crates/brk_computer/src/states/price_to_amount.rs b/crates/brk_computer/src/states/price_to_amount.rs index 40480e7be..0667c13f0 100644 --- a/crates/brk_computer/src/states/price_to_amount.rs +++ b/crates/brk_computer/src/states/price_to_amount.rs @@ -43,6 +43,14 @@ impl PriceToAmount { self.state.u().iter() } + /// Iterate over entries in a price range with custom bounds. + pub fn range>( + &self, + range: R, + ) -> impl Iterator { + self.state.u().range(range) + } + pub fn is_empty(&self) -> bool { self.state.u().is_empty() } diff --git a/crates/brk_computer/src/states/unrealized.rs b/crates/brk_computer/src/states/unrealized.rs index 52e8291f8..5e5073444 100644 --- a/crates/brk_computer/src/states/unrealized.rs +++ b/crates/brk_computer/src/states/unrealized.rs @@ -1,4 +1,9 @@ +use std::ops::Bound; + use brk_types::{Dollars, Sats}; +use vecdb::CheckedSub; + +use crate::PriceToAmount; #[derive(Debug, Default, Clone)] pub struct UnrealizedState { @@ -23,3 +28,201 @@ impl UnrealizedState { unrealized_loss: Dollars::ZERO, }; } + +/// Cached unrealized state for O(k) incremental updates. +/// k = number of entries in price flip range (typically tiny). +#[derive(Debug, Clone)] +pub struct CachedUnrealizedState { + pub state: UnrealizedState, + at_price: Dollars, +} + +impl CachedUnrealizedState { + /// Create new cache by computing from scratch. O(n). + pub fn compute_fresh(price: Dollars, price_to_amount: &PriceToAmount) -> Self { + let state = Self::compute_full_standalone(price, price_to_amount); + Self { + state, + at_price: price, + } + } + + /// Get unrealized state at new_price. O(k) where k = flip range size. + pub fn get_at_price( + &mut self, + new_price: Dollars, + price_to_amount: &PriceToAmount, + ) -> &UnrealizedState { + if new_price != self.at_price { + self.update_for_price_change(new_price, price_to_amount); + } + &self.state + } + + /// Update cached state when a receive happens. + /// Determines profit/loss classification relative to cached price. + pub fn on_receive(&mut self, purchase_price: Dollars, sats: Sats) { + if purchase_price <= self.at_price { + self.state.supply_in_profit += sats; + if purchase_price < self.at_price { + let diff = self.at_price.checked_sub(purchase_price).unwrap(); + self.state.unrealized_profit += diff * sats; + } + } else { + self.state.supply_in_loss += sats; + let diff = purchase_price.checked_sub(self.at_price).unwrap(); + self.state.unrealized_loss += diff * sats; + } + } + + /// Update cached state when a send happens from historical price. + pub fn on_send(&mut self, historical_price: Dollars, sats: Sats) { + if historical_price <= self.at_price { + // Was in profit + self.state.supply_in_profit -= sats; + if historical_price < self.at_price { + let diff = self.at_price.checked_sub(historical_price).unwrap(); + let profit_removed = diff * sats; + self.state.unrealized_profit = self + .state + .unrealized_profit + .checked_sub(profit_removed) + .unwrap_or(Dollars::ZERO); + } + } else { + // Was in loss + self.state.supply_in_loss -= sats; + let diff = historical_price.checked_sub(self.at_price).unwrap(); + let loss_removed = diff * sats; + self.state.unrealized_loss = self + .state + .unrealized_loss + .checked_sub(loss_removed) + .unwrap_or(Dollars::ZERO); + } + } + + /// Incremental update for price change. O(k) where k = entries in flip range. + fn update_for_price_change(&mut self, new_price: Dollars, price_to_amount: &PriceToAmount) { + let old_price = self.at_price; + let delta_f64 = f64::from(new_price) - f64::from(old_price); + + // Update profit/loss for entries that DON'T flip + // Profit changes by delta * supply_in_profit + // Loss changes by -delta * supply_in_loss + if delta_f64 > 0.0 { + // Price went up: profits increase, losses decrease + self.state.unrealized_profit += Dollars::from(delta_f64) * self.state.supply_in_profit; + let loss_decrease = Dollars::from(delta_f64) * self.state.supply_in_loss; + self.state.unrealized_loss = self + .state + .unrealized_loss + .checked_sub(loss_decrease) + .unwrap_or(Dollars::ZERO); + } else if delta_f64 < 0.0 { + // Price went down: profits decrease, losses increase + let profit_decrease = Dollars::from(-delta_f64) * self.state.supply_in_profit; + self.state.unrealized_profit = self + .state + .unrealized_profit + .checked_sub(profit_decrease) + .unwrap_or(Dollars::ZERO); + self.state.unrealized_loss += Dollars::from(-delta_f64) * self.state.supply_in_loss; + } + + // Handle flipped entries (only iterate the small range between prices) + if new_price > old_price { + // Price went up: entries where old < price <= new flip from loss to profit + for (&price, &sats) in + price_to_amount.range((Bound::Excluded(old_price), Bound::Included(new_price))) + { + // Move from loss to profit + self.state.supply_in_loss -= sats; + self.state.supply_in_profit += sats; + + // Undo the loss adjustment applied above for this entry + // We decreased loss by delta * sats, but this entry should be removed entirely + // Original loss: (price - old_price) * sats + // After global adjustment: original - delta * sats (negative, wrong) + // Correct: 0 (removed from loss) + // Correction: add back delta * sats, then add original loss + let delta_adj = Dollars::from(delta_f64) * sats; + self.state.unrealized_loss += delta_adj; + if price > old_price { + let original_loss = price.checked_sub(old_price).unwrap() * sats; + self.state.unrealized_loss += original_loss; + } + + // Undo the profit adjustment applied above for this entry + // We increased profit by delta * sats, but this entry was not in profit before + // Correct profit: (new_price - price) * sats + // Correction: subtract delta * sats, add correct profit + let profit_adj = Dollars::from(delta_f64) * sats; + self.state.unrealized_profit = self + .state + .unrealized_profit + .checked_sub(profit_adj) + .unwrap_or(Dollars::ZERO); + if new_price > price { + let correct_profit = new_price.checked_sub(price).unwrap() * sats; + self.state.unrealized_profit += correct_profit; + } + } + } else if new_price < old_price { + // Price went down: entries where new < price <= old flip from profit to loss + for (&price, &sats) in + price_to_amount.range((Bound::Excluded(new_price), Bound::Included(old_price))) + { + // Move from profit to loss + self.state.supply_in_profit -= sats; + self.state.supply_in_loss += sats; + + // Undo the profit adjustment applied above for this entry + let delta_adj = Dollars::from(-delta_f64) * sats; + self.state.unrealized_profit += delta_adj; + if old_price > price { + let original_profit = old_price.checked_sub(price).unwrap() * sats; + self.state.unrealized_profit += original_profit; + } + + // Undo the loss adjustment applied above for this entry + let loss_adj = Dollars::from(-delta_f64) * sats; + self.state.unrealized_loss = self + .state + .unrealized_loss + .checked_sub(loss_adj) + .unwrap_or(Dollars::ZERO); + if price > new_price { + let correct_loss = price.checked_sub(new_price).unwrap() * sats; + self.state.unrealized_loss += correct_loss; + } + } + } + + self.at_price = new_price; + } + + /// Full computation from scratch (no cache). O(n). + pub fn compute_full_standalone( + current_price: Dollars, + price_to_amount: &PriceToAmount, + ) -> UnrealizedState { + let mut state = UnrealizedState::ZERO; + + for (&price, &sats) in price_to_amount.iter() { + if price <= current_price { + state.supply_in_profit += sats; + if price < current_price { + let diff = current_price.checked_sub(price).unwrap(); + state.unrealized_profit += diff * sats; + } + } else { + state.supply_in_loss += sats; + let diff = price.checked_sub(current_price).unwrap(); + state.unrealized_loss += diff * sats; + } + } + + state + } +}