diff --git a/crates/brk_computer/src/grouped/price_percentiles.rs b/crates/brk_computer/src/grouped/price_percentiles.rs index c370ea94f..2f5af4888 100644 --- a/crates/brk_computer/src/grouped/price_percentiles.rs +++ b/crates/brk_computer/src/grouped/price_percentiles.rs @@ -3,7 +3,7 @@ use brk_traversable::{Traversable, TreeNode}; use brk_types::{Dollars, Height, Version}; use vecdb::{AnyExportableVec, AnyStoredVec, Database, EagerVec, Exit, GenericStoredVec, PcoVec}; -use crate::{Indexes, indexes}; +use crate::{Indexes, indexes, stateful::Flushable}; use super::{ComputedVecsFromHeight, Source, VecBuilderOptions}; @@ -84,7 +84,10 @@ impl PricePercentiles { .and_then(|i| self.vecs[i].as_ref()) } - pub fn safe_flush(&mut self, exit: &Exit) -> Result<()> { +} + +impl Flushable for PricePercentiles { + fn safe_flush(&mut self, exit: &Exit) -> Result<()> { for vec in self.vecs.iter_mut().flatten() { if let Some(height_vec) = vec.height.as_mut() { height_vec.safe_flush(exit)?; diff --git a/crates/brk_computer/src/stateful/common.rs b/crates/brk_computer/src/stateful/common.rs deleted file mode 100644 index 854396be2..000000000 --- a/crates/brk_computer/src/stateful/common.rs +++ /dev/null @@ -1,2359 +0,0 @@ -use brk_error::{Error, Result}; -use brk_grouper::{CohortContext, Filter}; -use brk_traversable::Traversable; -use brk_types::{ - Bitcoin, DateIndex, Dollars, Height, Sats, StoredF32, StoredF64, StoredU64, Version, -}; -use vecdb::{ - AnyStoredVec, AnyVec, Database, EagerVec, Exit, GenericStoredVec, ImportableVec, - IterableCloneableVec, IterableVec, PcoVec, TypedVecIterator, -}; - -use crate::{ - Indexes, - grouped::{ - ComputedHeightValueVecs, ComputedRatioVecsFromDateIndex, ComputedValueVecsFromDateIndex, - ComputedValueVecsFromHeight, ComputedVecsFromDateIndex, ComputedVecsFromHeight, - PricePercentiles, Source, VecBuilderOptions, - }, - indexes, price, - states::CohortState, - utils::OptionExt, -}; - -#[derive(Clone, Traversable)] -pub struct Vecs { - #[traversable(skip)] - pub filter: Filter, - - // Cumulative - pub height_to_realized_cap: Option>>, - pub height_to_supply: EagerVec>, - pub height_to_utxo_count: EagerVec>, - // Single - pub dateindex_to_supply_in_loss: Option>>, - pub dateindex_to_supply_in_profit: Option>>, - pub dateindex_to_unrealized_loss: Option>>, - pub dateindex_to_unrealized_profit: Option>>, - pub height_to_adjusted_value_created: Option>>, - pub height_to_adjusted_value_destroyed: Option>>, - pub height_to_max_price_paid: Option>>, - pub height_to_min_price_paid: Option>>, - pub height_to_realized_loss: Option>>, - pub height_to_realized_profit: Option>>, - pub height_to_supply_in_loss: Option>>, - pub height_to_supply_in_profit: Option>>, - pub height_to_unrealized_loss: Option>>, - pub height_to_unrealized_profit: Option>>, - pub height_to_value_created: Option>>, - pub height_to_value_destroyed: Option>>, - pub height_to_sent: EagerVec>, - pub height_to_satblocks_destroyed: EagerVec>, - pub height_to_satdays_destroyed: EagerVec>, - - pub indexes_to_sent: ComputedValueVecsFromHeight, - pub indexes_to_coinblocks_destroyed: ComputedVecsFromHeight, - pub indexes_to_coindays_destroyed: ComputedVecsFromHeight, - 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>>, - pub indexes_to_realized_cap_30d_delta: Option>, - 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>>, - pub indexes_to_adjusted_value_created: Option>, - pub indexes_to_adjusted_value_destroyed: Option>, - pub indexes_to_neg_realized_loss: Option>, - pub indexes_to_net_realized_pnl: Option>, - pub indexes_to_realized_cap: Option>, - pub indexes_to_realized_loss: Option>, - pub indexes_to_realized_price: Option>, - pub indexes_to_realized_price_extra: Option, - pub indexes_to_realized_profit: Option>, - pub indexes_to_realized_value: Option>, - pub height_to_supply_value: ComputedHeightValueVecs, - pub indexes_to_supply: ComputedValueVecsFromDateIndex, - pub indexes_to_utxo_count: ComputedVecsFromHeight, - pub indexes_to_value_created: Option>, - pub indexes_to_value_destroyed: Option>, - pub indexes_to_unrealized_profit: Option>, - pub indexes_to_unrealized_loss: Option>, - pub height_to_total_unrealized_pnl: Option>>, - pub indexes_to_total_unrealized_pnl: Option>, - pub height_to_total_realized_pnl: Option>>, - pub indexes_to_total_realized_pnl: Option>, - pub indexes_to_min_price_paid: Option>, - pub indexes_to_max_price_paid: Option>, - pub price_percentiles: Option, - pub height_to_supply_half_value: ComputedHeightValueVecs, - pub indexes_to_supply_half: ComputedValueVecsFromDateIndex, - 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_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>, - 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>, - 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>, - pub indexes_to_realized_cap_rel_to_own_market_cap: 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_supply_in_loss_value: Option, - pub height_to_supply_in_profit_value: Option, - pub indexes_to_supply_in_loss: Option, - pub indexes_to_supply_in_profit: Option, - pub height_to_supply_in_loss_rel_to_own_supply: Option>>, - pub height_to_supply_in_profit_rel_to_own_supply: Option>>, - pub indexes_to_supply_in_loss_rel_to_own_supply: Option>, - pub indexes_to_supply_in_profit_rel_to_own_supply: Option>, - pub indexes_to_supply_rel_to_circulating_supply: Option>, - pub height_to_supply_in_loss_rel_to_circulating_supply: - Option>>, - pub height_to_supply_in_profit_rel_to_circulating_supply: - Option>>, - pub indexes_to_supply_in_loss_rel_to_circulating_supply: - Option>, - pub indexes_to_supply_in_profit_rel_to_circulating_supply: - Option>, - 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>, - pub dateindex_to_realized_profit_to_loss_ratio: Option>>, -} - -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 option 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 (always computed) - 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, - )?, - - // Sent & destroyed (always computed) - height_to_sent: EagerVec::forced_import(db, &suffix("sent"), version + v0)?, - 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_sent: ComputedValueVecsFromHeight::forced_import( - db, - &suffix("sent"), - Source::Compute, - version + v0, - sum(), - compute_dollars, - indexes, - )?, - 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 & related (conditional on compute_dollars) - 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 ratio - 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() - }), - height_to_supply_in_loss_value: compute_dollars.then(|| { - ComputedHeightValueVecs::forced_import( - db, - &suffix("supply_in_loss"), - Source::None, - version + v0, - compute_dollars, - ) - .unwrap() - }), - 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_rel_to_own_supply: compute_dollars - .then(|| eager!(Height, StoredF64,"supply_in_loss_rel_to_own_supply", v1)), - height_to_supply_in_profit_rel_to_own_supply: compute_dollars - .then(|| eager!(Height, StoredF64,"supply_in_profit_rel_to_own_supply", v1)), - 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())), - 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_rel_to_circulating_supply: compute_rel_to_all - .then(|| computed_h!("supply_rel_to_circulating_supply", Source::Compute, v1, last())), - 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)), - 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)), - 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())), - 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())), - dateindex_to_supply_in_profit, - dateindex_to_supply_in_loss, - - // 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_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())), - 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())), - - // Unrealized rel to 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())), - - // Unrealized rel to 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())), - - // Unrealized rel to own total unrealized pnl - 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())), - - // Price paid - height_to_min_price_paid: compute_dollars.then(|| eager!(Height, Dollars,"min_price_paid", v0)), - height_to_max_price_paid: compute_dollars.then(|| eager!(Height, Dollars,"max_price_paid", v0)), - indexes_to_min_price_paid: compute_dollars - .then(|| computed_h!("min_price_paid", Source::None, v0, last())), - 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() - }), - - // Net realized pnl cumulative 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())), - }) - } - - 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() - } - - 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::Str("Unset")) - } - } - - pub fn validate_computed_versions(&mut self, base_version: Version) -> Result<()> { - 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(), - )?; - - 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(), - )?; - - let height_to_realized_profit_inner_version = self - .height_to_realized_profit - .u() - .inner_version(); - self.height_to_realized_profit - .um() - .validate_computed_version_or_reset( - base_version + height_to_realized_profit_inner_version, - )?; - let height_to_realized_loss_inner_version = self - .height_to_realized_loss - .u() - .inner_version(); - self.height_to_realized_loss - .um() - .validate_computed_version_or_reset( - base_version + height_to_realized_loss_inner_version, - )?; - let height_to_value_created_inner_version = self - .height_to_value_created - .u() - .inner_version(); - self.height_to_value_created - .um() - .validate_computed_version_or_reset( - base_version + height_to_value_created_inner_version, - )?; - let height_to_value_destroyed_inner_version = self - .height_to_value_destroyed - .u() - .inner_version(); - self.height_to_value_destroyed - .um() - .validate_computed_version_or_reset( - base_version + height_to_value_destroyed_inner_version, - )?; - let height_to_supply_in_profit_inner_version = self - .height_to_supply_in_profit - .u() - .inner_version(); - self.height_to_supply_in_profit - .um() - .validate_computed_version_or_reset( - base_version + height_to_supply_in_profit_inner_version, - )?; - let height_to_supply_in_loss_inner_version = self - .height_to_supply_in_loss - .u() - .inner_version(); - self.height_to_supply_in_loss - .um() - .validate_computed_version_or_reset( - base_version + height_to_supply_in_loss_inner_version, - )?; - let height_to_unrealized_profit_inner_version = self - .height_to_unrealized_profit - .u() - .inner_version(); - self.height_to_unrealized_profit - .um() - .validate_computed_version_or_reset( - base_version + height_to_unrealized_profit_inner_version, - )?; - let height_to_unrealized_loss_inner_version = self - .height_to_unrealized_loss - .u() - .inner_version(); - self.height_to_unrealized_loss - .um() - .validate_computed_version_or_reset( - base_version + height_to_unrealized_loss_inner_version, - )?; - let dateindex_to_supply_in_profit_inner_version = self - .dateindex_to_supply_in_profit - .u() - .inner_version(); - self.dateindex_to_supply_in_profit - .um() - .validate_computed_version_or_reset( - base_version + dateindex_to_supply_in_profit_inner_version, - )?; - let dateindex_to_supply_in_loss_inner_version = self - .dateindex_to_supply_in_loss - .u() - .inner_version(); - self.dateindex_to_supply_in_loss - .um() - .validate_computed_version_or_reset( - base_version + dateindex_to_supply_in_loss_inner_version, - )?; - let dateindex_to_unrealized_profit_inner_version = self - .dateindex_to_unrealized_profit - .u() - .inner_version(); - self.dateindex_to_unrealized_profit - .um() - .validate_computed_version_or_reset( - base_version + dateindex_to_unrealized_profit_inner_version, - )?; - let dateindex_to_unrealized_loss_inner_version = self - .dateindex_to_unrealized_loss - .u() - .inner_version(); - self.dateindex_to_unrealized_loss - .um() - .validate_computed_version_or_reset( - base_version + dateindex_to_unrealized_loss_inner_version, - )?; - let height_to_min_price_paid_inner_version = self - .height_to_min_price_paid - .u() - .inner_version(); - self.height_to_min_price_paid - .um() - .validate_computed_version_or_reset( - base_version + height_to_min_price_paid_inner_version, - )?; - let height_to_max_price_paid_inner_version = self - .height_to_max_price_paid - .u() - .inner_version(); - self.height_to_max_price_paid - .um() - .validate_computed_version_or_reset( - base_version + height_to_max_price_paid_inner_version, - )?; - - if self.height_to_adjusted_value_created.is_some() { - let height_to_adjusted_value_created_inner_version = self - .height_to_adjusted_value_created - .u() - .inner_version(); - self.height_to_adjusted_value_created - .um() - .validate_computed_version_or_reset( - base_version + height_to_adjusted_value_created_inner_version, - )?; - let height_to_adjusted_value_destroyed_inner_version = self - .height_to_adjusted_value_destroyed - .u() - .inner_version(); - self.height_to_adjusted_value_destroyed - .um() - .validate_computed_version_or_reset( - base_version + height_to_adjusted_value_destroyed_inner_version, - )?; - } - } - - Ok(()) - } - - 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_flush(exit)?; - self.height_to_utxo_count.safe_flush(exit)?; - self.height_to_sent.safe_flush(exit)?; - self.height_to_satdays_destroyed.safe_flush(exit)?; - self.height_to_satblocks_destroyed.safe_flush(exit)?; - - if let Some(height_to_realized_cap) = self.height_to_realized_cap.as_mut() { - height_to_realized_cap.safe_flush(exit)?; - self.height_to_realized_profit - .um() - .safe_flush(exit)?; - self.height_to_realized_loss - .um() - .safe_flush(exit)?; - self.height_to_value_created - .um() - .safe_flush(exit)?; - self.height_to_value_destroyed - .um() - .safe_flush(exit)?; - self.height_to_supply_in_profit - .um() - .safe_flush(exit)?; - self.height_to_supply_in_loss - .um() - .safe_flush(exit)?; - self.height_to_unrealized_profit - .um() - .safe_flush(exit)?; - self.height_to_unrealized_loss - .um() - .safe_flush(exit)?; - self.dateindex_to_supply_in_profit - .um() - .safe_flush(exit)?; - self.dateindex_to_supply_in_loss - .um() - .safe_flush(exit)?; - self.dateindex_to_unrealized_profit - .um() - .safe_flush(exit)?; - self.dateindex_to_unrealized_loss - .um() - .safe_flush(exit)?; - self.height_to_min_price_paid - .um() - .safe_flush(exit)?; - self.height_to_max_price_paid - .um() - .safe_flush(exit)?; - - if self.height_to_adjusted_value_created.is_some() { - self.height_to_adjusted_value_created - .um() - .safe_flush(exit)?; - self.height_to_adjusted_value_destroyed - .um() - .safe_flush(exit)?; - } - - if let Some(price_percentiles) = self.price_percentiles.as_mut() { - price_percentiles.safe_flush(exit)?; - } - } - - state.commit(height)?; - - Ok(()) - } - - 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/common/compute.rs b/crates/brk_computer/src/stateful/common/compute.rs new file mode 100644 index 000000000..6df334f19 --- /dev/null +++ b/crates/brk_computer/src/stateful/common/compute.rs @@ -0,0 +1,1241 @@ +//! 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/common/import.rs b/crates/brk_computer/src/stateful/common/import.rs new file mode 100644 index 000000000..154397128 --- /dev/null +++ b/crates/brk_computer/src/stateful/common/import.rs @@ -0,0 +1,914 @@ +//! 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::Compute, + 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::Str("Unset")) + } + } + + /// 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/common/mod.rs b/crates/brk_computer/src/stateful/common/mod.rs new file mode 100644 index 000000000..fc02e4666 --- /dev/null +++ b/crates/brk_computer/src/stateful/common/mod.rs @@ -0,0 +1,19 @@ +//! 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/common/push.rs b/crates/brk_computer/src/stateful/common/push.rs new file mode 100644 index 000000000..5324a3b5a --- /dev/null +++ b/crates/brk_computer/src/stateful/common/push.rs @@ -0,0 +1,178 @@ +//! 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_flush(exit)?; + self.height_to_utxo_count.safe_flush(exit)?; + self.height_to_sent.safe_flush(exit)?; + self.height_to_satdays_destroyed.safe_flush(exit)?; + self.height_to_satblocks_destroyed.safe_flush(exit)?; + + if let Some(height_to_realized_cap) = self.height_to_realized_cap.as_mut() { + height_to_realized_cap.safe_flush(exit)?; + self.height_to_realized_profit.um().safe_flush(exit)?; + self.height_to_realized_loss.um().safe_flush(exit)?; + self.height_to_value_created.um().safe_flush(exit)?; + self.height_to_value_destroyed.um().safe_flush(exit)?; + self.height_to_supply_in_profit.um().safe_flush(exit)?; + self.height_to_supply_in_loss.um().safe_flush(exit)?; + self.height_to_unrealized_profit.um().safe_flush(exit)?; + self.height_to_unrealized_loss.um().safe_flush(exit)?; + self.dateindex_to_supply_in_profit.um().safe_flush(exit)?; + self.dateindex_to_supply_in_loss.um().safe_flush(exit)?; + self.dateindex_to_unrealized_profit.um().safe_flush(exit)?; + self.dateindex_to_unrealized_loss.um().safe_flush(exit)?; + self.height_to_min_price_paid.um().safe_flush(exit)?; + self.height_to_max_price_paid.um().safe_flush(exit)?; + + if self.height_to_adjusted_value_created.is_some() { + self.height_to_adjusted_value_created + .um() + .safe_flush(exit)?; + self.height_to_adjusted_value_destroyed + .um() + .safe_flush(exit)?; + } + + // Uses Flushable trait - Option impl handles None case + self.price_percentiles.safe_flush(exit)?; + } + + state.commit(height)?; + + Ok(()) + } +} diff --git a/crates/brk_computer/src/stateful/common/vecs.rs b/crates/brk_computer/src/stateful/common/vecs.rs new file mode 100644 index 000000000..1ea0d06f9 --- /dev/null +++ b/crates/brk_computer/src/stateful/common/vecs.rs @@ -0,0 +1,210 @@ +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/flushable.rs b/crates/brk_computer/src/stateful/flushable.rs new file mode 100644 index 000000000..ded2fd130 --- /dev/null +++ b/crates/brk_computer/src/stateful/flushable.rs @@ -0,0 +1,70 @@ +//! 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<()>; +} + +/// 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(()) + } +} + +/// 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/mod.rs b/crates/brk_computer/src/stateful/mod.rs index df37cdbd4..2118c65c8 100644 --- a/crates/brk_computer/src/stateful/mod.rs +++ b/crates/brk_computer/src/stateful/mod.rs @@ -1,3 +1,33 @@ +//! 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; @@ -34,6 +64,7 @@ mod address_cohorts; mod address_indexes; mod addresstype; mod common; +mod flushable; mod range_map; mod readers; mod r#trait; @@ -42,6 +73,8 @@ mod utxo_cohort; mod utxo_cohorts; mod withaddressdatasource; +pub use flushable::{Flushable, HeightFlushable}; + use address_indexes::{AddressesDataVecs, AnyAddressIndexesVecs}; use addresstype::*; use range_map::*; diff --git a/crates/brk_computer/src/stateful/utxo_cohorts.rs b/crates/brk_computer/src/stateful/utxo_cohorts.rs index 61d622465..caf758cfc 100644 --- a/crates/brk_computer/src/stateful/utxo_cohorts.rs +++ b/crates/brk_computer/src/stateful/utxo_cohorts.rs @@ -18,7 +18,7 @@ use vecdb::{Database, Exit, IterableVec, VecIndex}; use crate::{ Indexes, indexes, price, - stateful::r#trait::DynCohortVecs, + stateful::{Flushable, HeightFlushable, r#trait::DynCohortVecs}, states::{BlockState, Transacted}, utils::OptionExt, }; @@ -614,13 +614,10 @@ impl Vecs { .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| { - if let Some(p2a) = v.price_to_amount.as_mut() { - p2a.flush(height)?; - } - if let Some(pp) = v.inner.price_percentiles.as_mut() { - pp.safe_flush(exit)?; - } + v.price_to_amount.flush_at_height(height, exit)?; + v.inner.price_percentiles.safe_flush(exit)?; Ok(()) }) } @@ -628,11 +625,7 @@ impl Vecs { /// 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| { - if let Some(p2a) = v.price_to_amount.as_mut() { - p2a.clean()?; - p2a.init(); - } - Ok(()) + v.price_to_amount.reset() }) } @@ -651,10 +644,8 @@ impl Vecs { }; for v in self.0.iter_aggregate_mut() { - if let Some(p2a) = v.price_to_amount.as_mut() { - // Match separate vecs: update prev_height to the checkpoint found - prev_height = prev_height.min(p2a.import_at_or_before(prev_height)?); - } + // 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()) diff --git a/crates/brk_computer/src/states/price_to_amount.rs b/crates/brk_computer/src/states/price_to_amount.rs index d558d2173..d75a34658 100644 --- a/crates/brk_computer/src/states/price_to_amount.rs +++ b/crates/brk_computer/src/states/price_to_amount.rs @@ -9,9 +9,9 @@ use brk_types::{Dollars, Height, Sats}; use derive_deref::{Deref, DerefMut}; use pco::standalone::{simple_decompress, simpler_compress}; use serde::{Deserialize, Serialize}; -use vecdb::Bytes; +use vecdb::{Bytes, Exit}; -use crate::{states::SupplyState, utils::OptionExt}; +use crate::{stateful::HeightFlushable, states::SupplyState, utils::OptionExt}; #[derive(Clone, Debug)] pub struct PriceToAmount { @@ -128,6 +128,22 @@ impl PriceToAmount { } } +impl HeightFlushable for PriceToAmount { + fn flush_at_height(&mut self, height: Height, _exit: &Exit) -> Result<()> { + self.flush(height) + } + + fn import_at_or_before(&mut self, height: Height) -> Result { + PriceToAmount::import_at_or_before(self, height) + } + + fn reset(&mut self) -> Result<()> { + self.clean()?; + self.init(); + Ok(()) + } +} + #[derive(Clone, Default, Debug, Deref, DerefMut, Serialize, Deserialize)] struct State(BTreeMap);