From 236b4097c549f687fc36f0ba3bbe0e6e96bd2c57 Mon Sep 17 00:00:00 2001 From: nym21 Date: Sun, 28 Dec 2025 20:24:38 +0100 Subject: [PATCH] computer: snapshot --- crates/brk_binder/src/python.rs | 54 +- crates/brk_binder/src/rust.rs | 54 +- .../src/stateful/cohorts/address.rs | 10 +- .../src/stateful/cohorts/address_cohorts.rs | 13 +- .../src/stateful/cohorts/traits.rs | 1 - .../brk_computer/src/stateful/cohorts/utxo.rs | 11 +- .../src/stateful/cohorts/utxo_cohorts/mod.rs | 425 +++++---- .../src/stateful/compute/aggregates.rs | 6 +- .../src/stateful/compute/block_loop.rs | 8 - .../src/stateful/compute/context.rs | 5 +- .../src/stateful/compute/write.rs | 2 - .../brk_computer/src/stateful/metrics/mod.rs | 23 +- .../src/stateful/metrics/relative.rs | 898 +++++++----------- crates/brk_computer/src/stateful/mod.rs | 1 - crates/brk_computer/src/stateful/vecs.rs | 35 +- 15 files changed, 632 insertions(+), 914 deletions(-) diff --git a/crates/brk_binder/src/python.rs b/crates/brk_binder/src/python.rs index c13e36982..86bed8125 100644 --- a/crates/brk_binder/src/python.rs +++ b/crates/brk_binder/src/python.rs @@ -726,27 +726,13 @@ fn generate_tree_class( .unwrap(); } } else if metadata.field_uses_accessor(field) { - let metric_path = if let TreeNode::Leaf(leaf) = child_node { - format!("/{}", leaf.name()) - } else { - format!("{{base_path}}/{}", field.name) - }; let accessor = metadata.find_index_set_pattern(&field.indexes).unwrap(); - if metric_path.contains("{base_path}") { - writeln!( - output, - " self.{}: {} = {}(client, f'{}')", - field_name_py, py_type, accessor.name, metric_path - ) - .unwrap(); - } else { - writeln!( - output, - " self.{}: {} = {}(client, '{}')", - field_name_py, py_type, accessor.name, metric_path - ) - .unwrap(); - } + writeln!( + output, + " self.{}: {} = {}(client, f'{{base_path}}/{}')", + field_name_py, py_type, accessor.name, field.name + ) + .unwrap(); } else if field.is_branch() { // Non-pattern branch - instantiate the nested class writeln!( @@ -756,27 +742,13 @@ fn generate_tree_class( ) .unwrap(); } else { - // Leaf - use MetricNode - let metric_path = if let TreeNode::Leaf(leaf) = child_node { - format!("/{}", leaf.name()) - } else { - format!("{{base_path}}/{}", field.name) - }; - if metric_path.contains("{base_path}") { - writeln!( - output, - " self.{}: {} = MetricNode(client, f'{}')", - field_name_py, py_type, metric_path - ) - .unwrap(); - } else { - writeln!( - output, - " self.{}: {} = MetricNode(client, '{}')", - field_name_py, py_type, metric_path - ) - .unwrap(); - } + // Leaf - use MetricNode with base_path + writeln!( + output, + " self.{}: {} = MetricNode(client, f'{{base_path}}/{}')", + field_name_py, py_type, field.name + ) + .unwrap(); } } diff --git a/crates/brk_binder/src/rust.rs b/crates/brk_binder/src/rust.rs index 98d09e4b4..26642f526 100644 --- a/crates/brk_binder/src/rust.rs +++ b/crates/brk_binder/src/rust.rs @@ -519,27 +519,13 @@ fn generate_tree_node( .unwrap(); } } else if metadata.field_uses_accessor(field) { - let metric_path = if let TreeNode::Leaf(leaf) = child_node { - format!("/{}", leaf.name()) - } else { - format!("{{base_path}}/{}", field.name) - }; let accessor = metadata.find_index_set_pattern(&field.indexes).unwrap(); - if metric_path.contains("{base_path}") { - writeln!( - output, - " {}: {}::new(client.clone(), &format!(\"{}\")),", - field_name, accessor.name, metric_path - ) - .unwrap(); - } else { - writeln!( - output, - " {}: {}::new(client.clone(), \"{}\"),", - field_name, accessor.name, metric_path - ) - .unwrap(); - } + writeln!( + output, + " {}: {}::new(client.clone(), &format!(\"{{base_path}}/{}\")),", + field_name, accessor.name, field.name + ) + .unwrap(); } else if field.is_branch() { // Non-pattern branch - instantiate the nested struct writeln!( @@ -549,27 +535,13 @@ fn generate_tree_node( ) .unwrap(); } else { - // Leaf - use MetricNode - let metric_path = if let TreeNode::Leaf(leaf) = child_node { - format!("/{}", leaf.name()) - } else { - format!("{{base_path}}/{}", field.name) - }; - if metric_path.contains("{base_path}") { - writeln!( - output, - " {}: MetricNode::new(client.clone(), format!(\"{}\")),", - field_name, metric_path - ) - .unwrap(); - } else { - writeln!( - output, - " {}: MetricNode::new(client.clone(), \"{}\".to_string()),", - field_name, metric_path - ) - .unwrap(); - } + // Leaf - use MetricNode with base_path + writeln!( + output, + " {}: MetricNode::new(client.clone(), format!(\"{{base_path}}/{}\")),", + field_name, field.name + ) + .unwrap(); } } diff --git a/crates/brk_computer/src/stateful/cohorts/address.rs b/crates/brk_computer/src/stateful/cohorts/address.rs index 045a083e3..ff39af06e 100644 --- a/crates/brk_computer/src/stateful/cohorts/address.rs +++ b/crates/brk_computer/src/stateful/cohorts/address.rs @@ -18,7 +18,7 @@ use crate::{ }; use super::{ - super::metrics::{CohortMetrics, ImportConfig}, + super::metrics::{CohortMetrics, ImportConfig, SupplyMetrics}, traits::{CohortVecs, DynCohortVecs}, }; @@ -47,6 +47,9 @@ pub struct AddressCohortVecs { impl AddressCohortVecs { /// Import address cohort from database. + /// + /// `all_supply` is the supply metrics from the "all" cohort, used as global + /// sources for `*_rel_to_market_cap` ratios. Pass `None` if not available. pub fn forced_import( db: &Database, filter: Filter, @@ -54,6 +57,7 @@ impl AddressCohortVecs { indexes: &indexes::Vecs, price: Option<&price::Vecs>, states_path: Option<&Path>, + all_supply: Option<&SupplyMetrics>, ) -> Result { let compute_dollars = price.is_some(); let full_name = filter.to_full_name(CohortContext::Address); @@ -73,7 +77,7 @@ impl AddressCohortVecs { state: states_path .map(|path| AddressCohortState::new(path, &full_name, compute_dollars)), - metrics: CohortMetrics::forced_import(&cfg)?, + metrics: CohortMetrics::forced_import(&cfg, all_supply)?, height_to_addr_count: EagerVec::forced_import( db, @@ -288,7 +292,6 @@ impl CohortVecs for AddressCohortVecs { 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>, exit: &Exit, @@ -298,7 +301,6 @@ impl CohortVecs for AddressCohortVecs { price, starting_indexes, height_to_supply, - dateindex_to_supply, height_to_market_cap, dateindex_to_market_cap, exit, diff --git a/crates/brk_computer/src/stateful/cohorts/address_cohorts.rs b/crates/brk_computer/src/stateful/cohorts/address_cohorts.rs index e8bb52ef5..f2600b870 100644 --- a/crates/brk_computer/src/stateful/cohorts/address_cohorts.rs +++ b/crates/brk_computer/src/stateful/cohorts/address_cohorts.rs @@ -13,7 +13,7 @@ use vecdb::{AnyStoredVec, Database, Exit, IterableVec}; use crate::{Indexes, indexes, price, stateful::DynCohortVecs}; -use super::{AddressCohortVecs, CohortVecs}; +use super::{super::metrics::SupplyMetrics, AddressCohortVecs, CohortVecs}; const VERSION: Version = Version::new(0); @@ -23,19 +23,23 @@ pub struct AddressCohorts(AddressGroups); impl AddressCohorts { /// Import all Address cohorts from database. + /// + /// `all_supply` is the supply metrics from the UTXO "all" cohort, used as global + /// sources for `*_rel_to_market_cap` ratios. pub fn forced_import( db: &Database, version: Version, indexes: &indexes::Vecs, price: Option<&price::Vecs>, states_path: &Path, + all_supply: Option<&SupplyMetrics>, ) -> Result { let v = version + VERSION + Version::ZERO; // Helper to create a cohort - only amount_range cohorts have state let create = |filter: Filter, has_state: bool| -> Result { let states_path = if has_state { Some(states_path) } else { None }; - AddressCohortVecs::forced_import(db, filter, v, indexes, price, states_path) + AddressCohortVecs::forced_import(db, filter, v, indexes, price, states_path, all_supply) }; let full = |f: Filter| create(f, true); @@ -183,20 +187,18 @@ impl AddressCohorts { /// Second phase of post-processing: compute relative metrics. #[allow(clippy::too_many_arguments)] - pub fn compute_rest_part2( + pub fn compute_rest_part2( &mut self, indexes: &indexes::Vecs, price: Option<&price::Vecs>, starting_indexes: &Indexes, height_to_supply: &S, - dateindex_to_supply: &D, height_to_market_cap: Option<&HM>, dateindex_to_market_cap: Option<&DM>, exit: &Exit, ) -> Result<()> where S: IterableVec + Sync, - D: IterableVec + Sync, HM: IterableVec + Sync, DM: IterableVec + Sync, { @@ -206,7 +208,6 @@ impl AddressCohorts { price, starting_indexes, height_to_supply, - dateindex_to_supply, height_to_market_cap, dateindex_to_market_cap, exit, diff --git a/crates/brk_computer/src/stateful/cohorts/traits.rs b/crates/brk_computer/src/stateful/cohorts/traits.rs index bbc982d56..abf8ec86a 100644 --- a/crates/brk_computer/src/stateful/cohorts/traits.rs +++ b/crates/brk_computer/src/stateful/cohorts/traits.rs @@ -61,7 +61,6 @@ pub trait CohortVecs: DynCohortVecs { 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>, exit: &Exit, diff --git a/crates/brk_computer/src/stateful/cohorts/utxo.rs b/crates/brk_computer/src/stateful/cohorts/utxo.rs index 99a55075b..de94a6c41 100644 --- a/crates/brk_computer/src/stateful/cohorts/utxo.rs +++ b/crates/brk_computer/src/stateful/cohorts/utxo.rs @@ -12,7 +12,7 @@ use crate::{ stateful::{CohortVecs, DynCohortVecs, states::UTXOCohortState}, }; -use super::super::metrics::{CohortMetrics, ImportConfig}; +use super::super::metrics::{CohortMetrics, ImportConfig, SupplyMetrics}; /// UTXO cohort with metrics and optional runtime state. #[derive(Clone, Traversable)] @@ -31,6 +31,10 @@ pub struct UTXOCohortVecs { impl UTXOCohortVecs { /// Import UTXO cohort from database. + /// + /// `all_supply` is the supply metrics from the "all" cohort, used as global + /// sources for `*_rel_to_market_cap` ratios. Pass `None` for the "all" cohort itself. + #[allow(clippy::too_many_arguments)] pub fn forced_import( db: &Database, filter: Filter, @@ -39,6 +43,7 @@ impl UTXOCohortVecs { price: Option<&price::Vecs>, states_path: &Path, state_level: StateLevel, + all_supply: Option<&SupplyMetrics>, ) -> Result { let compute_dollars = price.is_some(); let full_name = filter.to_full_name(CohortContext::Utxo); @@ -65,7 +70,7 @@ impl UTXOCohortVecs { None }, - metrics: CohortMetrics::forced_import(&cfg)?, + metrics: CohortMetrics::forced_import(&cfg, all_supply)?, }) } @@ -233,7 +238,6 @@ impl CohortVecs for UTXOCohortVecs { 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>, exit: &Exit, @@ -243,7 +247,6 @@ impl CohortVecs for UTXOCohortVecs { price, starting_indexes, height_to_supply, - dateindex_to_supply, height_to_market_cap, dateindex_to_market_cap, exit, diff --git a/crates/brk_computer/src/stateful/cohorts/utxo_cohorts/mod.rs b/crates/brk_computer/src/stateful/cohorts/utxo_cohorts/mod.rs index bffd473f7..c88d65729 100644 --- a/crates/brk_computer/src/stateful/cohorts/utxo_cohorts/mod.rs +++ b/crates/brk_computer/src/stateful/cohorts/utxo_cohorts/mod.rs @@ -46,212 +46,256 @@ impl UTXOCohorts { ) -> Result { let v = version + VERSION + Version::ZERO; - let create = |filter: Filter, state_level: StateLevel| -> Result { - UTXOCohortVecs::forced_import(db, filter, v, indexes, price, states_path, state_level) - }; + // Create "all" cohort first - it doesn't need global sources (it IS the global source) + let all = UTXOCohortVecs::forced_import( + db, + Filter::All, + version + VERSION + Version::ONE, + indexes, + price, + states_path, + StateLevel::PriceOnly, + None, + )?; - let full = |f: Filter| create(f, StateLevel::Full); - let none = |f: Filter| create(f, StateLevel::None); + // Get reference to all's supply for other cohorts to use as global source + let all_supply = Some(&all.metrics.supply); - Ok(Self(UTXOGroups { - all: UTXOCohortVecs::forced_import( + // Create all cohorts first (while borrowing all_supply), then assemble struct + let term = ByTerm { + short: UTXOCohortVecs::forced_import( db, - Filter::All, - version + VERSION + Version::ONE, + Filter::Term(Term::Sth), + v, indexes, price, states_path, StateLevel::PriceOnly, + all_supply, )?, + long: UTXOCohortVecs::forced_import( + db, + Filter::Term(Term::Lth), + v, + indexes, + price, + states_path, + StateLevel::PriceOnly, + all_supply, + )?, + }; - term: ByTerm { - short: create(Filter::Term(Term::Sth), StateLevel::PriceOnly)?, - long: create(Filter::Term(Term::Lth), StateLevel::PriceOnly)?, - }, + let full = |f: Filter| { + UTXOCohortVecs::forced_import( + db, + f, + v, + indexes, + price, + states_path, + StateLevel::Full, + all_supply, + ) + }; + let none = |f: Filter| { + UTXOCohortVecs::forced_import( + db, + f, + v, + indexes, + price, + states_path, + StateLevel::None, + all_supply, + ) + }; - epoch: ByEpoch { - _0: full(Filter::Epoch(HalvingEpoch::new(0)))?, - _1: full(Filter::Epoch(HalvingEpoch::new(1)))?, - _2: full(Filter::Epoch(HalvingEpoch::new(2)))?, - _3: full(Filter::Epoch(HalvingEpoch::new(3)))?, - _4: full(Filter::Epoch(HalvingEpoch::new(4)))?, - }, + let epoch = ByEpoch { + _0: full(Filter::Epoch(HalvingEpoch::new(0)))?, + _1: full(Filter::Epoch(HalvingEpoch::new(1)))?, + _2: full(Filter::Epoch(HalvingEpoch::new(2)))?, + _3: full(Filter::Epoch(HalvingEpoch::new(3)))?, + _4: full(Filter::Epoch(HalvingEpoch::new(4)))?, + }; - year: ByYear { - _2009: full(Filter::Year(Year::new(2009)))?, - _2010: full(Filter::Year(Year::new(2010)))?, - _2011: full(Filter::Year(Year::new(2011)))?, - _2012: full(Filter::Year(Year::new(2012)))?, - _2013: full(Filter::Year(Year::new(2013)))?, - _2014: full(Filter::Year(Year::new(2014)))?, - _2015: full(Filter::Year(Year::new(2015)))?, - _2016: full(Filter::Year(Year::new(2016)))?, - _2017: full(Filter::Year(Year::new(2017)))?, - _2018: full(Filter::Year(Year::new(2018)))?, - _2019: full(Filter::Year(Year::new(2019)))?, - _2020: full(Filter::Year(Year::new(2020)))?, - _2021: full(Filter::Year(Year::new(2021)))?, - _2022: full(Filter::Year(Year::new(2022)))?, - _2023: full(Filter::Year(Year::new(2023)))?, - _2024: full(Filter::Year(Year::new(2024)))?, - _2025: full(Filter::Year(Year::new(2025)))?, - _2026: full(Filter::Year(Year::new(2026)))?, - }, + let year = ByYear { + _2009: full(Filter::Year(Year::new(2009)))?, + _2010: full(Filter::Year(Year::new(2010)))?, + _2011: full(Filter::Year(Year::new(2011)))?, + _2012: full(Filter::Year(Year::new(2012)))?, + _2013: full(Filter::Year(Year::new(2013)))?, + _2014: full(Filter::Year(Year::new(2014)))?, + _2015: full(Filter::Year(Year::new(2015)))?, + _2016: full(Filter::Year(Year::new(2016)))?, + _2017: full(Filter::Year(Year::new(2017)))?, + _2018: full(Filter::Year(Year::new(2018)))?, + _2019: full(Filter::Year(Year::new(2019)))?, + _2020: full(Filter::Year(Year::new(2020)))?, + _2021: full(Filter::Year(Year::new(2021)))?, + _2022: full(Filter::Year(Year::new(2022)))?, + _2023: full(Filter::Year(Year::new(2023)))?, + _2024: full(Filter::Year(Year::new(2024)))?, + _2025: full(Filter::Year(Year::new(2025)))?, + _2026: full(Filter::Year(Year::new(2026)))?, + }; - type_: BySpendableType { - p2pk65: full(Filter::Type(OutputType::P2PK65))?, - p2pk33: full(Filter::Type(OutputType::P2PK33))?, - p2pkh: full(Filter::Type(OutputType::P2PKH))?, - p2sh: full(Filter::Type(OutputType::P2SH))?, - p2wpkh: full(Filter::Type(OutputType::P2WPKH))?, - p2wsh: full(Filter::Type(OutputType::P2WSH))?, - p2tr: full(Filter::Type(OutputType::P2TR))?, - p2a: full(Filter::Type(OutputType::P2A))?, - p2ms: full(Filter::Type(OutputType::P2MS))?, - empty: full(Filter::Type(OutputType::Empty))?, - unknown: full(Filter::Type(OutputType::Unknown))?, - }, + let type_ = BySpendableType { + p2pk65: full(Filter::Type(OutputType::P2PK65))?, + p2pk33: full(Filter::Type(OutputType::P2PK33))?, + p2pkh: full(Filter::Type(OutputType::P2PKH))?, + p2sh: full(Filter::Type(OutputType::P2SH))?, + p2wpkh: full(Filter::Type(OutputType::P2WPKH))?, + p2wsh: full(Filter::Type(OutputType::P2WSH))?, + p2tr: full(Filter::Type(OutputType::P2TR))?, + p2a: full(Filter::Type(OutputType::P2A))?, + p2ms: full(Filter::Type(OutputType::P2MS))?, + empty: full(Filter::Type(OutputType::Empty))?, + unknown: full(Filter::Type(OutputType::Unknown))?, + }; - max_age: ByMaxAge { - _1w: none(Filter::Time(TimeFilter::LowerThan(DAYS_1W)))?, - _1m: none(Filter::Time(TimeFilter::LowerThan(DAYS_1M)))?, - _2m: none(Filter::Time(TimeFilter::LowerThan(DAYS_2M)))?, - _3m: none(Filter::Time(TimeFilter::LowerThan(DAYS_3M)))?, - _4m: none(Filter::Time(TimeFilter::LowerThan(DAYS_4M)))?, - _5m: none(Filter::Time(TimeFilter::LowerThan(DAYS_5M)))?, - _6m: none(Filter::Time(TimeFilter::LowerThan(DAYS_6M)))?, - _1y: none(Filter::Time(TimeFilter::LowerThan(DAYS_1Y)))?, - _2y: none(Filter::Time(TimeFilter::LowerThan(DAYS_2Y)))?, - _3y: none(Filter::Time(TimeFilter::LowerThan(DAYS_3Y)))?, - _4y: none(Filter::Time(TimeFilter::LowerThan(DAYS_4Y)))?, - _5y: none(Filter::Time(TimeFilter::LowerThan(DAYS_5Y)))?, - _6y: none(Filter::Time(TimeFilter::LowerThan(DAYS_6Y)))?, - _7y: none(Filter::Time(TimeFilter::LowerThan(DAYS_7Y)))?, - _8y: none(Filter::Time(TimeFilter::LowerThan(DAYS_8Y)))?, - _10y: none(Filter::Time(TimeFilter::LowerThan(DAYS_10Y)))?, - _12y: none(Filter::Time(TimeFilter::LowerThan(DAYS_12Y)))?, - _15y: none(Filter::Time(TimeFilter::LowerThan(DAYS_15Y)))?, - }, + let max_age = ByMaxAge { + _1w: none(Filter::Time(TimeFilter::LowerThan(DAYS_1W)))?, + _1m: none(Filter::Time(TimeFilter::LowerThan(DAYS_1M)))?, + _2m: none(Filter::Time(TimeFilter::LowerThan(DAYS_2M)))?, + _3m: none(Filter::Time(TimeFilter::LowerThan(DAYS_3M)))?, + _4m: none(Filter::Time(TimeFilter::LowerThan(DAYS_4M)))?, + _5m: none(Filter::Time(TimeFilter::LowerThan(DAYS_5M)))?, + _6m: none(Filter::Time(TimeFilter::LowerThan(DAYS_6M)))?, + _1y: none(Filter::Time(TimeFilter::LowerThan(DAYS_1Y)))?, + _2y: none(Filter::Time(TimeFilter::LowerThan(DAYS_2Y)))?, + _3y: none(Filter::Time(TimeFilter::LowerThan(DAYS_3Y)))?, + _4y: none(Filter::Time(TimeFilter::LowerThan(DAYS_4Y)))?, + _5y: none(Filter::Time(TimeFilter::LowerThan(DAYS_5Y)))?, + _6y: none(Filter::Time(TimeFilter::LowerThan(DAYS_6Y)))?, + _7y: none(Filter::Time(TimeFilter::LowerThan(DAYS_7Y)))?, + _8y: none(Filter::Time(TimeFilter::LowerThan(DAYS_8Y)))?, + _10y: none(Filter::Time(TimeFilter::LowerThan(DAYS_10Y)))?, + _12y: none(Filter::Time(TimeFilter::LowerThan(DAYS_12Y)))?, + _15y: none(Filter::Time(TimeFilter::LowerThan(DAYS_15Y)))?, + }; - min_age: ByMinAge { - _1d: none(Filter::Time(TimeFilter::GreaterOrEqual(DAYS_1D)))?, - _1w: none(Filter::Time(TimeFilter::GreaterOrEqual(DAYS_1W)))?, - _1m: none(Filter::Time(TimeFilter::GreaterOrEqual(DAYS_1M)))?, - _2m: none(Filter::Time(TimeFilter::GreaterOrEqual(DAYS_2M)))?, - _3m: none(Filter::Time(TimeFilter::GreaterOrEqual(DAYS_3M)))?, - _4m: none(Filter::Time(TimeFilter::GreaterOrEqual(DAYS_4M)))?, - _5m: none(Filter::Time(TimeFilter::GreaterOrEqual(DAYS_5M)))?, - _6m: none(Filter::Time(TimeFilter::GreaterOrEqual(DAYS_6M)))?, - _1y: none(Filter::Time(TimeFilter::GreaterOrEqual(DAYS_1Y)))?, - _2y: none(Filter::Time(TimeFilter::GreaterOrEqual(DAYS_2Y)))?, - _3y: none(Filter::Time(TimeFilter::GreaterOrEqual(DAYS_3Y)))?, - _4y: none(Filter::Time(TimeFilter::GreaterOrEqual(DAYS_4Y)))?, - _5y: none(Filter::Time(TimeFilter::GreaterOrEqual(DAYS_5Y)))?, - _6y: none(Filter::Time(TimeFilter::GreaterOrEqual(DAYS_6Y)))?, - _7y: none(Filter::Time(TimeFilter::GreaterOrEqual(DAYS_7Y)))?, - _8y: none(Filter::Time(TimeFilter::GreaterOrEqual(DAYS_8Y)))?, - _10y: none(Filter::Time(TimeFilter::GreaterOrEqual(DAYS_10Y)))?, - _12y: none(Filter::Time(TimeFilter::GreaterOrEqual(DAYS_12Y)))?, - }, + let min_age = ByMinAge { + _1d: none(Filter::Time(TimeFilter::GreaterOrEqual(DAYS_1D)))?, + _1w: none(Filter::Time(TimeFilter::GreaterOrEqual(DAYS_1W)))?, + _1m: none(Filter::Time(TimeFilter::GreaterOrEqual(DAYS_1M)))?, + _2m: none(Filter::Time(TimeFilter::GreaterOrEqual(DAYS_2M)))?, + _3m: none(Filter::Time(TimeFilter::GreaterOrEqual(DAYS_3M)))?, + _4m: none(Filter::Time(TimeFilter::GreaterOrEqual(DAYS_4M)))?, + _5m: none(Filter::Time(TimeFilter::GreaterOrEqual(DAYS_5M)))?, + _6m: none(Filter::Time(TimeFilter::GreaterOrEqual(DAYS_6M)))?, + _1y: none(Filter::Time(TimeFilter::GreaterOrEqual(DAYS_1Y)))?, + _2y: none(Filter::Time(TimeFilter::GreaterOrEqual(DAYS_2Y)))?, + _3y: none(Filter::Time(TimeFilter::GreaterOrEqual(DAYS_3Y)))?, + _4y: none(Filter::Time(TimeFilter::GreaterOrEqual(DAYS_4Y)))?, + _5y: none(Filter::Time(TimeFilter::GreaterOrEqual(DAYS_5Y)))?, + _6y: none(Filter::Time(TimeFilter::GreaterOrEqual(DAYS_6Y)))?, + _7y: none(Filter::Time(TimeFilter::GreaterOrEqual(DAYS_7Y)))?, + _8y: none(Filter::Time(TimeFilter::GreaterOrEqual(DAYS_8Y)))?, + _10y: none(Filter::Time(TimeFilter::GreaterOrEqual(DAYS_10Y)))?, + _12y: none(Filter::Time(TimeFilter::GreaterOrEqual(DAYS_12Y)))?, + }; - age_range: ByAgeRange { - up_to_1d: full(Filter::Time(TimeFilter::Range(0..DAYS_1D)))?, - _1d_to_1w: full(Filter::Time(TimeFilter::Range(DAYS_1D..DAYS_1W)))?, - _1w_to_1m: full(Filter::Time(TimeFilter::Range(DAYS_1W..DAYS_1M)))?, - _1m_to_2m: full(Filter::Time(TimeFilter::Range(DAYS_1M..DAYS_2M)))?, - _2m_to_3m: full(Filter::Time(TimeFilter::Range(DAYS_2M..DAYS_3M)))?, - _3m_to_4m: full(Filter::Time(TimeFilter::Range(DAYS_3M..DAYS_4M)))?, - _4m_to_5m: full(Filter::Time(TimeFilter::Range(DAYS_4M..DAYS_5M)))?, - _5m_to_6m: full(Filter::Time(TimeFilter::Range(DAYS_5M..DAYS_6M)))?, - _6m_to_1y: full(Filter::Time(TimeFilter::Range(DAYS_6M..DAYS_1Y)))?, - _1y_to_2y: full(Filter::Time(TimeFilter::Range(DAYS_1Y..DAYS_2Y)))?, - _2y_to_3y: full(Filter::Time(TimeFilter::Range(DAYS_2Y..DAYS_3Y)))?, - _3y_to_4y: full(Filter::Time(TimeFilter::Range(DAYS_3Y..DAYS_4Y)))?, - _4y_to_5y: full(Filter::Time(TimeFilter::Range(DAYS_4Y..DAYS_5Y)))?, - _5y_to_6y: full(Filter::Time(TimeFilter::Range(DAYS_5Y..DAYS_6Y)))?, - _6y_to_7y: full(Filter::Time(TimeFilter::Range(DAYS_6Y..DAYS_7Y)))?, - _7y_to_8y: full(Filter::Time(TimeFilter::Range(DAYS_7Y..DAYS_8Y)))?, - _8y_to_10y: full(Filter::Time(TimeFilter::Range(DAYS_8Y..DAYS_10Y)))?, - _10y_to_12y: full(Filter::Time(TimeFilter::Range(DAYS_10Y..DAYS_12Y)))?, - _12y_to_15y: full(Filter::Time(TimeFilter::Range(DAYS_12Y..DAYS_15Y)))?, - from_15y: full(Filter::Time(TimeFilter::GreaterOrEqual(DAYS_15Y)))?, - }, + let age_range = ByAgeRange { + up_to_1d: full(Filter::Time(TimeFilter::Range(0..DAYS_1D)))?, + _1d_to_1w: full(Filter::Time(TimeFilter::Range(DAYS_1D..DAYS_1W)))?, + _1w_to_1m: full(Filter::Time(TimeFilter::Range(DAYS_1W..DAYS_1M)))?, + _1m_to_2m: full(Filter::Time(TimeFilter::Range(DAYS_1M..DAYS_2M)))?, + _2m_to_3m: full(Filter::Time(TimeFilter::Range(DAYS_2M..DAYS_3M)))?, + _3m_to_4m: full(Filter::Time(TimeFilter::Range(DAYS_3M..DAYS_4M)))?, + _4m_to_5m: full(Filter::Time(TimeFilter::Range(DAYS_4M..DAYS_5M)))?, + _5m_to_6m: full(Filter::Time(TimeFilter::Range(DAYS_5M..DAYS_6M)))?, + _6m_to_1y: full(Filter::Time(TimeFilter::Range(DAYS_6M..DAYS_1Y)))?, + _1y_to_2y: full(Filter::Time(TimeFilter::Range(DAYS_1Y..DAYS_2Y)))?, + _2y_to_3y: full(Filter::Time(TimeFilter::Range(DAYS_2Y..DAYS_3Y)))?, + _3y_to_4y: full(Filter::Time(TimeFilter::Range(DAYS_3Y..DAYS_4Y)))?, + _4y_to_5y: full(Filter::Time(TimeFilter::Range(DAYS_4Y..DAYS_5Y)))?, + _5y_to_6y: full(Filter::Time(TimeFilter::Range(DAYS_5Y..DAYS_6Y)))?, + _6y_to_7y: full(Filter::Time(TimeFilter::Range(DAYS_6Y..DAYS_7Y)))?, + _7y_to_8y: full(Filter::Time(TimeFilter::Range(DAYS_7Y..DAYS_8Y)))?, + _8y_to_10y: full(Filter::Time(TimeFilter::Range(DAYS_8Y..DAYS_10Y)))?, + _10y_to_12y: full(Filter::Time(TimeFilter::Range(DAYS_10Y..DAYS_12Y)))?, + _12y_to_15y: full(Filter::Time(TimeFilter::Range(DAYS_12Y..DAYS_15Y)))?, + from_15y: full(Filter::Time(TimeFilter::GreaterOrEqual(DAYS_15Y)))?, + }; - amount_range: ByAmountRange { - _0sats: full(Filter::Amount(AmountFilter::LowerThan(Sats::_1)))?, - _1sat_to_10sats: full(Filter::Amount(AmountFilter::Range(Sats::_1..Sats::_10)))?, - _10sats_to_100sats: full(Filter::Amount(AmountFilter::Range( - Sats::_10..Sats::_100, - )))?, - _100sats_to_1k_sats: full(Filter::Amount(AmountFilter::Range( - Sats::_100..Sats::_1K, - )))?, - _1k_sats_to_10k_sats: full(Filter::Amount(AmountFilter::Range( - Sats::_1K..Sats::_10K, - )))?, - _10k_sats_to_100k_sats: full(Filter::Amount(AmountFilter::Range( - Sats::_10K..Sats::_100K, - )))?, - _100k_sats_to_1m_sats: full(Filter::Amount(AmountFilter::Range( - Sats::_100K..Sats::_1M, - )))?, - _1m_sats_to_10m_sats: full(Filter::Amount(AmountFilter::Range( - Sats::_1M..Sats::_10M, - )))?, - _10m_sats_to_1btc: full(Filter::Amount(AmountFilter::Range( - Sats::_10M..Sats::_1BTC, - )))?, - _1btc_to_10btc: full(Filter::Amount(AmountFilter::Range( - Sats::_1BTC..Sats::_10BTC, - )))?, - _10btc_to_100btc: full(Filter::Amount(AmountFilter::Range( - Sats::_10BTC..Sats::_100BTC, - )))?, - _100btc_to_1k_btc: full(Filter::Amount(AmountFilter::Range( - Sats::_100BTC..Sats::_1K_BTC, - )))?, - _1k_btc_to_10k_btc: full(Filter::Amount(AmountFilter::Range( - Sats::_1K_BTC..Sats::_10K_BTC, - )))?, - _10k_btc_to_100k_btc: full(Filter::Amount(AmountFilter::Range( - Sats::_10K_BTC..Sats::_100K_BTC, - )))?, - _100k_btc_or_more: full(Filter::Amount(AmountFilter::GreaterOrEqual( - Sats::_100K_BTC, - )))?, - }, + let amount_range = ByAmountRange { + _0sats: full(Filter::Amount(AmountFilter::LowerThan(Sats::_1)))?, + _1sat_to_10sats: full(Filter::Amount(AmountFilter::Range(Sats::_1..Sats::_10)))?, + _10sats_to_100sats: full(Filter::Amount(AmountFilter::Range(Sats::_10..Sats::_100)))?, + _100sats_to_1k_sats: full(Filter::Amount(AmountFilter::Range(Sats::_100..Sats::_1K)))?, + _1k_sats_to_10k_sats: full(Filter::Amount(AmountFilter::Range(Sats::_1K..Sats::_10K)))?, + _10k_sats_to_100k_sats: full(Filter::Amount(AmountFilter::Range( + Sats::_10K..Sats::_100K, + )))?, + _100k_sats_to_1m_sats: full(Filter::Amount(AmountFilter::Range( + Sats::_100K..Sats::_1M, + )))?, + _1m_sats_to_10m_sats: full(Filter::Amount(AmountFilter::Range(Sats::_1M..Sats::_10M)))?, + _10m_sats_to_1btc: full(Filter::Amount(AmountFilter::Range(Sats::_10M..Sats::_1BTC)))?, + _1btc_to_10btc: full(Filter::Amount(AmountFilter::Range( + Sats::_1BTC..Sats::_10BTC, + )))?, + _10btc_to_100btc: full(Filter::Amount(AmountFilter::Range( + Sats::_10BTC..Sats::_100BTC, + )))?, + _100btc_to_1k_btc: full(Filter::Amount(AmountFilter::Range( + Sats::_100BTC..Sats::_1K_BTC, + )))?, + _1k_btc_to_10k_btc: full(Filter::Amount(AmountFilter::Range( + Sats::_1K_BTC..Sats::_10K_BTC, + )))?, + _10k_btc_to_100k_btc: full(Filter::Amount(AmountFilter::Range( + Sats::_10K_BTC..Sats::_100K_BTC, + )))?, + _100k_btc_or_more: full(Filter::Amount(AmountFilter::GreaterOrEqual( + Sats::_100K_BTC, + )))?, + }; - lt_amount: ByLowerThanAmount { - _10sats: none(Filter::Amount(AmountFilter::LowerThan(Sats::_10)))?, - _100sats: none(Filter::Amount(AmountFilter::LowerThan(Sats::_100)))?, - _1k_sats: none(Filter::Amount(AmountFilter::LowerThan(Sats::_1K)))?, - _10k_sats: none(Filter::Amount(AmountFilter::LowerThan(Sats::_10K)))?, - _100k_sats: none(Filter::Amount(AmountFilter::LowerThan(Sats::_100K)))?, - _1m_sats: none(Filter::Amount(AmountFilter::LowerThan(Sats::_1M)))?, - _10m_sats: none(Filter::Amount(AmountFilter::LowerThan(Sats::_10M)))?, - _1btc: none(Filter::Amount(AmountFilter::LowerThan(Sats::_1BTC)))?, - _10btc: none(Filter::Amount(AmountFilter::LowerThan(Sats::_10BTC)))?, - _100btc: none(Filter::Amount(AmountFilter::LowerThan(Sats::_100BTC)))?, - _1k_btc: none(Filter::Amount(AmountFilter::LowerThan(Sats::_1K_BTC)))?, - _10k_btc: none(Filter::Amount(AmountFilter::LowerThan(Sats::_10K_BTC)))?, - _100k_btc: none(Filter::Amount(AmountFilter::LowerThan(Sats::_100K_BTC)))?, - }, + let lt_amount = ByLowerThanAmount { + _10sats: none(Filter::Amount(AmountFilter::LowerThan(Sats::_10)))?, + _100sats: none(Filter::Amount(AmountFilter::LowerThan(Sats::_100)))?, + _1k_sats: none(Filter::Amount(AmountFilter::LowerThan(Sats::_1K)))?, + _10k_sats: none(Filter::Amount(AmountFilter::LowerThan(Sats::_10K)))?, + _100k_sats: none(Filter::Amount(AmountFilter::LowerThan(Sats::_100K)))?, + _1m_sats: none(Filter::Amount(AmountFilter::LowerThan(Sats::_1M)))?, + _10m_sats: none(Filter::Amount(AmountFilter::LowerThan(Sats::_10M)))?, + _1btc: none(Filter::Amount(AmountFilter::LowerThan(Sats::_1BTC)))?, + _10btc: none(Filter::Amount(AmountFilter::LowerThan(Sats::_10BTC)))?, + _100btc: none(Filter::Amount(AmountFilter::LowerThan(Sats::_100BTC)))?, + _1k_btc: none(Filter::Amount(AmountFilter::LowerThan(Sats::_1K_BTC)))?, + _10k_btc: none(Filter::Amount(AmountFilter::LowerThan(Sats::_10K_BTC)))?, + _100k_btc: none(Filter::Amount(AmountFilter::LowerThan(Sats::_100K_BTC)))?, + }; - ge_amount: ByGreatEqualAmount { - _1sat: none(Filter::Amount(AmountFilter::GreaterOrEqual(Sats::_1)))?, - _10sats: none(Filter::Amount(AmountFilter::GreaterOrEqual(Sats::_10)))?, - _100sats: none(Filter::Amount(AmountFilter::GreaterOrEqual(Sats::_100)))?, - _1k_sats: none(Filter::Amount(AmountFilter::GreaterOrEqual(Sats::_1K)))?, - _10k_sats: none(Filter::Amount(AmountFilter::GreaterOrEqual(Sats::_10K)))?, - _100k_sats: none(Filter::Amount(AmountFilter::GreaterOrEqual(Sats::_100K)))?, - _1m_sats: none(Filter::Amount(AmountFilter::GreaterOrEqual(Sats::_1M)))?, - _10m_sats: none(Filter::Amount(AmountFilter::GreaterOrEqual(Sats::_10M)))?, - _1btc: none(Filter::Amount(AmountFilter::GreaterOrEqual(Sats::_1BTC)))?, - _10btc: none(Filter::Amount(AmountFilter::GreaterOrEqual(Sats::_10BTC)))?, - _100btc: none(Filter::Amount(AmountFilter::GreaterOrEqual(Sats::_100BTC)))?, - _1k_btc: none(Filter::Amount(AmountFilter::GreaterOrEqual(Sats::_1K_BTC)))?, - _10k_btc: none(Filter::Amount(AmountFilter::GreaterOrEqual(Sats::_10K_BTC)))?, - }, + let ge_amount = ByGreatEqualAmount { + _1sat: none(Filter::Amount(AmountFilter::GreaterOrEqual(Sats::_1)))?, + _10sats: none(Filter::Amount(AmountFilter::GreaterOrEqual(Sats::_10)))?, + _100sats: none(Filter::Amount(AmountFilter::GreaterOrEqual(Sats::_100)))?, + _1k_sats: none(Filter::Amount(AmountFilter::GreaterOrEqual(Sats::_1K)))?, + _10k_sats: none(Filter::Amount(AmountFilter::GreaterOrEqual(Sats::_10K)))?, + _100k_sats: none(Filter::Amount(AmountFilter::GreaterOrEqual(Sats::_100K)))?, + _1m_sats: none(Filter::Amount(AmountFilter::GreaterOrEqual(Sats::_1M)))?, + _10m_sats: none(Filter::Amount(AmountFilter::GreaterOrEqual(Sats::_10M)))?, + _1btc: none(Filter::Amount(AmountFilter::GreaterOrEqual(Sats::_1BTC)))?, + _10btc: none(Filter::Amount(AmountFilter::GreaterOrEqual(Sats::_10BTC)))?, + _100btc: none(Filter::Amount(AmountFilter::GreaterOrEqual(Sats::_100BTC)))?, + _1k_btc: none(Filter::Amount(AmountFilter::GreaterOrEqual(Sats::_1K_BTC)))?, + _10k_btc: none(Filter::Amount(AmountFilter::GreaterOrEqual(Sats::_10K_BTC)))?, + }; + + Ok(Self(UTXOGroups { + all, + term, + epoch, + year, + type_, + max_age, + min_age, + age_range, + amount_range, + lt_amount, + ge_amount, })) } @@ -335,20 +379,18 @@ impl UTXOCohorts { /// Second phase of post-processing: compute relative metrics. #[allow(clippy::too_many_arguments)] - pub fn compute_rest_part2( + pub fn compute_rest_part2( &mut self, indexes: &indexes::Vecs, price: Option<&price::Vecs>, starting_indexes: &Indexes, height_to_supply: &S, - dateindex_to_supply: &D, height_to_market_cap: Option<&HM>, dateindex_to_market_cap: Option<&DM>, exit: &Exit, ) -> Result<()> where S: IterableVec + Sync, - D: IterableVec + Sync, HM: IterableVec + Sync, DM: IterableVec + Sync, { @@ -358,7 +400,6 @@ impl UTXOCohorts { price, starting_indexes, height_to_supply, - dateindex_to_supply, height_to_market_cap, dateindex_to_market_cap, exit, diff --git a/crates/brk_computer/src/stateful/compute/aggregates.rs b/crates/brk_computer/src/stateful/compute/aggregates.rs index f1ad84190..f7398740b 100644 --- a/crates/brk_computer/src/stateful/compute/aggregates.rs +++ b/crates/brk_computer/src/stateful/compute/aggregates.rs @@ -49,21 +49,19 @@ pub fn compute_rest_part1( /// /// Computes supply ratios, market cap ratios, etc. using total references. #[allow(clippy::too_many_arguments)] -pub fn compute_rest_part2( +pub fn compute_rest_part2( utxo_cohorts: &mut UTXOCohorts, address_cohorts: &mut AddressCohorts, indexes: &indexes::Vecs, price: Option<&price::Vecs>, starting_indexes: &Indexes, height_to_supply: &S, - dateindex_to_supply: &D, height_to_market_cap: Option<&HM>, dateindex_to_market_cap: Option<&DM>, exit: &Exit, ) -> Result<()> where S: IterableVec + Sync, - D: IterableVec + Sync, HM: IterableVec + Sync, DM: IterableVec + Sync, { @@ -74,7 +72,6 @@ where price, starting_indexes, height_to_supply, - dateindex_to_supply, height_to_market_cap, dateindex_to_market_cap, exit, @@ -85,7 +82,6 @@ where price, starting_indexes, height_to_supply, - dateindex_to_supply, height_to_market_cap, dateindex_to_market_cap, exit, diff --git a/crates/brk_computer/src/stateful/compute/block_loop.rs b/crates/brk_computer/src/stateful/compute/block_loop.rs index 363fee429..fa198fcbd 100644 --- a/crates/brk_computer/src/stateful/compute/block_loop.rs +++ b/crates/brk_computer/src/stateful/compute/block_loop.rs @@ -55,14 +55,6 @@ pub fn process_blocks( return Ok(()); } - info!( - "Processing blocks {} to {} (compute_dollars: {}, price_data: {})...", - ctx.starting_height, - ctx.last_height, - ctx.compute_dollars, - ctx.price.is_some() - ); - // References to vectors using correct field paths // From indexer.vecs: let height_to_first_txindex = &indexer.vecs.tx.height_to_first_txindex; diff --git a/crates/brk_computer/src/stateful/compute/context.rs b/crates/brk_computer/src/stateful/compute/context.rs index b2b4ad6cd..4360e64bf 100644 --- a/crates/brk_computer/src/stateful/compute/context.rs +++ b/crates/brk_computer/src/stateful/compute/context.rs @@ -51,7 +51,10 @@ impl<'a> ComputeContext<'a> { /// Get price at height (None if no price data or height out of range). pub fn price_at(&self, height: Height) -> Option { - self.height_to_price.as_ref()?.get(height.to_usize()).copied() + self.height_to_price + .as_ref()? + .get(height.to_usize()) + .copied() } /// Get timestamp at height. diff --git a/crates/brk_computer/src/stateful/compute/write.rs b/crates/brk_computer/src/stateful/compute/write.rs index b9911a70f..467edf3ea 100644 --- a/crates/brk_computer/src/stateful/compute/write.rs +++ b/crates/brk_computer/src/stateful/compute/write.rs @@ -31,8 +31,6 @@ pub fn process_address_updates( empty_updates: AddressTypeToTypeIndexMap, loaded_updates: AddressTypeToTypeIndexMap, ) -> Result<()> { - info!("Processing address updates..."); - let empty_result = process_empty_addresses(addresses_data, empty_updates)?; let loaded_result = process_loaded_addresses(addresses_data, loaded_updates)?; let all_updates = empty_result.merge(loaded_result); diff --git a/crates/brk_computer/src/stateful/metrics/mod.rs b/crates/brk_computer/src/stateful/metrics/mod.rs index 25104322e..0ff38096a 100644 --- a/crates/brk_computer/src/stateful/metrics/mod.rs +++ b/crates/brk_computer/src/stateful/metrics/mod.rs @@ -50,7 +50,11 @@ pub struct CohortMetrics { impl CohortMetrics { /// Import all metrics from database. - pub fn forced_import(cfg: &ImportConfig) -> Result { + /// + /// `all_supply` is the supply metrics from the "all" cohort, used as global + /// sources for `*_rel_to_market_cap` and `*_rel_to_circulating_supply` ratios. + /// Pass `None` for the "all" cohort itself. + pub fn forced_import(cfg: &ImportConfig, all_supply: Option<&SupplyMetrics>) -> Result { let compute_dollars = cfg.compute_dollars(); let supply = SupplyMetrics::forced_import(cfg)?; @@ -61,7 +65,7 @@ impl CohortMetrics { let relative = unrealized .as_ref() - .map(|u| RelativeMetrics::forced_import(cfg, u, &supply)) + .map(|u| RelativeMetrics::forced_import(cfg, u, &supply, all_supply)) .transpose()?; Ok(Self { @@ -290,7 +294,6 @@ impl CohortMetrics { 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>, exit: &Exit, @@ -307,20 +310,6 @@ impl CohortMetrics { )?; } - if let Some(relative) = self.relative.as_mut() { - relative.compute_rest_part2( - indexes, - starting_indexes, - height_to_supply, - dateindex_to_supply, - height_to_market_cap, - dateindex_to_market_cap, - &self.supply, - self.unrealized.as_ref(), - exit, - )?; - } - Ok(()) } } diff --git a/crates/brk_computer/src/stateful/metrics/relative.rs b/crates/brk_computer/src/stateful/metrics/relative.rs index db1d0bfda..4d90f6bd0 100644 --- a/crates/brk_computer/src/stateful/metrics/relative.rs +++ b/crates/brk_computer/src/stateful/metrics/relative.rs @@ -1,67 +1,70 @@ use brk_error::Result; use brk_traversable::Traversable; -use brk_types::{Bitcoin, DateIndex, Dollars, Height, Sats, StoredF32, StoredF64, Version}; -use vecdb::{ - EagerVec, Exit, ImportableVec, IterableCloneableVec, IterableVec, LazyVecFrom1, LazyVecFrom2, - Negate, PcoVec, -}; +use brk_types::{Bitcoin, Dollars, Height, Sats, StoredF32, StoredF64, Version}; +use vecdb::{IterableCloneableVec, LazyVecFrom2}; -use crate::{ - Indexes, - grouped::{ - ComputedVecsFromDateIndex, ComputedVecsFromHeight, LazyVecsFrom2FromDateIndex, - LazyVecsFromDateIndex, NegPercentageDollarsF32, NegRatio32, PercentageDollarsF32, - PercentageSatsF64, Ratio32, Source, VecBuilderOptions, - }, - indexes, +use crate::grouped::{ + LazyVecsFrom2FromDateIndex, NegPercentageDollarsF32, NegRatio32, PercentageBtcF64, + PercentageDollarsF32, PercentageSatsF64, Ratio32, }; use super::{ImportConfig, SupplyMetrics, UnrealizedMetrics}; /// Relative metrics comparing cohort values to global values. +/// All `rel_to_` vecs are lazy - computed on-demand from their sources. #[derive(Clone, Traversable)] pub struct RelativeMetrics { - // === Supply Relative to Circulating Supply === - pub indexes_to_supply_rel_to_circulating_supply: Option>, + // === Supply Relative to Circulating Supply (lazy from global supply) === + pub indexes_to_supply_rel_to_circulating_supply: + Option>, - // === Supply in Profit/Loss Relative to Own Supply === - pub height_to_supply_in_profit_rel_to_own_supply: EagerVec>, - pub height_to_supply_in_loss_rel_to_own_supply: EagerVec>, + // === Supply in Profit/Loss Relative to Own Supply (lazy) === + pub height_to_supply_in_profit_rel_to_own_supply: + LazyVecFrom2, + pub height_to_supply_in_loss_rel_to_own_supply: + LazyVecFrom2, pub indexes_to_supply_in_profit_rel_to_own_supply: LazyVecsFrom2FromDateIndex, pub indexes_to_supply_in_loss_rel_to_own_supply: LazyVecsFrom2FromDateIndex, - // === Supply in Profit/Loss Relative to Circulating Supply === + // === Supply in Profit/Loss Relative to Circulating Supply (lazy from global supply) === pub height_to_supply_in_profit_rel_to_circulating_supply: - Option>>, + Option>, pub height_to_supply_in_loss_rel_to_circulating_supply: - Option>>, + Option>, pub indexes_to_supply_in_profit_rel_to_circulating_supply: - Option>, + Option>, pub indexes_to_supply_in_loss_rel_to_circulating_supply: - Option>, + Option>, - // === Unrealized vs Market Cap === - pub height_to_unrealized_profit_rel_to_market_cap: EagerVec>, - pub height_to_unrealized_loss_rel_to_market_cap: EagerVec>, + // === Unrealized vs Market Cap (lazy from global market cap) === + 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: - LazyVecFrom1, - pub height_to_net_unrealized_pnl_rel_to_market_cap: EagerVec>, - pub indexes_to_unrealized_profit_rel_to_market_cap: ComputedVecsFromDateIndex, - pub indexes_to_unrealized_loss_rel_to_market_cap: ComputedVecsFromDateIndex, - pub indexes_to_neg_unrealized_loss_rel_to_market_cap: LazyVecsFromDateIndex, - pub indexes_to_net_unrealized_pnl_rel_to_market_cap: ComputedVecsFromDateIndex, + 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>, - // === Unrealized vs Own Market Cap (optional) === + // === Unrealized vs Own Market Cap (lazy) === pub height_to_unrealized_profit_rel_to_own_market_cap: - Option>>, + Option>, pub height_to_unrealized_loss_rel_to_own_market_cap: - Option>>, + Option>, pub height_to_neg_unrealized_loss_rel_to_own_market_cap: - Option>, + Option>, pub height_to_net_unrealized_pnl_rel_to_own_market_cap: - Option>>, + Option>, pub indexes_to_unrealized_profit_rel_to_own_market_cap: Option>, pub indexes_to_unrealized_loss_rel_to_own_market_cap: @@ -71,7 +74,7 @@ pub struct RelativeMetrics { pub indexes_to_net_unrealized_pnl_rel_to_own_market_cap: Option>, - // === Unrealized vs Own Total Unrealized PnL (optional) === + // === Unrealized vs Own Total Unrealized PnL (lazy) === pub height_to_unrealized_profit_rel_to_own_total_unrealized_pnl: Option>, pub height_to_unrealized_loss_rel_to_own_total_unrealized_pnl: @@ -92,176 +95,66 @@ pub struct RelativeMetrics { impl RelativeMetrics { /// Import relative metrics from database. + /// + /// All `rel_to_` metrics are lazy - computed on-demand from their sources. + /// `all_supply` provides global sources for `*_rel_to_market_cap` and `*_rel_to_circulating_supply`. pub fn forced_import( cfg: &ImportConfig, unrealized: &UnrealizedMetrics, supply: &SupplyMetrics, + all_supply: Option<&SupplyMetrics>, ) -> Result { let v0 = Version::ZERO; let v1 = Version::ONE; let v2 = Version::new(2); let extended = cfg.extended(); let compute_rel_to_all = cfg.compute_rel_to_all(); - let last = VecBuilderOptions::default().add_last(); - // Create sources for lazy neg vecs - let height_to_unrealized_loss_rel_to_market_cap: EagerVec> = - EagerVec::forced_import( - cfg.db, - &cfg.name("unrealized_loss_rel_to_market_cap"), - cfg.version + v0, - )?; + // Global sources from "all" cohort + let global_supply_sats = all_supply.map(|s| &s.indexes_to_supply.sats); + let global_supply_btc = all_supply.map(|s| &s.height_to_supply_value.bitcoin); + let global_market_cap = all_supply.and_then(|s| s.indexes_to_supply.dollars.as_ref()); + let global_market_cap_height = + all_supply.and_then(|s| s.height_to_supply_value.dollars.as_ref()); - let height_to_neg_unrealized_loss_rel_to_market_cap = LazyVecFrom1::transformed::( - &cfg.name("neg_unrealized_loss_rel_to_market_cap"), - cfg.version + v0, - height_to_unrealized_loss_rel_to_market_cap.boxed_clone(), - ); - - let indexes_to_unrealized_loss_rel_to_market_cap = - ComputedVecsFromDateIndex::forced_import( - cfg.db, - &cfg.name("unrealized_loss_rel_to_market_cap"), - Source::Compute, - cfg.version + v1, - cfg.indexes, - last, - )?; - - let indexes_to_neg_unrealized_loss_rel_to_market_cap = - LazyVecsFromDateIndex::from_computed::( - &cfg.name("neg_unrealized_loss_rel_to_market_cap"), - cfg.version + v1, - indexes_to_unrealized_loss_rel_to_market_cap - .dateindex - .as_ref() - .map(|v| v.boxed_clone()), - &indexes_to_unrealized_loss_rel_to_market_cap, - ); - - // Optional: own market cap vecs - let height_to_unrealized_loss_rel_to_own_market_cap: Option< - EagerVec>, - > = (extended && compute_rel_to_all) - .then(|| { - EagerVec::forced_import( - cfg.db, - &cfg.name("unrealized_loss_rel_to_own_market_cap"), - cfg.version + v1, - ) - }) - .transpose()?; - - let height_to_neg_unrealized_loss_rel_to_own_market_cap = - height_to_unrealized_loss_rel_to_own_market_cap - .as_ref() - .map(|source| { - LazyVecFrom1::transformed::( - &cfg.name("neg_unrealized_loss_rel_to_own_market_cap"), - cfg.version + v1, - source.boxed_clone(), - ) - }); - - // Optional: own total unrealized pnl vecs (lazy from unrealized sources) - let height_to_unrealized_profit_rel_to_own_total_unrealized_pnl = extended.then(|| { - LazyVecFrom2::transformed::( - &cfg.name("unrealized_profit_rel_to_own_total_unrealized_pnl"), - cfg.version + v0, - unrealized.height_to_unrealized_profit.boxed_clone(), - unrealized.height_to_total_unrealized_pnl.boxed_clone(), - ) - }); - - let height_to_unrealized_loss_rel_to_own_total_unrealized_pnl = extended.then(|| { - LazyVecFrom2::transformed::( - &cfg.name("unrealized_loss_rel_to_own_total_unrealized_pnl"), - cfg.version + v0, - unrealized.height_to_unrealized_loss.boxed_clone(), - unrealized.height_to_total_unrealized_pnl.boxed_clone(), - ) - }); - - let height_to_neg_unrealized_loss_rel_to_own_total_unrealized_pnl = extended.then(|| { - LazyVecFrom2::transformed::( - &cfg.name("neg_unrealized_loss_rel_to_own_total_unrealized_pnl"), - cfg.version + v0, - unrealized.height_to_unrealized_loss.boxed_clone(), - unrealized.height_to_total_unrealized_pnl.boxed_clone(), - ) - }); - - let height_to_net_unrealized_pnl_rel_to_own_total_unrealized_pnl = extended.then(|| { - LazyVecFrom2::transformed::( - &cfg.name("net_unrealized_pnl_rel_to_own_total_unrealized_pnl"), - cfg.version + v1, - unrealized.height_to_net_unrealized_pnl.boxed_clone(), - unrealized.height_to_total_unrealized_pnl.boxed_clone(), - ) - }); - - let indexes_to_unrealized_profit_rel_to_own_total_unrealized_pnl = extended.then(|| { - LazyVecsFrom2FromDateIndex::from_computed::( - &cfg.name("unrealized_profit_rel_to_own_total_unrealized_pnl"), - cfg.version + v1, - &unrealized.indexes_to_unrealized_profit, - &unrealized.indexes_to_total_unrealized_pnl, - ) - }); - - let indexes_to_unrealized_loss_rel_to_own_total_unrealized_pnl = extended.then(|| { - LazyVecsFrom2FromDateIndex::from_computed::( - &cfg.name("unrealized_loss_rel_to_own_total_unrealized_pnl"), - cfg.version + v1, - &unrealized.indexes_to_unrealized_loss, - &unrealized.indexes_to_total_unrealized_pnl, - ) - }); - - let indexes_to_neg_unrealized_loss_rel_to_own_total_unrealized_pnl = extended.then(|| { - LazyVecsFrom2FromDateIndex::from_computed::( - &cfg.name("neg_unrealized_loss_rel_to_own_total_unrealized_pnl"), - cfg.version + v1, - &unrealized.indexes_to_unrealized_loss, - &unrealized.indexes_to_total_unrealized_pnl, - ) - }); - - let indexes_to_net_unrealized_pnl_rel_to_own_total_unrealized_pnl = extended.then(|| { - LazyVecsFrom2FromDateIndex::from_computed::( - &cfg.name("net_unrealized_pnl_rel_to_own_total_unrealized_pnl"), - cfg.version + v1, - &unrealized.indexes_to_net_unrealized_pnl, - &unrealized.indexes_to_total_unrealized_pnl, - ) - }); + // Own market cap source + let own_market_cap = supply.indexes_to_supply.dollars.as_ref(); + let own_market_cap_height = supply.height_to_supply_value.dollars.as_ref(); Ok(Self { - // === Supply Relative to Circulating Supply === - indexes_to_supply_rel_to_circulating_supply: compute_rel_to_all - .then(|| { - ComputedVecsFromHeight::forced_import( - cfg.db, - &cfg.name("supply_rel_to_circulating_supply"), - Source::Compute, - cfg.version + v1, - cfg.indexes, - last, - ) - }) - .transpose()?, + // === Supply Relative to Circulating Supply (lazy from global supply) === + indexes_to_supply_rel_to_circulating_supply: (compute_rel_to_all + && global_supply_sats.is_some()) + .then(|| { + LazyVecsFrom2FromDateIndex::from_computed::( + &cfg.name("supply_rel_to_circulating_supply"), + cfg.version + v1, + &supply.indexes_to_supply.sats, + global_supply_sats.unwrap(), + ) + }), - // === Supply in Profit/Loss Relative to Own Supply === - height_to_supply_in_profit_rel_to_own_supply: EagerVec::forced_import( - cfg.db, + // === Supply in Profit/Loss Relative to Own Supply (lazy) === + height_to_supply_in_profit_rel_to_own_supply: LazyVecFrom2::transformed::< + PercentageBtcF64, + >( &cfg.name("supply_in_profit_rel_to_own_supply"), cfg.version + v1, - )?, - height_to_supply_in_loss_rel_to_own_supply: EagerVec::forced_import( - cfg.db, + unrealized + .height_to_supply_in_profit_value + .bitcoin + .boxed_clone(), + supply.height_to_supply_value.bitcoin.boxed_clone(), + ), + height_to_supply_in_loss_rel_to_own_supply: LazyVecFrom2::transformed::( &cfg.name("supply_in_loss_rel_to_own_supply"), cfg.version + v1, - )?, + unrealized + .height_to_supply_in_loss_value + .bitcoin + .boxed_clone(), + supply.height_to_supply_value.bitcoin.boxed_clone(), + ), indexes_to_supply_in_profit_rel_to_own_supply: LazyVecsFrom2FromDateIndex::from_computed::( &cfg.name("supply_in_profit_rel_to_own_supply"), @@ -278,412 +171,283 @@ impl RelativeMetrics { &supply.indexes_to_supply.sats, ), - // === Supply in Profit/Loss Relative to Circulating Supply === - height_to_supply_in_profit_rel_to_circulating_supply: compute_rel_to_all - .then(|| { - EagerVec::forced_import( - cfg.db, - &cfg.name("supply_in_profit_rel_to_circulating_supply"), - cfg.version + v1, - ) - }) - .transpose()?, - height_to_supply_in_loss_rel_to_circulating_supply: compute_rel_to_all - .then(|| { - EagerVec::forced_import( - cfg.db, - &cfg.name("supply_in_loss_rel_to_circulating_supply"), - cfg.version + v1, - ) - }) - .transpose()?, - indexes_to_supply_in_profit_rel_to_circulating_supply: compute_rel_to_all - .then(|| { - ComputedVecsFromDateIndex::forced_import( - cfg.db, - &cfg.name("supply_in_profit_rel_to_circulating_supply"), - Source::Compute, - cfg.version + v1, - cfg.indexes, - last, - ) - }) - .transpose()?, - indexes_to_supply_in_loss_rel_to_circulating_supply: compute_rel_to_all - .then(|| { - ComputedVecsFromDateIndex::forced_import( - cfg.db, - &cfg.name("supply_in_loss_rel_to_circulating_supply"), - Source::Compute, - cfg.version + v1, - cfg.indexes, - last, - ) - }) - .transpose()?, + // === Supply in Profit/Loss Relative to Circulating Supply (lazy from global supply) === + height_to_supply_in_profit_rel_to_circulating_supply: (compute_rel_to_all + && global_supply_btc.is_some()) + .then(|| { + LazyVecFrom2::transformed::( + &cfg.name("supply_in_profit_rel_to_circulating_supply"), + cfg.version + v1, + unrealized + .height_to_supply_in_profit_value + .bitcoin + .boxed_clone(), + global_supply_btc.unwrap().boxed_clone(), + ) + }), + height_to_supply_in_loss_rel_to_circulating_supply: (compute_rel_to_all + && global_supply_btc.is_some()) + .then(|| { + LazyVecFrom2::transformed::( + &cfg.name("supply_in_loss_rel_to_circulating_supply"), + cfg.version + v1, + unrealized + .height_to_supply_in_loss_value + .bitcoin + .boxed_clone(), + global_supply_btc.unwrap().boxed_clone(), + ) + }), + indexes_to_supply_in_profit_rel_to_circulating_supply: (compute_rel_to_all + && global_supply_sats.is_some()) + .then(|| { + LazyVecsFrom2FromDateIndex::from_computed::( + &cfg.name("supply_in_profit_rel_to_circulating_supply"), + cfg.version + v1, + &unrealized.indexes_to_supply_in_profit.sats, + global_supply_sats.unwrap(), + ) + }), + indexes_to_supply_in_loss_rel_to_circulating_supply: (compute_rel_to_all + && global_supply_sats.is_some()) + .then(|| { + LazyVecsFrom2FromDateIndex::from_computed::( + &cfg.name("supply_in_loss_rel_to_circulating_supply"), + cfg.version + v1, + &unrealized.indexes_to_supply_in_loss.sats, + global_supply_sats.unwrap(), + ) + }), - // === Unrealized vs Market Cap === - height_to_unrealized_profit_rel_to_market_cap: EagerVec::forced_import( - cfg.db, - &cfg.name("unrealized_profit_rel_to_market_cap"), - cfg.version + v0, - )?, - height_to_unrealized_loss_rel_to_market_cap, - height_to_neg_unrealized_loss_rel_to_market_cap, - height_to_net_unrealized_pnl_rel_to_market_cap: EagerVec::forced_import( - cfg.db, - &cfg.name("net_unrealized_pnl_rel_to_market_cap"), - cfg.version + v1, - )?, - indexes_to_unrealized_profit_rel_to_market_cap: - ComputedVecsFromDateIndex::forced_import( - cfg.db, + // === Unrealized vs Market Cap (lazy from global market cap) === + height_to_unrealized_profit_rel_to_market_cap: global_market_cap_height.map(|mc| { + LazyVecFrom2::transformed::( &cfg.name("unrealized_profit_rel_to_market_cap"), - Source::Compute, - cfg.version + v1, - cfg.indexes, - last, - )?, - indexes_to_unrealized_loss_rel_to_market_cap, - indexes_to_neg_unrealized_loss_rel_to_market_cap, - indexes_to_net_unrealized_pnl_rel_to_market_cap: - ComputedVecsFromDateIndex::forced_import( - cfg.db, + cfg.version + v0, + unrealized.height_to_unrealized_profit.boxed_clone(), + mc.boxed_clone(), + ) + }), + height_to_unrealized_loss_rel_to_market_cap: global_market_cap_height.map(|mc| { + LazyVecFrom2::transformed::( + &cfg.name("unrealized_loss_rel_to_market_cap"), + cfg.version + v0, + unrealized.height_to_unrealized_loss.boxed_clone(), + mc.boxed_clone(), + ) + }), + height_to_neg_unrealized_loss_rel_to_market_cap: global_market_cap_height.map(|mc| { + LazyVecFrom2::transformed::( + &cfg.name("neg_unrealized_loss_rel_to_market_cap"), + cfg.version + v0, + unrealized.height_to_unrealized_loss.boxed_clone(), + mc.boxed_clone(), + ) + }), + height_to_net_unrealized_pnl_rel_to_market_cap: global_market_cap_height.map(|mc| { + LazyVecFrom2::transformed::( &cfg.name("net_unrealized_pnl_rel_to_market_cap"), - Source::Compute, cfg.version + v1, - cfg.indexes, - last, - )?, + unrealized.height_to_net_unrealized_pnl.boxed_clone(), + mc.boxed_clone(), + ) + }), + indexes_to_unrealized_profit_rel_to_market_cap: global_market_cap.map(|mc| { + LazyVecsFrom2FromDateIndex::from_computed::( + &cfg.name("unrealized_profit_rel_to_market_cap"), + cfg.version + v2, + &unrealized.indexes_to_unrealized_profit, + mc, + ) + }), + indexes_to_unrealized_loss_rel_to_market_cap: global_market_cap.map(|mc| { + LazyVecsFrom2FromDateIndex::from_computed::( + &cfg.name("unrealized_loss_rel_to_market_cap"), + cfg.version + v2, + &unrealized.indexes_to_unrealized_loss, + mc, + ) + }), + indexes_to_neg_unrealized_loss_rel_to_market_cap: global_market_cap.map(|mc| { + LazyVecsFrom2FromDateIndex::from_computed::( + &cfg.name("neg_unrealized_loss_rel_to_market_cap"), + cfg.version + v2, + &unrealized.indexes_to_unrealized_loss, + mc, + ) + }), + indexes_to_net_unrealized_pnl_rel_to_market_cap: global_market_cap.map(|mc| { + LazyVecsFrom2FromDateIndex::from_computed::( + &cfg.name("net_unrealized_pnl_rel_to_market_cap"), + cfg.version + v2, + &unrealized.indexes_to_net_unrealized_pnl, + mc, + ) + }), - // === Unrealized vs Own Market Cap (optional) === + // === Unrealized vs Own Market Cap (lazy, optional) === height_to_unrealized_profit_rel_to_own_market_cap: (extended && compute_rel_to_all) .then(|| { - EagerVec::forced_import( - cfg.db, - &cfg.name("unrealized_profit_rel_to_own_market_cap"), - cfg.version + v1, - ) + own_market_cap_height.map(|mc| { + LazyVecFrom2::transformed::( + &cfg.name("unrealized_profit_rel_to_own_market_cap"), + cfg.version + v1, + unrealized.height_to_unrealized_profit.boxed_clone(), + mc.boxed_clone(), + ) + }) }) - .transpose()?, - height_to_unrealized_loss_rel_to_own_market_cap, - height_to_neg_unrealized_loss_rel_to_own_market_cap, + .flatten(), + height_to_unrealized_loss_rel_to_own_market_cap: (extended && compute_rel_to_all) + .then(|| { + own_market_cap_height.map(|mc| { + LazyVecFrom2::transformed::( + &cfg.name("unrealized_loss_rel_to_own_market_cap"), + cfg.version + v1, + unrealized.height_to_unrealized_loss.boxed_clone(), + mc.boxed_clone(), + ) + }) + }) + .flatten(), + height_to_neg_unrealized_loss_rel_to_own_market_cap: (extended && compute_rel_to_all) + .then(|| { + own_market_cap_height.map(|mc| { + LazyVecFrom2::transformed::( + &cfg.name("neg_unrealized_loss_rel_to_own_market_cap"), + cfg.version + v1, + unrealized.height_to_unrealized_loss.boxed_clone(), + mc.boxed_clone(), + ) + }) + }) + .flatten(), height_to_net_unrealized_pnl_rel_to_own_market_cap: (extended && compute_rel_to_all) .then(|| { - EagerVec::forced_import( - cfg.db, - &cfg.name("net_unrealized_pnl_rel_to_own_market_cap"), - cfg.version + v2, - ) + own_market_cap_height.map(|mc| { + LazyVecFrom2::transformed::( + &cfg.name("net_unrealized_pnl_rel_to_own_market_cap"), + cfg.version + v2, + unrealized.height_to_net_unrealized_pnl.boxed_clone(), + mc.boxed_clone(), + ) + }) }) - .transpose()?, + .flatten(), indexes_to_unrealized_profit_rel_to_own_market_cap: (extended && compute_rel_to_all) .then(|| { - supply - .indexes_to_supply - .dollars - .as_ref() - .map(|supply_dollars| { - LazyVecsFrom2FromDateIndex::from_computed::( - &cfg.name("unrealized_profit_rel_to_own_market_cap"), - cfg.version + v2, - &unrealized.indexes_to_unrealized_profit, - supply_dollars, - ) - }) + own_market_cap.map(|mc| { + LazyVecsFrom2FromDateIndex::from_computed::( + &cfg.name("unrealized_profit_rel_to_own_market_cap"), + cfg.version + v2, + &unrealized.indexes_to_unrealized_profit, + mc, + ) + }) }) .flatten(), indexes_to_unrealized_loss_rel_to_own_market_cap: (extended && compute_rel_to_all) .then(|| { - supply - .indexes_to_supply - .dollars - .as_ref() - .map(|supply_dollars| { - LazyVecsFrom2FromDateIndex::from_computed::( - &cfg.name("unrealized_loss_rel_to_own_market_cap"), - cfg.version + v2, - &unrealized.indexes_to_unrealized_loss, - supply_dollars, - ) - }) + own_market_cap.map(|mc| { + LazyVecsFrom2FromDateIndex::from_computed::( + &cfg.name("unrealized_loss_rel_to_own_market_cap"), + cfg.version + v2, + &unrealized.indexes_to_unrealized_loss, + mc, + ) + }) }) .flatten(), indexes_to_neg_unrealized_loss_rel_to_own_market_cap: (extended && compute_rel_to_all) .then(|| { - supply - .indexes_to_supply - .dollars - .as_ref() - .map(|supply_dollars| { - LazyVecsFrom2FromDateIndex::from_computed::( - &cfg.name("neg_unrealized_loss_rel_to_own_market_cap"), - cfg.version + v2, - &unrealized.indexes_to_unrealized_loss, - supply_dollars, - ) - }) + own_market_cap.map(|mc| { + LazyVecsFrom2FromDateIndex::from_computed::( + &cfg.name("neg_unrealized_loss_rel_to_own_market_cap"), + cfg.version + v2, + &unrealized.indexes_to_unrealized_loss, + mc, + ) + }) }) .flatten(), indexes_to_net_unrealized_pnl_rel_to_own_market_cap: (extended && compute_rel_to_all) .then(|| { - supply - .indexes_to_supply - .dollars - .as_ref() - .map(|supply_dollars| { - LazyVecsFrom2FromDateIndex::from_computed::( - &cfg.name("net_unrealized_pnl_rel_to_own_market_cap"), - cfg.version + v2, - &unrealized.indexes_to_net_unrealized_pnl, - supply_dollars, - ) - }) + own_market_cap.map(|mc| { + LazyVecsFrom2FromDateIndex::from_computed::( + &cfg.name("net_unrealized_pnl_rel_to_own_market_cap"), + cfg.version + v2, + &unrealized.indexes_to_net_unrealized_pnl, + mc, + ) + }) }) .flatten(), - // === Unrealized vs Own Total Unrealized PnL (optional) === - height_to_unrealized_profit_rel_to_own_total_unrealized_pnl, - height_to_unrealized_loss_rel_to_own_total_unrealized_pnl, - height_to_neg_unrealized_loss_rel_to_own_total_unrealized_pnl, - height_to_net_unrealized_pnl_rel_to_own_total_unrealized_pnl, - indexes_to_unrealized_profit_rel_to_own_total_unrealized_pnl, - indexes_to_unrealized_loss_rel_to_own_total_unrealized_pnl, - indexes_to_neg_unrealized_loss_rel_to_own_total_unrealized_pnl, - indexes_to_net_unrealized_pnl_rel_to_own_total_unrealized_pnl, + // === Unrealized vs Own Total Unrealized PnL (lazy, optional) === + height_to_unrealized_profit_rel_to_own_total_unrealized_pnl: extended.then(|| { + LazyVecFrom2::transformed::( + &cfg.name("unrealized_profit_rel_to_own_total_unrealized_pnl"), + cfg.version + v0, + unrealized.height_to_unrealized_profit.boxed_clone(), + unrealized.height_to_total_unrealized_pnl.boxed_clone(), + ) + }), + height_to_unrealized_loss_rel_to_own_total_unrealized_pnl: extended.then(|| { + LazyVecFrom2::transformed::( + &cfg.name("unrealized_loss_rel_to_own_total_unrealized_pnl"), + cfg.version + v0, + unrealized.height_to_unrealized_loss.boxed_clone(), + unrealized.height_to_total_unrealized_pnl.boxed_clone(), + ) + }), + height_to_neg_unrealized_loss_rel_to_own_total_unrealized_pnl: extended.then(|| { + LazyVecFrom2::transformed::( + &cfg.name("neg_unrealized_loss_rel_to_own_total_unrealized_pnl"), + cfg.version + v0, + unrealized.height_to_unrealized_loss.boxed_clone(), + unrealized.height_to_total_unrealized_pnl.boxed_clone(), + ) + }), + height_to_net_unrealized_pnl_rel_to_own_total_unrealized_pnl: extended.then(|| { + LazyVecFrom2::transformed::( + &cfg.name("net_unrealized_pnl_rel_to_own_total_unrealized_pnl"), + cfg.version + v1, + unrealized.height_to_net_unrealized_pnl.boxed_clone(), + unrealized.height_to_total_unrealized_pnl.boxed_clone(), + ) + }), + indexes_to_unrealized_profit_rel_to_own_total_unrealized_pnl: extended.then(|| { + LazyVecsFrom2FromDateIndex::from_computed::( + &cfg.name("unrealized_profit_rel_to_own_total_unrealized_pnl"), + cfg.version + v1, + &unrealized.indexes_to_unrealized_profit, + &unrealized.indexes_to_total_unrealized_pnl, + ) + }), + indexes_to_unrealized_loss_rel_to_own_total_unrealized_pnl: extended.then(|| { + LazyVecsFrom2FromDateIndex::from_computed::( + &cfg.name("unrealized_loss_rel_to_own_total_unrealized_pnl"), + cfg.version + v1, + &unrealized.indexes_to_unrealized_loss, + &unrealized.indexes_to_total_unrealized_pnl, + ) + }), + indexes_to_neg_unrealized_loss_rel_to_own_total_unrealized_pnl: extended.then(|| { + LazyVecsFrom2FromDateIndex::from_computed::( + &cfg.name("neg_unrealized_loss_rel_to_own_total_unrealized_pnl"), + cfg.version + v1, + &unrealized.indexes_to_unrealized_loss, + &unrealized.indexes_to_total_unrealized_pnl, + ) + }), + indexes_to_net_unrealized_pnl_rel_to_own_total_unrealized_pnl: extended.then(|| { + LazyVecsFrom2FromDateIndex::from_computed::( + &cfg.name("net_unrealized_pnl_rel_to_own_total_unrealized_pnl"), + cfg.version + v1, + &unrealized.indexes_to_net_unrealized_pnl, + &unrealized.indexes_to_total_unrealized_pnl, + ) + }), }) } - - /// Second phase of computed metrics (ratios, relative values). - /// - /// This computes percentage ratios comparing cohort metrics to global metrics: - /// - Supply relative to circulating supply - /// - Supply in profit/loss relative to own supply and circulating supply - /// - Unrealized profit/loss relative to market cap, total unrealized - /// - /// See `stateful/common/compute.rs` lines 800-1200 for the full original implementation. - #[allow(clippy::too_many_arguments)] - pub fn compute_rest_part2( - &mut self, - indexes: &indexes::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>, - supply: &SupplyMetrics, - unrealized: Option<&super::UnrealizedMetrics>, - exit: &Exit, - ) -> Result<()> { - // === Supply Relative to Circulating Supply === - 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, - &supply.height_to_supply_value.bitcoin, - height_to_supply, - exit, - )?; - Ok(()) - })?; - } - - // === Supply in Profit/Loss Relative to Own Supply === - // Note: indexes_to_* versions are now lazy (LazyVecsFrom2FromDateIndex) - if let Some(unrealized) = unrealized { - self.height_to_supply_in_profit_rel_to_own_supply - .compute_percentage( - starting_indexes.height, - &unrealized.height_to_supply_in_profit_value.bitcoin, - &supply.height_to_supply_value.bitcoin, - exit, - )?; - self.height_to_supply_in_loss_rel_to_own_supply - .compute_percentage( - starting_indexes.height, - &unrealized.height_to_supply_in_loss_value.bitcoin, - &supply.height_to_supply_value.bitcoin, - exit, - )?; - } - - // === Supply in Profit/Loss Relative to Circulating Supply === - if let (Some(unrealized), Some(v)) = ( - unrealized, - self.height_to_supply_in_profit_rel_to_circulating_supply - .as_mut(), - ) { - v.compute_percentage( - starting_indexes.height, - &unrealized.height_to_supply_in_profit_value.bitcoin, - height_to_supply, - exit, - )?; - } - if let (Some(unrealized), Some(v)) = ( - unrealized, - self.height_to_supply_in_loss_rel_to_circulating_supply - .as_mut(), - ) { - v.compute_percentage( - starting_indexes.height, - &unrealized.height_to_supply_in_loss_value.bitcoin, - height_to_supply, - exit, - )?; - } - - // === Unrealized vs Market Cap === - if let (Some(unrealized), Some(height_to_mc)) = (unrealized, height_to_market_cap) { - self.height_to_unrealized_profit_rel_to_market_cap - .compute_percentage( - starting_indexes.height, - &unrealized.height_to_unrealized_profit, - height_to_mc, - exit, - )?; - self.height_to_unrealized_loss_rel_to_market_cap - .compute_percentage( - starting_indexes.height, - &unrealized.height_to_unrealized_loss, - height_to_mc, - exit, - )?; - self.height_to_net_unrealized_pnl_rel_to_market_cap - .compute_percentage( - starting_indexes.height, - &unrealized.height_to_net_unrealized_pnl, - height_to_mc, - exit, - )?; - } - - if let Some(dateindex_to_mc) = dateindex_to_market_cap - && let Some(unrealized) = unrealized - { - self.indexes_to_unrealized_profit_rel_to_market_cap - .compute_all(starting_indexes, exit, |v| { - v.compute_percentage( - starting_indexes.dateindex, - &unrealized.dateindex_to_unrealized_profit, - dateindex_to_mc, - exit, - )?; - Ok(()) - })?; - self.indexes_to_unrealized_loss_rel_to_market_cap - .compute_all(starting_indexes, exit, |v| { - v.compute_percentage( - starting_indexes.dateindex, - &unrealized.dateindex_to_unrealized_loss, - dateindex_to_mc, - exit, - )?; - Ok(()) - })?; - } - - if let Some(dateindex_to_mc) = dateindex_to_market_cap - && let Some(unrealized) = unrealized - && let Some(dateindex_vec) = unrealized.indexes_to_net_unrealized_pnl.dateindex.as_ref() - { - self.indexes_to_net_unrealized_pnl_rel_to_market_cap - .compute_all(starting_indexes, exit, |v| { - v.compute_percentage( - starting_indexes.dateindex, - dateindex_vec, - dateindex_to_mc, - exit, - )?; - Ok(()) - })?; - } - - // === Supply in Profit/Loss Relative to Circulating Supply (indexes) === - if let Some(v) = self - .indexes_to_supply_in_profit_rel_to_circulating_supply - .as_mut() - && let Some(unrealized) = unrealized - && let Some(dateindex_vec) = unrealized - .indexes_to_supply_in_profit - .bitcoin - .dateindex - .as_ref() - { - v.compute_all(starting_indexes, exit, |vec| { - vec.compute_percentage( - starting_indexes.dateindex, - dateindex_vec, - dateindex_to_supply, - exit, - )?; - Ok(()) - })?; - } - - if let Some(v) = self - .indexes_to_supply_in_loss_rel_to_circulating_supply - .as_mut() - && let Some(unrealized) = unrealized - && let Some(dateindex_vec) = unrealized - .indexes_to_supply_in_loss - .bitcoin - .dateindex - .as_ref() - { - v.compute_all(starting_indexes, exit, |vec| { - vec.compute_percentage( - starting_indexes.dateindex, - dateindex_vec, - dateindex_to_supply, - exit, - )?; - Ok(()) - })?; - } - - // === Unrealized vs Own Market Cap === - // own_market_cap = supply_value.dollars - // Note: indexes_to_* versions are now lazy (LazyVecsFrom2FromDateIndex) - if let Some(unrealized) = unrealized { - if let Some(v) = self - .height_to_unrealized_profit_rel_to_own_market_cap - .as_mut() - && let Some(supply_dollars) = supply.height_to_supply_value.dollars.as_ref() - { - v.compute_percentage( - starting_indexes.height, - &unrealized.height_to_unrealized_profit, - supply_dollars, - exit, - )?; - } - if let Some(v) = self - .height_to_unrealized_loss_rel_to_own_market_cap - .as_mut() - && let Some(supply_dollars) = supply.height_to_supply_value.dollars.as_ref() - { - v.compute_percentage( - starting_indexes.height, - &unrealized.height_to_unrealized_loss, - supply_dollars, - exit, - )?; - } - if let Some(v) = self - .height_to_net_unrealized_pnl_rel_to_own_market_cap - .as_mut() - && let Some(supply_dollars) = supply.height_to_supply_value.dollars.as_ref() - { - v.compute_percentage( - starting_indexes.height, - &unrealized.height_to_net_unrealized_pnl, - supply_dollars, - exit, - )?; - } - } - - Ok(()) - } } diff --git a/crates/brk_computer/src/stateful/mod.rs b/crates/brk_computer/src/stateful/mod.rs index 12ce46bcb..5a6841486 100644 --- a/crates/brk_computer/src/stateful/mod.rs +++ b/crates/brk_computer/src/stateful/mod.rs @@ -7,7 +7,6 @@ mod range_map; mod states; mod vecs; -use states::*; pub use range_map::RangeMap; pub use vecs::Vecs; diff --git a/crates/brk_computer/src/stateful/vecs.rs b/crates/brk_computer/src/stateful/vecs.rs index 6b8d31772..ade8d9f6a 100644 --- a/crates/brk_computer/src/stateful/vecs.rs +++ b/crates/brk_computer/src/stateful/vecs.rs @@ -89,6 +89,16 @@ impl Vecs { let utxo_cohorts = UTXOCohorts::forced_import(&db, version, indexes, price, &states_path)?; + // Create address cohorts with reference to utxo "all" cohort's supply for global ratios + let address_cohorts = AddressCohorts::forced_import( + &db, + version, + indexes, + price, + &states_path, + Some(&utxo_cohorts.all.metrics.supply), + )?; + // Create address data BytesVecs first so we can also use them for identity mappings let loadedaddressindex_to_loadedaddressdata = BytesVec::forced_import_with( vecdb::ImportOptions::new(&db, "loadedaddressdata", v0) @@ -212,14 +222,7 @@ impl Vecs { )?, utxo_cohorts, - - address_cohorts: AddressCohorts::forced_import( - &db, - version, - indexes, - price, - &states_path, - )?, + address_cohorts, any_address_indexes: AnyAddressIndexesVecs::forced_import(&db, v0)?, addresses_data: AddressesDataVecs { @@ -347,10 +350,6 @@ impl Vecs { }) .collect(); - info!( - "State recovery: resumed from checkpoint at height {}", - recovered_height - ); (recovered_height, chain_state) }; @@ -453,16 +452,6 @@ impl Vecs { .bitcoin .clone(); - let dateindex_to_supply = self - .utxo_cohorts - .all - .metrics - .supply - .indexes_to_supply - .bitcoin - .dateindex - .clone(); - let height_to_market_cap = self.height_to_market_cap.clone(); let dateindex_to_market_cap = self @@ -470,7 +459,6 @@ impl Vecs { .as_ref() .map(|v| v.dateindex.u().clone()); - let dateindex_to_supply_ref = dateindex_to_supply.u(); let height_to_market_cap_ref = height_to_market_cap.as_ref(); let dateindex_to_market_cap_ref = dateindex_to_market_cap.as_ref(); @@ -481,7 +469,6 @@ impl Vecs { price, starting_indexes, height_to_supply, - dateindex_to_supply_ref, height_to_market_cap_ref, dateindex_to_market_cap_ref, exit,